summaryrefslogtreecommitdiff
path: root/src/repl/scala/tools/nsc/interpreter/IMain.scala
diff options
context:
space:
mode:
authorSom Snytt <som.snytt@gmail.com>2015-09-13 17:22:11 -0700
committerSom Snytt <som.snytt@gmail.com>2016-05-19 11:27:02 -0700
commit3cddeaa525fd6fe9860a27019fdf484297a8d3dd (patch)
tree76d1bc7142e7f28e6290d25c51c82d2df072ab41 /src/repl/scala/tools/nsc/interpreter/IMain.scala
parent15189d14953335f7a3a8310861d045d21ab22d48 (diff)
downloadscala-3cddeaa525fd6fe9860a27019fdf484297a8d3dd.tar.gz
scala-3cddeaa525fd6fe9860a27019fdf484297a8d3dd.tar.bz2
scala-3cddeaa525fd6fe9860a27019fdf484297a8d3dd.zip
SI-7916: ScriptEngine support
Refactor the ScriptEngine support to an adaptor atop the IMain API. Allow references to resolve to context attributes. (The attributes must be defined at compilation time, though they may resolve to updated values at evaluation time.) This means that attributes are not bound statically in REPL history. In particular, we forgo the trick of binding attributes named "name: Type" as typed values. Instead, an `x` bound in dynamic context is injected into the script as a dynamic selection `$ctx.x` where `ctx` performs the look-up in the script context. When a compiled script is re-evaluated, a new instance of the script class is created and defined symbols are rebound. The context stdout writer is handled with `Console.withOut`, with bytes decoded using the default charset. Compilation errors are thrown as ScriptException with the first reported error. This commit doesn't attempt dynamic selection from objects in context. Currently, script must cast.
Diffstat (limited to 'src/repl/scala/tools/nsc/interpreter/IMain.scala')
-rw-r--r--src/repl/scala/tools/nsc/interpreter/IMain.scala203
1 files changed, 41 insertions, 162 deletions
diff --git a/src/repl/scala/tools/nsc/interpreter/IMain.scala b/src/repl/scala/tools/nsc/interpreter/IMain.scala
index 8c91242b36..a42a12a6fc 100644
--- a/src/repl/scala/tools/nsc/interpreter/IMain.scala
+++ b/src/repl/scala/tools/nsc/interpreter/IMain.scala
@@ -20,7 +20,6 @@ import scala.tools.nsc.typechecker.{StructuredTypeStrings, TypeStrings}
import scala.tools.nsc.util._
import ScalaClassLoader.URLClassLoader
import scala.tools.nsc.util.Exceptional.unwrap
-import javax.script.{AbstractScriptEngine, Bindings, Compilable, CompiledScript, ScriptContext, ScriptEngine, ScriptEngineFactory, ScriptException}
import java.net.URL
import scala.tools.util.PathResolver
@@ -56,10 +55,11 @@ import scala.tools.util.PathResolver
* @author Moez A. Abdel-Gawad
* @author Lex Spoon
*/
-class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Settings, protected val out: JPrintWriter) extends AbstractScriptEngine with Compilable with Imports with PresentationCompilation {
+class IMain(initialSettings: Settings, protected val out: JPrintWriter) extends Imports with PresentationCompilation {
imain =>
- setBindings(createBindings, ScriptContext.ENGINE_SCOPE)
+ def this(initialSettings: Settings) = this(initialSettings, IMain.defaultOut)
+
object replOutput extends ReplOutput(settings.Yreploutdir) { }
@deprecated("Use replOutput.dir instead", "2.11.0")
@@ -104,13 +104,6 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set
finally if (!saved) settings.nowarn.value = false
}
- /** construct an interpreter that reports to Console */
- def this(settings: Settings, out: JPrintWriter) = this(null, settings, out)
- def this(factory: ScriptEngineFactory, settings: Settings) = this(factory, settings, new NewLinePrintWriter(new ConsoleWriter, true))
- def this(settings: Settings) = this(settings, new NewLinePrintWriter(new ConsoleWriter, true))
- def this(factory: ScriptEngineFactory) = this(factory, new Settings())
- def this() = this(new Settings())
-
// the expanded prompt but without color escapes and without leading newline, for purposes of indenting
lazy val formatting = Formatting.forPrompt(replProps.promptText)
lazy val reporter: ReplReporter = new ReplReporter(this)
@@ -464,7 +457,7 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set
pos
}
- private[interpreter] def requestFromLine(line: String, synthetic: Boolean): Either[IR.Result, Request] = {
+ private[interpreter] def requestFromLine(line: String, synthetic: Boolean = false): Either[IR.Result, Request] = {
val content = line
val trees: List[global.Tree] = parse(content) match {
@@ -559,77 +552,8 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set
*/
def interpret(line: String): IR.Result = interpret(line, synthetic = false)
def interpretSynthetic(line: String): IR.Result = interpret(line, synthetic = true)
- def interpret(line: String, synthetic: Boolean): IR.Result = compile(line, synthetic) match {
- case Left(result) => result
- case Right(req) => new WrappedRequest(req).loadAndRunReq
- }
-
- private def compile(line: String, synthetic: Boolean): Either[IR.Result, Request] = {
- if (global == null) Left(IR.Error)
- else requestFromLine(line, synthetic) match {
- case Left(result) => Left(result)
- case Right(req) =>
- // null indicates a disallowed statement type; otherwise compile and
- // fail if false (implying e.g. a type error)
- if (req == null || !req.compile) Left(IR.Error) else Right(req)
- }
- }
-
- var code = ""
- var bound = false
- def compiled(script: String): CompiledScript = {
- if (!bound) {
- quietBind("engine" -> this.asInstanceOf[ScriptEngine])
- bound = true
- }
- val cat = code + script
- compile(cat, false) match {
- case Left(result) => result match {
- case IR.Incomplete => {
- code = cat + "\n"
- new CompiledScript {
- def eval(context: ScriptContext): Object = null
- def getEngine: ScriptEngine = IMain.this
- }
- }
- case _ => {
- code = ""
- throw new ScriptException("compile-time error")
- }
- }
- case Right(req) => {
- code = ""
- new WrappedRequest(req)
- }
- }
- }
-
- private class WrappedRequest(val req: Request) extends CompiledScript {
- var recorded = false
-
- /** In Java we would have to wrap any checked exception in the declared
- * ScriptException. Runtime exceptions and errors would be ok and would
- * not need to be caught. So let us do the same in Scala : catch and
- * wrap any checked exception, and let runtime exceptions and errors
- * escape. We could have wrapped runtime exceptions just like other
- * exceptions in ScriptException, this is a choice.
- */
- @throws[ScriptException]
- def eval(context: ScriptContext): Object = {
- val result = req.lineRep.evalEither match {
- case Left(e: RuntimeException) => throw e
- case Left(e: Exception) => throw new ScriptException(e)
- case Left(e) => throw e
- case Right(result) => result.asInstanceOf[Object]
- }
- if (!recorded) {
- recordRequest(req)
- recorded = true
- }
- result
- }
-
- def loadAndRunReq = classLoader.asContext {
+ def interpret(line: String, synthetic: Boolean): IR.Result = {
+ def loadAndRunReq(req: Request) = classLoader.asContext {
val (result, succeeded) = req.loadAndRun
/** To our displeasure, ConsoleReporter offers only printMessage,
@@ -654,12 +578,32 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set
}
}
- def getEngine: ScriptEngine = IMain.this
+ compile(line, synthetic) match {
+ case Left(result) => result
+ case Right(req) => loadAndRunReq(req)
+ }
+ }
+
+ // create a Request and compile it
+ private[interpreter] def compile(line: String, synthetic: Boolean): Either[IR.Result, Request] = {
+ if (global == null) Left(IR.Error)
+ else requestFromLine(line, synthetic) match {
+ case Right(null) => Left(IR.Error) // disallowed statement type
+ case Right(req) if !req.compile => Left(IR.Error) // compile error
+ case ok @ Right(req) => ok
+ case err @ Left(result) => err
+ }
}
/** Bind a specified name to a specified value. The name may
* later be used by expressions passed to interpret.
*
+ * A fresh `ReadEvalPrint`, which defines a `line` package, is used to compile
+ * a custom `eval` object that wraps the bound value.
+ *
+ * If the bound value is successfully installed, then bind the name
+ * by interpreting `val name = $line42.$eval.value`.
+ *
* @param name the variable name to bind
* @param boundType the type of the variable, as a string
* @param value the object value to bind to it
@@ -667,22 +611,22 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set
*/
def bind(name: String, boundType: String, value: Any, modifiers: List[String] = Nil): IR.Result = {
val bindRep = new ReadEvalPrint()
- bindRep.compile("""
- |object %s {
- | var value: %s = _
- | def set(x: Any) = value = x.asInstanceOf[%s]
+ bindRep.compile(s"""
+ |object ${bindRep.evalName} {
+ | var value: $boundType = _
+ | def set(x: Any) = value = x.asInstanceOf[$boundType]
|}
- """.stripMargin.format(bindRep.evalName, boundType, boundType)
- )
+ """.stripMargin
+ )
bindRep.callEither("set", value) match {
case Left(ex) =>
repldbg("Set failed in bind(%s, %s, %s)".format(name, boundType, value))
repldbg(util.stackTraceString(ex))
IR.Error
-
case Right(_) =>
- val line = "%sval %s = %s.value".format(modifiers map (_ + " ") mkString, name, bindRep.evalPath)
- repldbg("Interpreting: " + line)
+ val mods = if (modifiers.isEmpty) "" else modifiers.mkString("", " ", " ")
+ val line = s"${mods}val $name = ${ bindRep.evalPath }.value"
+ repldbg(s"Interpreting: $line")
interpret(line)
}
}
@@ -1046,31 +990,6 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set
override def toString = "Request(line=%s, %s trees)".format(line, trees.size)
}
- def createBindings: Bindings = new IBindings {
- override def put(name: String, value: Object): Object = {
- val n = name.indexOf(":")
- val p: NamedParam = if (n < 0) (name, value) else {
- val nme = name.substring(0, n).trim
- val tpe = name.substring(n + 1).trim
- NamedParamClass(nme, tpe, value)
- }
- if (!p.name.startsWith("javax.script")) bind(p)
- null
- }
- }
-
- @throws[ScriptException]
- def compile(script: String): CompiledScript = eval("new javax.script.CompiledScript { def eval(context: javax.script.ScriptContext): Object = { " + script + " }.asInstanceOf[Object]; def getEngine: javax.script.ScriptEngine = engine }").asInstanceOf[CompiledScript]
-
- @throws[ScriptException]
- def compile(reader: java.io.Reader): CompiledScript = compile(stringFromReader(reader))
-
- @throws[ScriptException]
- def eval(script: String, context: ScriptContext): Object = compiled(script).eval(context)
-
- @throws[ScriptException]
- def eval(reader: java.io.Reader, context: ScriptContext): Object = eval(stringFromReader(reader), context)
-
override def finalize = close
/** Returns the name of the most recent interpreter result.
@@ -1267,54 +1186,9 @@ class IMain(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Set
/** Utility methods for the Interpreter. */
object IMain {
- import java.util.Arrays.{ asList => asJavaList }
/** Dummy identifier fragement inserted at the cursor before presentation compilation. Needed to support completion of `global.def<TAB>` */
val DummyCursorFragment = "_CURSOR_"
- class Factory extends ScriptEngineFactory {
- @BeanProperty
- val engineName = "Scala Interpreter"
-
- @BeanProperty
- val engineVersion = "1.0"
-
- @BeanProperty
- val extensions: JList[String] = asJavaList("scala")
-
- @BeanProperty
- val languageName = "Scala"
-
- @BeanProperty
- val languageVersion = scala.util.Properties.versionString
-
- def getMethodCallSyntax(obj: String, m: String, args: String*): String = null
-
- @BeanProperty
- val mimeTypes: JList[String] = asJavaList("application/x-scala")
-
- @BeanProperty
- val names: JList[String] = asJavaList("scala")
-
- def getOutputStatement(toDisplay: String): String = null
-
- def getParameter(key: String): Object = key match {
- case ScriptEngine.ENGINE => engineName
- case ScriptEngine.ENGINE_VERSION => engineVersion
- case ScriptEngine.LANGUAGE => languageName
- case ScriptEngine.LANGUAGE_VERSION => languageVersion
- case ScriptEngine.NAME => names.get(0)
- case _ => null
- }
-
- def getProgram(statements: String*): String = null
-
- def getScriptEngine: ScriptEngine = {
- val settings = new Settings()
- settings.usemanifestcp.value = true
- new IMain(this, settings)
- }
- }
-
// The two name forms this is catching are the two sides of this assignment:
//
// $line3.$read.$iw.$iw.Bippy =
@@ -1366,5 +1240,10 @@ object IMain {
def stripImpl(str: String): String = naming.unmangle(str)
}
+ private[interpreter] def defaultSettings = new Settings()
+ private[scala] def defaultOut = new NewLinePrintWriter(new ConsoleWriter, true)
+
+ /** construct an interpreter that reports to Console */
+ def apply(initialSettings: Settings = defaultSettings, out: JPrintWriter = defaultOut) = new IMain(initialSettings, out)
}