diff options
-rw-r--r-- | src/dotty/tools/dotc/repl/CompilingInterpreter.scala | 62 | ||||
-rw-r--r-- | src/dotty/tools/dotc/repl/Interpreter.scala | 11 | ||||
-rw-r--r-- | src/dotty/tools/dotc/repl/InterpreterLoop.scala | 25 | ||||
-rw-r--r-- | src/dotty/tools/dotc/repl/REPL.scala | 28 |
4 files changed, 110 insertions, 16 deletions
diff --git a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala index 7b8ba698a..b6a3e388e 100644 --- a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala +++ b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala @@ -228,6 +228,65 @@ class CompilingInterpreter(out: PrintWriter, ictx: Context) extends Compiler wit } } + private def loadAndSetValue(objectName: String, value: AnyRef) = { + /** This terrible string is the wrapped class's full name inside the + * classloader: + * lineX$object$$iw$$iw$list$object + */ + val objName: String = List( + currentLineName + INTERPRETER_WRAPPER_SUFFIX, + INTERPRETER_IMPORT_WRAPPER, + INTERPRETER_IMPORT_WRAPPER, + objectName + ).mkString("$") + + try { + val resObj: Class[_] = Class.forName(objName, true, classLoader) + val setMethod = resObj.getDeclaredMethods.find(_.getName == "set") + + setMethod.fold(false) { method => + method.invoke(resObj, value) == null + } + } catch { + case NonFatal(_) => + // Unable to set value on object due to exception during reflection + false + } + } + + /** This bind is implemented by creating an object with a set method and a + * field `value`. The value is then set via Java reflection. + * + * Example: We want to bind a value `List(1,2,3)` to identifier `list` from + * sbt. The bind method accomplishes this by creating the following: + * {{{ + * object ContainerObjectWithUniqueID { + * var value: List[Int] = _ + * def set(x: Any) = value = x.asInstanceOf[List[Int]] + * } + * val list = ContainerObjectWithUniqueID.value + * }}} + * + * Between the object being created and the value being assigned, the value + * inside the object is set via reflection. + */ + override def bind(id: String, boundType: String, value: AnyRef)(implicit ctx: Context): Interpreter.Result = + interpret( + """ + |object %s { + | var value: %s = _ + | def set(x: Any) = value = x.asInstanceOf[%s] + |} + """.stripMargin.format(id + INTERPRETER_WRAPPER_SUFFIX, boundType, boundType) + ) match { + case Interpreter.Success if loadAndSetValue(id + INTERPRETER_WRAPPER_SUFFIX, value) => + val line = "val %s = %s.value".format(id, id + INTERPRETER_WRAPPER_SUFFIX) + interpret(line) + case Interpreter.Error | Interpreter.Incomplete => + out.println("Set failed in bind(%s, %s, %s)".format(id, boundType, value)) + Interpreter.Error + } + /** Trait collecting info about one of the statements of an interpreter request */ private trait StatementInfo { /** The statement */ @@ -738,6 +797,9 @@ class CompilingInterpreter(out: PrintWriter, ictx: Context) extends Compiler wit INTERPRETER_LINE_PREFIX + num } + private def currentLineName = + INTERPRETER_LINE_PREFIX + (nextLineNo - 1) + /** next result variable number to use */ private var nextVarNameNo = 0 diff --git a/src/dotty/tools/dotc/repl/Interpreter.scala b/src/dotty/tools/dotc/repl/Interpreter.scala index 6a292dfe2..e11fbf5cc 100644 --- a/src/dotty/tools/dotc/repl/Interpreter.scala +++ b/src/dotty/tools/dotc/repl/Interpreter.scala @@ -25,12 +25,15 @@ object Interpreter { trait Interpreter { import Interpreter._ - /** Interpret one line of input. All feedback, including parse errors - * and evaluation results, are printed via the context's reporter. - * reporter. Values defined are available for future interpreted strings. - */ + /** Interpret one line of input. All feedback, including parse errors and + * evaluation results, are printed via the context's reporter. Values + * defined are available for future interpreted strings. + */ def interpret(line: String)(implicit ctx: Context): Result + /** Tries to bind an id to a value, returns the outcome of trying to bind */ + def bind(id: String, boundType: String, value: AnyRef)(implicit ctx: Context): Result + /** Suppress output during evaluation of `operation`. */ def beQuietDuring[T](operation: => T): T diff --git a/src/dotty/tools/dotc/repl/InterpreterLoop.scala b/src/dotty/tools/dotc/repl/InterpreterLoop.scala index 14a50fdf1..7e5dcc7f1 100644 --- a/src/dotty/tools/dotc/repl/InterpreterLoop.scala +++ b/src/dotty/tools/dotc/repl/InterpreterLoop.scala @@ -68,17 +68,6 @@ class InterpreterLoop(compiler: Compiler, config: REPL.Config)(implicit ctx: Con val version = ".next (pre-alpha)" - /** The first interpreted command always takes a couple of seconds - * due to classloading. To bridge the gap, we warm up the interpreter - * by letting it interpret a dummy line while waiting for the first - * line of input to be entered. - */ - def firstLine(): String = { - interpreter.beQuietDuring( - interpreter.interpret("val theAnswerToLifeInTheUniverseAndEverything = 21 * 2")) - in.readLine(prompt) - } - /** The main read-eval-print loop for the interpreter. It calls * `command()` for each line of input. */ @@ -177,6 +166,15 @@ class InterpreterLoop(compiler: Compiler, config: REPL.Config)(implicit ctx: Con (true, shouldReplay) } + def silentlyRun(cmds: List[String]): Unit = cmds.foreach { cmd => + interpreter.beQuietDuring(interpreter.interpret(cmd)) + } + + def silentlyBind(values: Array[(String, Any)]): Unit = values.foreach { case (id, value) => + interpreter.beQuietDuring( + interpreter.bind(id, value.asInstanceOf[AnyRef].getClass.getName, value.asInstanceOf[AnyRef])) + } + /** Interpret expressions starting with the first line. * Read lines until a complete compilation unit is available * or until a syntax error has been seen. If a full unit is @@ -207,7 +205,10 @@ class InterpreterLoop(compiler: Compiler, config: REPL.Config)(implicit ctx: Con try { if (!ctx.reporter.hasErrors) { // if there are already errors, no sense to continue printWelcome() - repl(firstLine()) + silentlyRun(config.initialCommands) + silentlyBind(config.boundValues) + repl(in.readLine(prompt)) + silentlyRun(config.cleanupCommands) } } finally { closeInterpreter() diff --git a/src/dotty/tools/dotc/repl/REPL.scala b/src/dotty/tools/dotc/repl/REPL.scala index 977f67719..1fcb055d6 100644 --- a/src/dotty/tools/dotc/repl/REPL.scala +++ b/src/dotty/tools/dotc/repl/REPL.scala @@ -52,6 +52,34 @@ object REPL { def context(ctx: Context): Context = ctx + /** The first interpreted commands always take a couple of seconds due to + * classloading. To bridge the gap, we warm up the interpreter by letting + * it interpret at least a dummy line while waiting for the first line of + * input to be entered. + */ + val initialCommands: List[String] = + "val theAnswerToLifeInTheUniverseAndEverything = 21 * 2" :: Nil + + /** Before exiting, the interpreter will also run the cleanup commands + * issued in the variable below. This is useful if your REPL creates + * things during its run that should be dealt with before shutdown. + */ + val cleanupCommands: List[String] = Nil + + /** Initial values in the REPL can also be bound from runtime. Override + * this variable in the following manner to bind a variable at the start + * of the REPL session: + * + * {{{ + * override val boundValues = Array("exampleList" -> List(1, 1, 2, 3, 5)) + * }}} + * + * This is useful if you've integrated the REPL as part of your project + * and already have objects available during runtime that you'd like to + * inspect. + */ + val boundValues: Array[(String, Any)] = Array.empty[(String, Any)] + /** The default input reader */ def input(in: Interpreter)(implicit ctx: Context): InteractiveReader = { val emacsShell = System.getProperty("env.emacs", "") != "" |