読者です 読者をやめる 読者になる 読者になる

( ꒪⌓꒪) ゆるよろ日記

( ゚∀゚)o彡°オパーイ!オパーイ! ( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

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できるよね。