From cf492f472aa6d1154cd7336c37bab0b78125b872 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sat, 29 Jan 2011 01:43:10 +0000 Subject: There is a lot of housecleaning to be done. up the stray interpreter files and put them in the interpreter package. Would really love to change the name of that package. Went looking for some consistent divisions of responsibility and consistent naming. Made some progress. There are deprecated versions of most everything I changed so hopefully the carnage will be limited. This isn't completely baked but I just realized I broke the build earlier and this should fix it. I'll keep the oven on. No review. --- src/compiler/scala/tools/nsc/Interpreter.scala | 1321 +------------------- .../scala/tools/nsc/InterpreterCommand.scala | 18 +- src/compiler/scala/tools/nsc/InterpreterLoop.scala | 710 +---------- .../scala/tools/nsc/InterpreterResults.scala | 24 - .../scala/tools/nsc/InterpreterSettings.scala | 113 -- .../scala/tools/nsc/MainGenericRunner.scala | 7 +- src/compiler/scala/tools/nsc/MainInterpreter.scala | 8 +- .../nsc/interpreter/AbstractFileClassLoader.scala | 2 +- .../scala/tools/nsc/interpreter/ByteCode.scala | 2 +- .../scala/tools/nsc/interpreter/CommandLine.scala | 14 + .../scala/tools/nsc/interpreter/Completion.scala | 366 +----- .../tools/nsc/interpreter/CompletionOutput.scala | 5 +- .../scala/tools/nsc/interpreter/Eval.scala | 33 + .../scala/tools/nsc/interpreter/Formatting.scala | 35 + .../scala/tools/nsc/interpreter/History.scala | 54 +- .../scala/tools/nsc/interpreter/ILoop.scala | 651 ++++++++++ .../scala/tools/nsc/interpreter/IMain.scala | 1267 +++++++++++++++++++ .../scala/tools/nsc/interpreter/ISettings.scala | 61 + .../tools/nsc/interpreter/InteractiveReader.scala | 11 +- .../tools/nsc/interpreter/JLineCompletion.scala | 346 +++++ .../scala/tools/nsc/interpreter/JLineReader.scala | 63 +- .../scala/tools/nsc/interpreter/LoopCommands.scala | 59 + .../scala/tools/nsc/interpreter/NamedParam.scala | 42 +- .../scala/tools/nsc/interpreter/Power.scala | 17 +- .../nsc/interpreter/ReflectionCompletion.scala | 14 +- .../scala/tools/nsc/interpreter/Results.scala | 22 + .../scala/tools/nsc/interpreter/RichClass.scala | 29 + .../scala/tools/nsc/interpreter/Runner.scala | 11 + .../scala/tools/nsc/interpreter/SimpleReader.scala | 3 +- .../scala/tools/nsc/interpreter/TypeStrings.scala | 78 ++ .../scala/tools/nsc/interpreter/package.scala | 25 +- src/compiler/scala/tools/nsc/io/Socket.scala | 1 - src/compiler/scala/tools/nsc/package.scala | 13 + .../scala/tools/nsc/symtab/SymbolTable.scala | 2 +- test/files/run/treePrint.scala | 6 +- 35 files changed, 2816 insertions(+), 2617 deletions(-) delete mode 100644 src/compiler/scala/tools/nsc/InterpreterResults.scala create mode 100644 src/compiler/scala/tools/nsc/interpreter/CommandLine.scala create mode 100644 src/compiler/scala/tools/nsc/interpreter/Eval.scala create mode 100644 src/compiler/scala/tools/nsc/interpreter/Formatting.scala create mode 100644 src/compiler/scala/tools/nsc/interpreter/ILoop.scala create mode 100644 src/compiler/scala/tools/nsc/interpreter/IMain.scala create mode 100644 src/compiler/scala/tools/nsc/interpreter/ISettings.scala create mode 100644 src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala create mode 100644 src/compiler/scala/tools/nsc/interpreter/LoopCommands.scala create mode 100644 src/compiler/scala/tools/nsc/interpreter/Results.scala create mode 100644 src/compiler/scala/tools/nsc/interpreter/RichClass.scala create mode 100644 src/compiler/scala/tools/nsc/interpreter/Runner.scala create mode 100644 src/compiler/scala/tools/nsc/interpreter/TypeStrings.scala create mode 100644 src/compiler/scala/tools/nsc/package.scala diff --git a/src/compiler/scala/tools/nsc/Interpreter.scala b/src/compiler/scala/tools/nsc/Interpreter.scala index d5a9e80326..ad39ee2b26 100644 --- a/src/compiler/scala/tools/nsc/Interpreter.scala +++ b/src/compiler/scala/tools/nsc/Interpreter.scala @@ -1,1323 +1,12 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Martin Odersky - */ - package scala.tools.nsc -import Predef.{ println => _, _ } -import java.io.{ PrintWriter } -import java.io.File.pathSeparator -import java.lang.reflect -import java.net.URL -import util.{ Set => _, _ } import interpreter._ -import io.VirtualDirectory -import reporters.{ ConsoleReporter, Reporter } -import symtab.{ Flags, Names } -import scala.tools.nsc.{ InterpreterResults => IR } -import scala.tools.util.PathResolver -import scala.tools.nsc.util.{ ScalaClassLoader, Exceptional } -import ScalaClassLoader.URLClassLoader -import Exceptional.unwrap - -import scala.collection.{ mutable, immutable } -import scala.collection.mutable.{ ListBuffer, ArrayBuffer } -import scala.PartialFunction.{ cond, condOpt } -import scala.util.control.Exception.{ ultimately } -import scala.reflect.NameTransformer - -import Interpreter._ +import java.io._ -/**

- * An interpreter for Scala code. - *

- *

- * The main public entry points are compile(), - * interpret(), and bind(). - * The compile() method loads a - * complete Scala file. The interpret() method executes one - * line of Scala code at the request of the user. The bind() - * method binds an object to a variable that can then be used by later - * interpreted code. - *

- *

- * 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 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 "scala_repl_result". To accomodate 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 +/** A compatibility stub. */ -class Interpreter(val settings: Settings, out: PrintWriter) { - repl => - - /** whether to print out result lines */ - private[nsc] var printResults: Boolean = true - - /** whether to print errors */ - private[nsc] var totalSilence: Boolean = false - - private val RESULT_OBJECT_PREFIX = "RequestResult$" - - def println(x: Any) = { - out.println(x) - out.flush() - } - - /** construct an interpreter that reports to Console */ +@deprecated("Use a class in the scala.tools.nsc.interpreter package.") +class Interpreter(settings: Settings, out: PrintWriter) extends IMain(settings, out) { def this(settings: Settings) = this(settings, new NewLinePrintWriter(new ConsoleWriter, true)) def this() = this(new Settings()) - - /** directory to save .class files to */ - val virtualDirectory = new VirtualDirectory("(memory)", None) - - /** reporter */ - object reporter extends ConsoleReporter(settings, null, out) { - override def printMessage(msg: String) { - if (totalSilence) - return - - out println ( - if (truncationOK) clean(msg) - else cleanNoTruncate(msg) - ) - out.flush() - } - } - - /** We're going to go to some trouble to initialize the compiler asynchronously. - * It's critical that nothing call into it until it's been initialized or we will - * run into unrecoverable issues, but the perceived repl startup time goes - * through the roof if we wait for it. So we initialize it with a future and - * use a lazy val to ensure that any attempt to use the compiler object waits - * on the future. - */ - private val _compiler: Global = newCompiler(settings, reporter) - private def _initialize(): Boolean = { - val source = """ - |// this is assembled to force the loading of approximately the - |// classes which will be loaded on the first expression anyway. - |class $repl_$init { - | val x = "abc".reverse.length + (5 max 5) - | scala.runtime.ScalaRunTime.stringOf(x) - |} - |""".stripMargin - - try { - new _compiler.Run() compileSources List(new BatchSourceFile("", source)) - if (isReplDebug || settings.debug.value) - println("Repl compiler initialized.") - true - } - catch { - case x: AbstractMethodError => - println(""" - |Failed to initialize compiler: abstract method error. - |This is most often remedied by a full clean and recompile. - |""".stripMargin - ) - x.printStackTrace() - false - case x: MissingRequirementError => println(""" - |Failed to initialize compiler: %s not found. - |** Note that as of 2.8 scala does not assume use of the java classpath. - |** For the old behavior pass -usejavacp to scala, or if using a Settings - |** object programatically, settings.usejavacp.value = true.""".stripMargin.format(x.req) - ) - false - } - } - - // set up initialization future - private var _isInitialized: () => Boolean = null - def initialize() = synchronized { - if (_isInitialized == null) - _isInitialized = scala.concurrent.ops future _initialize() - } - - /** the public, go through the future compiler */ - lazy val global: Global = { - initialize() - - // blocks until it is ; false means catastrophic failure - if (_isInitialized()) _compiler - else null - } - @deprecated("Use `global` for access to the compiler instance.") - lazy val compiler = global - - import global._ - import definitions.{ EmptyPackage, getMember } - import nme.{ - INTERPRETER_VAR_PREFIX, INTERPRETER_SYNTHVAR_PREFIX, INTERPRETER_LINE_PREFIX, - INTERPRETER_IMPORT_WRAPPER, INTERPRETER_WRAPPER_SUFFIX, USCOREkw - } - - /** Temporarily be quiet */ - def beQuietDuring[T](operation: => T): T = { - val wasPrinting = printResults - ultimately(printResults = wasPrinting) { - printResults = false - operation - } - } - def beSilentDuring[T](operation: => T): T = { - val saved = totalSilence - totalSilence = true - try operation - finally totalSilence = saved - } - - def quietRun[T](code: String) = beQuietDuring(interpret(code)) - - /** whether to bind the lastException variable */ - private var bindLastException = true - - /** Temporarily stop binding lastException */ - def withoutBindingLastException[T](operation: => T): T = { - val wasBinding = bindLastException - ultimately(bindLastException = wasBinding) { - bindLastException = false - operation - } - } - - protected def createLineManager(): Line.Manager = new Line.Manager - lazy val lineManager = createLineManager() - - /** interpreter settings */ - lazy val isettings = new InterpreterSettings(this) - - /** Instantiate a compiler. Subclasses can override this to - * change the compiler class used by this interpreter. */ - protected def newCompiler(settings: Settings, reporter: Reporter) = { - settings.outputDirs setSingleOutput virtualDirectory - new Global(settings, reporter) - } - - /** the compiler's classpath, as URL's */ - lazy val compilerClasspath: List[URL] = new PathResolver(settings) 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. - */ - private var _classLoader: AbstractFileClassLoader = null - def resetClassLoader() = _classLoader = makeClassLoader() - def classLoader: AbstractFileClassLoader = { - if (_classLoader == null) - resetClassLoader() - - _classLoader - } - private def makeClassLoader(): AbstractFileClassLoader = { - val parent = - if (parentClassLoader == null) ScalaClassLoader fromURLs compilerClasspath - else new URLClassLoader(compilerClasspath, parentClassLoader) - - new AbstractFileClassLoader(virtualDirectory, parent) - } - private def loadByName(s: String): Class[_] = (classLoader tryToInitializeClass s).get - private def methodByName(c: Class[_], name: String): reflect.Method = - c.getMethod(name, classOf[Object]) - - protected def parentClassLoader: ClassLoader = - settings.explicitParentLoader.getOrElse( this.getClass.getClassLoader() ) - - def getInterpreterClassLoader() = classLoader - - // Set the current Java "context" class loader to this interpreter's class loader - def setContextClassLoader() = classLoader.setAsContext() - - /** the previous requests this interpreter has processed */ - private val prevRequests = new ArrayBuffer[Request]() - private val referencedNameMap = new mutable.HashMap[Name, Request]() - private val boundNameMap = new mutable.HashMap[Name, Request]() - private def allHandlers = prevRequests.toList flatMap (_.handlers) - private def allReqAndHandlers = prevRequests.toList flatMap (req => req.handlers map (req -> _)) - - def printAllTypeOf = { - prevRequests foreach { req => - req.typeOf foreach { case (k, v) => Console.println(k + " => " + v) } - } - } - - /** Most recent tree handled which wasn't wholly synthetic. */ - private def mostRecentlyHandledTree: Option[Tree] = { - for { - req <- prevRequests.reverse - handler <- req.handlers.reverse - name <- handler.generatesValue - if !isSynthVarName(name) - } return Some(handler.member) - - None - } - - /** Stubs for work in progress. */ - def handleTypeRedefinition(name: TypeName, old: Request, req: Request) = { - for (t1 <- old.simpleNameOfType(name) ; t2 <- req.simpleNameOfType(name)) { - DBG("Redefining type '%s'\n %s -> %s".format(name, t1, t2)) - } - } - - def handleTermRedefinition(name: TermName, old: Request, req: Request) = { - for (t1 <- old.compilerTypeOf get name ; t2 <- req.compilerTypeOf get name) { - // Printing the types here has a tendency to cause assertion errors, like - // assertion failed: fatal: has owner value x, but a class owner is required - // so DBG is by-name now to keep it in the family. (It also traps the assertion error, - // but we don't want to unnecessarily risk hosing the compiler's internal state.) - DBG("Redefining term '%s'\n %s -> %s".format(name, t1, t2)) - } - } - - def recordRequest(req: Request) { - def tripart[T](set1: Set[T], set2: Set[T]) = { - val intersect = set1 intersect set2 - List(set1 -- intersect, intersect, set2 -- intersect) - } - - prevRequests += req - req.referencedNames foreach (x => referencedNameMap(x) = req) - - req.boundNames foreach { name => - if (boundNameMap contains name) { - if (name.isTypeName) handleTypeRedefinition(name.toTypeName, boundNameMap(name), req) - else handleTermRedefinition(name.toTermName, boundNameMap(name), req) - } - boundNameMap(name) = req - } - - // XXX temporarily putting this here because of tricky initialization order issues - // so right now it's not bound until after you issue a command. - if (prevRequests.size == 1) - quietBind("settings", isettings) - - // println("\n s1 = %s\n s2 = %s\n s3 = %s".format( - // tripart(referencedNameMap.keysIterator.toSet, boundNameMap.keysIterator.toSet): _* - // )) - } - - private def keyList[T](x: collection.Map[T, _]): List[T] = x.keys.toList sortBy (_.toString) - def allreferencedNames = keyList(referencedNameMap) - def allBoundNames = keyList(boundNameMap) - def allSeenTypes = prevRequests.toList flatMap (_.typeOf.values.toList) distinct - def allDefinedTypes = prevRequests.toList flatMap (_.definedTypes.values.toList) distinct - def allValueGeneratingNames = allHandlers flatMap (_.generatesValue) - def allImplicits = partialFlatMap(allHandlers) { - case x: MemberHandler if x.definesImplicit => x.boundNames - } - - /** Generates names pre0, pre1, etc. via calls to apply method */ - class NameCreator(pre: String) { - private var x = -1 - var mostRecent: String = "" - - def apply(): String = { - x += 1 - val name = pre + x.toString - // make sure we don't overwrite their unwisely named res3 etc. - mostRecent = - if (allBoundNames exists (_.toString == name)) apply() - else name - - mostRecent - } - def reset(): Unit = x = -1 - def didGenerate(name: String) = - (name startsWith pre) && ((name drop pre.length) forall (_.isDigit)) - } - - /** allocate a fresh line name */ - private lazy val lineNameCreator = new NameCreator(INTERPRETER_LINE_PREFIX) - - /** allocate a fresh var name */ - private lazy val varNameCreator = new NameCreator(INTERPRETER_VAR_PREFIX) - - /** allocate a fresh internal variable name */ - private lazy val synthVarNameCreator = new NameCreator(INTERPRETER_SYNTHVAR_PREFIX) - - /** Check if a name looks like it was generated by varNameCreator */ - private def isGeneratedVarName(name: String): Boolean = varNameCreator didGenerate name - private def isSynthVarName(name: String): Boolean = synthVarNameCreator didGenerate name - private def isSynthVarName(name: Name): Boolean = synthVarNameCreator didGenerate name.toString - - def getVarName = varNameCreator() - def getSynthVarName = synthVarNameCreator() - - /** Truncate a string if it is longer than isettings.maxPrintString */ - private def truncPrintString(str: String): String = { - val maxpr = isettings.maxPrintString - val trailer = "..." - - if (maxpr <= 0 || str.length <= maxpr) str - else str.substring(0, maxpr-3) + trailer - } - - /** Clean up a string for output */ - private def clean(str: String) = truncPrintString(cleanNoTruncate(str)) - private def cleanNoTruncate(str: String) = - if (isettings.unwrapStrings) stripWrapperGunk(str) - else str - - /** Indent some code by the width of the scala> prompt. - * This way, compiler error messages read better. - */ - private final val spaces = List.fill(7)(" ").mkString - def indentCode(code: String) = { - /** Heuristic to avoid indenting and thereby corrupting """-strings and XML literals. */ - val noIndent = (code contains "\n") && (List("\"\"\"", "") exists (code contains _)) - stringFromWriter(str => - for (line <- code.lines) { - if (!noIndent) - str.print(spaces) - - str.print(line + "\n") - str.flush() - }) - } - def indentString(s: String) = s split "\n" map (spaces + _ + "\n") mkString - - implicit def name2string(name: Name) = name.toString - - /** 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 traverested 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 case class ComputedImports(prepend: String, append: String, access: String) - private def importsCode(wanted: Set[Name]): ComputedImports = { - /** 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. - */ - case class ReqAndHandler(req: Request, handler: MemberHandler) { } - - def reqsToUse: List[ReqAndHandler] = { - /** Loop through a list of MemberHandlers and select which ones to keep. - * 'wanted' is the set of names that need to be imported. - */ - def select(reqs: List[ReqAndHandler], wanted: Set[Name]): List[ReqAndHandler] = { - val isWanted = wanted contains _ - // Single symbol imports might be implicits! See bug #1752. Rather than - // try to finesse this, we will mimic all imports for now. - def keepHandler(handler: MemberHandler) = handler match { - case _: ImportHandler => true - case x => x.definesImplicit || (x.boundNames exists isWanted) - } - - reqs match { - case Nil => Nil - case rh :: rest if !keepHandler(rh.handler) => select(rest, wanted) - case rh :: rest => - val importedNames = rh.handler match { case x: ImportHandler => x.importedNames ; case _ => Nil } - import rh.handler._ - val newWanted = wanted ++ referencedNames -- boundNames -- importedNames - rh :: select(rest, newWanted) - } - } - - /** Flatten the handlers out and pair each with the original request */ - select(allReqAndHandlers reverseMap { case (r, h) => ReqAndHandler(r, h) }, wanted).reverse - } - - val code, trailingBraces, accessPath = new StringBuilder - val currentImps = mutable.HashSet[Name]() - - // add code for a new object to hold some imports - def addWrapper() { - val impname = INTERPRETER_IMPORT_WRAPPER - code append "object %s {\n".format(impname) - trailingBraces append "}\n" - accessPath append ("." + impname) - - currentImps.clear - } - - addWrapper() - - // loop through previous requests, adding imports for each one - for (ReqAndHandler(req, handler) <- reqsToUse) { - handler match { - // If the user entered an import, then just use it; add an import wrapping - // level if the import might conflict with some other import - case x: ImportHandler => - if (x.importsWildcard || (currentImps exists (x.importedNames contains _))) - addWrapper() - - code append (x.member.toString + "\n") - - // give wildcard imports a import wrapper all to their own - if (x.importsWildcard) addWrapper() - else currentImps ++= x.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. - case x => - for (imv <- x.boundNames) { - if (currentImps contains imv) addWrapper() - - code append ("import %s\n" format (req fullPath imv)) - currentImps += imv - } - } - } - // add one extra wrapper, to prevent warnings in the common case of - // redefining the value bound in the last interpreter request. - addWrapper() - ComputedImports(code.toString, trailingBraces.toString, accessPath.toString) - } - - /** Parse a line into a sequence of trees. Returns None if the input is incomplete. */ - def parse(line: String): Option[List[Tree]] = { - var justNeedsMore = false - reporter.withIncompleteHandler((pos,msg) => {justNeedsMore = true}) { - // simple parse: just parse it, nothing else - def simpleParse(code: String): List[Tree] = { - reporter.reset - val unit = new CompilationUnit(new BatchSourceFile("", code)) - val scanner = new syntaxAnalyzer.UnitParser(unit) - - scanner.templateStatSeq(false)._2 - } - val trees = simpleParse(line) - - if (reporter.hasErrors) Some(Nil) // the result did not parse, so stop - else if (justNeedsMore) None - else Some(trees) - } - } - def isParseable(line: String): Boolean = { - beSilentDuring { - parse(line) match { - case Some(xs) => xs.nonEmpty - case _ => false - } - } - } - - /** Compile an nsc SourceFile. Returns true if there are - * no compilation errors, or false otherwise. - */ - def compileSources(sources: SourceFile*): Boolean = { - reporter.reset - new Run() compileSources sources.toList - !reporter.hasErrors - } - - /** Compile a string. Returns true if there are no - * compilation errors, or false otherwise. - */ - def compileString(code: String): Boolean = - compileSources(new BatchSourceFile("