aboutsummaryrefslogtreecommitdiff
path: root/compiler/src/dotty/tools/dotc/repl/CompilingInterpreter.scala
diff options
context:
space:
mode:
authorFelix Mulder <felix.mulder@gmail.com>2016-11-02 11:08:28 +0100
committerGuillaume Martres <smarter@ubuntu.com>2016-11-22 01:35:07 +0100
commit8a61ff432543a29234193cd1f7c14abd3f3d31a0 (patch)
treea8147561d307af862c295cfc8100d271063bb0dd /compiler/src/dotty/tools/dotc/repl/CompilingInterpreter.scala
parent6a455fe6da5ff9c741d91279a2dc6fe2fb1b472f (diff)
downloaddotty-8a61ff432543a29234193cd1f7c14abd3f3d31a0.tar.gz
dotty-8a61ff432543a29234193cd1f7c14abd3f3d31a0.tar.bz2
dotty-8a61ff432543a29234193cd1f7c14abd3f3d31a0.zip
Move compiler and compiler tests to compiler dir
Diffstat (limited to 'compiler/src/dotty/tools/dotc/repl/CompilingInterpreter.scala')
-rw-r--r--compiler/src/dotty/tools/dotc/repl/CompilingInterpreter.scala966
1 files changed, 966 insertions, 0 deletions
diff --git a/compiler/src/dotty/tools/dotc/repl/CompilingInterpreter.scala b/compiler/src/dotty/tools/dotc/repl/CompilingInterpreter.scala
new file mode 100644
index 000000000..5b3669d5e
--- /dev/null
+++ b/compiler/src/dotty/tools/dotc/repl/CompilingInterpreter.scala
@@ -0,0 +1,966 @@
+package dotty.tools
+package dotc
+package repl
+
+import java.io.{
+ File, PrintWriter, PrintStream, StringWriter, Writer, OutputStream,
+ ByteArrayOutputStream => ByteOutputStream
+}
+import java.lang.{Class, ClassLoader}
+import java.net.{URL, URLClassLoader}
+
+import scala.collection.immutable.ListSet
+import scala.collection.mutable
+import scala.collection.mutable.{ListBuffer, HashSet, ArrayBuffer}
+
+//import ast.parser.SyntaxAnalyzer
+import io.{PlainFile, VirtualDirectory}
+import scala.reflect.io.{PlainDirectory, Directory}
+import reporting.{ConsoleReporter, Reporter}
+import core.Flags
+import util.{SourceFile, NameTransformer}
+import io.ClassPath
+import ast.Trees._
+import parsing.Parsers._
+import core._
+import dotty.tools.backend.jvm.GenBCode
+import Symbols._, Types._, Contexts._, StdNames._, Names._, NameOps._
+import Decorators._
+import scala.util.control.NonFatal
+import printing.SyntaxHighlighting
+
+/** An interpreter for Scala code which is based on the `dotc` compiler.
+ *
+ * The overall approach is based on compiling the requested code and then
+ * using a Java classloader and Java reflection to run the code
+ * and access its results.
+ *
+ * In more detail, a single compiler instance is used
+ * to accumulate all successfully compiled or interpreted Scala code. To
+ * "interpret" a line of code, the compiler generates a fresh object that
+ * includes the line of code and which has public definition(s) to export
+ * all variables defined by that code. To extract the result of an
+ * interpreted line to show the user, a second "result object" is created
+ * which imports the variables exported by the above object and then
+ * exports a single definition named "result". To accommodate user expressions
+ * that read from variables or methods defined in previous statements, "import"
+ * statements are used.
+ *
+ * This interpreter shares the strengths and weaknesses of using the
+ * full compiler-to-Java. The main strength is that interpreted code
+ * behaves exactly as does compiled code, including running at full speed.
+ * The main weakness is that redefining classes and methods is not handled
+ * properly, because rebinding at the Java level is technically difficult.
+ *
+ * @author Moez A. Abdel-Gawad
+ * @author Lex Spoon
+ * @author Martin Odersky
+ *
+ * @param out The output to use for diagnostics
+ * @param ictx The context to use for initialization of the interpreter,
+ * needed to access the current classpath.
+ */
+class CompilingInterpreter(
+ out: PrintWriter,
+ ictx: Context,
+ parentClassLoader: Option[ClassLoader]
+) extends Compiler with Interpreter {
+ import ast.untpd._
+ import CompilingInterpreter._
+
+ ictx.base.initialize()(ictx)
+
+ /** directory to save .class files to */
+ val virtualDirectory =
+ if (ictx.settings.d.isDefault(ictx)) new VirtualDirectory("(memory)", None)
+ else new PlainDirectory(new Directory(new java.io.File(ictx.settings.d.value(ictx)))) // for now, to help debugging
+
+ /** A GenBCode phase that uses `virtualDirectory` for its output */
+ private class REPLGenBCode extends GenBCode {
+ override def outputDir(implicit ctx: Context) = virtualDirectory
+ }
+
+ /** Phases of this compiler use `REPLGenBCode` instead of `GenBCode`. */
+ override def phases = Phases.replace(
+ classOf[GenBCode], _ => new REPLGenBCode :: Nil, super.phases)
+
+ /** whether to print out result lines */
+ private var printResults: Boolean = true
+ private var delayOutput: Boolean = false
+
+ val previousOutput = ListBuffer.empty[String]
+
+ override def lastOutput() = {
+ val prev = previousOutput.toList
+ previousOutput.clear()
+ prev
+ }
+
+ override def delayOutputDuring[T](operation: => T): T = {
+ val old = delayOutput
+ try {
+ delayOutput = true
+ operation
+ } finally {
+ delayOutput = old
+ }
+ }
+
+ /** Temporarily be quiet */
+ override def beQuietDuring[T](operation: => T): T = {
+ val wasPrinting = printResults
+ try {
+ printResults = false
+ operation
+ } finally {
+ printResults = wasPrinting
+ }
+ }
+
+ private def newReporter =
+ new ConsoleReporter(Console.in, out) {
+ override def printMessage(msg: String) =
+ if (!delayOutput) {
+ out.print(/*clean*/(msg) + "\n")
+ // Suppress clean for now for compiler messages
+ // Otherwise we will completely delete all references to
+ // line$object$ module classes. The previous interpreter did not
+ // have the project because the module class was written without the final `$'
+ // and therefore escaped the purge. We can turn this back on once
+ // we drop the final `$' from module classes.
+ out.flush()
+ } else {
+ previousOutput += (/*clean*/(msg) + "\n")
+ }
+ }
+
+ /** the previous requests this interpreter has processed */
+ private val prevRequests = new ArrayBuffer[Request]()
+
+ /** the compiler's classpath, as URL's */
+ val compilerClasspath: List[URL] = ictx.platform.classPath(ictx).asURLs
+
+ /* A single class loader is used for all commands interpreted by this Interpreter.
+ It would also be possible to create a new class loader for each command
+ to interpret. The advantages of the current approach are:
+
+ - Expressions are only evaluated one time. This is especially
+ significant for I/O, e.g. "val x = Console.readLine"
+
+ The main disadvantage is:
+
+ - Objects, classes, and methods cannot be rebound. Instead, definitions
+ shadow the old ones, and old code objects refer to the old
+ definitions.
+ */
+ /** class loader used to load compiled code */
+ val classLoader: ClassLoader = {
+ lazy val parent = new URLClassLoader(compilerClasspath.toArray,
+ classOf[Interpreter].getClassLoader)
+
+ new AbstractFileClassLoader(virtualDirectory, parentClassLoader.getOrElse(parent))
+ }
+
+ // Set the current Java "context" class loader to this interpreter's class loader
+ Thread.currentThread.setContextClassLoader(classLoader)
+
+ /** Parse a line into a sequence of trees. Returns None if the input is incomplete. */
+ private def parse(line: String)(implicit ctx: Context): Option[List[Tree]] = {
+ var justNeedsMore = false
+ val reporter = newReporter
+ reporter.withIncompleteHandler { _ => _ => justNeedsMore = true } {
+ // simple parse: just parse it, nothing else
+ def simpleParse(code: String)(implicit ctx: Context): List[Tree] = {
+ val source = new SourceFile("<console>", code.toCharArray())
+ val parser = new Parser(source)
+ val (selfDef, stats) = parser.templateStatSeq
+ stats
+ }
+ val trees = simpleParse(line)(ctx.fresh.setReporter(reporter))
+ if (reporter.hasErrors) {
+ Some(Nil) // the result did not parse, so stop
+ } else if (justNeedsMore) {
+ None
+ } else {
+ Some(trees)
+ }
+ }
+ }
+
+ /** Compile a SourceFile. Returns the root context of the run that compiled the file.
+ */
+ def compileSources(sources: List[SourceFile])(implicit ctx: Context): Context = {
+ val reporter = newReporter
+ val run = newRun(ctx.fresh.setReporter(reporter))
+ run.compileSources(sources)
+ run.runContext
+ }
+
+ /** Compile a string. Returns true if there are no
+ * compilation errors, or false otherwise.
+ */
+ def compileString(code: String)(implicit ctx: Context): Boolean = {
+ val runCtx = compileSources(List(new SourceFile("<script>", code.toCharArray)))
+ !runCtx.reporter.hasErrors
+ }
+
+ override def interpret(line: String)(implicit ctx: Context): Interpreter.Result = {
+ // if (prevRequests.isEmpty)
+ // new Run(this) // initialize the compiler // (not sure this is needed)
+ // parse
+ parse(line) match {
+ case None => Interpreter.Incomplete
+ case Some(Nil) => Interpreter.Error // parse error or empty input
+ case Some(tree :: Nil) if tree.isTerm && !tree.isInstanceOf[Assign] =>
+ previousOutput.clear() // clear previous error reporting
+ interpret(s"val $newVarName =\n$line")
+ case Some(trees) =>
+ previousOutput.clear() // clear previous error reporting
+ val req = new Request(line, newLineName)
+ if (!req.compile())
+ Interpreter.Error // an error happened during compilation, e.g. a type error
+ else {
+ val (resultStrings, succeeded) = req.loadAndRun()
+ if (delayOutput)
+ previousOutput ++= resultStrings.map(clean)
+ else if (printResults || !succeeded)
+ resultStrings.foreach(x => out.print(clean(x)))
+ if (succeeded) {
+ prevRequests += req
+ Interpreter.Success
+ }
+ else Interpreter.Error
+ }
+ }
+ }
+
+ 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 */
+ def statement: Tree
+
+ /** The names defined previously and referred to in the statement */
+ def usedNames: List[Name]
+
+ /** The names defined in the statement */
+ val boundNames: List[Name]
+
+ /** Statement is an import that contains a wildcard */
+ val importsWildcard: Boolean
+
+ /** The names imported by the statement (if it is an import clause) */
+ val importedNames: Seq[Name]
+
+ /** Statement defines an implicit calue or method */
+ val definesImplicit: Boolean
+ }
+
+ /** One line of code submitted by the user for interpretation */
+ private class Request(val line: String, val lineName: String)(implicit ctx: Context) {
+ private val trees = {
+ val parsed = parse(line)
+ previousOutput.clear() // clear previous error reporting
+ parsed match {
+ case Some(ts) => ts
+ case None => Nil
+ }
+ }
+
+ /** name to use for the object that will compute "line" */
+ private def objectName = lineName + INTERPRETER_WRAPPER_SUFFIX
+
+ /** name of the object that retrieves the result from the above object */
+ private def resultObjectName = "RequestResult$" + objectName
+
+ private def chooseHandler(stat: Tree): StatementHandler = stat match {
+ case stat: DefDef => new DefHandler(stat)
+ case stat: ValDef => new ValHandler(stat)
+ case stat: PatDef => new PatHandler(stat)
+ case stat @ Assign(Ident(_), _) => new AssignHandler(stat)
+ case stat: ModuleDef => new ModuleHandler(stat)
+ case stat: TypeDef if stat.isClassDef => new ClassHandler(stat)
+ case stat: TypeDef => new TypeAliasHandler(stat)
+ case stat: Import => new ImportHandler(stat)
+// case DocDef(_, documented) => chooseHandler(documented)
+ case stat => new GenericHandler(stat)
+ }
+
+ private val handlers: List[StatementHandler] = trees.map(chooseHandler)
+
+ /** all (public) names defined by these statements */
+ private val boundNames = ListSet(handlers.flatMap(_.boundNames): _*).toList
+
+ /** list of names used by this expression */
+ private val usedNames: List[Name] = handlers.flatMap(_.usedNames)
+
+ private val (importsPreamble, importsTrailer, accessPath) =
+ importsCode(usedNames.toSet)
+
+ /** Code to access a variable with the specified name */
+ private def fullPath(vname: String): String = s"$objectName$accessPath.`$vname`"
+
+ /** Code to access a variable with the specified name */
+ private def fullPath(vname: Name): String = fullPath(vname.toString)
+
+ /** the line of code to compute */
+ private def toCompute = line
+
+ /** generate the source code for the object that computes this request
+ * TODO Reformulate in a functional way
+ */
+ private def objectSourceCode: String =
+ stringFrom { code =>
+ // header for the wrapper object
+ code.println(s"object $objectName{")
+ code.print(importsPreamble)
+ code.println(toCompute)
+ handlers.foreach(_.extraCodeToEvaluate(this,code))
+ code.println(importsTrailer)
+ //end the wrapper object
+ code.println(";}")
+ }
+
+ /** Types of variables defined by this request. They are computed
+ after compilation of the main object */
+ private var typeOf: Map[Name, String] = _
+
+ /** generate source code for the object that retrieves the result
+ from objectSourceCode */
+ private def resultObjectSourceCode: String =
+ stringFrom(code => {
+ code.println(s"object $resultObjectName")
+ code.println("{ val result: String = {")
+ code.println(s"$objectName$accessPath;") // evaluate the object, to make sure its constructor is run
+ code.print("(\"\"") // print an initial empty string, so later code can
+ // uniformly be: + morestuff
+ handlers.foreach(_.resultExtractionCode(this, code))
+ code.println("\n)}")
+ code.println(";}")
+ })
+
+
+ /** Compile the object file. Returns whether the compilation succeeded.
+ * If all goes well, the "types" map is computed. */
+ def compile(): Boolean = {
+ val compileCtx = compileSources(
+ List(new SourceFile("<console>", objectSourceCode.toCharArray)))
+ !compileCtx.reporter.hasErrors && {
+ this.typeOf = findTypes(compileCtx)
+ val resultCtx = compileSources(
+ List(new SourceFile("<console>", resultObjectSourceCode.toCharArray)))
+ !resultCtx.reporter.hasErrors
+ }
+ }
+
+ /** Dig the types of all bound variables out of the compiler run.
+ * TODO: Change the interface so that we typecheck, and then transform
+ * directly. Treating the compiler as less of a blackbox will require
+ * much less magic here.
+ */
+ private def findTypes(implicit ctx: Context): Map[Name, String] = {
+ def valAndVarNames = handlers.flatMap(_.valAndVarNames)
+ def defNames = handlers.flatMap(_.defNames)
+
+ def getTypes(names: List[Name], nameMap: Name => Name): Map[Name, String] = {
+ /** the outermost wrapper object */
+ val outerResObjSym: Symbol =
+ defn.EmptyPackageClass.info.decl(objectName.toTermName).symbol
+
+ /** the innermost object inside the wrapper, found by
+ * following accessPath into the outer one. */
+ val resObjSym =
+ (accessPath.split("\\.")).foldLeft(outerResObjSym) { (sym,str) =>
+ if (str.isEmpty) sym
+ else
+ ctx.atPhase(ctx.typerPhase.next) { implicit ctx =>
+ sym.info.member(str.toTermName).symbol
+ }
+ }
+
+ names.foldLeft(Map.empty[Name,String]) { (map, name) =>
+ val rawType =
+ ctx.atPhase(ctx.typerPhase.next) { implicit ctx =>
+ resObjSym.info.member(name).info
+ }
+
+ // the types are all =>T; remove the =>
+ val cleanedType = rawType.widenExpr
+
+ map + (name ->
+ ctx.atPhase(ctx.typerPhase.next) { implicit ctx =>
+ cleanedType.show
+ })
+ }
+ }
+
+ val names1 = getTypes(valAndVarNames, n => n.toTermName.fieldName)
+ val names2 = getTypes(defNames, identity)
+ names1 ++ names2
+ }
+
+ /** Sets both System.{out,err} and Console.{out,err} to supplied
+ * `os: OutputStream`
+ */
+ private def withOutput[T](os: ByteOutputStream)(op: ByteOutputStream => T) = {
+ val ps = new PrintStream(os)
+ val oldOut = System.out
+ val oldErr = System.err
+ System.setOut(ps)
+ System.setErr(ps)
+
+ try {
+ Console.withOut(os)(Console.withErr(os)(op(os)))
+ } finally {
+ System.setOut(oldOut)
+ System.setErr(oldErr)
+ }
+ }
+
+ /** load and run the code using reflection.
+ * @return A pair consisting of the run's result as a `List[String]`, and
+ * a boolean indicating whether the run succeeded without throwing
+ * an exception.
+ */
+ def loadAndRun(): (List[String], Boolean) = {
+ val interpreterResultObject: Class[_] =
+ Class.forName(resultObjectName, true, classLoader)
+ val valMethodRes: java.lang.reflect.Method =
+ interpreterResultObject.getMethod("result")
+ try {
+ withOutput(new ByteOutputStream) { ps =>
+ val rawRes = valMethodRes.invoke(interpreterResultObject).toString
+ val res =
+ if (ictx.useColors) new String(SyntaxHighlighting(rawRes).toArray)
+ else rawRes
+ val prints = ps.toString("utf-8")
+ val printList = if (prints != "") prints :: Nil else Nil
+
+ if (!delayOutput) out.print(prints)
+
+ (printList :+ res, true)
+ }
+ } catch {
+ case NonFatal(ex) =>
+ def cause(ex: Throwable): Throwable =
+ if (ex.getCause eq null) ex else cause(ex.getCause)
+ val orig = cause(ex)
+ (stringFrom(str => orig.printStackTrace(str)) :: Nil, false)
+ }
+ }
+
+ /** Compute imports that allow definitions from previous
+ * requests to be visible in a new request. Returns
+ * three pieces of related code as strings:
+ *
+ * 1. A _preamble_: An initial code fragment that should go before
+ * the code of the new request.
+ *
+ * 2. A _trailer_: A code fragment that should go after the code
+ * of the new request.
+ *
+ * 3. An _access path_ which can be traversed to access
+ * any bindings inside code wrapped by #1 and #2 .
+ *
+ * The argument is a set of Names that need to be imported.
+ *
+ * Limitations: This method is not as precise as it could be.
+ * (1) It does not process wildcard imports to see what exactly
+ * they import.
+ * (2) If it imports any names from a request, it imports all
+ * of them, which is not really necessary.
+ * (3) It imports multiple same-named implicits, but only the
+ * last one imported is actually usable.
+ */
+ private def importsCode(wanted: Set[Name]): (String, String, String) = {
+ /** Narrow down the list of requests from which imports
+ * should be taken. Removes requests which cannot contribute
+ * useful imports for the specified set of wanted names.
+ */
+ def reqsToUse: List[(Request, StatementInfo)] = {
+ /** Loop through a list of StatementHandlers and select
+ * which ones to keep. 'wanted' is the set of
+ * names that need to be imported.
+ */
+ def select(reqs: List[(Request, StatementInfo)], wanted: Set[Name]): List[(Request, StatementInfo)] = {
+ reqs match {
+ case Nil => Nil
+
+ case (req, handler) :: rest =>
+ val keepit =
+ (handler.definesImplicit ||
+ handler.importsWildcard ||
+ handler.importedNames.exists(wanted.contains(_)) ||
+ handler.boundNames.exists(wanted.contains(_)))
+
+ val newWanted =
+ if (keepit) {
+ (wanted
+ ++ handler.usedNames
+ -- handler.boundNames
+ -- handler.importedNames)
+ } else {
+ wanted
+ }
+
+ val restToKeep = select(rest, newWanted)
+
+ if (keepit)
+ (req, handler) :: restToKeep
+ else
+ restToKeep
+ }
+ }
+
+ val rhpairs = for {
+ req <- prevRequests.toList.reverse
+ handler <- req.handlers
+ } yield (req, handler)
+
+ select(rhpairs, wanted).reverse
+ }
+
+ val preamble = new StringBuffer
+ val trailingBraces = new StringBuffer
+ val accessPath = new StringBuffer
+ val impname = INTERPRETER_IMPORT_WRAPPER
+ val currentImps = mutable.Set[Name]()
+
+ // add code for a new object to hold some imports
+ def addWrapper(): Unit = {
+ preamble.append("object " + impname + "{\n")
+ trailingBraces.append("}\n")
+ accessPath.append("." + impname)
+ currentImps.clear
+ }
+
+ addWrapper()
+
+ // loop through previous requests, adding imports
+ // for each one
+ for ((req, handler) <- reqsToUse) {
+ // If the user entered an import, then just use it
+
+ // add an import wrapping level if the import might
+ // conflict with some other import
+ if (handler.importsWildcard ||
+ currentImps.exists(handler.importedNames.contains))
+ if (!currentImps.isEmpty)
+ addWrapper()
+
+ if (handler.statement.isInstanceOf[Import])
+ preamble.append(handler.statement.show + ";\n")
+
+ // give wildcard imports a import wrapper all to their own
+ if (handler.importsWildcard)
+ addWrapper()
+ else
+ currentImps ++= handler.importedNames
+
+ // For other requests, import each bound variable.
+ // import them explicitly instead of with _, so that
+ // ambiguity errors will not be generated. Also, quote
+ // the name of the variable, so that we don't need to
+ // handle quoting keywords separately.
+ for (imv <- handler.boundNames) {
+ if (currentImps.contains(imv))
+ addWrapper()
+ preamble.append("import ")
+ preamble.append(req.objectName + req.accessPath + ".`" + imv + "`;\n")
+ currentImps += imv
+ }
+ }
+
+ addWrapper() // Add one extra wrapper, to prevent warnings
+ // in the frequent case of redefining
+ // the value bound in the last interpreter
+ // request.
+
+ (preamble.toString, trailingBraces.toString, accessPath.toString)
+ }
+
+ // ------ Handlers ------------------------------------------
+
+ /** Class to handle one statement among all the statements included
+ * in a single interpreter request.
+ */
+ private sealed abstract class StatementHandler(val statement: Tree) extends StatementInfo {
+ val usedNames: List[Name] = {
+ val ivt = new UntypedTreeAccumulator[mutable.Set[Name]] {
+ override def apply(ns: mutable.Set[Name], tree: Tree)(implicit ctx: Context) =
+ tree match {
+ case Ident(name) => ns += name
+ case _ => foldOver(ns, tree)
+ }
+ }
+ ivt.foldOver(HashSet(), statement).toList
+ }
+ val boundNames: List[Name] = Nil
+ def valAndVarNames: List[Name] = Nil
+ def defNames: List[Name] = Nil
+ val importsWildcard = false
+ val importedNames: Seq[Name] = Nil
+ val definesImplicit = statement match {
+ case tree: MemberDef => tree.mods.is(Flags.Implicit)
+ case _ => false
+ }
+
+ def extraCodeToEvaluate(req: Request, code: PrintWriter) = {}
+ def resultExtractionCode(req: Request, code: PrintWriter) = {}
+ }
+
+ private class GenericHandler(statement: Tree) extends StatementHandler(statement)
+
+ private abstract class ValOrPatHandler(statement: Tree)
+ extends StatementHandler(statement) {
+ override val boundNames: List[Name] = _boundNames
+ override def valAndVarNames = boundNames
+
+ override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
+ if (!shouldShowResult(req)) return
+ val resultExtractors = boundNames.map(name => resultExtractor(req, name))
+ code.print(resultExtractors.mkString(""))
+ }
+
+ private def resultExtractor(req: Request, varName: Name): String = {
+ val prettyName = varName.decode
+ val varType = string2code(req.typeOf(varName))
+ val fullPath = req.fullPath(varName)
+
+ s""" + "$prettyName: $varType = " + {
+ | if ($fullPath.asInstanceOf[AnyRef] != null) {
+ | (if ($fullPath.toString().contains('\\n')) "\\n" else "") +
+ | $fullPath.toString() + "\\n"
+ | } else {
+ | "null\\n"
+ | }
+ |}""".stripMargin
+ }
+
+ protected def _boundNames: List[Name]
+ protected def shouldShowResult(req: Request): Boolean
+ }
+
+ private class ValHandler(statement: ValDef) extends ValOrPatHandler(statement) {
+ override def _boundNames = List(statement.name)
+
+ override def shouldShowResult(req: Request): Boolean =
+ !statement.mods.is(Flags.AccessFlags) &&
+ !(isGeneratedVarName(statement.name.toString) &&
+ req.typeOf(statement.name.encode) == "Unit")
+ }
+
+
+ private class PatHandler(statement: PatDef) extends ValOrPatHandler(statement) {
+ override def _boundNames = statement.pats.flatMap(findVariableNames)
+
+ override def shouldShowResult(req: Request): Boolean =
+ !statement.mods.is(Flags.AccessFlags)
+
+ private def findVariableNames(tree: Tree): List[Name] = tree match {
+ case Ident(name) if name.toString != "_" => List(name)
+ case _ => VariableNameFinder(Nil, tree).reverse
+ }
+
+ private object VariableNameFinder extends UntypedDeepFolder[List[Name]](
+ (acc: List[Name], t: Tree) => t match {
+ case _: BackquotedIdent => acc
+ case Ident(name) if name.isVariableName && name.toString != "_" => name :: acc
+ case Bind(name, _) if name.isVariableName => name :: acc
+ case _ => acc
+ }
+ )
+ }
+
+ private class DefHandler(defDef: DefDef) extends StatementHandler(defDef) {
+ override val boundNames = List(defDef.name)
+ override def defNames = boundNames
+
+ override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
+ if (!defDef.mods.is(Flags.AccessFlags))
+ code.print("+\"" + string2code(defDef.name.toString) + ": " +
+ string2code(req.typeOf(defDef.name)) + "\\n\"")
+ }
+ }
+
+ private class AssignHandler(statement: Assign) extends StatementHandler(statement) {
+ val lhs = statement.lhs.asInstanceOf[Ident] // an unfortunate limitation
+
+ val helperName = newInternalVarName().toTermName
+ override val valAndVarNames = List(helperName)
+
+ override def extraCodeToEvaluate(req: Request, code: PrintWriter): Unit = {
+ code.println(i"val $helperName = ${statement.lhs};")
+ }
+
+ /** Print out lhs instead of the generated varName */
+ override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
+ code.print(" + \"" + lhs.show + ": " +
+ string2code(req.typeOf(helperName.encode)) +
+ " = \" + " +
+ string2code(req.fullPath(helperName))
+ + " + \"\\n\"")
+ }
+ }
+
+ private class ModuleHandler(module: ModuleDef) extends StatementHandler(module) {
+ override val boundNames = List(module.name)
+
+ override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
+ code.println(" + \"defined module " +
+ string2code(module.name.toString)
+ + "\\n\"")
+ }
+ }
+
+ private class ClassHandler(classdef: TypeDef)
+ extends StatementHandler(classdef) {
+ override val boundNames =
+ List(classdef.name) :::
+ (if (classdef.mods.is(Flags.Case))
+ List(classdef.name.toTermName)
+ else
+ Nil)
+
+ // TODO: MemberDef.keyword does not include "trait";
+ // otherwise it could be used here
+ def keyword: String =
+ if (classdef.mods.is(Flags.Trait)) "trait" else "class"
+
+ override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
+ code.print(
+ " + \"defined " +
+ keyword +
+ " " +
+ string2code(classdef.name.toString) +
+ "\\n\"")
+ }
+ }
+
+ private class TypeAliasHandler(typeDef: TypeDef)
+ extends StatementHandler(typeDef) {
+ override val boundNames =
+ if (!typeDef.mods.is(Flags.AccessFlags) && !typeDef.rhs.isInstanceOf[TypeBoundsTree])
+ List(typeDef.name)
+ else
+ Nil
+
+ override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
+ code.println(" + \"defined type alias " +
+ string2code(typeDef.name.toString) + "\\n\"")
+ }
+ }
+
+ private class ImportHandler(imp: Import) extends StatementHandler(imp) {
+ override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
+ code.println("+ \"" + imp.show + "\\n\"")
+ }
+
+ def isWildcardSelector(tree: Tree) = tree match {
+ case Ident(nme.USCOREkw) => true
+ case _ => false
+ }
+
+ /** Whether this import includes a wildcard import */
+ override val importsWildcard = imp.selectors.exists(isWildcardSelector)
+
+ /** The individual names imported by this statement */
+ override val importedNames: Seq[Name] =
+ imp.selectors.filterNot(isWildcardSelector).flatMap {
+ case sel: RefTree => List(sel.name.toTypeName, sel.name.toTermName)
+ case _ => Nil
+ }
+ }
+
+ } // end Request
+
+ // ------- String handling ----------------------------------
+
+ /** next line number to use */
+ private var nextLineNo = 0
+
+ /** allocate a fresh line name */
+ private def newLineName = {
+ val num = nextLineNo
+ nextLineNo += 1
+ INTERPRETER_LINE_PREFIX + num
+ }
+
+ private def currentLineName =
+ INTERPRETER_LINE_PREFIX + (nextLineNo - 1)
+
+ /** next result variable number to use */
+ private var nextVarNameNo = 0
+
+ /** allocate a fresh variable name */
+ private def newVarName = {
+ val num = nextVarNameNo
+ nextVarNameNo += 1
+ INTERPRETER_VAR_PREFIX + num
+ }
+
+ /** next internal variable number to use */
+ private var nextInternalVarNo = 0
+
+ /** allocate a fresh internal variable name */
+ private def newInternalVarName() = {
+ val num = nextVarNameNo
+ nextVarNameNo += 1
+ INTERPRETER_SYNTHVAR_PREFIX + num
+ }
+
+ /** Check if a name looks like it was generated by newVarName */
+ private def isGeneratedVarName(name: String): Boolean =
+ name.startsWith(INTERPRETER_VAR_PREFIX) && {
+ val suffix = name.drop(INTERPRETER_VAR_PREFIX.length)
+ suffix.forall(_.isDigit)
+ }
+
+ /** generate a string using a routine that wants to write on a stream */
+ private def stringFrom(writer: PrintWriter => Unit): String = {
+ val stringWriter = new StringWriter()
+ val stream = new NewLinePrintWriter(stringWriter)
+ writer(stream)
+ stream.close()
+ stringWriter.toString
+ }
+
+ /** Truncate a string if it is longer than settings.maxPrintString */
+ private def truncPrintString(str: String)(implicit ctx: Context): String = {
+ val maxpr = ctx.settings.XreplLineWidth.value
+
+ if (maxpr <= 0)
+ return str
+
+ if (str.length <= maxpr)
+ return str
+
+ val trailer = "..."
+ if (maxpr >= trailer.length-1)
+ str.substring(0, maxpr-3) + trailer + "\n"
+ else
+ str.substring(0, maxpr-1)
+ }
+
+ /** Clean up a string for output */
+ private def clean(str: String)(implicit ctx: Context) =
+ truncPrintString(stripWrapperGunk(str))
+}
+
+/** Utility methods for the Interpreter. */
+object CompilingInterpreter {
+ val INTERPRETER_WRAPPER_SUFFIX = "$object"
+ val INTERPRETER_LINE_PREFIX = "line"
+ val INTERPRETER_VAR_PREFIX = "res"
+ val INTERPRETER_IMPORT_WRAPPER = "$iw"
+ val INTERPRETER_SYNTHVAR_PREFIX = "synthvar$"
+
+ /** Delete a directory tree recursively. Use with care!
+ */
+ private[repl] def deleteRecursively(path: File): Unit = {
+ path match {
+ case _ if !path.exists =>
+ ()
+ case _ if path.isDirectory =>
+ for (p <- path.listFiles)
+ deleteRecursively(p)
+ path.delete
+ case _ =>
+ path.delete
+ }
+ }
+
+ /** Heuristically strip interpreter wrapper prefixes
+ * from an interpreter output string.
+ */
+ def stripWrapperGunk(str: String): String = {
+ val wrapregex = "(line[0-9]+\\$object[$.])?(\\$iw[$.])*"
+ str.replaceAll(wrapregex, "")
+ }
+
+ /** Convert a string into code that can recreate the string.
+ * This requires replacing all special characters by escape
+ * codes. It does not add the surrounding " marks. */
+ def string2code(str: String): String = {
+ /** Convert a character to a backslash-u escape */
+ def char2uescape(c: Char): String = {
+ var rest = c.toInt
+ val buf = new StringBuilder
+ for (i <- 1 to 4) {
+ buf ++= (rest % 16).toHexString
+ rest = rest / 16
+ }
+ "\\" + "u" + buf.toString.reverse
+ }
+ val res = new StringBuilder
+ for (c <- str) {
+ if ("'\"\\" contains c) {
+ res += '\\'
+ res += c
+ } else if (!c.isControl) {
+ res += c
+ } else {
+ res ++= char2uescape(c)
+ }
+ }
+ res.toString
+ }
+}