aboutsummaryrefslogtreecommitdiff
path: root/src/dotty/tools
diff options
context:
space:
mode:
authorMartin Odersky <odersky@gmail.com>2016-02-14 15:56:22 +0100
committerMartin Odersky <odersky@gmail.com>2016-02-17 18:39:35 +0100
commit4550b524771af0719846eb906795a5ba83106eb9 (patch)
tree39d008ec7e26d341beff0041d219c6f99fa35cda /src/dotty/tools
parent37b6df435f1595f8610ec6d5fbe16d079da2eff7 (diff)
downloaddotty-4550b524771af0719846eb906795a5ba83106eb9.tar.gz
dotty-4550b524771af0719846eb906795a5ba83106eb9.tar.bz2
dotty-4550b524771af0719846eb906795a5ba83106eb9.zip
Revisions to REPL
Changes necessary to make basic REPL functionality work. Major refactoing: Code of Interpreter is now in CompilingInterpreter.scala. Interpreter.scala contains just the API.
Diffstat (limited to 'src/dotty/tools')
-rw-r--r--src/dotty/tools/dotc/repl/CompilingInterpreter.scala831
-rw-r--r--src/dotty/tools/dotc/repl/InteractiveReader.scala3
-rw-r--r--src/dotty/tools/dotc/repl/Interpreter.scala918
-rw-r--r--src/dotty/tools/dotc/repl/InterpreterLoop.scala54
-rw-r--r--src/dotty/tools/dotc/repl/InterpreterResults.scala19
-rw-r--r--src/dotty/tools/dotc/repl/Main.scala27
-rw-r--r--src/dotty/tools/dotc/repl/REPL.scala (renamed from src/dotty/tools/dotc/REPL.scala)16
7 files changed, 923 insertions, 945 deletions
diff --git a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala
new file mode 100644
index 000000000..f60b3c000
--- /dev/null
+++ b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala
@@ -0,0 +1,831 @@
+package dotty.tools
+package dotc
+package repl
+
+import java.io.{File, PrintWriter, StringWriter, Writer}
+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
+
+/** 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) extends Compiler with Interpreter {
+ import ast.untpd._
+ import CompilingInterpreter._
+
+ /** 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
+
+ /** Temporarily be quiet */
+ override def beQuietDuring[T](operation: => T): T = {
+ val wasPrinting = printResults
+ try {
+ printResults = false
+ operation
+ } finally {
+ printResults = wasPrinting
+ }
+ }
+
+ /** interpreter settings */
+ override val isettings = new InterpreterSettings
+
+ private def newReporter = new ConsoleReporter(Console.in, out) {
+ override def printMessage(msg: String) = {
+ out.print(clean(msg) + "\n")
+ out.flush()
+ }
+ }
+
+ /** 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
+
+ protected def parentClassLoader: ClassLoader = classOf[Interpreter].getClassLoader
+
+ /* 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 = {
+ val parent = new URLClassLoader(compilerClasspath.toArray, parentClassLoader)
+ new AbstractFileClassLoader(virtualDirectory, 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(indentCode(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] =>
+ interpret(s"val $newVarName =\n$line")
+ case Some(trees) =>
+ val req = new Request(line, newLineName)
+ if (!req.compile())
+ Interpreter.Error // an error happened during compilation, e.g. a type error
+ else {
+ val (interpreterResultString, succeeded) = req.loadAndRun()
+ if (printResults || !succeeded)
+ out.print(clean(interpreterResultString))
+ if (succeeded) {
+ prevRequests += req
+ Interpreter.Success
+ }
+ else Interpreter.Error
+ }
+ }
+ }
+
+ /** A counter used for numbering objects created by <code>bind()</code>. */
+ private var binderNum = 0
+
+ override def bind(name: String, boundType: String, value: Any)(implicit ctx: Context): Interpreter.Result = {
+ val binderName = "binder" + binderNum
+ binderNum += 1
+
+ compileString(
+ "object " + binderName +
+ "{ var value: " + boundType + " = _; " +
+ " def set(x: Any) = value=x.asInstanceOf[" + boundType + "]; }")
+
+ val binderObject =
+ Class.forName(binderName, true, classLoader)
+ val setterMethod =
+ binderObject.getDeclaredMethods.find(_.getName == "set").get
+ var argsHolder: Array[Any] = null // this roundabout approach is to try and make sure the value is boxed
+ argsHolder = List(value).toArray
+ setterMethod.invoke(null, argsHolder.asInstanceOf[Array[AnyRef]])
+
+ interpret("val " + name + " = " + binderName + ".value")
+ }
+
+ /** 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 = parse(line) 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 @ 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("object " + objectName + " {")
+ code.print(importsPreamble)
+ code.println(indentCode(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("object " + resultObjectName)
+ code.println("{ val result: String = {")
+ code.println(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
+ }
+
+ /** load and run the code using reflection.
+ * @return A pair consisting of the run's result as a string, and
+ * a boolean indicating whether the run succeeded without throwing
+ * an exception.
+ */
+ def loadAndRun(): (String, Boolean) = {
+ val interpreterResultObject: Class[_] =
+ Class.forName(resultObjectName, true, classLoader)
+ val resultValMethod: java.lang.reflect.Method =
+ interpreterResultObject.getMethod("result")
+ try {
+ (resultValMethod.invoke(interpreterResultObject).toString, 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)), 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.toString + ";\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 class ValHandler(statement: ValDef) extends StatementHandler(statement) {
+ override val boundNames = List(statement.name)
+ override def valAndVarNames = boundNames
+
+ override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
+ val vname = statement.name
+ if (!statement.mods.is(Flags.AccessFlags) &&
+ !(isGeneratedVarName(vname.toString) &&
+ req.typeOf(vname.encode) == "Unit")) {
+ val prettyName = vname.decode
+ code.print(" + \"" + prettyName + ": " +
+ string2code(req.typeOf(vname)) +
+ " = \" + " +
+ " (if(" +
+ req.fullPath(vname) +
+ ".asInstanceOf[AnyRef] != null) " +
+ " ((if(" +
+ req.fullPath(vname) +
+ ".toString().contains('\\n')) " +
+ " \"\\n\" else \"\") + " +
+ req.fullPath(vname) + ".toString() + \"\\n\") else \"null\\n\") ")
+ }
+ }
+ }
+
+ 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("val " + helperName + " = " + statement.lhs + ";")
+ }
+
+ /** Print out lhs instead of the generated varName */
+ override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
+ code.print(" + \"" + lhs + ": " +
+ 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.toString + "\\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
+ }
+
+ /** 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): String = {
+ val maxpr = isettings.maxPrintString
+
+ if (maxpr <= 0)
+ return str
+
+ if (str.length <= maxpr)
+ return str
+
+ val trailer = "..."
+ if (maxpr >= trailer.length+1)
+ return str.substring(0, maxpr-3) + trailer
+
+ str.substring(0, maxpr)
+ }
+
+ /** Clean up a string for output */
+ private def clean(str: String) = str
+ // truncPrintString(stripWrapperGunk(str)) // TODO: enable
+
+ /** Indent some code by the width of the scala> prompt.
+ * This way, compiler error messages read better.
+ */
+ def indentCode(code: String) = {
+ val spaces = " "
+
+ stringFrom(str =>
+ for (line <- code.lines) {
+ str.print(spaces)
+ str.print(line + "\n")
+ str.flush()
+ })
+ }
+}
+
+/** 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
+ }
+}
diff --git a/src/dotty/tools/dotc/repl/InteractiveReader.scala b/src/dotty/tools/dotc/repl/InteractiveReader.scala
index 55f7de0b2..96c55ebd0 100644
--- a/src/dotty/tools/dotc/repl/InteractiveReader.scala
+++ b/src/dotty/tools/dotc/repl/InteractiveReader.scala
@@ -8,6 +8,9 @@ trait InteractiveReader {
val interactive: Boolean
}
+/** TODO Enable jline support.
+ * The current Scala REPL know how to do this flexibly.
+ */
object InteractiveReader {
/** Create an interactive reader. Uses JLine if the
* library is available, but otherwise uses a
diff --git a/src/dotty/tools/dotc/repl/Interpreter.scala b/src/dotty/tools/dotc/repl/Interpreter.scala
index bac912d33..9967aa347 100644
--- a/src/dotty/tools/dotc/repl/Interpreter.scala
+++ b/src/dotty/tools/dotc/repl/Interpreter.scala
@@ -2,914 +2,54 @@ package dotty.tools
package dotc
package repl
-import java.io.{File, PrintWriter, StringWriter, Writer}
-import java.lang.{Class, ClassLoader}
-import java.net.{URL, URLClassLoader}
+import core.Contexts.Context
-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 reporting.{ConsoleReporter, Reporter}
-import core.Flags
-import util.{SourceFile, NameTransformer}
-import io.ClassPath
-import repl.{InterpreterResults=>IR}
-import ast.Trees._
-import parsing.Parsers._
-import core._
-import dotty.tools.backend.jvm.GenBCode
-import Symbols._, Types._, Contexts._, StdNames._, Names._, NameOps._
-import Decorators._
-import Interpreter._
-
-/** <p>
- * An interpreter for Scala code.
- * </p>
- * <p>
- * The main public entry points are <code>compile()</code> and
- * <code>interpret()</code>. The <code>compile()</code> method loads a
- * complete Scala file. The <code>interpret()</code> method executes one
- * line of Scala code at the request of the user.
- * </p>
- * <p>
- * 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.
- * </p>
- * <p>
- * 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 member(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 member named "result". To accomodate user expressions
- * that read from variables or methods defined in previous statements, "import"
- * statements are used.
- * </p>
- * <p>
- * 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.
- * </p>
- *
- * @author Moez A. Abdel-Gawad
- * @author Lex Spoon
- */
-class Interpreter(out: PrintWriter, ictx: Context) extends Compiler {
-
- import ast.untpd._
- import Interpreter._
-
- /** directory to save .class files to */
- val virtualDirectory = new VirtualDirectory("(memory)", None)
-
- class REPLGenBCode extends GenBCode {
- override def outputDir(implicit ctx: Context) = virtualDirectory
- }
-
- override def phases = Phases.replace(
- classOf[GenBCode], _ => new REPLGenBCode :: Nil, super.phases)
-
- /** whether to print out result lines */
- private var printResults: Boolean = true
-
- /** Be quiet. Do not print out the results of each
- * submitted command unless an exception is thrown. */
- def beQuiet = { printResults = false }
-
- /** Temporarily be quiet */
- def beQuietDuring[T](operation: => T): T = {
- val wasPrinting = printResults
- try {
- printResults = false
- operation
- } finally {
- printResults = wasPrinting
- }
- }
-
- /** interpreter settings */
- val isettings = new InterpreterSettings
-
- def newReporter = new ConsoleReporter(Console.in, writer = out) {
- //override def printMessage(msg: String) { out.println(clean(msg)) }
- override def printMessage(msg: String) = {
- out.print(clean(msg) + "\n")
- out.flush()
- }
- }
-
- /** 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:
+/** This object defines the type of interpreter results */
+object Interpreter {
- - 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 = {
- val parent = new URLClassLoader(compilerClasspath.toArray, parentClassLoader)
- new AbstractFileClassLoader(virtualDirectory, parent)
- }
+ /** A result from interpreting one line of input. */
+ abstract sealed class Result
- protected def parentClassLoader: ClassLoader = classOf[Interpreter].getClassLoader
+ /** The line was interpreted successfully. */
+ case object Success extends Result
- /** Set the current Java "context" class loader to this
- * interpreter's class loader
- */
- def setContextClassLoader(): Unit = {
- Thread.currentThread.setContextClassLoader(classLoader)
- }
+ /** The line was erroneous in some way. */
+ case object Error extends Result
- /** Parse a line into a sequence of trees. Returns None if the input
- * is incomplete.
+ /** The input was incomplete. The caller should request more input.
*/
- 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): List[Tree] = {
- val source = new SourceFile("<console>", code.toCharArray())
- val parser = new Parser(source)
- val (selfDef, stats) = parser.templateStatSeq
- stats
- }
- val trees = simpleParse(line)
- if (reporter.hasErrors) {
- Some(Nil) // the result did not parse, so stop
- } else if (justNeedsMore) {
- None
- } else {
- Some(trees)
- }
- }
- }
+ case object Incomplete extends Result
+}
- /** Compile a SourceFile. Returns true if there are
- * no compilation errors, or false othrewise.
- */
- def compileSources(sources: List[SourceFile])(implicit ctx: Context): Boolean = {
- val reporter = newReporter
- val run = new Run(this)(ctx.fresh.setReporter(reporter))
- run.compileSources(sources)
- !reporter.hasErrors
- }
+/** The exported functionality of the interpreter */
+trait Interpreter {
+ import Interpreter._
- /** Compile a string. Returns true if there are no
- * compilation errors, or false otherwise.
+ /** 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.
*/
- def compileString(code: String)(implicit ctx: Context): Boolean =
- compileSources(List(new SourceFile("<script>", code.toCharArray)))
+ def interpret(line: String)(implicit ctx: Context): Result
- /** <p>
- * Interpret one line of input. All feedback, including parse errors
- * and evaluation results, are printed via the supplied compiler's
- * reporter. Values defined are available for future interpreted
- * strings.
- * </p>
- * <p>
- * The return value is whether the line was interpreter successfully,
- * e.g. that there were no parse errors.
- * </p>
- *
- * @param line ...
- * @return ...
+ /** Compile a string without exectuting the result.
+ * Returns true if there are no compilation errors, false otherwise.
*/
- def interpret(line: String)(implicit ctx: Context): IR.Result = {
- if (prevRequests.isEmpty)
- new Run(this) // initialize the compiler
-
- // parse
- val trees = parse(indentCode(line)) match {
- case None => return IR.Incomplete
- case Some(Nil) => return IR.Error // parse error or empty input
- case Some(trees) => trees
- }
-
- trees match {
- case List(_: Assign) => ()
-
- case List(_: TermTree) | List(_: Ident) | List(_: Select) =>
- // Treat a single expression specially.
- // This is necessary due to it being hard to modify
- // code at a textual level, and it being hard to
- // submit an AST to the compiler.
- return interpret("val " + newVarName() + " = \n" + line)
-
- case _ => ()
- }
+ def compileString(code: String)(implicit ctx: Context): Boolean
- val lineName = newLineName
+ /** Suppress output during evaluation of `operation`. */
+ def beQuietDuring[T](operation: => T): T
- // figure out what kind of request
- val req = buildRequest(trees, line, lineName)
- if (req eq null) return IR.Error // a disallowed statement type
-
- if (!req.compile)
- return IR.Error // an error happened during compilation, e.g. a type error
-
- val (interpreterResultString, succeeded) = req.loadAndRun
-
- if (printResults || !succeeded) {
- // print the result
- out.print(clean(interpreterResultString))
- }
-
- // book-keeping
- if (succeeded)
- prevRequests += req
-
- if (succeeded) IR.Success else IR.Error
- }
-
- /** A counter used for numbering objects created by <code>bind()</code>. */
- private var binderNum = 0
+ /** The interpreter settings */
+ def isettings: InterpreterSettings
/** Bind a specified name to a specified value. The name may
- * later be used by expressions passed to interpret.
+ * later be used by expressions passed to interpret. Can be used to
+ * programmatically change intepreter settings.
*
* @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
* @return an indication of whether the binding succeeded
*/
- def bind(name: String, boundType: String, value: Any)(implicit ctx: Context): IR.Result = {
- val binderName = "binder" + binderNum
- binderNum += 1
-
- compileString(
- "object " + binderName +
- "{ var value: " + boundType + " = _; " +
- " def set(x: Any) = value=x.asInstanceOf[" + boundType + "]; }")
-
- val binderObject =
- Class.forName(binderName, true, classLoader)
- val setterMethod =
- (binderObject
- .getDeclaredMethods
- .toList
- .find(meth => meth.getName == "set")
- .get)
- var argsHolder: Array[Any] = null // this roundabout approach is to try and
- // make sure the value is boxed
- argsHolder = List(value).toArray
- setterMethod.invoke(null, argsHolder.asInstanceOf[Array[AnyRef]])
-
- interpret("val " + name + " = " + binderName + ".value")
- }
-
- /** Class to handle one member among all the members included
- * in a single interpreter request.
- */
- private trait Handler {
- def member: Tree
- def usedNames: List[Name]
- val boundNames: List[Name]
- def valAndVarNames: List[Name]
- def defNames: List[Name]
- val importsWildcard: Boolean
- val importedNames: Seq[Name]
- val definesImplicit: Boolean
- def extraCodeToEvaluate(req: Request, code: PrintWriter): Unit
- def resultExtractionCode(req: Request, code: PrintWriter): Unit
- }
-
- /** Build a request from the user. <code>trees</code> is <code>line</code>
- * after being parsed.
- */
- private def buildRequest(trees: List[Tree], line: String, lineName: String)(implicit ctx: Context): Request =
- new Request(line, lineName)
-
- /** One line of code submitted by the user for interpretation */
- private class Request(val line: String, val lineName: String)(implicit ctx: Context) {
- val trees = parse(line) match {
- case Some(ts) => ts
- case None => Nil
- }
-
- /** name to use for the object that will compute "line" */
- def objectName = lineName + INTERPRETER_WRAPPER_SUFFIX
-
- /** name of the object that retrieves the result from the above object */
- def resultObjectName = "RequestResult$" + objectName
-
- private val handlers: List[MemberHandler] = trees.flatMap(chooseHandler(_))
-
- /** all (public) names defined by these statements */
- val boundNames = (ListSet() ++ handlers.flatMap(_.boundNames)).toList
-
- /** list of names used by this expression */
- val usedNames: List[Name] = handlers.flatMap(_.usedNames)
-
- def myImportsCode = importsCode(Set.empty ++ usedNames)
-
- /** Code to append to objectName to access anything that
- * the request binds. */
- val accessPath = myImportsCode._3
-
-
- /** Code to access a variable with the specified name */
- def fullPath(vname: String): String =
- objectName + accessPath + ".`" + vname + "`"
-
- /** Code to access a variable with the specified name */
- def fullPath(vname: Name): String = fullPath(vname.toString)
-
- /** the line of code to compute */
- def toCompute = line
-
- /** generate the source code for the object that computes this request */
- def objectSourceCode: String =
- stringFrom { code =>
- // header for the wrapper object
- code.println("object " + objectName + " {")
-
- val (importsPreamble, importsTrailer, _) = myImportsCode
-
- code.print(importsPreamble)
-
- code.println(indentCode(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 */
- var typeOf: Map[Name, String] = _
-
- /** generate source code for the object that retrieves the result
- from objectSourceCode */
- def resultObjectSourceCode: String =
- stringFrom(code => {
- code.println("object " + resultObjectName)
- code.println("{ val result: String = {")
- code.println(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 =
- compileSources(
- List(new SourceFile("<console>", objectSourceCode.toCharArray))) && {
- // extract and remember types
- this.typeOf = findTypes()
- compileSources(
- List(new SourceFile("<console>", resultObjectSourceCode.toCharArray)))
- }
-
- /** Dig the types of all bound variables out of the compiler run.
- *
- * @param objRun ...
- * @return ...
- */
- def findTypes(): 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
- }
-
- /** load and run the code using reflection */
- def loadAndRun: (String, Boolean) = {
- val interpreterResultObject: Class[_] =
- Class.forName(resultObjectName, true, classLoader)
- val resultValMethod: java.lang.reflect.Method =
- interpreterResultObject.getMethod("result", null)
- try {
- (resultValMethod.invoke(interpreterResultObject, null).toString(),
- true)
- } catch {
- case e =>
- def caus(e: Throwable): Throwable =
- if (e.getCause eq null) e else caus(e.getCause)
- val orig = caus(e)
- (stringFrom(str => orig.printStackTrace(str)), false)
- }
- }
-
- /** Compute imports that allow definitions from previous
- * requests to be visible in a new request. Returns
- * three pieces of related code:
- *
- * 1. An initial code fragment that should go before
- * the code of the new request.
- *
- * 2. 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, Handler)] = {
- /** Loop through a list of MemberHandlers and select
- * which ones to keep. 'wanted' is the set of
- * names that need to be imported, and
- * 'shadowed' is the list of names useless to import
- * because a later request will re-import it anyway.
- */
- def select(reqs: List[(Request, Handler)], wanted: Set[Name]): List[(Request, Handler)] = {
- 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 code = new StringBuffer
- val trailingBraces = new StringBuffer
- val accessPath = new StringBuffer
- val impname = INTERPRETER_IMPORT_WRAPPER
- val currentImps = mutable.Set.empty[Name]
-
- // add code for a new object to hold some imports
- def addWrapper(): Unit = {
- code.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.member.isInstanceOf[Import])
- code.append(handler.member.toString + ";\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()
- code.append("import ")
- code.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.
-
- (code.toString, trailingBraces.toString, accessPath.toString)
- }
-
- // ------ Handlers ------------------------------------------
-
- private def chooseHandler(member: Tree): Option[MemberHandler] =
- member match {
- case member: DefDef =>
- Some(new DefHandler(member))
- case member: ValDef =>
- Some(new ValHandler(member))
- case member @ Assign(Ident(_), _) => Some(new AssignHandler(member))
- case member: ModuleDef => Some(new ModuleHandler(member))
- case member: TypeDef =>
- Some(if (member.isClassDef) new ClassHandler(member)
- else new TypeAliasHandler(member))
- case member: Import => Some(new ImportHandler(member))
- // case DocDef(_, documented) => chooseHandler(documented)
- case member => Some(new GenericHandler(member))
- }
-
- /** A traverser that finds all mentioned identifiers, i.e. things
- * that need to be imported.
- * It might return extra names.
- */
- private class ImportVarsTraverser(definedVars: List[Name]) extends TreeTraverser {
- val importVars = new HashSet[Name]()
-
- override def traverse(ast: Tree)(implicit ctx: Context): Unit = {
- ast match {
- case Ident(name) => importVars += name
- case _ => traverseChildren(ast)
- }
- }
- }
-
- /** Class to handle one member among all the members included
- * in a single interpreter request.
- */
- private sealed abstract class MemberHandler(val member: Tree) extends Handler {
- val usedNames: List[Name] = {
- val ivt = new ImportVarsTraverser(boundNames)
- ivt.traverse(member)
- ivt.importVars.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 =
- member 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(member: Tree) extends MemberHandler(member)
-
- private class ValHandler(member: ValDef) extends MemberHandler(member) {
- override val boundNames = List(member.name)
- override def valAndVarNames = boundNames
-
- override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
- val vname = member.name
- if (!member.mods.is(Flags.AccessFlags) &&
- !(isGeneratedVarName(vname.toString) &&
- req.typeOf(vname.encode) == "Unit")) {
- val prettyName = vname.decode
- code.print(" + \"" + prettyName + ": " +
- string2code(req.typeOf(vname)) +
- " = \" + " +
- " (if(" +
- req.fullPath(vname) +
- ".asInstanceOf[AnyRef] != null) " +
- " ((if(" +
- req.fullPath(vname) +
- ".toString().contains('\\n')) " +
- " \"\\n\" else \"\") + " +
- req.fullPath(vname) + ".toString() + \"\\n\") else \"null\\n\") ")
- }
- }
- }
-
- private class DefHandler(defDef: DefDef) extends MemberHandler(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(member: Assign) extends MemberHandler(member) {
- val lhs = member.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("val " + helperName + " = " + member.lhs + ";")
- }
-
- /** Print out lhs instead of the generated varName */
- override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
- code.print(" + \"" + lhs + ": " +
- string2code(req.typeOf(helperName.encode)) +
- " = \" + " +
- string2code(req.fullPath(helperName))
- + " + \"\\n\"")
- }
- }
-
- private class ModuleHandler(module: ModuleDef) extends MemberHandler(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 MemberHandler(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 MemberHandler(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 MemberHandler(imp) {
- override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
- code.println("+ \"" + imp.toString + "\\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
- }
-
- /** 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): String = {
- val maxpr = isettings.maxPrintString
-
- if (maxpr <= 0)
- return str
-
- if (str.length <= maxpr)
- return str
-
- val trailer = "..."
- if (maxpr >= trailer.length+1)
- return str.substring(0, maxpr-3) + trailer
-
- str.substring(0, maxpr)
- }
-
- /** Clean up a string for output */
- private def clean(str: String) =
- truncPrintString(Interpreter.stripWrapperGunk(str))
-
- /** Indent some code by the width of the scala> prompt.
- * This way, compiler error messages read better.
- */
- def indentCode(code: String) = {
- val spaces = " "
-
- stringFrom(str =>
- for (line <- code.lines) {
- str.print(spaces)
- str.print(line + "\n")
- str.flush()
- })
- }
-}
-
-/** Utility methods for the Interpreter. */
-object Interpreter {
- 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
- }
+ def bind(name: String, boundType: String, value: Any)(implicit ctx: Context): Result
}
diff --git a/src/dotty/tools/dotc/repl/InterpreterLoop.scala b/src/dotty/tools/dotc/repl/InterpreterLoop.scala
index 1a7a6d115..0167c47c5 100644
--- a/src/dotty/tools/dotc/repl/InterpreterLoop.scala
+++ b/src/dotty/tools/dotc/repl/InterpreterLoop.scala
@@ -13,21 +13,13 @@ import Contexts._
import annotation.tailrec
import scala.concurrent.ExecutionContext.Implicits.global
-import repl.{InterpreterResults => IR}
-
-/** The
- * <a href="http://scala-lang.org/" target="_top">Scala</a>
- * interactive shell. It provides a read-eval-print loop around
+/** The interactive shell. It provides a read-eval-print loop around
* the Interpreter class.
- * After instantiation, clients should call the <code>main()</code> method.
- *
- * <p>If no in0 is specified, then input will come from the console, and
- * the class will attempt to provide input editing feature such as
- * input history.
+ * After instantiation, clients should call the `run` method.
*
* @author Moez A. Abdel-Gawad
* @author Lex Spoon
- * @version 1.2
+ * @author Martin Odersky
*/
class InterpreterLoop(
compiler: Compiler,
@@ -47,7 +39,7 @@ class InterpreterLoop(
/** A list of commands to replay if the user requests a :replay */
def replayCommands = replayCommandsRev.reverse
- /** Record a command for replay should the user requset a :replay */
+ /** Record a command for replay should the user request a :replay */
def addReplay(cmd: String) =
replayCommandsRev = cmd :: replayCommandsRev
@@ -80,7 +72,7 @@ class InterpreterLoop(
/** Print a welcome message */
def printWelcome(): Unit = {
- out.println("Welcome to Scala.next " + " (" +
+ out.println(s"Welcome to Scala$version " + " (" +
System.getProperty("java.vm.name") + ", Java " + System.getProperty("java.version") + ")." )
out.println("Type in expressions to have them evaluated.")
out.println("Type :help for more information.")
@@ -88,7 +80,10 @@ class InterpreterLoop(
}
/** Prompt to print when awaiting input */
- val prompt = "scala> "
+ val prompt = "scala> "
+ val continuationPrompt = " | "
+
+ val version = ".next (pre-alpha)"
/** The first interpreted command always takes a couple of seconds
* due to classloading. To bridge the gap, wait until the welcome message
@@ -98,21 +93,20 @@ class InterpreterLoop(
*/
def firstLine(): String = {
val futLine = Future(in.readLine(prompt))
- bindSettings()
+ //bindSettings() // TODO enable
Await.result(futLine, Duration.Inf)
}
/** The main read-eval-print loop for the interpreter. It calls
- * <code>command()</code> for each line of input, and stops when
- * <code>command()</code> returns <code>false</code>.
+ * `command()` for each line of input.
*/
- @tailrec final def repl(line: String): Unit =
+ @tailrec final def repl(line: String = in.readLine(prompt)): Unit =
if (line != null) {
val (keepGoing, finalLineOpt) = command(line)
if (keepGoing) {
finalLineOpt.foreach(addReplay)
out.flush()
- repl(in.readLine(prompt))
+ repl()
}
}
@@ -121,7 +115,7 @@ class InterpreterLoop(
val fileIn = try {
new FileReader(filename)
} catch {
- case _:IOException =>
+ case _: IOException =>
out.println("Error opening file: " + filename)
return
}
@@ -132,7 +126,7 @@ class InterpreterLoop(
in = new SimpleReader(inFile, out, false)
out.println("Loading " + filename + "...")
out.flush
- repl(in.readLine(prompt))
+ repl()
} finally {
in = oldIn
replayCommandsRev = oldReplay
@@ -158,12 +152,12 @@ class InterpreterLoop(
val spaceIdx = command.indexOf(' ')
if (spaceIdx <= 0) {
out.println("That command requires a filename to be specified.")
- return ()
+ return
}
val filename = command.substring(spaceIdx).trim
- if (! new File(filename).exists) {
+ if (!new File(filename).exists) {
out.println("That file does not exist")
- return ()
+ return
}
action(filename)
}
@@ -186,7 +180,7 @@ class InterpreterLoop(
})
}
else if (line matches replayRegexp)
- replay
+ replay()
else if (line startsWith ":")
out.println("Unknown command. Type :help for help.")
else
@@ -203,14 +197,14 @@ class InterpreterLoop(
*/
def interpretStartingWith(code: String): Option[String] = {
interpreter.interpret(code) match {
- case IR.Success => Some(code)
- case IR.Error => None
- case IR.Incomplete =>
+ case Interpreter.Success => Some(code)
+ case Interpreter.Error => None
+ case Interpreter.Incomplete =>
if (in.interactive && code.endsWith("\n\n")) {
out.println("You typed two blank lines. Starting a new command.")
None
} else {
- val nextLine = in.readLine(" | ")
+ val nextLine = in.readLine(continuationPrompt)
if (nextLine == null)
None // end of file
else
@@ -235,7 +229,7 @@ class InterpreterLoop(
def run(): Reporter = {
// loadFiles(settings)
try {
- if (!ctx.reporter.hasErrors) {
+ if (!ctx.reporter.hasErrors) { // if there are already errors, no sense to continue
printWelcome()
repl(firstLine())
}
diff --git a/src/dotty/tools/dotc/repl/InterpreterResults.scala b/src/dotty/tools/dotc/repl/InterpreterResults.scala
deleted file mode 100644
index e5f8affa8..000000000
--- a/src/dotty/tools/dotc/repl/InterpreterResults.scala
+++ /dev/null
@@ -1,19 +0,0 @@
-package dotty.tools.dotc.repl
-
-object InterpreterResults {
-
- /** A result from interpreting one line of input. */
- abstract sealed class Result
-
- /** The line was interpreted successfully. */
- case object Success extends Result
-
- /** The line was erroneous in some way. */
- case object Error extends Result
-
- /** The input was incomplete. The caller should request more
- * input.
- */
- case object Incomplete extends Result
-
-}
diff --git a/src/dotty/tools/dotc/repl/Main.scala b/src/dotty/tools/dotc/repl/Main.scala
new file mode 100644
index 000000000..ef362ce94
--- /dev/null
+++ b/src/dotty/tools/dotc/repl/Main.scala
@@ -0,0 +1,27 @@
+package dotty.tools
+package dotc
+package repl
+
+/* This REPL was adapted from an old (2008-ish) version of the Scala
+ * REPL. The original version from which the adaptation was done is found in:
+ *
+ * https://github.com/odersky/legacy-svn-scala/tree/spoon
+ *
+ * The reason this version was picked instead of a more current one is that
+ * the older version is much smaller, therefore easier to port. It is also
+ * considerably less intertwined with nsc than later versions.
+ *
+ * There are a number of TODOs:
+ *
+ * - re-enable bindings (urgent, easy)
+ * - re-enable jline support (urgent, easy)
+ * - create or port REPL tests (urgent, intermediate)
+ * - copy improvements of current Scala REPL wrt to this version
+ * (somewhat urgent, intermediate)
+ * - make string generation more functional (not urgent, easy)
+ * - better handling of ^C (not urgent, intermediate)
+ * - syntax highlighting (not urgent, intermediate)
+ * - integrate with presentation compiler for command completion (not urgent, hard)
+ */
+/** The main entry point of the REPL */
+object Main extends REPL \ No newline at end of file
diff --git a/src/dotty/tools/dotc/REPL.scala b/src/dotty/tools/dotc/repl/REPL.scala
index fdc8f690d..2d6a3c742 100644
--- a/src/dotty/tools/dotc/REPL.scala
+++ b/src/dotty/tools/dotc/repl/REPL.scala
@@ -1,14 +1,10 @@
package dotty.tools
package dotc
+package repl
-import core.Phases
import core.Contexts.Context
import reporting.Reporter
-import java.io.EOFException
-import scala.annotation.tailrec
-import io.VirtualDirectory
import java.io.{BufferedReader, File, FileReader, PrintWriter}
-import repl._
/** A compiler which stays resident between runs.
* Usage:
@@ -27,6 +23,7 @@ import repl._
*/
class REPL extends Driver {
+ /** The default input reader */
def input(implicit ctx: Context): InteractiveReader = {
val emacsShell = System.getProperty("env.emacs", "") != ""
//println("emacsShell="+emacsShell) //debug
@@ -34,14 +31,19 @@ class REPL extends Driver {
else InteractiveReader.createDefault()
}
+ /** The defult output writer */
def output: PrintWriter = new NewLinePrintWriter(new ConsoleWriter, true)
- override def newCompiler(implicit ctx: Context): Compiler = new repl.Interpreter(output, ctx)
+ override def newCompiler(implicit ctx: Context): Compiler =
+ new repl.CompilingInterpreter(output, ctx)
override def sourcesRequired = false
override def doCompile(compiler: Compiler, fileNames: List[String])(implicit ctx: Context): Reporter = {
- new InterpreterLoop(compiler, input, output).run()
+ if (fileNames.isEmpty)
+ new InterpreterLoop(compiler, input, output).run()
+ else
+ ctx.error(s"don't now what to do with $fileNames%, %")
ctx.reporter
}
}