/* NSC -- new Scala compiler * Copyright 2005-2009 LAMP/EPFL * @author Martin Odersky */ // $Id: Interpreter.scala 18610 2009-08-30 20:42:17Z extempore $ package scala.tools.nsc import java.io.{ File, PrintWriter, StringWriter, Writer } import java.lang.{ Class, ClassLoader } import java.net.{ MalformedURLException, URL } import java.lang.reflect import reflect.InvocationTargetException import scala.collection.immutable.ListSet import scala.collection.mutable import scala.collection.mutable.{ ListBuffer, HashSet, ArrayBuffer } import scala.util.{ ScalaClassLoader, URLClassLoader } import scala.util.control.Exception.{ Catcher, catching, ultimately, unwrapping } import io.{ PlainFile, VirtualDirectory } import reporters.{ ConsoleReporter, Reporter } import symtab.{ Flags, Names } import util.{ SourceFile, BatchSourceFile, ClassPath } import scala.util.NameTransformer import scala.tools.nsc.{ InterpreterResults => IR } import interpreter._ import Interpreter._ /** <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 "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(val settings: Settings, out: PrintWriter) { /** directory to save .class files to */ val virtualDirectory = new VirtualDirectory("(memory)", None) /** the compiler to compile expressions with */ val compiler: Global = newCompiler(settings, reporter) import compiler.{ Traverser, CompilationUnit, Symbol, Name, Type } import compiler.{ Tree, TermTree, ValOrDefDef, ValDef, DefDef, Assign, ClassDef, ModuleDef, Ident, Select, TypeDef, Import, MemberDef, DocDef } import compiler.{ nme, newTermName } import nme.{ INTERPRETER_VAR_PREFIX, INTERPRETER_SYNTHVAR_PREFIX, INTERPRETER_LINE_PREFIX, INTERPRETER_IMPORT_WRAPPER, INTERPRETER_WRAPPER_SUFFIX, USCOREkw } /** construct an interpreter that reports to Console */ def this(settings: Settings) = this(settings, new NewLinePrintWriter(new ConsoleWriter, true)) /** whether to print out result lines */ private[nsc] var printResults: Boolean = true /** Temporarily be quiet */ def beQuietDuring[T](operation: => T): T = { val wasPrinting = printResults ultimately(printResults = wasPrinting) { printResults = false operation } } /** interpreter settings */ lazy val isettings = new InterpreterSettings object reporter extends ConsoleReporter(settings, null, out) { override def printMessage(msg: String) { out.print(clean(msg) + "\n"); out.flush() } } /** 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 */ val compilerClasspath: List[URL] = { import scala.net.Utility.parseURL val classpathPart = ClassPath.expandPath(compiler.settings.classpath.value).map(s => new File(s).toURL) val codebasePart = (compiler.settings.Xcodebase.value.split(" ")).toList flatMap parseURL classpathPart ::: codebasePart } /* 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: ScalaClassLoader = makeClassLoader() private def makeClassLoader(): ScalaClassLoader = { 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 = this.getClass.getClassLoader() // 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]() val prevImports = new ListBuffer[Import]() private def allUsedNames = prevRequests.toList.flatMap(_.usedNames).removeDuplicates private def allBoundNames = prevRequests.toList.flatMap(_.boundNames).removeDuplicates // private def allImportedNames = prevImports.toList.flatMap(_.importedNames).removeDuplicates /** Generates names pre0, pre1, etc. via calls to apply method */ class NameCreator(pre: String) { private var x = -1 def apply(): String = { x += 1 val name = pre + x.toString // make sure we don't overwrite their unwisely named res3 etc. if (allBoundNames exists (_.toString == name)) apply() else name } def reset(): Unit = x = -1 def didGenerate(name: String) = (name startsWith pre) && ((name drop pre.length) forall (_.isDigit)) } /** allocate a fresh line name */ private val lineNameCreator = new NameCreator(INTERPRETER_LINE_PREFIX) /** allocate a fresh var name */ private val varNameCreator = new NameCreator(INTERPRETER_VAR_PREFIX) /** allocate a fresh internal variable name */ private def 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 /** 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 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(stripWrapperGunk(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) = stringFrom(str => for (line <- code.lines) { str.print(spaces) str.print(line + "\n") str.flush() }) 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 _ def keepHandler(handler: MemberHandler): Boolean = { import handler._ // Single symbol imports might be implicits! See bug #1752. Rather than // try to finesse this, we will mimic all imports for now. def isImport = handler.isInstanceOf[ImportHandler] definesImplicit || isImport || (importedNames ++ boundNames).exists(isWanted) } reqs match { case Nil => Nil case rh :: rest if !keepHandler(rh.handler) => select(rest, wanted) case rh :: rest => import rh.handler._ val newWanted = wanted ++ usedNames -- boundNames -- importedNames rh :: select(rest, newWanted) } } val rhpairs = for { req <- prevRequests.toList.reverse handler <- req.handlers } yield ReqAndHandler(req, handler) select(rhpairs, wanted).reverse } val code, trailingBraces, accessPath = new StringBuffer val currentImps = mutable.Set.empty[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) { import handler._ // 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 (importsWildcard || currentImps.exists(importedNames.contains)) addWrapper() if (member.isInstanceOf[Import]) code append (member.toString + "\n") // give wildcard imports a import wrapper all to their own if (importsWildcard) addWrapper() else currentImps ++= 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 <- boundNames) { if (currentImps contains imv) addWrapper() code append ("import " + 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. */ private 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 compiler.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) } } /** Compile an nsc SourceFile. Returns true if there are * no compilation errors, or false othrewise. */ def compileSources(sources: SourceFile*): Boolean = { reporter.reset new compiler.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)) /** 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): Request = new Request(line, lineName) 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) } /** <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 = { // initialize the compiler if (prevRequests.isEmpty) new compiler.Run() // 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 } // 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 => return interpret("val %s =\n%s".format(varNameCreator(), line)) case _ => } // figure out what kind of request val req = buildRequest(trees, line, lineNameCreator()) // null is a disallowed statement type; otherwise compile and fail if false (implying e.g. a type error) if (req == null || !req.compile) return IR.Error val (result, succeeded) = req.loadAndRun if (printResults || !succeeded) out print clean(result) if (succeeded) { prevRequests += req // book-keeping IR.Success } else IR.Error } /** A name creator used for objects created by <code>bind()</code>. */ private val newBinder = new NameCreator("binder") /** 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() // "binder" + binderNum() 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") // this roundabout approach is to ensure the value is boxed var argsHolder: Array[Any] = null argsHolder = List(value).toArray setterMethod.invoke(null, argsHolder.asInstanceOf[Array[AnyRef]]: _*) interpret("val %s = %s.value".format(name, binderName)) } /** Reset this interpreter, forgetting all user-specified requests. */ def reset() { virtualDirectory.clear classLoader = makeClassLoader 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(definedVars: List[Name]) extends Traverser { val importVars = new HashSet[Name]() override def traverse(ast: Tree) = ast match { case Ident(name) => 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) { val usedNames: List[Name] = { val ivt = new ImportVarsTraverser(boundNames) ivt.traverseTrees(List(member)) ivt.importVars.toList } def 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 hasFlag 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) { lazy val ValDef(mods, vname, _, _) = member lazy val prettyName = NameTransformer.decode(vname) lazy val isLazy = mods hasFlag Flags.LAZY override lazy val boundNames = List(vname) override def valAndVarNames = boundNames override def resultExtractionCode(req: Request, code: PrintWriter) { val isInternal = isGeneratedVarName(vname) && req.typeOfEnc(vname) == "Unit" if (!mods.isPublic || isInternal) return lazy val extractor = """ | { | val s = scala.runtime.ScalaRunTime.stringOf(%s) | val nl = if (s.contains('\n')) "\n" else "" | nl + s + "\n" | } """.stripMargin.format(req fullPath vname) // if this is a lazy val we avoid evaluating it here val resultString = if (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, _, _, _, _) = defDef override lazy val boundNames = List(name) override def defNames = boundNames 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 val valAndVarNames = List(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 typeOfEnc 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 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 hasFlag Flags.CASE) 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 && compiler.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) { /** Whether this import includes a wildcard import */ override val importsWildcard = imp.selectors.map(_._1) contains USCOREkw /** The individual names imported by this statement */ override val importedNames: Seq[Name] = for { (_, sel) <- imp.selectors if (sel != null && sel != USCOREkw) name <- List(sel.toTypeName, sel.toTermName) } yield name // record the import prevImports += imp 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 = parse(line) getOrElse 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 /** handlers for each tree in this request */ val handlers: List[MemberHandler] = trees map 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) /** 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 ++ usedNames) /** Code to access a variable with the specified name */ def fullPath(vname: String): String = "%s.`%s`\n".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 = stringFrom { code => // whitespace compatible with interpreter.scala val preamble = """object %s { | %s%s """.stripMargin.format(objectName, importsPreamble, indentCode(toCompute)) // val preamble = """ // | object %s { // | %s %s // """.stripMargin.format(objectName, importsPreamble, indentCode(toCompute)) val postamble = importsTrailer + "; }" code println preamble handlers foreach { _.extraCodeToEvaluate(this, code) } code println postamble } /** Types of variables defined by this request. They are computed after compilation of the main object */ var typeOf: Map[Name, String] = _ def typeOfEnc(vname: Name) = typeOf(compiler encode vname) /** generate source code for the object that retrieves the result from objectSourceCode */ def resultObjectSourceCode: String = stringFrom { code => val preamble = """ | object %s { | val result: String = { | %s // evaluate object to make sure constructor is run | ("" // an initial "" so later code can uniformly be: + etc """.stripMargin.format(resultObjectName, objectName + accessPath) val postamble = """ | ) | } | } """.stripMargin code println preamble handlers foreach { _.resultExtractionCode(this, code) } code println postamble } /** 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 val objRun = new compiler.Run() objRun.compileSources(List(new BatchSourceFile("<console>", objectSourceCode))) if (reporter.hasErrors) return false // extract and remember types typeOf = findTypes(objRun) // compile the result-extraction object new compiler.Run().compileSources(List(new BatchSourceFile("<console>", resultObjectSourceCode))) // success !reporter.hasErrors } /** Dig the types of all bound variables out of the compiler run. * * @param objRun ... * @return ... */ def findTypes(objRun: compiler.Run): Map[Name, String] = { import compiler.definitions.{ EmptyPackage, getMember } def valAndVarNames = handlers flatMap { _.valAndVarNames } def defNames = handlers flatMap { _.defNames } def getTypes(names: List[Name], nameMap: Name => Name): Map[Name, String] = { def atNextPhase[T](op: => T): T = compiler.atPhase(objRun.typerPhase.next)(op) /** the outermost wrapper object */ val outerResObjSym: Symbol = getMember(EmptyPackage, newTermName(objectName)) /** the innermost object inside the wrapper, found by * following accessPath into the outer one. */ val resObjSym = accessPath.split("\\.").foldLeft(outerResObjSym) { (sym, name) => if (name == "") sym else atNextPhase(sym.info member newTermName(name)) } names.foldLeft(Map.empty[Name, String]) { (map, name) => val rawType = atNextPhase(resObjSym.info.member(name).tpe) // the types are all =>T; remove the => val cleanedType = rawType match { case compiler.PolyType(Nil, rt) => rt case rawType => rawType } map + (name -> atNextPhase(cleanedType.toString)) } } val names1 = getTypes(valAndVarNames, nme.getterToLocal(_)) val names2 = getTypes(defNames, identity) names1 ++ names2 } /** load and run the code using reflection */ def loadAndRun: (String, Boolean) = { val resultObject: Class[_] = loadByName(resultObjectName) val resultValMethod: reflect.Method = resultObject getMethod "result" // XXX if wrapperExceptions isn't type-annotated we crash scalac val wrapperExceptions: List[Class[_ <: Throwable]] = List(classOf[InvocationTargetException], classOf[ExceptionInInitializerError]) def onErr: Catcher[(String, Boolean)] = { case t: Throwable => beQuietDuring { bind("lastException", "java.lang.Throwable", t) } (stringFrom(t.printStackTrace(_)), false) } catching(onErr) { unwrapping(wrapperExceptions: _*) { (resultValMethod.invoke(resultObject).toString, true) } } } } /** These methods are exposed so REPL commands can access them. * The command infrastructure is in InterpreterLoop. */ def dumpState(xs: List[String]): String = { // println("Imports for " + req + " => " + req.importsPreamble) // req.handlers foreach { h => println("Handler " + h + " used names: " + h.usedNames) } // req.trees foreach { x => println("Tree: " + x) } // xs foreach { x => println("membersOfIdentifier(" + x + ") = " + membersOfIdentifier(x)) } List( "allUsedNames = " + allUsedNames, "allBoundNames = " + allBoundNames, prevRequests.toList.map(req => " \"" + req.line + "\" => " + req.objectSourceCode) ).mkString("", "\n", "\n") } // very simple right now, will get more interesting def dumpTrees(xs: List[String]): String = { val treestrs = List.flatten( for (x <- xs ; name <- nameOfIdent(x) ; req <- requestForName(name)) yield req.trees ) if (treestrs.isEmpty) "No trees found." else treestrs.map(t => t.toString + " (" + t.getClass.getSimpleName + ")\n").mkString } def powerUser(): String = { beQuietDuring { val mkTypeCmd = """def mkType(name: String, what: String) = interpreter.interpret("type " + name + " = " + what)""" this.bind("interpreter", "scala.tools.nsc.Interpreter", this) interpret(mkTypeCmd) } """** Power User mode enabled - BEEP BOOP ** |** New vals! Try interpreter.<tab> ** |** New defs! Try mkType("T", "String") ** |** New cmds! :help to discover them **""".stripMargin } def nameOfIdent(line: String): Option[Name] = { parse(line) match { case Some(List(Ident(x))) => Some(x) case _ => None } } private def requestForName(name: Name): Option[Request] = { for (req <- prevRequests.toList.reverse) { if (req.handlers.exists(_.boundNames contains name)) return Some(req) } None } // XXX at the moment this is imperfect because scala's protected semantics // differ from java's, so protected methods appear public via reflection; // yet scala enforces the protection. The result is that protected members // appear in completion yet cannot actually be called. Fixing this // properly requires a scala.reflect.* API. Fixing it uglily is possible // too (cast to structural type!) but I deem poor use of energy. private val filterFlags: Int = { import java.lang.reflect.Modifier._ STATIC | PRIVATE | PROTECTED } private val methodsCode = """ . | asInstanceOf[AnyRef].getClass.getMethods . | filter(x => (x.getModifiers & %d) == 0) . | map(_.getName) . | mkString(" ")""".stripMargin.format(filterFlags) /** The main entry point for tab-completion. When the user types x.<tab> * this method is called with "x" as an argument, and it discovers the * fields and methods of x via reflection and returns their names to jline. */ def membersOfIdentifier(line: String): List[String] = { import Completion.{ isValidCompletion } import NameTransformer.{ decode, encode } // e.g. $plus$plus => ++ val res = beQuietDuring { for (name <- nameOfIdent(line) ; req <- requestForName(name)) yield { if (interpret("val " + synthVarNameCreator() + " = " + name + methodsCode) != IR.Success) Nil else { val result = prevRequests.last.resultObjectName val resultObj = (classLoader tryToInitializeClass result).get val valMethod = resultObj getMethod "result" val str = valMethod.invoke(resultObj).toString str.substring(str.indexOf('=') + 1).trim . split(" ").toList . map(decode) . filter(isValidCompletion) . removeDuplicates } } } res getOrElse Nil } /** Another entry point for tab-completion, ids in scope */ def unqualifiedIds(): List[String] = allBoundNames . map(_.toString) . filter(!isSynthVarName(_)) /** For static/object method completion */ def getClassObject(path: String): Option[Class[_]] = classLoader tryToLoadClass path // debugging private var debuggingOutput = false def DBG(s: String) = if (debuggingOutput) out println s else () } /** Utility methods for the Interpreter. */ object Interpreter { object DebugParam { implicit def tuple2debugparam[T](x: (String, T))(implicit m: scala.reflect.Manifest[T]): DebugParam[T] = DebugParam(x._1, x._2) implicit def any2debugparam[T](x: T)(implicit m: scala.reflect.Manifest[T]): DebugParam[T] = DebugParam("p" + getCount(), x) private var counter = 0 def getCount() = { counter += 1; counter } } case class DebugParam[T](name: String, param: T)(implicit m: scala.reflect.Manifest[T]) { val manifest = m val typeStr = { val str = manifest.toString // I'm sure there are more to be discovered... val regexp1 = """(.*?)\[(.*)\]""".r val regexp2str = """.*\.type#""" val regexp2 = (regexp2str + """(.*)""").r (str.replaceAll("""\n""", "")) match { case regexp1(clazz, typeArgs) => "%s[%s]".format(clazz, typeArgs.replaceAll(regexp2str, "")) case regexp2(clazz) => clazz case _ => str } } } def breakIf(assertion: => Boolean, args: DebugParam[_]*): Unit = if (assertion) break(args.toList) // start a repl, binding supplied args def break(args: List[DebugParam[_]]): Unit = { val intLoop = new InterpreterLoop intLoop.settings = new Settings(Console.println) intLoop.createInterpreter intLoop.in = InteractiveReader.createDefault(intLoop.interpreter) // rebind exit so people don't accidentally call System.exit by way of predef intLoop.interpreter.beQuietDuring { intLoop.interpreter.interpret("""def exit = println("Type :quit to resume program execution.")""") for (p <- args) { intLoop.interpreter.bind(p.name, p.typeStr, p.param) println("%s: %s".format(p.name, p.typeStr)) } } intLoop.repl() intLoop.closeInterpreter } /** 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, "") } 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 "" xs.map("\"" + string2code(_) + "\"").mkString(front, " + ", "") } /** 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) c match { case '"' | '\'' | '\\' => res += '\\' ; res += c case _ if c.isControl => res ++= char2uescape(c) case _ => res += c } res.toString } }