Skip to content

Prepare REPL for dotty-bridge #1299

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jun 3, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions src/dotty/tools/dotc/repl/CompilingInterpreter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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

Expand Down
11 changes: 7 additions & 4 deletions src/dotty/tools/dotc/repl/Interpreter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
25 changes: 13 additions & 12 deletions src/dotty/tools/dotc/repl/InterpreterLoop.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand Down
28 changes: 28 additions & 0 deletions src/dotty/tools/dotc/repl/REPL.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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", "") != ""
Expand Down