Skip to content

Commit 030ff82

Browse files
committed
Merge pull request #1299 from felixmulder/topic/prepare-repl-for-bridge
Prepare REPL for dotty-bridge
2 parents 845b981 + 5c29e0a commit 030ff82

File tree

4 files changed

+110
-16
lines changed

4 files changed

+110
-16
lines changed

src/dotty/tools/dotc/repl/CompilingInterpreter.scala

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,65 @@ class CompilingInterpreter(out: PrintWriter, ictx: Context) extends Compiler wit
228228
}
229229
}
230230

231+
private def loadAndSetValue(objectName: String, value: AnyRef) = {
232+
/** This terrible string is the wrapped class's full name inside the
233+
* classloader:
234+
* lineX$object$$iw$$iw$list$object
235+
*/
236+
val objName: String = List(
237+
currentLineName + INTERPRETER_WRAPPER_SUFFIX,
238+
INTERPRETER_IMPORT_WRAPPER,
239+
INTERPRETER_IMPORT_WRAPPER,
240+
objectName
241+
).mkString("$")
242+
243+
try {
244+
val resObj: Class[_] = Class.forName(objName, true, classLoader)
245+
val setMethod = resObj.getDeclaredMethods.find(_.getName == "set")
246+
247+
setMethod.fold(false) { method =>
248+
method.invoke(resObj, value) == null
249+
}
250+
} catch {
251+
case NonFatal(_) =>
252+
// Unable to set value on object due to exception during reflection
253+
false
254+
}
255+
}
256+
257+
/** This bind is implemented by creating an object with a set method and a
258+
* field `value`. The value is then set via Java reflection.
259+
*
260+
* Example: We want to bind a value `List(1,2,3)` to identifier `list` from
261+
* sbt. The bind method accomplishes this by creating the following:
262+
* {{{
263+
* object ContainerObjectWithUniqueID {
264+
* var value: List[Int] = _
265+
* def set(x: Any) = value = x.asInstanceOf[List[Int]]
266+
* }
267+
* val list = ContainerObjectWithUniqueID.value
268+
* }}}
269+
*
270+
* Between the object being created and the value being assigned, the value
271+
* inside the object is set via reflection.
272+
*/
273+
override def bind(id: String, boundType: String, value: AnyRef)(implicit ctx: Context): Interpreter.Result =
274+
interpret(
275+
"""
276+
|object %s {
277+
| var value: %s = _
278+
| def set(x: Any) = value = x.asInstanceOf[%s]
279+
|}
280+
""".stripMargin.format(id + INTERPRETER_WRAPPER_SUFFIX, boundType, boundType)
281+
) match {
282+
case Interpreter.Success if loadAndSetValue(id + INTERPRETER_WRAPPER_SUFFIX, value) =>
283+
val line = "val %s = %s.value".format(id, id + INTERPRETER_WRAPPER_SUFFIX)
284+
interpret(line)
285+
case Interpreter.Error | Interpreter.Incomplete =>
286+
out.println("Set failed in bind(%s, %s, %s)".format(id, boundType, value))
287+
Interpreter.Error
288+
}
289+
231290
/** Trait collecting info about one of the statements of an interpreter request */
232291
private trait StatementInfo {
233292
/** The statement */
@@ -738,6 +797,9 @@ class CompilingInterpreter(out: PrintWriter, ictx: Context) extends Compiler wit
738797
INTERPRETER_LINE_PREFIX + num
739798
}
740799

800+
private def currentLineName =
801+
INTERPRETER_LINE_PREFIX + (nextLineNo - 1)
802+
741803
/** next result variable number to use */
742804
private var nextVarNameNo = 0
743805

src/dotty/tools/dotc/repl/Interpreter.scala

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,15 @@ object Interpreter {
2525
trait Interpreter {
2626
import Interpreter._
2727

28-
/** Interpret one line of input. All feedback, including parse errors
29-
* and evaluation results, are printed via the context's reporter.
30-
* reporter. Values defined are available for future interpreted strings.
31-
*/
28+
/** Interpret one line of input. All feedback, including parse errors and
29+
* evaluation results, are printed via the context's reporter. Values
30+
* defined are available for future interpreted strings.
31+
*/
3232
def interpret(line: String)(implicit ctx: Context): Result
3333

34+
/** Tries to bind an id to a value, returns the outcome of trying to bind */
35+
def bind(id: String, boundType: String, value: AnyRef)(implicit ctx: Context): Result
36+
3437
/** Suppress output during evaluation of `operation`. */
3538
def beQuietDuring[T](operation: => T): T
3639

src/dotty/tools/dotc/repl/InterpreterLoop.scala

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,6 @@ class InterpreterLoop(compiler: Compiler, config: REPL.Config)(implicit ctx: Con
6868

6969
val version = ".next (pre-alpha)"
7070

71-
/** The first interpreted command always takes a couple of seconds
72-
* due to classloading. To bridge the gap, we warm up the interpreter
73-
* by letting it interpret a dummy line while waiting for the first
74-
* line of input to be entered.
75-
*/
76-
def firstLine(): String = {
77-
interpreter.beQuietDuring(
78-
interpreter.interpret("val theAnswerToLifeInTheUniverseAndEverything = 21 * 2"))
79-
in.readLine(prompt)
80-
}
81-
8271
/** The main read-eval-print loop for the interpreter. It calls
8372
* `command()` for each line of input.
8473
*/
@@ -177,6 +166,15 @@ class InterpreterLoop(compiler: Compiler, config: REPL.Config)(implicit ctx: Con
177166
(true, shouldReplay)
178167
}
179168

169+
def silentlyRun(cmds: List[String]): Unit = cmds.foreach { cmd =>
170+
interpreter.beQuietDuring(interpreter.interpret(cmd))
171+
}
172+
173+
def silentlyBind(values: Array[(String, Any)]): Unit = values.foreach { case (id, value) =>
174+
interpreter.beQuietDuring(
175+
interpreter.bind(id, value.asInstanceOf[AnyRef].getClass.getName, value.asInstanceOf[AnyRef]))
176+
}
177+
180178
/** Interpret expressions starting with the first line.
181179
* Read lines until a complete compilation unit is available
182180
* 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
207205
try {
208206
if (!ctx.reporter.hasErrors) { // if there are already errors, no sense to continue
209207
printWelcome()
210-
repl(firstLine())
208+
silentlyRun(config.initialCommands)
209+
silentlyBind(config.boundValues)
210+
repl(in.readLine(prompt))
211+
silentlyRun(config.cleanupCommands)
211212
}
212213
} finally {
213214
closeInterpreter()

src/dotty/tools/dotc/repl/REPL.scala

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,34 @@ object REPL {
5252

5353
def context(ctx: Context): Context = ctx
5454

55+
/** The first interpreted commands always take a couple of seconds due to
56+
* classloading. To bridge the gap, we warm up the interpreter by letting
57+
* it interpret at least a dummy line while waiting for the first line of
58+
* input to be entered.
59+
*/
60+
val initialCommands: List[String] =
61+
"val theAnswerToLifeInTheUniverseAndEverything = 21 * 2" :: Nil
62+
63+
/** Before exiting, the interpreter will also run the cleanup commands
64+
* issued in the variable below. This is useful if your REPL creates
65+
* things during its run that should be dealt with before shutdown.
66+
*/
67+
val cleanupCommands: List[String] = Nil
68+
69+
/** Initial values in the REPL can also be bound from runtime. Override
70+
* this variable in the following manner to bind a variable at the start
71+
* of the REPL session:
72+
*
73+
* {{{
74+
* override val boundValues = Array("exampleList" -> List(1, 1, 2, 3, 5))
75+
* }}}
76+
*
77+
* This is useful if you've integrated the REPL as part of your project
78+
* and already have objects available during runtime that you'd like to
79+
* inspect.
80+
*/
81+
val boundValues: Array[(String, Any)] = Array.empty[(String, Any)]
82+
5583
/** The default input reader */
5684
def input(in: Interpreter)(implicit ctx: Context): InteractiveReader = {
5785
val emacsShell = System.getProperty("env.emacs", "") != ""

0 commit comments

Comments
 (0)