Skip to content

Commit d4d9009

Browse files
committed
Add ability to bind values from runtime
1 parent dded547 commit d4d9009

File tree

4 files changed

+78
-4
lines changed

4 files changed

+78
-4
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: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,11 @@ class InterpreterLoop(compiler: Compiler, config: REPL.Config)(implicit ctx: Con
170170
interpreter.beQuietDuring(interpreter.interpret(cmd))
171171
}
172172

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+
173178
/** Interpret expressions starting with the first line.
174179
* Read lines until a complete compilation unit is available
175180
* or until a syntax error has been seen. If a full unit is
@@ -201,6 +206,7 @@ class InterpreterLoop(compiler: Compiler, config: REPL.Config)(implicit ctx: Con
201206
if (!ctx.reporter.hasErrors) { // if there are already errors, no sense to continue
202207
printWelcome()
203208
silentlyRun(config.initialCommands)
209+
silentlyBind(config.boundValues)
204210
repl(in.readLine(prompt))
205211
silentlyRun(config.cleanupCommands)
206212
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ object REPL {
6565
*/
6666
val cleanupCommands: List[String] = Nil
6767

68+
/** We also allow binding initial values */
69+
val boundValues: Array[(String, Any)] = Array.empty[(String, Any)]
70+
6871
/** The default input reader */
6972
def input(in: Interpreter)(implicit ctx: Context): InteractiveReader = {
7073
val emacsShell = System.getProperty("env.emacs", "") != ""

0 commit comments

Comments
 (0)