Scala Interpreter Hacks!! ―REPLを組み込んだりevalしたり
最近REPLをHackするのが流行ってるみたいなんで俺もやってみた。scala.tools.nsc.interpreter.{IMain, ILoop}あたりのソースコードを読めば大体分かるよ。
REPLをコードに組み込む
これは結構簡単で。scala.tools.nsc.interpreter.ILoopを作ってやればよい。ちなみにscala2.8ではscala.tools.nsc.interpreter.InterpreterLoopって名前だったけど2.9ではILoopになった。
import scala.tools.nsc.interpreter._ import scala.tools.nsc.Settings import java.io.{PrintWriter, FileOutputStream} object MyREPL extends App{ val settings = new Settings val out = new PrintWriter(new FileOutputStream("output.txt")) val iLoop = new ILoop(Console.in, out) iLoop process settings }
このサンプルはREPLの出力をoutput.txtというファイルに吐く。それだけ。
文字列をevalする
次はeval。scala.tools.nsc.interpreter.IMainってクラスがあってコイツがinterpret(code:String):scala.tools.nsc.interpreter.Results.Resultってメソッドを持ってるのでコレを呼べばオーケーです。うまくいったらResults.Successってオブジェクトが返る。コンパイルエラーだったらErrorってのが返る。2.7の頃よりだいぶ楽になった。遅いけど、な。
Scala2.7の頃はevalした結果をリフレクションでほげほげするという涙ぐましい努力をしていたけど、2.8からはIMainにvalueOfTermとかvisibleTermNamesとか色々と便利なのができたのでそれでInterperter上の変数を取得できる。ま、evalなんて実用性はあまりないけど、REPLの中身はこんな風に動いているっていう参考程度に見てくださいなーーー。
import scala.tools.nsc.Settings import java.io.{PrintWriter, ByteArrayOutputStream, FileOutputStream} import scala.tools.nsc.interpreter.{ IMain, Results => IR } object Eval extends App{ val settings = new Settings settings.usejavacp.value = true // interpret one line val main = new IMain(settings) main.interpret( "1 to 10 withFilter { _ % 2 == 0 } map{ _ * 2 }") main.visibleTermNames foreach println println( main.mostRecentVar ) println( main.valueOfTerm("res0")) // import on interpreter main.quietImport("java.util.Date") main.interpret("val d = new Date") println( main.mostRecentVar ) val d = main.valueOfTerm( "d" ) println("evaluated value of d: %s" format d ) // method definitions val code = """ def test(n:Int, s:String):String = { val now = new Date "%n:%s at %s" format(n, s, now) }""" main.interpret(code) // calling defined method main.interpret("""test(99, "hoge")""") println( main.mostRecentVar ) println( main.mostRecentVar ) // complie error val rv = main.interpret("abababa nono )(!!") println(rv) main.close() }
あとは、こんなユーティリティクラス作っておけばいつでもevalできるよね。