diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc/Interpreter.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/Interpreter.scala | 1321 |
1 files changed, 5 insertions, 1316 deletions
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._ -/** <p> - * An interpreter for Scala code. - * </p> - * <p> - * The main public entry points are <code>compile()</code>, - * <code>interpret()</code>, and <code>bind()</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. The <code>bind()</code> - * method binds an object to a variable that can then be used by later - * interpreted code. - * </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 "scala_repl_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 +/** 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("<init>", 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: <refinement> 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("<console>", 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("<script>", code)) - - def compileAndSaveRun(label: String, code: String) = { - /** Secret bookcase entrance for repl debuggers: end the line - * with "// show" and see what's going on. - */ - if (code.lines exists (_.trim endsWith "// show")) { - Console println code - parse(code) match { - case Some(trees) => trees foreach (t => DBG(asCompactString(t))) - case _ => DBG("Parse error:\n\n" + code) - } - } - val run = new Run() - run.compileSources(List(new BatchSourceFile(label, code))) - run - } - - /** Build a request from the user. <code>trees</code> is <code>line</code> - * after being parsed. - */ - private def buildRequest(line: String, lineName: String, trees: List[Tree]): Request = - new Request(line, lineName, trees) - - private def chooseHandler(member: Tree): MemberHandler = member match { - case member: DefDef => new DefHandler(member) - case member: ValDef => new ValHandler(member) - case member@Assign(Ident(_), _) => new AssignHandler(member) - case member: ModuleDef => new ModuleHandler(member) - case member: ClassDef => new ClassHandler(member) - case member: TypeDef => new TypeAliasHandler(member) - case member: Import => new ImportHandler(member) - case DocDef(_, documented) => chooseHandler(documented) - case member => new GenericHandler(member) - } - - private def requestFromLine(line: String, synthetic: Boolean): Either[IR.Result, Request] = { - val trees = parse(indentCode(line)) match { - case None => return Left(IR.Incomplete) - case Some(Nil) => return Left(IR.Error) // parse error or empty input - case Some(trees) => trees - } - - // use synthetic vars to avoid filling up the resXX slots - def varName = if (synthetic) getSynthVarName else getVarName - - // Treat a single bare 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. - if (trees.size == 1) trees.head match { - case _:Assign => // we don't want to include assignments - case _:TermTree | _:Ident | _:Select => // ... but do want these as valdefs. - requestFromLine("val %s =\n%s".format(varName, line), synthetic) match { - case Right(req) => return Right(req withOriginalLine line) - case x => return x - } - case _ => - } - - // figure out what kind of request - Right(buildRequest(line, lineNameCreator(), trees)) - } - - /** <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 ... - */ - def interpret(line: String): IR.Result = interpret(line, false) - def interpret(line: String, synthetic: Boolean): IR.Result = { - def loadAndRunReq(req: Request) = { - val (result, succeeded) = req.loadAndRun - // don't truncate stack traces - if (!succeeded) out print cleanNoTruncate(result) - else if (printResults) out print clean(result) - - // book-keeping - if (succeeded && !synthetic) - recordRequest(req) - - if (succeeded) IR.Success - else IR.Error - } - - if (global == null) IR.Error - else requestFromLine(line, synthetic) match { - case Left(result) => result - case Right(req) => - // null indicates a disallowed statement type; otherwise compile and - // fail if false (implying e.g. a type error) - if (req == null || !req.compile) IR.Error - else loadAndRunReq(req) - } - } - - /** A name creator used for objects created by <code>bind()</code>. */ - private lazy val newBinder = new NameCreator("binder") - - def bind[T](p: NamedParam[T]): IR.Result = - bind(p.name, p.tpe, p.value) - - def bindToType[T: ClassManifest](name: String, value: T): IR.Result = - bind(name, classManifest[T].erasure.getName, value) - - def bind[T: ClassManifest](name: String, value: Any): IR.Result = - bind(name, classManifest[T].erasure.getName, value) - - /** Bind a specified name to a specified value. The name may - * later be used by expressions passed to interpret. - * - * @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): IR.Result = { - val binderName = newBinder() - - compileString(""" - |object %s { - | var value: %s = _ - | def set(x: Any) = value = x.asInstanceOf[%s] - |} - """.stripMargin.format(binderName, boundType, boundType)) - - val binderObject = loadByName(binderName) - val setterMethod = methodByName(binderObject, "set") - - setterMethod.invoke(null, value.asInstanceOf[AnyRef]) - interpret("val %s = %s.value".format(name, binderName)) - } - - def quietBind[T: ClassManifest](name: String, value: T): IR.Result = - quietBind(name, classManifest[T].erasure.getName, value) - def quietBind(name: String, clazz: Class[_], value: Any): IR.Result = - quietBind(name, clazz.getName, value) // XXX need to port toTypeString - def quietBind(name: String, boundType: String, value: Any): IR.Result = - beQuietDuring { bind(name, boundType, value) } - - /** Reset this interpreter, forgetting all user-specified requests. */ - def reset() { - virtualDirectory.clear - resetClassLoader() - lineNameCreator.reset() - varNameCreator.reset() - prevRequests.clear - } - - /** <p> - * This instance is no longer needed, so release any resources - * it is using. The reporter's output gets flushed. - * </p> - */ - def close() { - reporter.flush - } - - /** A traverser that finds all mentioned identifiers, i.e. things - * that need to be imported. It might return extra names. - */ - private class ImportVarsTraverser extends Traverser { - val importVars = new mutable.HashSet[Name]() - - override def traverse(ast: Tree) = ast match { - case Ident(name) => - // XXX this is obviously inadequate but it's going to require some effort - // to get right. - if (name.toString startsWith "x$") () - else importVars += name - case _ => super.traverse(ast) - } - } - - /** Class to handle one member among all the members included - * in a single interpreter request. - */ - private sealed abstract class MemberHandler(val member: Tree) { - lazy val referencedNames: List[Name] = { - val ivt = new ImportVarsTraverser() - ivt traverse member - ivt.importVars.toList - } - def boundNames: List[Name] = Nil - val definesImplicit = cond(member) { - case tree: MemberDef => tree.mods.isImplicit - } - def generatesValue: Option[Name] = None - - def extraCodeToEvaluate(req: Request, code: PrintWriter) { } - def resultExtractionCode(req: Request, code: PrintWriter) { } - - override def toString = "%s(used = %s)".format(this.getClass.toString split '.' last, referencedNames) - } - - private class GenericHandler(member: Tree) extends MemberHandler(member) - - private class ValHandler(member: ValDef) extends MemberHandler(member) { - val maxStringElements = 1000 // no need to mkString billions of elements - lazy val ValDef(mods, vname, _, _) = member - lazy val prettyName = NameTransformer.decode(vname) - - override lazy val boundNames = List(vname) - override def generatesValue = Some(vname) - - override def resultExtractionCode(req: Request, code: PrintWriter) { - val isInternal = isGeneratedVarName(vname) && req.lookupTypeOf(vname) == "Unit" - if (!mods.isPublic || isInternal) return - - lazy val extractor = "scala.runtime.ScalaRunTime.stringOf(%s, %s)".format(req fullPath vname, maxStringElements) - - // if this is a lazy val we avoid evaluating it here - val resultString = if (mods.isLazy) codegenln(false, "<lazy>") else extractor - val codeToPrint = - """ + "%s: %s = " + %s""".format(prettyName, string2code(req typeOf vname), resultString) - - code print codeToPrint - } - } - - private class DefHandler(defDef: DefDef) extends MemberHandler(defDef) { - lazy val DefDef(mods, name, _, vparamss, _, _) = defDef - override lazy val boundNames = List(name) - // true if 0-arity - override def generatesValue = - if (vparamss.isEmpty || vparamss.head.isEmpty) Some(name) - else None - - override def resultExtractionCode(req: Request, code: PrintWriter) = - if (mods.isPublic) code print codegenln(name, ": ", req.typeOf(name)) - } - - private class AssignHandler(member: Assign) extends MemberHandler(member) { - val lhs = member.lhs.asInstanceOf[Ident] // an unfortunate limitation - val helperName = newTermName(synthVarNameCreator()) - override def generatesValue = Some(helperName) - - override def extraCodeToEvaluate(req: Request, code: PrintWriter) = - code println """val %s = %s""".format(helperName, lhs) - - /** Print out lhs instead of the generated varName */ - override def resultExtractionCode(req: Request, code: PrintWriter) { - val lhsType = string2code(req lookupTypeOf helperName) - val res = string2code(req fullPath helperName) - val codeToPrint = """ + "%s: %s = " + %s + "\n" """.format(lhs, lhsType, res) - - code println codeToPrint - } - } - - private class ModuleHandler(module: ModuleDef) extends MemberHandler(module) { - lazy val ModuleDef(mods, name, _) = module - override lazy val boundNames = List(name) - override def generatesValue = Some(name) - - override def resultExtractionCode(req: Request, code: PrintWriter) = - code println codegenln("defined module ", name) - } - - private class ClassHandler(classdef: ClassDef) extends MemberHandler(classdef) { - lazy val ClassDef(mods, name, _, _) = classdef - override lazy val boundNames = - name :: (if (mods.isCase) List(name.toTermName) else Nil) - - override def resultExtractionCode(req: Request, code: PrintWriter) = - code print codegenln("defined %s %s".format(classdef.keyword, name)) - } - - private class TypeAliasHandler(typeDef: TypeDef) extends MemberHandler(typeDef) { - lazy val TypeDef(mods, name, _, _) = typeDef - def isAlias() = mods.isPublic && treeInfo.isAliasTypeDef(typeDef) - override lazy val boundNames = if (isAlias) List(name) else Nil - - override def resultExtractionCode(req: Request, code: PrintWriter) = - code println codegenln("defined type alias ", name) - } - - private class ImportHandler(imp: Import) extends MemberHandler(imp) { - val Import(expr, selectors) = imp - def targetType = stringToCompilerType(expr.toString) match { - case NoType => None - case x => Some(x) - } - - private def selectorWild = selectors filter (_.name == USCOREkw) // wildcard imports, e.g. import foo._ - private def selectorRenames = selectors map (_.rename) filterNot (_ == null) - - /** Whether this import includes a wildcard import */ - val importsWildcard = selectorWild.nonEmpty - - /** Complete list of names imported by a wildcard */ - def wildcardImportedNames: List[Name] = ( - for (tpe <- targetType ; if importsWildcard) yield - tpe.nonPrivateMembers filter (x => x.isMethod && x.isPublic) map (_.name) distinct - ).toList.flatten - - /** The individual names imported by this statement */ - /** XXX come back to this and see what can be done with wildcards now that - * we know how to enumerate the identifiers. - */ - val importedNames: List[Name] = - selectorRenames filterNot (_ == USCOREkw) flatMap (_.bothNames) - - override def resultExtractionCode(req: Request, code: PrintWriter) = { - code println codegenln(imp.toString) - } - } - - /** One line of code submitted by the user for interpretation */ - private class Request(val line: String, val lineName: String, val trees: List[Tree]) { - private var _originalLine: String = null - def withOriginalLine(s: String): this.type = { _originalLine = s ; this } - def originalLine = if (_originalLine == null) line else _originalLine - - /** 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 = RESULT_OBJECT_PREFIX + objectName - - /** handlers for each tree in this request */ - val handlers: List[MemberHandler] = trees map chooseHandler - - /** all (public) names defined by these statements */ - val boundNames = handlers flatMap (_.boundNames) - - /** list of names used by this expression */ - val referencedNames: List[Name] = handlers flatMap (_.referencedNames) - - /** def and val names */ - def defNames = partialFlatMap(handlers) { case x: DefHandler => x.boundNames } - def valueNames = partialFlatMap(handlers) { - case x: AssignHandler => List(x.helperName) - case x: ValHandler => boundNames - case x: ModuleHandler => List(x.name) - } - /** Type names */ - def typeNames = handlers collect { - case x: ClassHandler => x.name - case x: TypeAliasHandler => x.name - } - - /** Code to import bound names from previous lines - accessPath is code to - * append to objectName to access anything bound by request. - */ - val ComputedImports(importsPreamble, importsTrailer, accessPath) = - importsCode(Set.empty ++ referencedNames) - - /** Code to access a variable with the specified name */ - def fullPath(vname: String): String = "%s.`%s`".format(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 = stringFromWriter { code => - val preamble = """ - |object %s { - | %s%s - """.stripMargin.format(objectName, importsPreamble, indentCode(toCompute)) - val postamble = importsTrailer + "\n}" - - code println preamble - handlers foreach { _.extraCodeToEvaluate(this, code) } - code println postamble - } - - /** generate source code for the object that retrieves the result - from objectSourceCode */ - def resultObjectSourceCode: String = stringFromWriter { code => - /** We only want to generate this code when the result - * is a value which can be referred to as-is. - */ - val valueExtractor = handlers.last.generatesValue match { - case Some(vname) if typeOf contains vname => - """ - |lazy val scala_repl_value = { - | scala_repl_result - | %s - |}""".stripMargin.format(fullPath(vname)) - case _ => "" - } - - // first line evaluates object to make sure constructor is run - // initial "" so later code can uniformly be: + etc - val preamble = """ - |object %s { - | %s - | val scala_repl_result: String = { - | %s - | ("" - """.stripMargin.format(resultObjectName, valueExtractor, objectName + accessPath) - - val postamble = """ - | ) - | } - |} - """.stripMargin - - code println preamble - handlers foreach { _.resultExtractionCode(this, code) } - code println postamble - } - - // compile the object containing the user's code - lazy val objRun = compileAndSaveRun("<console>", objectSourceCode) - - // compile the result-extraction object - lazy val extractionObjectRun = compileAndSaveRun("<console>", resultObjectSourceCode) - - lazy val loadedResultObject = loadByName(resultObjectName) - - def extractionValue(): Option[AnyRef] = { - // ensure it has run - extractionObjectRun - - // load it and retrieve the value - try Some(loadedResultObject getMethod "scala_repl_value" invoke loadedResultObject) - catch { case _: Exception => None } - } - - /** Compile the object file. Returns whether the compilation succeeded. - * If all goes well, the "types" map is computed. */ - def compile(): Boolean = { - // error counting is wrong, hence interpreter may overlook failure - so we reset - reporter.reset - - // compile the main object - objRun - - // bail on error - if (reporter.hasErrors) - return false - - // extract and remember types - typeOf - definedTypes - - // compile the result-extraction object - extractionObjectRun - - // success - !reporter.hasErrors - } - - def afterTyper[T](op: => T): T = atPhase(objRun.typerPhase.next)(op) - - /** The outermost wrapper object */ - lazy val outerResObjSym: Symbol = getMember(EmptyPackage, newTermName(objectName)) - - /** The innermost object inside the wrapper, found by - * following accessPath into the outer one. */ - lazy val resObjSym = - accessPath.split("\\.").foldLeft(outerResObjSym) { (sym, name) => - if (name == "") sym else - afterTyper(sym.info member newTermName(name)) - } - - /* typeOf lookup with encoding */ - def lookupTypeOf(name: Name) = typeOf.getOrElse(name, typeOf(global.encode(name))) - def simpleNameOfType(name: TypeName) = (compilerTypeOf get name) map (_.typeSymbol.simpleName) - - private def typeMap[T](f: Type => T): Map[Name, T] = { - def toType(name: Name): T = { - // the types are all =>T; remove the => - val tp1 = afterTyper(resObjSym.info.nonPrivateDecl(name).tpe match { - case NullaryMethodType(tp) => tp - case tp => tp - }) - // normalize non-public types so we don't see protected aliases like Self - afterTyper(tp1 match { - case TypeRef(_, sym, _) if !sym.isPublic => f(tp1.normalize) - case tp => f(tp) - }) - } - valueNames ++ defNames ++ typeNames map (x => x -> toType(x)) toMap - } - /** Types of variables defined by this request. */ - lazy val compilerTypeOf = typeMap[Type](x => x) - /** String representations of same. */ - lazy val typeOf = typeMap[String](_.toString) - - lazy val definedTypes: Map[Name, Type] = { - typeNames map (x => x -> afterTyper(resObjSym.info.nonPrivateDecl(x).tpe)) toMap - } - - private def bindExceptionally(t: Throwable) = { - val ex: Exceptional = - if (isettings.showInternalStackTraces) Exceptional(t) - else new Exceptional(t) { - override def spanFn(frame: JavaStackFrame) = !(frame.className startsWith resultObjectName) - override def contextPrelude = super.contextPrelude + "/* The repl internal portion of the stack trace is elided. */\n" - } - - quietBind("lastException", ex) - ex.contextHead + "\n(access lastException for the full trace)" - } - private def bindUnexceptionally(t: Throwable) = { - quietBind("lastException", t) - stackTraceString(t) - } - - /** load and run the code using reflection */ - def loadAndRun: (String, Boolean) = { - import interpreter.Line._ - - def handleException(t: Throwable) = { - /** We turn off the binding to accomodate ticket #2817 */ - withoutBindingLastException { - val message = - if (opt.richExes) bindExceptionally(unwrap(t)) - else bindUnexceptionally(unwrap(t)) - - (message, false) - } - } - - try { - val resultValMethod = loadedResultObject getMethod "scala_repl_result" - val execution = lineManager.set(originalLine)(resultValMethod invoke loadedResultObject) - - execution.await() - execution.state match { - case Done => ("" + execution.get(), true) - case Threw => if (bindLastException) handleException(execution.caught()) else throw execution.caught() - case Cancelled => ("Execution interrupted by signal.\n", false) - case Running => ("Execution still running! Seems impossible.", false) - } - } - finally lineManager.clear() - } - - override def toString = "Request(line=%s, %s trees)".format(line, trees.size) - } - - /** Returns the name of the most recent interpreter result. - * Mostly this exists so you can conveniently invoke methods on - * the previous result. - */ - def mostRecentVar: String = - if (mostRecentlyHandledTree.isEmpty) "" - else mostRecentlyHandledTree.get match { - case x: ValOrDefDef => x.name - case Assign(Ident(name), _) => name - case ModuleDef(_, name, _) => name - case _ => varNameCreator.mostRecent - } - - private def requestForName(name: Name): Option[Request] = - prevRequests.reverse find (_.boundNames contains name) - - private def requestForIdent(line: String): Option[Request] = requestForName(newTermName(line)) - - // XXX literals. - def stringToCompilerType(id: String): Type = { - // if it's a recognized identifier, the type of that; otherwise treat the - // String like a value (e.g. scala.collection.Map) . - def findType = typeForIdent(id) match { - case Some(x) => definitions.getClass(newTermName(x)).tpe - case _ => definitions.getModule(newTermName(id)).tpe - } - - try findType catch { case _: MissingRequirementError => NoType } - } - - def typeForIdent(id: String): Option[String] = - requestForIdent(id) flatMap (x => x.typeOf get newTermName(id)) - - def methodsOf(name: String) = - evalExpr[List[String]](methodsCode(name)) map (x => NameTransformer.decode(getOriginalName(x))) - - def completionAware(name: String) = { - // XXX working around "object is not a value" crash, i.e. - // import java.util.ArrayList ; ArrayList.<tab> - clazzForIdent(name) flatMap (_ => evalExpr[Option[CompletionAware]](asCompletionAwareCode(name))) - } - - def extractionValueForIdent(id: String): Option[AnyRef] = - requestForIdent(id) flatMap (_.extractionValue) - - /** Executes code looking for a manifest of type T. - */ - def manifestFor[T: Manifest] = - evalExpr[Manifest[T]]("""manifest[%s]""".format(manifest[T])) - - /** Executes code looking for an implicit value of type T. - */ - def implicitFor[T: Manifest] = { - val s = manifest[T].toString - evalExpr[Option[T]]("{ def f(implicit x: %s = null): %s = x ; Option(f) }".format(s, s)) - // We don't use implicitly so as to fail without failing. - // evalExpr[T]("""implicitly[%s]""".format(manifest[T])) - } - /** Executes code looking for an implicit conversion from the type - * of the given identifier to CompletionAware. - */ - def completionAwareImplicit[T](id: String) = { - val f1string = "%s => %s".format(typeForIdent(id).get, classOf[CompletionAware].getName) - val code = """{ - | def f(implicit x: (%s) = null): %s = x - | val f1 = f - | if (f1 == null) None else Some(f1(%s)) - |}""".stripMargin.format(f1string, f1string, id) - - evalExpr[Option[CompletionAware]](code) - } - - def clazzForIdent(id: String): Option[Class[_]] = - extractionValueForIdent(id) flatMap (x => Option(x) map (_.getClass)) - - private def methodsCode(name: String) = - "%s.%s(%s)".format(classOf[ReflectionCompletion].getName, "methodsOf", name) - - private def asCompletionAwareCode(name: String) = - "%s.%s(%s)".format(classOf[CompletionAware].getName, "unapply", name) - - private def getOriginalName(name: String): String = - nme.originalName(newTermName(name)).toString - - case class InterpreterEvalException(msg: String) extends Exception(msg) - def evalError(msg: String) = throw InterpreterEvalException(msg) - - /** The user-facing eval in :power mode wraps an Option. - */ - def eval[T: Manifest](line: String): Option[T] = - try Some(evalExpr[T](line)) - catch { case InterpreterEvalException(msg) => out println indentString(msg) ; None } - - def evalExpr[T: Manifest](line: String): T = { - // Nothing means the type could not be inferred. - if (manifest[T] eq Manifest.Nothing) - evalError("Could not infer type: try 'eval[SomeType](%s)' instead".format(line)) - - val lhs = getSynthVarName - beQuietDuring { interpret("val " + lhs + " = { " + line + " } ") } - - // TODO - can we meaningfully compare the inferred type T with - // the internal compiler Type assigned to lhs? - // def assignedType = prevRequests.last.typeOf(newTermName(lhs)) - - val req = requestFromLine(lhs, true) match { - case Left(result) => evalError(result.toString) - case Right(req) => req - } - if (req == null || !req.compile || req.handlers.size != 1) - evalError("Eval error.") - - try req.extractionValue.get.asInstanceOf[T] catch { - case e: Exception => evalError(e.getMessage) - } - } - - def interpretExpr[T: Manifest](code: String): Option[T] = beQuietDuring { - interpret(code) match { - case IR.Success => - try prevRequests.last.extractionValue map (_.asInstanceOf[T]) - catch { case e: Exception => out println e ; None } - case _ => None - } - } - - /** Another entry point for tab-completion, ids in scope */ - private def unqualifiedIdNames() = partialFlatMap(allHandlers) { - case x: AssignHandler => List(x.helperName) - case x: ValHandler => List(x.vname) - case x: ModuleHandler => List(x.name) - case x: DefHandler => List(x.name) - case x: ImportHandler => x.importedNames - } filterNot isSynthVarName - - /** Types which have been wildcard imported, such as: - * val x = "abc" ; import x._ // type java.lang.String - * import java.lang.String._ // object java.lang.String - * - * Used by tab completion. - * - * XXX right now this gets import x._ and import java.lang.String._, - * but doesn't figure out import String._. There's a lot of ad hoc - * scope twiddling which should be swept away in favor of digging - * into the compiler scopes. - */ - def wildcardImportedTypes(): List[Type] = { - val xs = allHandlers collect { case x: ImportHandler if x.importsWildcard => x.targetType } - xs.flatten.reverse.distinct - } - - /** Another entry point for tab-completion, ids in scope */ - def unqualifiedIds() = (unqualifiedIdNames() map (_.toString)).distinct.sorted - - /** For static/object method completion */ - def getClassObject(path: String): Option[Class[_]] = classLoader tryToLoadClass path - - /** Parse the ScalaSig to find type aliases */ - def aliasForType(path: String) = ByteCode.aliasForType(path) - - // Coming soon - // implicit def string2liftedcode(s: String): LiftedCode = new LiftedCode(s) - // case class LiftedCode(code: String) { - // val lifted: String = { - // beQuietDuring { interpret(code) } - // eval2[String]("({ " + code + " }).toString") - // } - // def >> : String = lifted - // } - - // debugging - def isCompletionDebug = settings.Ycompletion.value - def DBG(s: => String) = - try if (isReplDebug) repldbg(s) - catch { case x: AssertionError => repldbg("Assertion error printing debug string:\n " + x) } -} - -/** Utility methods for the Interpreter. */ -object Interpreter { - import scala.collection.generic.CanBuildFrom - def partialFlatMap[A, B, CC[X] <: Traversable[X]] - (coll: CC[A]) - (pf: PartialFunction[A, CC[B]]) - (implicit bf: CanBuildFrom[CC[A], B, CC[B]]) = - { - val b = bf(coll) - for (x <- coll collect pf) - b ++= x - - b.result - } - - def codegenln(leadingPlus: Boolean, xs: String*): String = codegen(leadingPlus, (xs ++ Array("\n")): _*) - def codegenln(xs: String*): String = codegenln(true, xs: _*) - - def codegen(xs: String*): String = codegen(true, xs: _*) - def codegen(leadingPlus: Boolean, xs: String*): String = { - val front = if (leadingPlus) "+ " else "" - front + (xs map string2codeQuoted mkString " + ") - } - - def string2codeQuoted(str: String) = "\"" + string2code(str) + "\"" - - /** 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 = { - val res = new StringBuilder - for (c <- str) c match { - case '"' | '\'' | '\\' => res += '\\' ; res += c - case _ if c.isControl => res ++= Chars.char2uescape(c) - case _ => res += c - } - res.toString - } -} +}
\ No newline at end of file |