diff options
34 files changed, 2798 insertions, 2599 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 diff --git a/src/compiler/scala/tools/nsc/InterpreterCommand.scala b/src/compiler/scala/tools/nsc/InterpreterCommand.scala index 99e3e3892c..ae2530ce40 100644 --- a/src/compiler/scala/tools/nsc/InterpreterCommand.scala +++ b/src/compiler/scala/tools/nsc/InterpreterCommand.scala @@ -1,16 +1,8 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Martin Odersky - */ - package scala.tools.nsc -/** A command line for the interpreter. - * - * @author Lex Spoon - * @version 1.0 +import interpreter._ + +/** A compatibility stub. */ -class InterpreterCommand(arguments: List[String], error: String => Unit) extends CompilerCommand(arguments, error) { - override val cmdName = "scala" - override lazy val fileEndings = List(".scalaint") -} +@deprecated("Use a class in the scala.tools.nsc.interpreter package.") +class InterpreterCommand(arguments: List[String], error: String => Unit) extends CommandLine(arguments, error) { }
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/InterpreterLoop.scala b/src/compiler/scala/tools/nsc/InterpreterLoop.scala index 6bb9428528..8eb381f18a 100644 --- a/src/compiler/scala/tools/nsc/InterpreterLoop.scala +++ b/src/compiler/scala/tools/nsc/InterpreterLoop.scala @@ -1,712 +1,12 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Alexander Spoon - */ - package scala.tools.nsc -import Predef.{ println => _, _ } -import java.io.{ BufferedReader, FileReader, PrintWriter } -import java.io.IOException - -import scala.sys.process.Process -import scala.tools.nsc.{ InterpreterResults => IR } -import scala.tools.util.SignalManager -import scala.annotation.tailrec -import scala.util.control.Exception.{ ignoring } -import scala.collection.mutable.ListBuffer -import scala.concurrent.ops -import util.{ ClassPath } import interpreter._ -import io.File - -// Classes to wrap up interpreter commands and their results -// You can add new commands by adding entries to val commands -// inside InterpreterLoop. -trait InterpreterControl { - self: InterpreterLoop => - - private def isQuoted(s: String) = - (s.length >= 2) && (s.head == s.last) && ("\"'" contains s.head) - - // a single interpreter command - sealed abstract class Command extends Function1[List[String], Result] { - def name: String - def help: String - def commandError(msg: String) = { - out.println(":" + name + " " + msg + ".") - Result(true, None) - } - def usage(): String - } +import java.io._ - case class NoArgs(name: String, help: String, f: () => Result) extends Command { - def usage(): String = ":" + name - def apply(args: List[String]) = if (args.isEmpty) f() else commandError("accepts no arguments") - } - - case class LineArg(name: String, help: String, f: (String) => Result) extends Command { - def usage(): String = ":" + name + " " - def apply(args: List[String]) = f(args mkString " ") - } - - case class OneArg(name: String, help: String, f: (String) => Result) extends Command { - def usage(): String = ":" + name + " " - def apply(args: List[String]) = - if (args.size == 1) f(args.head) - else commandError("requires exactly one argument") - } - - case class VarArgs(name: String, help: String, f: (List[String]) => Result) extends Command { - def usage(): String = ":" + name + " [arg]" - def apply(args: List[String]) = f(args) - } - - // the result of a single command - case class Result(val keepRunning: Boolean, val lineToRecord: Option[String]) - object Result { - // the default result means "keep running, and don't record that line" - val default = Result(true, None) - - // most commands do not want to micromanage the Result, but they might want - // to print something to the console, so we accomodate Unit and String returns. - implicit def resultFromUnit(x: Unit): Result = default - implicit def resultFromString(msg: String): Result = { - out println msg - default - } - } -} - -/** The Scala interactive shell. It provides a read-eval-print loop - * around the Interpreter class. - * After instantiation, clients should call the main() method. - * - * If no in0 is specified, then input will come from the console, and - * the class will attempt to provide input editing feature such as - * input history. - * - * @author Moez A. Abdel-Gawad - * @author Lex Spoon - * @version 1.2 +/** A compatibility stub. */ -class InterpreterLoop(in0: Option[BufferedReader], protected val out: PrintWriter) extends InterpreterControl { +@deprecated("Use a class in the scala.tools.nsc.interpreter package.") +class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) extends ILoop(in0, out) { def this(in0: BufferedReader, out: PrintWriter) = this(Some(in0), out) - def this() = this(None, new PrintWriter(Console.out)) - - var in: InteractiveReader = _ // the input stream from which commands come - var settings: Settings = _ - var intp: Interpreter = _ - var power: Power = _ - - /** The context class loader at the time this object was created */ - protected val originalClassLoader = Thread.currentThread.getContextClassLoader - - // classpath entries added via :cp - var addedClasspath: String = "" - - /** A reverse list of commands to replay if the user requests a :replay */ - var replayCommandStack: List[String] = Nil - - /** A list of commands to replay if the user requests a :replay */ - def replayCommands = replayCommandStack.reverse - - /** Record a command for replay should the user request a :replay */ - def addReplay(cmd: String) = replayCommandStack ::= cmd - - /** Try to install sigint handler: ignore failure. Signal handler - * will interrupt current line execution if any is in progress. - * - * Attempting to protect the repl from accidental exit, we only honor - * a single ctrl-C if the current buffer is empty: otherwise we look - * for a second one within a short time. - */ - private def installSigIntHandler() { - def onExit() { - Console.println("") // avoiding "shell prompt in middle of line" syndrome - sys.exit(1) - } - ignoring(classOf[Exception]) { - SignalManager("INT") = { - if (intp == null) - onExit() - else if (intp.lineManager.running) - intp.lineManager.cancel() - else if (in.currentLine != "") { - // non-empty buffer, so make them hit ctrl-C a second time - SignalManager("INT") = onExit() - io.timer(5)(installSigIntHandler()) // and restore original handler if they don't - } - else onExit() - } - } - } - - /** Close the interpreter and set the var to null. */ - def closeInterpreter() { - if (intp ne null) { - intp.close - intp = null - power = null - Thread.currentThread.setContextClassLoader(originalClassLoader) - } - } - - /** Create a new interpreter. */ - def createInterpreter() { - if (addedClasspath != "") - settings.classpath append addedClasspath - - intp = new Interpreter(settings, out) { - override protected def createLineManager() = new Line.Manager { - override def onRunaway(line: Line[_]): Unit = { - val template = """ - |// She's gone rogue, captain! Have to take her out! - |// Calling Thread.stop on runaway %s with offending code: - |// scala> %s""".stripMargin - - println(template.format(line.thread, line.code)) - // XXX no way to suppress the deprecation warning - line.thread.stop() - in.redrawLine() - } - } - override protected def parentClassLoader = - settings.explicitParentLoader.getOrElse( classOf[InterpreterLoop].getClassLoader ) - } - intp.setContextClassLoader() - installSigIntHandler() - // intp.quietBind("settings", "scala.tools.nsc.InterpreterSettings", intp.isettings) - } - - /** print a friendly help message */ - def printHelp() = { - out println "All commands can be abbreviated - for example :he instead of :help.\n" - val cmds = commands map (x => (x.usage, x.help)) - val width: Int = cmds map { case (x, _) => x.length } max - val formatStr = "%-" + width + "s %s" - cmds foreach { case (usage, help) => out println formatStr.format(usage, help) } - } - - /** Print a welcome message */ - def printWelcome() { - import Properties._ - val welcomeMsg = - """|Welcome to Scala %s (%s, Java %s). - |Type in expressions to have them evaluated. - |Type :help for more information.""" . - stripMargin.format(versionString, javaVmName, javaVersion) - - plushln(welcomeMsg) - } - - /** Show the history */ - def printHistory(xs: List[String]): Result = { - if (in.history eq History.Empty) - return "No history available." - - val defaultLines = 20 - val current = in.history.index - val count = try xs.head.toInt catch { case _: Exception => defaultLines } - val lines = in.history.asList takeRight count - val offset = current - lines.size + 1 - - for ((line, index) <- lines.zipWithIndex) - println("%3d %s".format(index + offset, line.value)) - } - - /** Some print conveniences */ - def println(x: Any) = out println x - def plush(x: Any) = { out print x ; out.flush() } - def plushln(x: Any) = { out println x ; out.flush() } - - /** Search the history */ - def searchHistory(_cmdline: String) { - val cmdline = _cmdline.toLowerCase - val offset = in.history.index - in.history.size + 1 - - for ((line, index) <- in.history.asStrings.zipWithIndex ; if line.toLowerCase contains cmdline) - println("%d %s".format(index + offset, line)) - } - - /** Prompt to print when awaiting input */ - def prompt = Properties.shellPromptString - - /** Standard commands **/ - val standardCommands: List[Command] = { - List( - LineArg("cp", "add an entry (jar or directory) to the classpath", addClasspath), - NoArgs("help", "print this help message", printHelp), - VarArgs("history", "show the history (optional arg: lines to show)", printHistory), - LineArg("h?", "search the history", searchHistory), - OneArg("load", "load and interpret a Scala file", load), - NoArgs("power", "enable power user mode", powerCmd), - NoArgs("quit", "exit the interpreter", () => Result(false, None)), - NoArgs("replay", "reset execution and replay all previous commands", replay), - LineArg("sh", "fork a shell and run a command", runShellCmd), - NoArgs("silent", "disable/enable automatic printing of results", verbosity) - ) - } - - /** Power user commands */ - val powerCommands: List[Command] = { - List( - NoArgs("dump", "displays a view of the interpreter's internal state", power.toString _), - LineArg("phase", "set the implicit phase for power commands", phaseCommand), - LineArg("symfilter", "change the filter for symbol printing", symfilterCmd) - ) - } - private def symfilterCmd(line: String): Result = { - if (line == "") { - power.vars.symfilter set "_ => true" - "Remove symbol filter." - } - else { - power.vars.symfilter set line - "Set symbol filter to '" + line + "'." - } - } - private def phaseCommand(_name: String): Result = { - val name = _name.toLowerCase - // This line crashes us in TreeGen: - // - // if (intp.power.phased set name) "..." - // - // Exception in thread "main" java.lang.AssertionError: assertion failed: ._7.type - // at scala.Predef$.assert(Predef.scala:99) - // at scala.tools.nsc.ast.TreeGen.mkAttributedQualifier(TreeGen.scala:69) - // at scala.tools.nsc.ast.TreeGen.mkAttributedQualifier(TreeGen.scala:44) - // at scala.tools.nsc.ast.TreeGen.mkAttributedRef(TreeGen.scala:101) - // at scala.tools.nsc.ast.TreeGen.mkAttributedStableRef(TreeGen.scala:143) - // - // But it works like so, type annotated. - val x: Phased = power.phased - if (name == "") "Active phase is '" + x.get + "'" - else if (x set name) "Active phase is now '" + name + "'" - else "'" + name + "' does not appear to be a valid phase." - } - - /** Available commands */ - def commands: List[Command] = standardCommands ++ ( - if (power == null) Nil - else powerCommands - ) - - /** The main read-eval-print loop for the repl. It calls - * command() for each line of input, and stops when - * command() returns false. - */ - def loop() { - def readOneLine() = { - out.flush - in readLine prompt - } - // return false if repl should exit - def processLine(line: String): Boolean = - if (line eq null) false // assume null means EOF - else command(line) match { - case Result(false, _) => false - case Result(_, Some(finalLine)) => addReplay(finalLine) ; true - case _ => true - } - - while (processLine(readOneLine)) { } - } - - /** interpret all lines from a specified file */ - def interpretAllFrom(file: File) { - val oldIn = in - val oldReplay = replayCommandStack - - try file applyReader { reader => - in = new SimpleReader(reader, out, false) - plushln("Loading " + file + "...") - loop() - } - finally { - in = oldIn - replayCommandStack = oldReplay - } - } - - /** create a new interpreter and replay all commands so far */ - def replay() { - closeInterpreter() - createInterpreter() - for (cmd <- replayCommands) { - plushln("Replaying: " + cmd) // flush because maybe cmd will have its own output - command(cmd) - out.println - } - } - - /** fork a shell and run a command */ - def runShellCmd(cmd: String) { - intp.beQuietDuring { intp.interpret("import _root_.scala.sys.process._") } - val xs = Process(cmd).lines - if (xs.nonEmpty) - intp.bind("stdout", "scala.Stream[String]", xs) - } - - def withFile(filename: String)(action: File => Unit) { - val f = File(filename) - - if (f.exists) action(f) - else out.println("That file does not exist") - } - - def load(arg: String) = { - var shouldReplay: Option[String] = None - withFile(arg)(f => { - interpretAllFrom(f) - shouldReplay = Some(":load " + arg) - }) - Result(true, shouldReplay) - } - - def addClasspath(arg: String): Unit = { - val f = File(arg).normalize - if (f.exists) { - addedClasspath = ClassPath.join(addedClasspath, f.path) - val totalClasspath = ClassPath.join(settings.classpath.value, addedClasspath) - println("Added '%s'. Your new classpath is:\n\"%s\"".format(f.path, totalClasspath)) - replay() - } - else out.println("The path '" + f + "' doesn't seem to exist.") - } - - def powerCmd(): Result = { - if (power != null) - return "Already in power mode." - - power = new Power(intp) { } - power.unleash() - injectOne("history", in.history.asList) - injectOne("completion", in.completion) - - power.banner - } - - def verbosity() = { - val old = intp.printResults - intp.printResults = !old - out.println("Switched " + (if (old) "off" else "on") + " result printing.") - } - - /** Run one command submitted by the user. Two values are returned: - * (1) whether to keep running, (2) the line to record for replay, - * if any. */ - def command(line: String): Result = { - def withError(msg: String) = { - out println msg - Result(true, None) - } - def ambiguous(cmds: List[Command]) = "Ambiguous: did you mean " + cmds.map(":" + _.name).mkString(" or ") + "?" - - // not a command - if (!line.startsWith(":")) { - // Notice failure to create compiler - if (intp.global == null) return Result(false, None) - else return Result(true, interpretStartingWith(line)) - } - - val tokens = (line drop 1 split """\s+""").toList - if (tokens.isEmpty) - return withError(ambiguous(commands)) - - val (cmd :: args) = tokens - - // this lets us add commands willy-nilly and only requires enough command to disambiguate - commands.filter(_.name startsWith cmd) match { - case List(x) => x(args) - case Nil => withError("Unknown command. Type :help for help.") - case xs => withError(ambiguous(xs)) - } - } - - private val CONTINUATION_STRING = " | " - private val PROMPT_STRING = "scala> " - - /** If it looks like they're pasting in a scala interpreter - * transcript, remove all the formatting we inserted so we - * can make some sense of it. - */ - private var pasteStamp: Long = 0 - - /** Returns true if it's long enough to quit. */ - def updatePasteStamp(): Boolean = { - /* Enough milliseconds between readLines to call it a day. */ - val PASTE_FINISH = 1000 - - val prevStamp = pasteStamp - pasteStamp = System.currentTimeMillis - - (pasteStamp - prevStamp > PASTE_FINISH) - - } - /** TODO - we could look for the usage of resXX variables in the transcript. - * Right now backreferences to auto-named variables will break. - */ - - /** The trailing lines complication was an attempt to work around the introduction - * of newlines in e.g. email messages of repl sessions. It doesn't work because - * an unlucky newline can always leave you with a syntactically valid first line, - * which is executed before the next line is considered. So this doesn't actually - * accomplish anything, but I'm leaving it in case I decide to try harder. - */ - case class PasteCommand(cmd: String, trailing: ListBuffer[String] = ListBuffer[String]()) - - /** Commands start on lines beginning with "scala>" and each successive - * line which begins with the continuation string is appended to that command. - * Everything else is discarded. When the end of the transcript is spotted, - * all the commands are replayed. - */ - @tailrec private def cleanTranscript(lines: List[String], acc: List[PasteCommand]): List[PasteCommand] = lines match { - case Nil => acc.reverse - case x :: xs if x startsWith PROMPT_STRING => - val first = x stripPrefix PROMPT_STRING - val (xs1, xs2) = xs span (_ startsWith CONTINUATION_STRING) - val rest = xs1 map (_ stripPrefix CONTINUATION_STRING) - val result = (first :: rest).mkString("", "\n", "\n") - - cleanTranscript(xs2, PasteCommand(result) :: acc) - - case ln :: lns => - val newacc = acc match { - case Nil => Nil - case PasteCommand(cmd, trailing) :: accrest => - PasteCommand(cmd, trailing :+ ln) :: accrest - } - cleanTranscript(lns, newacc) - } - - /** The timestamp is for safety so it doesn't hang looking for the end - * of a transcript. Ad hoc parsing can't be too demanding. You can - * also use ctrl-D to start it parsing. - */ - @tailrec private def interpretAsPastedTranscript(lines: List[String]) { - val line = in.readLine("") - val finished = updatePasteStamp() - - if (line == null || finished || line.trim == PROMPT_STRING.trim) { - val xs = cleanTranscript(lines.reverse, Nil) - println("Replaying %d commands from interpreter transcript." format xs.size) - for (PasteCommand(cmd, trailing) <- xs) { - out.flush() - def runCode(code: String, extraLines: List[String]) { - (intp interpret code) match { - case IR.Incomplete if extraLines.nonEmpty => - runCode(code + "\n" + extraLines.head, extraLines.tail) - case _ => () - } - } - runCode(cmd, trailing.toList) - } - } - else - interpretAsPastedTranscript(line :: lines) - } - - /** Interpret expressions starting with the first line. - * Read lines until a complete compilation unit is available - * or until a syntax error has been seen. If a full unit is - * read, go ahead and interpret it. Return the full string - * to be recorded for replay, if any. - */ - def interpretStartingWith(code: String): Option[String] = { - // signal completion non-completion input has been received - in.completion.resetVerbosity() - - def reallyInterpret = intp.interpret(code) match { - case IR.Error => None - case IR.Success => Some(code) - case IR.Incomplete => - if (in.interactive && code.endsWith("\n\n")) { - out.println("You typed two blank lines. Starting a new command.") - None - } - else in.readLine(CONTINUATION_STRING) match { - case null => - // we know compilation is going to fail since we're at EOF and the - // parser thinks the input is still incomplete, but since this is - // a file being read non-interactively we want to fail. So we send - // it straight to the compiler for the nice error message. - intp.compileString(code) - None - - case line => interpretStartingWith(code + "\n" + line) - } - } - - /** Here we place ourselves between the user and the interpreter and examine - * the input they are ostensibly submitting. We intervene in several cases: - * - * 1) If the line starts with "scala> " it is assumed to be an interpreter paste. - * 2) If the line starts with "." (but not ".." or "./") it is treated as an invocation - * on the previous result. - * 3) If the Completion object's execute returns Some(_), we inject that value - * and avoid the interpreter, as it's likely not valid scala code. - */ - if (code == "") None - else if (code startsWith PROMPT_STRING) { - updatePasteStamp() - interpretAsPastedTranscript(List(code)) - None - } - else if (Completion.looksLikeInvocation(code) && intp.mostRecentVar != "") { - interpretStartingWith(intp.mostRecentVar + code) - } - else { - if (intp.isParseable(code)) reallyInterpret - else { - val res = (in.completion execute code) map injectAndName - if (res.isDefined) None // completion took responsibility, so do not parse - else reallyInterpret // we know it will fail, this is to show the error - } - } - } - - // runs :load `file` on any files passed via -i - def loadFiles(settings: Settings) = settings match { - case settings: GenericRunnerSettings => - for (filename <- settings.loadfiles.value) { - val cmd = ":load " + filename - command(cmd) - addReplay(cmd) - out.println() - } - case _ => - } - - def main(settings: Settings) { - this.settings = settings - createInterpreter() - - // sets in to some kind of reader depending on environmental cues - in = in0 match { - case Some(in0) => new SimpleReader(in0, out, true) - case None => - // the interpreter is passed as an argument to expose tab completion info - if (settings.Xnojline.value || Properties.isEmacsShell) new SimpleReader - else InteractiveReader( - if (settings.noCompletion.value) Completion.Empty - else Completion(intp) - ) - } - - loadFiles(settings) - try { - // it is broken on startup; go ahead and exit - if (intp.reporter.hasErrors) return - - printWelcome() - - // this is about the illusion of snappiness. We call initialize() - // which spins off a separate thread, then print the prompt and try - // our best to look ready. Ideally the user will spend a - // couple seconds saying "wow, it starts so fast!" and by the time - // they type a command the compiler is ready to roll. - intp.initialize() - if (sys.props contains PowerProperty) { - plushln("Starting in power mode, one moment...\n") - powerCmd() - } - loop() - } - finally closeInterpreter() - } - - private def objClass(x: Any) = x.asInstanceOf[AnyRef].getClass - private def objName(x: Any) = { - val clazz = objClass(x) - clazz.getName + tpString(clazz) - } - - def tpString[T: ClassManifest] : String = - tpString(classManifest[T].erasure) - - def tpString(clazz: Class[_]): String = { - clazz.getTypeParameters.size match { - case 0 => "" - case x => List.fill(x)("_").mkString("[", ", ", "]") - } - } - - def inject[T: ClassManifest](name: String, value: T): (String, String) = { - intp.bind[T](name, value) - (name, objName(value)) - } - - // injects one value into the repl; returns pair of name and class - def injectOne(name: String, obj: Any): (String, String) = { - val className = objName(obj) - intp.quietBind(name, className, obj) - (name, className) - } - def injectAndName(obj: Any): (String, String) = { - val name = intp.getVarName - val className = objName(obj) - intp.bind(name, className, obj) - (name, className) - } - - // injects list of values into the repl; returns summary string - def injectDebug(args: List[Any]): String = { - val strs = - for ((arg, i) <- args.zipWithIndex) yield { - val varName = "p" + (i + 1) - val (vname, vtype) = injectOne(varName, arg) - vname + ": " + vtype - } - - if (strs.isEmpty) "Set no variables." - else "Variables set:\n" + strs.mkString("\n") - } - - /** process command-line arguments and do as they request */ - def main(args: Array[String]) { - def error1(msg: String) = out println ("scala: " + msg) - val command = new InterpreterCommand(args.toList, error1) - def neededHelp(): String = - (if (command.settings.help.value) command.usageMsg + "\n" else "") + - (if (command.settings.Xhelp.value) command.xusageMsg + "\n" else "") - - // if they asked for no help and command is valid, we call the real main - neededHelp() match { - case "" => if (command.ok) main(command.settings) // else nothing - case help => plush(help) - } - } + def this() = this(None, new PrintWriter(scala.Console.out)) } - -object InterpreterLoop { - implicit def loopToInterpreter(repl: InterpreterLoop): Interpreter = repl.intp - - // provide the enclosing type T - // in order to set up the interpreter's classpath and parent class loader properly - def breakIf[T: Manifest](assertion: => Boolean, args: NamedParam[_]*): Unit = - if (assertion) break[T](args.toList) - - // start a repl, binding supplied args - def break[T: Manifest](args: List[NamedParam[_]]): Unit = { - val msg = if (args.isEmpty) "" else " Binding " + args.size + " value%s.".format( - if (args.size == 1) "" else "s" - ) - Console.println("Debug repl starting." + msg) - val repl = new InterpreterLoop { - override def prompt = "\ndebug> " - } - repl.settings = new Settings(Console println _) - repl.settings.embeddedDefaults[T] - repl.createInterpreter() - repl.in = InteractiveReader(repl) - - // rebind exit so people don't accidentally call sys.exit by way of predef - repl.quietRun("""def exit = println("Type :quit to resume program execution.")""") - args foreach (p => repl.bind(p.name, p.tpe, p.value)) - repl.loop() - - Console.println("\nDebug repl exiting.") - repl.closeInterpreter() - } -}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/InterpreterSettings.scala b/src/compiler/scala/tools/nsc/InterpreterSettings.scala index da113e1637..e69de29bb2 100644 --- a/src/compiler/scala/tools/nsc/InterpreterSettings.scala +++ b/src/compiler/scala/tools/nsc/InterpreterSettings.scala @@ -1,113 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2011 LAMP/EPFL - * @author Alexander Spoon - */ - -package scala.tools.nsc - -/** Settings for the interpreter - * - * @version 1.0 - * @author Lex Spoon, 2007/3/24 - **/ -class InterpreterSettings(repl: Interpreter) { - /** A list of paths where :load should look */ - var loadPath = List(".") - - /** Set this to true to see repl machinery under -Yrich-exceptions. - */ - var showInternalStackTraces = false - - /** The maximum length of toString to use when printing the result - * of an evaluation. 0 means no maximum. If a printout requires - * more than this number of characters, then the printout is - * truncated. - */ - var maxPrintString = 800 - - /** The maximum number of completion candidates to print for tab - * completion without requiring confirmation. - */ - var maxAutoprintCompletion = 250 - - /** String unwrapping can be disabled if it is causing issues. - * Settings this to false means you will see Strings like "$iw.$iw.". - */ - var unwrapStrings = true - - def deprecation_=(x: Boolean) = { - val old = repl.settings.deprecation.value - repl.settings.deprecation.value = x - if (!old && x) println("Enabled -deprecation output.") - else if (old && !x) println("Disabled -deprecation output.") - } - def deprecation: Boolean = repl.settings.deprecation.value - - def allSettings = Map( - "maxPrintString" -> maxPrintString, - "maxAutoprintCompletion" -> maxAutoprintCompletion, - "unwrapStrings" -> unwrapStrings, - "deprecation" -> deprecation - ) - - private def allSettingsString = - allSettings.toList sortBy (_._1) map { case (k, v) => " " + k + " = " + v + "\n" } mkString - - override def toString = """ - | InterpreterSettings { - | %s - | }""".stripMargin.format(allSettingsString) -} - -/* Utilities for the InterpreterSettings class - * - * @version 1.0 - * @author Lex Spoon, 2007/5/24 - */ -object InterpreterSettings { - /** Source code for the InterpreterSettings class. This is - * used so that the interpreter is sure to have the code - * available. - * - * XXX I'm not seeing why this degree of defensiveness is necessary. - * If files are missing the repl's not going to work, it's not as if - * we have string source backups for anything else. - */ - val sourceCodeForClass = -""" -package scala.tools.nsc - -/** Settings for the interpreter - * - * @version 1.0 - * @author Lex Spoon, 2007/3/24 - **/ -class InterpreterSettings(repl: Interpreter) { - /** A list of paths where :load should look */ - var loadPath = List(".") - - /** The maximum length of toString to use when printing the result - * of an evaluation. 0 means no maximum. If a printout requires - * more than this number of characters, then the printout is - * truncated. - */ - var maxPrintString = 2400 - - def deprecation_=(x: Boolean) = { - val old = repl.settings.deprecation.value - repl.settings.deprecation.value = x - if (!old && x) println("Enabled -deprecation output.") - else if (old && !x) println("Disabled -deprecation output.") - } - def deprecation: Boolean = repl.settings.deprecation.value - - override def toString = - "InterpreterSettings {\n" + -// " loadPath = " + loadPath + "\n" + - " maxPrintString = " + maxPrintString + "\n" + - "}" -} - -""" - -} diff --git a/src/compiler/scala/tools/nsc/MainGenericRunner.scala b/src/compiler/scala/tools/nsc/MainGenericRunner.scala index 6b6f140dcc..3acb9b4c12 100644 --- a/src/compiler/scala/tools/nsc/MainGenericRunner.scala +++ b/src/compiler/scala/tools/nsc/MainGenericRunner.scala @@ -3,17 +3,16 @@ * @author Lex Spoon */ - package scala.tools.nsc import java.io.IOException -import java.lang.{ClassNotFoundException, NoSuchMethodException} -import java.net.{ URL, MalformedURLException } +import java.net.URL import scala.tools.util.PathResolver import io.{ File } import util.{ ClassPath, ScalaClassLoader } import Properties.{ versionString, copyrightString } +import interpreter.{ ILoop } /** An object that runs Scala code. It has three possible * sources for the code to run: pre-compiled code, a script file, @@ -74,7 +73,7 @@ object MainGenericRunner { else command.thingToRun match { case None => // We start the repl when no arguments are given. - new InterpreterLoop main settings + new ILoop main settings true // not actually reached in general case Some(thingToRun) => diff --git a/src/compiler/scala/tools/nsc/MainInterpreter.scala b/src/compiler/scala/tools/nsc/MainInterpreter.scala index db7b4b5a0c..9055466dd7 100644 --- a/src/compiler/scala/tools/nsc/MainInterpreter.scala +++ b/src/compiler/scala/tools/nsc/MainInterpreter.scala @@ -5,9 +5,9 @@ package scala.tools.nsc -/** A command-line wrapper for the interpreter */ +import interpreter._ + +@deprecated("Use a class in the scala.tools.nsc.interpreter package.") object MainInterpreter { - def main(args: Array[String]) { - (new InterpreterLoop).main(args) - } + def main(args: Array[String]): Unit = new ILoop main args } diff --git a/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala b/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala index 3fbdfaecd1..955e558461 100644 --- a/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala +++ b/src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala @@ -29,7 +29,7 @@ class AbstractFileClassLoader(root: AbstractFile, parent: ClassLoader) file.toByteArray } - override def findClass(name: String): Class[_] = { + override def findClass(name: String): JClass = { val bytes = getBytesForClass(name) defineClass(name, bytes, 0, bytes.length) } diff --git a/src/compiler/scala/tools/nsc/interpreter/ByteCode.scala b/src/compiler/scala/tools/nsc/interpreter/ByteCode.scala index da52c80184..2dadba3b4c 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ByteCode.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ByteCode.scala @@ -21,7 +21,7 @@ object ByteCode { for (clazz <- getSystemLoader.tryToLoadClass[AnyRef]("scala.tools.scalap.Decode$")) yield clazz.getField("MODULE$").get() - private def decoderMethod(name: String, args: Class[_]*): Option[reflect.Method] = { + private def decoderMethod(name: String, args: JClass*): Option[reflect.Method] = { for (decoder <- DECODER ; m <- Option(decoder.getClass.getMethod(name, args: _*))) yield m } diff --git a/src/compiler/scala/tools/nsc/interpreter/CommandLine.scala b/src/compiler/scala/tools/nsc/interpreter/CommandLine.scala new file mode 100644 index 0000000000..94bcf3e4c8 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/CommandLine.scala @@ -0,0 +1,14 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Lex Spoon + */ + +package scala.tools.nsc +package interpreter + +/** A command line for the interpreter. + */ +class CommandLine(arguments: List[String], error: String => Unit) extends CompilerCommand(arguments, error) { + override val cmdName = "scala" + override lazy val fileEndings = List(".scalaint") +} diff --git a/src/compiler/scala/tools/nsc/interpreter/Completion.scala b/src/compiler/scala/tools/nsc/interpreter/Completion.scala index 04a1a4014a..3436c6631e 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Completion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Completion.scala @@ -3,24 +3,34 @@ * @author Paul Phillips */ - package scala.tools.nsc package interpreter -import scala.tools.jline._ -import scala.tools.jline.console.completer._ import java.util.{ List => JList } -import util.returning + +/** An implementation-agnostic completion interface which makes no + * reference to the jline classes. + */ +trait Completion { + type ExecResult + trait Instance { + def complete(buffer: String, cursor: Int, candidates: JList[CharSequence]): Int + } + def resetVerbosity(): Unit + def execute(line: String): Option[ExecResult] + def completer(): Instance +} object Completion { object Empty extends Completion { + type ExecResult = Nothing + object NullCompleter extends Instance { + def complete(buffer: String, cursor: Int, candidates: JList[CharSequence]) = -1 + } def resetVerbosity() = () def execute(line: String) = None - def completer() = new NullCompleter + def completer() = NullCompleter } - - def apply(repl: Interpreter): Completion = new CompletionImpl(repl) - def looksLikeInvocation(code: String) = ( (code != null) && (code startsWith ".") @@ -31,7 +41,6 @@ object Completion { def looksLikePath(code: String) = (code != null) && (code.length >= 2) && ( Set("/", "\\", "./", "../", "~/") exists (code startsWith _) ) - object Forwarder { def apply(forwardTo: () => Option[CompletionAware]): CompletionAware = new CompletionAware { def completions(verbosity: Int) = forwardTo() map (_ completions verbosity) getOrElse Nil @@ -39,342 +48,3 @@ object Completion { } } } -import Completion._ - -trait Completion { - def resetVerbosity(): Unit - def execute(line: String): Option[Any] - def completer(): Completer -} - -// REPL completor - queries supplied interpreter for valid -// completions based on current contents of buffer. -class CompletionImpl(val repl: Interpreter) extends Completion with CompletionOutput { - val global: repl.global.type = repl.global - import global._ - import definitions.{ PredefModule, RootClass, AnyClass, AnyRefClass, ScalaPackage, JavaLangPackage } - - // verbosity goes up with consecutive tabs - private var verbosity: Int = 0 - def resetVerbosity() = verbosity = 0 - - def isCompletionDebug = repl.isCompletionDebug - def DBG(msg: => Any) = if (isCompletionDebug) println(msg.toString) - def debugging[T](msg: String): T => T = (res: T) => returning[T](res)(x => DBG(msg + x)) - - // XXX not yet used. - lazy val dottedPaths = { - def walk(tp: Type): scala.List[Symbol] = { - val pkgs = tp.nonPrivateMembers filter (_.isPackage) - pkgs ++ (pkgs map (_.tpe) flatMap walk) - } - walk(RootClass.tpe) - } - - def getType(name: String, isModule: Boolean) = { - val f = if (isModule) definitions.getModule(_: Name) else definitions.getClass(_: Name) - try Some(f(name).tpe) - catch { case _: MissingRequirementError => None } - } - - def typeOf(name: String) = getType(name, false) - def moduleOf(name: String) = getType(name, true) - - trait CompilerCompletion { - def tp: Type - def effectiveTp = tp match { - case MethodType(Nil, resType) => resType - case NullaryMethodType(resType) => resType - case _ => tp - } - - // for some reason any's members don't show up in subclasses, which - // we need so 5.<tab> offers asInstanceOf etc. - private def anyMembers = AnyClass.tpe.nonPrivateMembers - def anyRefMethodsToShow = List("isInstanceOf", "asInstanceOf", "toString") - - def tos(sym: Symbol) = sym.name.decode.toString - def memberNamed(s: String) = members find (x => tos(x) == s) - def hasMethod(s: String) = methods exists (x => tos(x) == s) - - // XXX we'd like to say "filterNot (_.isDeprecated)" but this causes the - // compiler to crash for reasons not yet known. - def members = (effectiveTp.nonPrivateMembers ++ anyMembers) filter (_.isPublic) - def methods = members filter (_.isMethod) - def packages = members filter (_.isPackage) - def aliases = members filter (_.isAliasType) - - def memberNames = members map tos - def methodNames = methods map tos - def packageNames = packages map tos - def aliasNames = aliases map tos - } - - object TypeMemberCompletion { - def apply(tp: Type): TypeMemberCompletion = { - if (tp.typeSymbol.isPackageClass) new PackageCompletion(tp) - else new TypeMemberCompletion(tp) - } - def imported(tp: Type) = new ImportCompletion(tp) - } - - class TypeMemberCompletion(val tp: Type) extends CompletionAware - with CompilerCompletion { - def excludeEndsWith: List[String] = Nil - def excludeStartsWith: List[String] = List("<") // <byname>, <repeated>, etc. - def excludeNames: List[String] = - anyref.methodNames.filterNot(anyRefMethodsToShow contains) ++ List("_root_") - - def methodSignatureString(sym: Symbol) = { - def asString = new MethodSymbolOutput(sym).methodString() - atPhase(currentRun.typerPhase)(asString) - } - - def exclude(name: String): Boolean = ( - (name contains "$") || - (excludeNames contains name) || - (excludeEndsWith exists (name endsWith _)) || - (excludeStartsWith exists (name startsWith _)) - ) - def filtered(xs: List[String]) = xs filterNot exclude distinct - - def completions(verbosity: Int) = - debugging(tp + " completions ==> ")(filtered(memberNames)) - - override def follow(s: String): Option[CompletionAware] = - debugging(tp + " -> '" + s + "' ==> ")(memberNamed(s) map (x => TypeMemberCompletion(x.tpe))) - - override def alternativesFor(id: String): List[String] = - debugging(id + " alternatives ==> ") { - val alts = members filter (x => x.isMethod && tos(x) == id) map methodSignatureString - - if (alts.nonEmpty) "" :: alts else Nil - } - - override def toString = "TypeMemberCompletion(%s)".format(tp) - } - - class PackageCompletion(tp: Type) extends TypeMemberCompletion(tp) { - override def excludeNames = anyref.methodNames - } - - class LiteralCompletion(lit: Literal) extends TypeMemberCompletion(lit.value.tpe) { - override def completions(verbosity: Int) = verbosity match { - case 0 => filtered(memberNames) - case _ => memberNames - } - } - - class ImportCompletion(tp: Type) extends TypeMemberCompletion(tp) { - override def completions(verbosity: Int) = verbosity match { - case 0 => filtered(members filterNot (_.isSetter) map tos) - case _ => super.completions(verbosity) - } - } - - // not for completion but for excluding - object anyref extends TypeMemberCompletion(AnyRefClass.tpe) { } - - // the unqualified vals/defs/etc visible in the repl - object ids extends CompletionAware { - override def completions(verbosity: Int) = repl.unqualifiedIds ::: List("classOf") - // we try to use the compiler and fall back on reflection if necessary - // (which at present is for anything defined in the repl session.) - override def follow(id: String) = - if (completions(0) contains id) { - for (clazz <- repl clazzForIdent id) yield { - // XXX The isMemberClass check is a workaround for the crasher described - // in the comments of #3431. The issue as described by iulian is: - // - // Inner classes exist as symbols - // inside their enclosing class, but also inside their package, with a mangled - // name (A$B). The mangled names should never be loaded, and exist only for the - // optimizer, which sometimes cannot get the right symbol, but it doesn't care - // and loads the bytecode anyway. - // - // So this solution is incorrect, but in the short term the simple fix is - // to skip the compiler any time completion is requested on a nested class. - if (clazz.isMemberClass) new InstanceCompletion(clazz) - else (typeOf(clazz.getName) map TypeMemberCompletion.apply) getOrElse new InstanceCompletion(clazz) - } - } - else None - } - - // wildcard imports in the repl like "import global._" or "import String._" - private def imported = repl.wildcardImportedTypes map TypeMemberCompletion.imported - - // literal Ints, Strings, etc. - object literals extends CompletionAware { - def simpleParse(code: String): Tree = { - val unit = new CompilationUnit(new util.BatchSourceFile("<console>", code)) - val scanner = new syntaxAnalyzer.UnitParser(unit) - val tss = scanner.templateStatSeq(false)._2 - - if (tss.size == 1) tss.head else EmptyTree - } - - def completions(verbosity: Int) = Nil - - override def follow(id: String) = simpleParse(id) match { - case x: Literal => Some(new LiteralCompletion(x)) - case _ => None - } - } - - // top level packages - object rootClass extends TypeMemberCompletion(RootClass.tpe) { } - // members of Predef - object predef extends TypeMemberCompletion(PredefModule.tpe) { - override def excludeEndsWith = super.excludeEndsWith ++ List("Wrapper", "ArrayOps") - override def excludeStartsWith = super.excludeStartsWith ++ List("wrap") - override def excludeNames = anyref.methodNames - - override def exclude(name: String) = super.exclude(name) || ( - (name contains "2") - ) - - override def completions(verbosity: Int) = verbosity match { - case 0 => Nil - case _ => super.completions(verbosity) - } - } - // members of scala.* - object scalalang extends PackageCompletion(ScalaPackage.tpe) { - def arityClasses = List("Product", "Tuple", "Function") - def skipArity(name: String) = arityClasses exists (x => name != x && (name startsWith x)) - override def exclude(name: String) = super.exclude(name) || ( - skipArity(name) - ) - - override def completions(verbosity: Int) = verbosity match { - case 0 => filtered(packageNames ++ aliasNames) - case _ => super.completions(verbosity) - } - } - // members of java.lang.* - object javalang extends PackageCompletion(JavaLangPackage.tpe) { - override lazy val excludeEndsWith = super.excludeEndsWith ++ List("Exception", "Error") - override lazy val excludeStartsWith = super.excludeStartsWith ++ List("CharacterData") - - override def completions(verbosity: Int) = verbosity match { - case 0 => filtered(packageNames) - case _ => super.completions(verbosity) - } - } - - // the list of completion aware objects which should be consulted - lazy val topLevelBase: List[CompletionAware] = List(ids, rootClass, predef, scalalang, javalang, literals) - def topLevel = topLevelBase ++ imported - - // the first tier of top level objects (doesn't include file completion) - def topLevelFor(parsed: Parsed) = topLevel flatMap (_ completionsFor parsed) - - // the most recent result - def lastResult = Forwarder(() => ids follow repl.mostRecentVar) - - def lastResultFor(parsed: Parsed) = { - /** The logic is a little tortured right now because normally '.' is - * ignored as a delimiter, but on .<tab> it needs to be propagated. - */ - val xs = lastResult completionsFor parsed - if (parsed.isEmpty) xs map ("." + _) else xs - } - - // chasing down results which won't parse - def execute(line: String): Option[Any] = { - val parsed = Parsed(line) - def noDotOrSlash = line forall (ch => ch != '.' && ch != '/') - - if (noDotOrSlash) None // we defer all unqualified ids to the repl. - else { - (ids executionFor parsed) orElse - (rootClass executionFor parsed) orElse - (FileCompletion executionFor line) - } - } - - // generic interface for querying (e.g. interpreter loop, testing) - def completions(buf: String): List[String] = - topLevelFor(Parsed.dotted(buf + ".", buf.length + 1)) - - def completer() = new JLineCompletion - - /** This gets a little bit hairy. It's no small feat delegating everything - * and also keeping track of exactly where the cursor is and where it's supposed - * to end up. The alternatives mechanism is a little hacky: if there is an empty - * string in the list of completions, that means we are expanding a unique - * completion, so don't update the "last" buffer because it'll be wrong. - */ - class JLineCompletion extends Completer { - // For recording the buffer on the last tab hit - private var lastBuf: String = "" - private var lastCursor: Int = -1 - - // Does this represent two consecutive tabs? - def isConsecutiveTabs(buf: String, cursor: Int) = - cursor == lastCursor && buf == lastBuf - - // Longest common prefix - def commonPrefix(xs: List[String]) = - if (xs.isEmpty) "" - else xs.reduceLeft(_ zip _ takeWhile (x => x._1 == x._2) map (_._1) mkString) - - // This is jline's entry point for completion. - override def complete(_buf: String, cursor: Int, candidates: JList[CharSequence]): Int = { - val buf = if (_buf == null) "" else _buf - verbosity = if (isConsecutiveTabs(buf, cursor)) verbosity + 1 else 0 - DBG("\ncomplete(%s, %d) last = (%s, %d), verbosity: %s".format(buf, cursor, lastBuf, lastCursor, verbosity)) - - // we don't try lower priority completions unless higher ones return no results. - def tryCompletion(p: Parsed, completionFunction: Parsed => List[String]): Option[Int] = { - completionFunction(p) match { - case Nil => None - case xs => - // modify in place and return the position - xs foreach (candidates add _) - - // update the last buffer unless this is an alternatives list - if (xs contains "") Some(p.cursor) - else { - val advance = commonPrefix(xs) - lastCursor = p.position + advance.length - lastBuf = (buf take p.position) + advance - - DBG("tryCompletion(%s, _) lastBuf = %s, lastCursor = %s, p.position = %s".format(p, lastBuf, lastCursor, p.position)) - Some(p.position) - } - } - } - - def mkDotted = Parsed.dotted(buf, cursor) withVerbosity verbosity - def mkUndelimited = Parsed.undelimited(buf, cursor) withVerbosity verbosity - - // a single dot is special cased to completion on the previous result - def lastResultCompletion = - if (!looksLikeInvocation(buf)) None - else tryCompletion(Parsed.dotted(buf drop 1, cursor), lastResultFor) - - def regularCompletion = tryCompletion(mkDotted, topLevelFor) - def fileCompletion = - if (!looksLikePath(buf)) None - else tryCompletion(mkUndelimited, FileCompletion completionsFor _.buffer) - - /** This is the kickoff point for all manner of theoretically possible compiler - * unhappiness - fault may be here or elsewhere, but we don't want to crash the - * repl regardless. Hopefully catching Exception is enough, but because the - * compiler still throws some Errors it may not be. - */ - try { - (lastResultCompletion orElse regularCompletion orElse fileCompletion) getOrElse cursor - } - catch { - case ex: Exception => - DBG("Error: complete(%s, %s, _) provoked %s".format(_buf, cursor, ex)) - candidates add " " - candidates add "<completion error>" - cursor - } - } - } -} diff --git a/src/compiler/scala/tools/nsc/interpreter/CompletionOutput.scala b/src/compiler/scala/tools/nsc/interpreter/CompletionOutput.scala index 047acdf701..0dc35559a6 100644 --- a/src/compiler/scala/tools/nsc/interpreter/CompletionOutput.scala +++ b/src/compiler/scala/tools/nsc/interpreter/CompletionOutput.scala @@ -12,11 +12,12 @@ package interpreter * as is also in progress with error messages. */ trait CompletionOutput { - self: CompletionImpl => - + val global: Global import global._ import definitions.{ NothingClass, AnyClass, isTupleTypeOrSubtype, isFunctionType, isRepeatedParamType } + def DBG(msg: => Any): Unit + /** Reducing fully qualified noise for some common packages. */ val typeTransforms = List( diff --git a/src/compiler/scala/tools/nsc/interpreter/Eval.scala b/src/compiler/scala/tools/nsc/interpreter/Eval.scala new file mode 100644 index 0000000000..6a59cbb6bf --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/Eval.scala @@ -0,0 +1,33 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package interpreter + +trait Eval { + /** 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) + // } + + // 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 + // } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interpreter/Formatting.scala b/src/compiler/scala/tools/nsc/interpreter/Formatting.scala new file mode 100644 index 0000000000..6339dca72f --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/Formatting.scala @@ -0,0 +1,35 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package interpreter + +import util.stringFromWriter + +trait Formatting { + def prompt: String + + def spaces(code: String): String = { + /** Heuristic to avoid indenting and thereby corrupting """-strings and XML literals. */ + val tokens = List("\"\"\"", "</", "/>") + val noIndent = (code contains "\n") && (tokens exists code.contains) + + if (noIndent) "" + else prompt drop 1 map (_ => ' ') + } + /** Indent some code by the width of the scala> prompt. + * This way, compiler error messages read better. + */ + def indentCode(code: String) = { + val indent = spaces(code) + stringFromWriter(str => + for (line <- code.lines) { + str print indent + str print (line + "\n") + str.flush() + } + ) + } +} diff --git a/src/compiler/scala/tools/nsc/interpreter/History.scala b/src/compiler/scala/tools/nsc/interpreter/History.scala index da91a55365..e0364b5c6e 100644 --- a/src/compiler/scala/tools/nsc/interpreter/History.scala +++ b/src/compiler/scala/tools/nsc/interpreter/History.scala @@ -6,47 +6,23 @@ package scala.tools.nsc package interpreter -import java.io.File -import scala.tools.jline.console.history._ -import scala.tools.jline.console.history.{ FileHistory, PersistentHistory, History => JHistory } -import scala.tools.jline.console.history.History.{ Entry => JEntry } -import scala.tools.jline.console.ConsoleReader -import scala.collection.JavaConverters._ -import Properties.userHome - -/** Primarily, a wrapper for JLine's History. +/** An implementation-agnostic history interface which makes no + * reference to the jline classes. */ -class History(val jhistory: JHistory) { - def asJavaList = jhistory.entries() - def asStrings = asList map (_.value.toString) - def asList: List[JEntry] = asJavaList.asScala.toList - def index = jhistory.index() - def size = jhistory.size() - - def grep(s: String) = asStrings filter (_ contains s) - def flush() = jhistory match { - case x: PersistentHistory => x.flush() - case _ => () - } +trait History { + def asStrings: List[String] + def index: Int + def size: Int + def grep(s: String): List[String] + def flush(): Unit } object History { - val Empty: History = null - val ScalaHistoryFile = ".scala_history" - - def apply(): History = new History( - try new FileHistory(new File(userHome, ScalaHistoryFile)) { - // flush after every add to avoid installing a shutdown hook. - // (The shutdown hook approach also loses history when they aren't run.) - override def add(item: CharSequence): Unit = { - super.add(item) - flush() - } - } - catch { - case x: Exception => - Console.println("Error creating file history: memory history only. " + x) - new MemoryHistory() - } - ) + object Empty extends History { + def asStrings = Nil + def grep(s: String) = Nil + def index = 0 + def size = 0 + def flush() = () + } } diff --git a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala new file mode 100644 index 0000000000..413f08dfb1 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala @@ -0,0 +1,651 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Alexander Spoon + */ + +package scala.tools.nsc +package interpreter + +import Predef.{ println => _, _ } +import java.io.{ BufferedReader, FileReader, PrintWriter } +import java.io.IOException + +import scala.sys.process.Process +import scala.tools.nsc.interpreter.{ Results => IR } +import scala.tools.util.SignalManager +import scala.annotation.tailrec +import scala.util.control.Exception.{ ignoring } +import scala.collection.mutable.ListBuffer +import scala.concurrent.ops +import util.{ ClassPath } +import interpreter._ +import io.File + +/** The Scala interactive shell. It provides a read-eval-print loop + * around the Interpreter class. + * After instantiation, clients should call the main() method. + * + * If no in0 is specified, then input will come from the console, and + * the class will attempt to provide input editing feature such as + * input history. + * + * @author Moez A. Abdel-Gawad + * @author Lex Spoon + * @version 1.2 + */ +class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) + extends AnyRef + with LoopCommands +{ + def this(in0: BufferedReader, out: PrintWriter) = this(Some(in0), out) + def this() = this(None, new PrintWriter(Console.out)) + + var in: InteractiveReader = _ // the input stream from which commands come + var settings: Settings = _ + var intp: IMain = _ + var power: Power = _ + + @deprecated("Use `intp` instead.") + def interpreter = intp + + /** The context class loader at the time this object was created */ + protected val originalClassLoader = Thread.currentThread.getContextClassLoader + + // classpath entries added via :cp + var addedClasspath: String = "" + + /** A reverse list of commands to replay if the user requests a :replay */ + var replayCommandStack: List[String] = Nil + + /** A list of commands to replay if the user requests a :replay */ + def replayCommands = replayCommandStack.reverse + + /** Record a command for replay should the user request a :replay */ + def addReplay(cmd: String) = replayCommandStack ::= cmd + + /** Try to install sigint handler: ignore failure. Signal handler + * will interrupt current line execution if any is in progress. + * + * Attempting to protect the repl from accidental exit, we only honor + * a single ctrl-C if the current buffer is empty: otherwise we look + * for a second one within a short time. + */ + private def installSigIntHandler() { + def onExit() { + Console.println("") // avoiding "shell prompt in middle of line" syndrome + sys.exit(1) + } + ignoring(classOf[Exception]) { + SignalManager("INT") = { + if (intp == null) + onExit() + else if (intp.lineManager.running) + intp.lineManager.cancel() + else if (in.currentLine != "") { + // non-empty buffer, so make them hit ctrl-C a second time + SignalManager("INT") = onExit() + io.timer(5)(installSigIntHandler()) // and restore original handler if they don't + } + else onExit() + } + } + } + + /** Close the interpreter and set the var to null. */ + def closeInterpreter() { + if (intp ne null) { + intp.close + intp = null + power = null + Thread.currentThread.setContextClassLoader(originalClassLoader) + } + } + + /** Create a new interpreter. */ + def createInterpreter() { + if (addedClasspath != "") + settings.classpath append addedClasspath + + intp = new IMain(settings, out) { + override lazy val formatting = new Formatting { + def prompt = ILoop.this.prompt + } + override protected def createLineManager() = new Line.Manager { + override def onRunaway(line: Line[_]): Unit = { + val template = """ + |// She's gone rogue, captain! Have to take her out! + |// Calling Thread.stop on runaway %s with offending code: + |// scala> %s""".stripMargin + + println(template.format(line.thread, line.code)) + // XXX no way to suppress the deprecation warning + line.thread.stop() + in.redrawLine() + } + } + override protected def parentClassLoader = + settings.explicitParentLoader.getOrElse( classOf[ILoop].getClassLoader ) + } + intp.setContextClassLoader() + installSigIntHandler() + // intp.quietBind("settings", "scala.tools.nsc.InterpreterSettings", intp.isettings) + } + + /** print a friendly help message */ + def printHelp() = { + out println "All commands can be abbreviated - for example :he instead of :help.\n" + val cmds = commands map (x => (x.usage, x.help)) + val width: Int = cmds map { case (x, _) => x.length } max + val formatStr = "%-" + width + "s %s" + cmds foreach { case (usage, help) => out println formatStr.format(usage, help) } + } + + /** Print a welcome message */ + def printWelcome() { + import Properties._ + val welcomeMsg = + """|Welcome to Scala %s (%s, Java %s). + |Type in expressions to have them evaluated. + |Type :help for more information.""" . + stripMargin.format(versionString, javaVmName, javaVersion) + + plushln(welcomeMsg) + } + + /** Show the history */ + def printHistory(xs: List[String]): Result = { + if (in.history eq History.Empty) + return "No history available." + + val defaultLines = 20 + val current = in.history.index + val count = try xs.head.toInt catch { case _: Exception => defaultLines } + val lines = in.history.asStrings takeRight count + val offset = current - lines.size + 1 + + for ((line, index) <- lines.zipWithIndex) + println("%3d %s".format(index + offset, line)) + } + + /** Some print conveniences */ + def println(x: Any) = out println x + def plush(x: Any) = { out print x ; out.flush() } + def plushln(x: Any) = { out println x ; out.flush() } + + /** Search the history */ + def searchHistory(_cmdline: String) { + val cmdline = _cmdline.toLowerCase + val offset = in.history.index - in.history.size + 1 + + for ((line, index) <- in.history.asStrings.zipWithIndex ; if line.toLowerCase contains cmdline) + println("%d %s".format(index + offset, line)) + } + + private var currentPrompt = Properties.shellPromptString + def setPrompt(prompt: String) = currentPrompt = prompt + /** Prompt to print when awaiting input */ + def prompt = currentPrompt + + /** Standard commands **/ + val standardCommands: List[LoopCommand] = { + List( + LineArg("cp", "add an entry (jar or directory) to the classpath", addClasspath), + NoArgs("help", "print this help message", printHelp), + VarArgs("history", "show the history (optional arg: lines to show)", printHistory), + LineArg("h?", "search the history", searchHistory), + OneArg("load", "load and interpret a Scala file", load), + NoArgs("power", "enable power user mode", powerCmd), + NoArgs("quit", "exit the interpreter", () => Result(false, None)), + NoArgs("replay", "reset execution and replay all previous commands", replay), + LineArg("sh", "fork a shell and run a command", runShellCmd), + NoArgs("silent", "disable/enable automatic printing of results", verbosity) + ) + } + + /** Power user commands */ + val powerCommands: List[LoopCommand] = { + List( + NoArgs("dump", "displays a view of the interpreter's internal state", power.toString _), + LineArg("phase", "set the implicit phase for power commands", phaseCommand), + LineArg("symfilter", "change the filter for symbol printing", symfilterCmd) + ) + } + private def symfilterCmd(line: String): Result = { + if (line == "") { + power.vars.symfilter set "_ => true" + "Remove symbol filter." + } + else { + power.vars.symfilter set line + "Set symbol filter to '" + line + "'." + } + } + private def phaseCommand(_name: String): Result = { + val name = _name.toLowerCase + // This line crashes us in TreeGen: + // + // if (intp.power.phased set name) "..." + // + // Exception in thread "main" java.lang.AssertionError: assertion failed: ._7.type + // at scala.Predef$.assert(Predef.scala:99) + // at scala.tools.nsc.ast.TreeGen.mkAttributedQualifier(TreeGen.scala:69) + // at scala.tools.nsc.ast.TreeGen.mkAttributedQualifier(TreeGen.scala:44) + // at scala.tools.nsc.ast.TreeGen.mkAttributedRef(TreeGen.scala:101) + // at scala.tools.nsc.ast.TreeGen.mkAttributedStableRef(TreeGen.scala:143) + // + // But it works like so, type annotated. + val x: Phased = power.phased + if (name == "") "Active phase is '" + x.get + "'" + else if (x set name) "Active phase is now '" + name + "'" + else "'" + name + "' does not appear to be a valid phase." + } + + /** Available commands */ + def commands: List[LoopCommand] = standardCommands ++ ( + if (power == null) Nil + else powerCommands + ) + + /** The main read-eval-print loop for the repl. It calls + * command() for each line of input, and stops when + * command() returns false. + */ + def loop() { + def readOneLine() = { + out.flush + in readLine prompt + } + // return false if repl should exit + def processLine(line: String): Boolean = + if (line eq null) false // assume null means EOF + else command(line) match { + case Result(false, _) => false + case Result(_, Some(finalLine)) => addReplay(finalLine) ; true + case _ => true + } + + while (processLine(readOneLine)) { } + } + + /** interpret all lines from a specified file */ + def interpretAllFrom(file: File) { + val oldIn = in + val oldReplay = replayCommandStack + + try file applyReader { reader => + in = new SimpleReader(reader, out, false) + plushln("Loading " + file + "...") + loop() + } + finally { + in = oldIn + replayCommandStack = oldReplay + } + } + + /** create a new interpreter and replay all commands so far */ + def replay() { + closeInterpreter() + createInterpreter() + for (cmd <- replayCommands) { + plushln("Replaying: " + cmd) // flush because maybe cmd will have its own output + command(cmd) + out.println + } + } + + /** fork a shell and run a command */ + def runShellCmd(cmd: String) { + intp.beQuietDuring { intp.interpret("import _root_.scala.sys.process._") } + val xs = Process(cmd).lines + if (xs.nonEmpty) + intp.bind("stdout", "scala.Stream[String]", xs) + } + + def withFile(filename: String)(action: File => Unit) { + val f = File(filename) + + if (f.exists) action(f) + else out.println("That file does not exist") + } + + def load(arg: String) = { + var shouldReplay: Option[String] = None + withFile(arg)(f => { + interpretAllFrom(f) + shouldReplay = Some(":load " + arg) + }) + Result(true, shouldReplay) + } + + def addClasspath(arg: String): Unit = { + val f = File(arg).normalize + if (f.exists) { + addedClasspath = ClassPath.join(addedClasspath, f.path) + val totalClasspath = ClassPath.join(settings.classpath.value, addedClasspath) + println("Added '%s'. Your new classpath is:\n\"%s\"".format(f.path, totalClasspath)) + replay() + } + else out.println("The path '" + f + "' doesn't seem to exist.") + } + + def powerCmd(): Result = { + if (power != null) + return "Already in power mode." + + power = new Power(this) + power.unleash() + power.banner + } + + def verbosity() = { + val old = intp.printResults + intp.printResults = !old + out.println("Switched " + (if (old) "off" else "on") + " result printing.") + } + + /** Run one command submitted by the user. Two values are returned: + * (1) whether to keep running, (2) the line to record for replay, + * if any. */ + def command(line: String): Result = { + def withError(msg: String) = { + out println msg + Result(true, None) + } + def ambiguous(cmds: List[LoopCommand]) = "Ambiguous: did you mean " + cmds.map(":" + _.name).mkString(" or ") + "?" + + // not a command + if (!line.startsWith(":")) { + // Notice failure to create compiler + if (intp.global == null) return Result(false, None) + else return Result(true, interpretStartingWith(line)) + } + + val tokens = (line drop 1 split """\s+""").toList + if (tokens.isEmpty) + return withError(ambiguous(commands)) + + val (cmd :: args) = tokens + + // this lets us add commands willy-nilly and only requires enough command to disambiguate + commands.filter(_.name startsWith cmd) match { + case List(x) => x(args) + case Nil => withError("Unknown command. Type :help for help.") + case xs => withError(ambiguous(xs)) + } + } + + private val CONTINUATION_STRING = " | " + private val PROMPT_STRING = "scala> " + + /** If it looks like they're pasting in a scala interpreter + * transcript, remove all the formatting we inserted so we + * can make some sense of it. + */ + private var pasteStamp: Long = 0 + + /** Returns true if it's long enough to quit. */ + def updatePasteStamp(): Boolean = { + /* Enough milliseconds between readLines to call it a day. */ + val PASTE_FINISH = 1000 + + val prevStamp = pasteStamp + pasteStamp = System.currentTimeMillis + + (pasteStamp - prevStamp > PASTE_FINISH) + + } + /** TODO - we could look for the usage of resXX variables in the transcript. + * Right now backreferences to auto-named variables will break. + */ + + /** The trailing lines complication was an attempt to work around the introduction + * of newlines in e.g. email messages of repl sessions. It doesn't work because + * an unlucky newline can always leave you with a syntactically valid first line, + * which is executed before the next line is considered. So this doesn't actually + * accomplish anything, but I'm leaving it in case I decide to try harder. + */ + case class PasteCommand(cmd: String, trailing: ListBuffer[String] = ListBuffer[String]()) + + /** Commands start on lines beginning with "scala>" and each successive + * line which begins with the continuation string is appended to that command. + * Everything else is discarded. When the end of the transcript is spotted, + * all the commands are replayed. + */ + @tailrec private def cleanTranscript(lines: List[String], acc: List[PasteCommand]): List[PasteCommand] = lines match { + case Nil => acc.reverse + case x :: xs if x startsWith PROMPT_STRING => + val first = x stripPrefix PROMPT_STRING + val (xs1, xs2) = xs span (_ startsWith CONTINUATION_STRING) + val rest = xs1 map (_ stripPrefix CONTINUATION_STRING) + val result = (first :: rest).mkString("", "\n", "\n") + + cleanTranscript(xs2, PasteCommand(result) :: acc) + + case ln :: lns => + val newacc = acc match { + case Nil => Nil + case PasteCommand(cmd, trailing) :: accrest => + PasteCommand(cmd, trailing :+ ln) :: accrest + } + cleanTranscript(lns, newacc) + } + + /** The timestamp is for safety so it doesn't hang looking for the end + * of a transcript. Ad hoc parsing can't be too demanding. You can + * also use ctrl-D to start it parsing. + */ + @tailrec private def interpretAsPastedTranscript(lines: List[String]) { + val line = in.readLine("") + val finished = updatePasteStamp() + + if (line == null || finished || line.trim == PROMPT_STRING.trim) { + val xs = cleanTranscript(lines.reverse, Nil) + println("Replaying %d commands from interpreter transcript." format xs.size) + for (PasteCommand(cmd, trailing) <- xs) { + out.flush() + def runCode(code: String, extraLines: List[String]) { + (intp interpret code) match { + case IR.Incomplete if extraLines.nonEmpty => + runCode(code + "\n" + extraLines.head, extraLines.tail) + case _ => () + } + } + runCode(cmd, trailing.toList) + } + } + else + interpretAsPastedTranscript(line :: lines) + } + + /** Interpret expressions starting with the first line. + * Read lines until a complete compilation unit is available + * or until a syntax error has been seen. If a full unit is + * read, go ahead and interpret it. Return the full string + * to be recorded for replay, if any. + */ + def interpretStartingWith(code: String): Option[String] = { + // signal completion non-completion input has been received + in.completion.resetVerbosity() + + def reallyInterpret = intp.interpret(code) match { + case IR.Error => None + case IR.Success => Some(code) + case IR.Incomplete => + if (in.interactive && code.endsWith("\n\n")) { + out.println("You typed two blank lines. Starting a new command.") + None + } + else in.readLine(CONTINUATION_STRING) match { + case null => + // we know compilation is going to fail since we're at EOF and the + // parser thinks the input is still incomplete, but since this is + // a file being read non-interactively we want to fail. So we send + // it straight to the compiler for the nice error message. + intp.compileString(code) + None + + case line => interpretStartingWith(code + "\n" + line) + } + } + + /** Here we place ourselves between the user and the interpreter and examine + * the input they are ostensibly submitting. We intervene in several cases: + * + * 1) If the line starts with "scala> " it is assumed to be an interpreter paste. + * 2) If the line starts with "." (but not ".." or "./") it is treated as an invocation + * on the previous result. + * 3) If the Completion object's execute returns Some(_), we inject that value + * and avoid the interpreter, as it's likely not valid scala code. + */ + if (code == "") None + else if (code startsWith PROMPT_STRING) { + updatePasteStamp() + interpretAsPastedTranscript(List(code)) + None + } + else if (Completion.looksLikeInvocation(code) && intp.mostRecentVar != "") { + interpretStartingWith(intp.mostRecentVar + code) + } + else { + if (intp.isParseable(code)) reallyInterpret + else (in.completion execute code) match { + // completion took responsibility, so do not parse + // but do directly inject the result + case Some(res) => injectAndName(res) ; None + case _ => reallyInterpret // we know it will fail, this is to show the error + } + } + } + + // runs :load `file` on any files passed via -i + def loadFiles(settings: Settings) = settings match { + case settings: GenericRunnerSettings => + for (filename <- settings.loadfiles.value) { + val cmd = ":load " + filename + command(cmd) + addReplay(cmd) + out.println() + } + case _ => + } + + def process(settings: Settings): Boolean = { + this.settings = settings + createInterpreter() + + // sets in to some kind of reader depending on environmental cues + in = in0 match { + case Some(in0) => new SimpleReader(in0, out, true) + case None => + // the interpreter is passed as an argument to expose tab completion info + if (settings.Xnojline.value || Properties.isEmacsShell) new SimpleReader + else JLineReader( + if (settings.noCompletion.value) Completion.Empty + else new JLineCompletion(intp) + ) + } + + loadFiles(settings) + try { + // it is broken on startup; go ahead and exit + if (intp.reporter.hasErrors) return false + + printWelcome() + + // this is about the illusion of snappiness. We call initialize() + // which spins off a separate thread, then print the prompt and try + // our best to look ready. Ideally the user will spend a + // couple seconds saying "wow, it starts so fast!" and by the time + // they type a command the compiler is ready to roll. + intp.initialize() + if (sys.props contains PowerProperty) { + plushln("Starting in power mode, one moment...\n") + powerCmd() + } + loop() + } + finally closeInterpreter() + true + } + + private def objClass(x: Any) = x.asInstanceOf[AnyRef].getClass + private def objName(x: Any) = { + val clazz = objClass(x) + clazz.getName + tpString(clazz) + } + + def tpString[T: Manifest] : String = + tpString(manifest[T].erasure) + + def tpString(clazz: Class[_]): String = { + clazz.getTypeParameters.size match { + case 0 => "" + case x => List.fill(x)("_").mkString("[", ", ", "]") + } + } + + def inject[T: Manifest](name: String, value: T): (String, String) = { + intp.bind[T](name, value) + (name, objName(value)) + } + def injectAndName(obj: Any): (String, String) = { + val name = intp.getVarName + val className = objName(obj) + intp.bind(name, className, obj) + (name, className) + } + + /** process command-line arguments and do as they request */ + def process(args: Array[String]): Boolean = { + def error1(msg: String) = out println ("scala: " + msg) + val command = new CommandLine(args.toList, error1) + def neededHelp(): String = + (if (command.settings.help.value) command.usageMsg + "\n" else "") + + (if (command.settings.Xhelp.value) command.xusageMsg + "\n" else "") + + // if they asked for no help and command is valid, we call the real main + neededHelp() match { + case "" => if (command.ok) main(command.settings) // else nothing + case help => plush(help) + } + true + } + + @deprecated("Use `process` instead") + def main(args: Array[String]): Unit = process(args) + @deprecated("Use `process` instead") + def main(settings: Settings): Unit = process(settings) +} + +object ILoop { + implicit def loopToInterpreter(repl: ILoop): IMain = repl.intp + + // provide the enclosing type T + // in order to set up the interpreter's classpath and parent class loader properly + def breakIf[T: Manifest](assertion: => Boolean, args: NamedParam*): Unit = + if (assertion) break[T](args.toList) + + // start a repl, binding supplied args + def break[T: Manifest](args: List[NamedParam]): Unit = { + val msg = if (args.isEmpty) "" else " Binding " + args.size + " value%s.".format( + if (args.size == 1) "" else "s" + ) + Console.println("Debug repl starting." + msg) + val repl = new ILoop { + override def prompt = "\ndebug> " + } + repl.settings = new Settings(Console println _) + repl.settings.embeddedDefaults[T] + repl.createInterpreter() + repl.in = JLineReader(repl) + + // rebind exit so people don't accidentally call sys.exit by way of predef + repl.quietRun("""def exit = println("Type :quit to resume program execution.")""") + args foreach (p => repl.bind(p.name, p.tpe, p.value)) + repl.loop() + + Console.println("\nDebug repl exiting.") + repl.closeInterpreter() + } +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interpreter/IMain.scala b/src/compiler/scala/tools/nsc/interpreter/IMain.scala new file mode 100644 index 0000000000..84189cddad --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/IMain.scala @@ -0,0 +1,1267 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Martin Odersky + */ + +package scala.tools.nsc +package interpreter + +import Predef.{ println => _, _ } +import java.io.{ PrintWriter } +import java.lang.reflect +import java.net.URL +import util.{ Set => _, _ } +import io.VirtualDirectory +import reporters.{ ConsoleReporter, Reporter } +import symtab.{ Flags, Names } +import scala.tools.nsc.interpreter.{ Results => 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.PartialFunction.{ cond, condOpt } +import scala.util.control.Exception.{ ultimately } +import scala.reflect.NameTransformer +import IMain._ + +/** <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 + */ +class IMain(val settings: Settings, out: PrintWriter) { + repl => + + /** construct an interpreter that reports to Console */ + def this(settings: Settings) = this(settings, new NewLinePrintWriter(new ConsoleWriter, true)) + def this() = this(new Settings()) + + /** 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$" + + lazy val formatting: Formatting = new Formatting { + val prompt = Properties.shellPromptString + } + import formatting._ + + def println(x: Any) = { + out.println(x) + out.flush() + } + + + /** 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 ISettings(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 mutable.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) + } + + 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 + + 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") + + /** 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(p: NamedParam): IR.Result = beQuietDuring(bind(p)) + def bind(p: NamedParam): IR.Result = bind(p.name, p.tpe, p.value) + def bind[T: Manifest](name: String, value: T): IR.Result = bind((name, value)) + + /** Reset this interpreter, forgetting all user-specified requests. */ + def reset() { + virtualDirectory.clear + resetClassLoader() + lineNameCreator.reset() + varNameCreator.reset() + prevRequests.clear + } + + /** This instance is no longer needed, so release any resources + * it is using. The reporter's output gets flushed. + */ + 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])) + } + + 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 indentCode(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) + + // 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 IMain { + 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 + } +} diff --git a/src/compiler/scala/tools/nsc/interpreter/ISettings.scala b/src/compiler/scala/tools/nsc/interpreter/ISettings.scala new file mode 100644 index 0000000000..59c933632b --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/ISettings.scala @@ -0,0 +1,61 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Alexander Spoon + */ + +package scala.tools.nsc +package interpreter + +/** Settings for the interpreter + * + * @version 1.0 + * @author Lex Spoon, 2007/3/24 + **/ +class ISettings(intp: IMain) { + /** A list of paths where :load should look */ + var loadPath = List(".") + + /** Set this to true to see repl machinery under -Yrich-exceptions. + */ + var showInternalStackTraces = false + + /** The maximum length of toString to use when printing the result + * of an evaluation. 0 means no maximum. If a printout requires + * more than this number of characters, then the printout is + * truncated. + */ + var maxPrintString = 800 + + /** The maximum number of completion candidates to print for tab + * completion without requiring confirmation. + */ + var maxAutoprintCompletion = 250 + + /** String unwrapping can be disabled if it is causing issues. + * Settings this to false means you will see Strings like "$iw.$iw.". + */ + var unwrapStrings = true + + def deprecation_=(x: Boolean) = { + val old = intp.settings.deprecation.value + intp.settings.deprecation.value = x + if (!old && x) println("Enabled -deprecation output.") + else if (old && !x) println("Disabled -deprecation output.") + } + def deprecation: Boolean = intp.settings.deprecation.value + + def allSettings = Map( + "maxPrintString" -> maxPrintString, + "maxAutoprintCompletion" -> maxAutoprintCompletion, + "unwrapStrings" -> unwrapStrings, + "deprecation" -> deprecation + ) + + private def allSettingsString = + allSettings.toList sortBy (_._1) map { case (k, v) => " " + k + " = " + v + "\n" } mkString + + override def toString = """ + | ISettings { + | %s + | }""".stripMargin.format(allSettingsString) +} diff --git a/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala b/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala index f094aab104..e7ef50ddf7 100644 --- a/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala +++ b/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala @@ -18,7 +18,6 @@ trait InteractiveReader { def history: History def completion: Completion - def init(): Unit def reset(): Unit @@ -41,14 +40,8 @@ trait InteractiveReader { object InteractiveReader { val msgEINTR = "Interrupted system call" - def apply(): InteractiveReader = new SimpleReader - def apply(repl: Interpreter): InteractiveReader = apply(Completion(repl)) - def apply(comp: Completion): InteractiveReader = { - try new JLineReader(comp) - catch { case e @ (_: Exception | _: NoClassDefFoundError) => apply() } - } - @deprecated("Use `apply` instead") def createDefault(repl: Interpreter): InteractiveReader = apply(repl) - @deprecated("Use `apply` instead") def createDefault(comp: Completion): InteractiveReader = apply(comp) + // @deprecated("Use `apply` instead") def createDefault(intp: IMain): InteractiveReader = apply(intp) + // @deprecated("Use `apply` instead") def createDefault(comp: Completion): InteractiveReader = apply(comp) } diff --git a/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala new file mode 100644 index 0000000000..3f2003a2f7 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala @@ -0,0 +1,346 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package interpreter + +import scala.tools.jline._ +import scala.tools.jline.console.completer._ +import java.util.{ List => JList } +import util.returning +import Completion._ + +// REPL completor - queries supplied interpreter for valid +// completions based on current contents of buffer. +class JLineCompletion(val intp: IMain) extends Completion with CompletionOutput { + val global: intp.global.type = intp.global + import global._ + import definitions.{ PredefModule, RootClass, AnyClass, AnyRefClass, ScalaPackage, JavaLangPackage } + type ExecResult = Any + + // verbosity goes up with consecutive tabs + private var verbosity: Int = 0 + def resetVerbosity() = verbosity = 0 + + def isCompletionDebug = intp.isCompletionDebug + def DBG(msg: => Any) = if (isCompletionDebug) println(msg.toString) + def debugging[T](msg: String): T => T = (res: T) => returning[T](res)(x => DBG(msg + x)) + + // XXX not yet used. + lazy val dottedPaths = { + def walk(tp: Type): scala.List[Symbol] = { + val pkgs = tp.nonPrivateMembers filter (_.isPackage) + pkgs ++ (pkgs map (_.tpe) flatMap walk) + } + walk(RootClass.tpe) + } + + def getType(name: String, isModule: Boolean) = { + val f = if (isModule) definitions.getModule(_: Name) else definitions.getClass(_: Name) + try Some(f(name).tpe) + catch { case _: MissingRequirementError => None } + } + + def typeOf(name: String) = getType(name, false) + def moduleOf(name: String) = getType(name, true) + + trait CompilerCompletion { + def tp: Type + def effectiveTp = tp match { + case MethodType(Nil, resType) => resType + case NullaryMethodType(resType) => resType + case _ => tp + } + + // for some reason any's members don't show up in subclasses, which + // we need so 5.<tab> offers asInstanceOf etc. + private def anyMembers = AnyClass.tpe.nonPrivateMembers + def anyRefMethodsToShow = List("isInstanceOf", "asInstanceOf", "toString") + + def tos(sym: Symbol) = sym.name.decode.toString + def memberNamed(s: String) = members find (x => tos(x) == s) + def hasMethod(s: String) = methods exists (x => tos(x) == s) + + // XXX we'd like to say "filterNot (_.isDeprecated)" but this causes the + // compiler to crash for reasons not yet known. + def members = (effectiveTp.nonPrivateMembers ++ anyMembers) filter (_.isPublic) + def methods = members filter (_.isMethod) + def packages = members filter (_.isPackage) + def aliases = members filter (_.isAliasType) + + def memberNames = members map tos + def methodNames = methods map tos + def packageNames = packages map tos + def aliasNames = aliases map tos + } + + object TypeMemberCompletion { + def apply(tp: Type): TypeMemberCompletion = { + if (tp.typeSymbol.isPackageClass) new PackageCompletion(tp) + else new TypeMemberCompletion(tp) + } + def imported(tp: Type) = new ImportCompletion(tp) + } + + class TypeMemberCompletion(val tp: Type) extends CompletionAware + with CompilerCompletion { + def excludeEndsWith: List[String] = Nil + def excludeStartsWith: List[String] = List("<") // <byname>, <repeated>, etc. + def excludeNames: List[String] = + anyref.methodNames.filterNot(anyRefMethodsToShow contains) ++ List("_root_") + + def methodSignatureString(sym: Symbol) = { + def asString = new MethodSymbolOutput(sym).methodString() + atPhase(currentRun.typerPhase)(asString) + } + + def exclude(name: String): Boolean = ( + (name contains "$") || + (excludeNames contains name) || + (excludeEndsWith exists (name endsWith _)) || + (excludeStartsWith exists (name startsWith _)) + ) + def filtered(xs: List[String]) = xs filterNot exclude distinct + + def completions(verbosity: Int) = + debugging(tp + " completions ==> ")(filtered(memberNames)) + + override def follow(s: String): Option[CompletionAware] = + debugging(tp + " -> '" + s + "' ==> ")(memberNamed(s) map (x => TypeMemberCompletion(x.tpe))) + + override def alternativesFor(id: String): List[String] = + debugging(id + " alternatives ==> ") { + val alts = members filter (x => x.isMethod && tos(x) == id) map methodSignatureString + + if (alts.nonEmpty) "" :: alts else Nil + } + + override def toString = "TypeMemberCompletion(%s)".format(tp) + } + + class PackageCompletion(tp: Type) extends TypeMemberCompletion(tp) { + override def excludeNames = anyref.methodNames + } + + class LiteralCompletion(lit: Literal) extends TypeMemberCompletion(lit.value.tpe) { + override def completions(verbosity: Int) = verbosity match { + case 0 => filtered(memberNames) + case _ => memberNames + } + } + + class ImportCompletion(tp: Type) extends TypeMemberCompletion(tp) { + override def completions(verbosity: Int) = verbosity match { + case 0 => filtered(members filterNot (_.isSetter) map tos) + case _ => super.completions(verbosity) + } + } + + // not for completion but for excluding + object anyref extends TypeMemberCompletion(AnyRefClass.tpe) { } + + // the unqualified vals/defs/etc visible in the repl + object ids extends CompletionAware { + override def completions(verbosity: Int) = intp.unqualifiedIds :+ "classOf" + // we try to use the compiler and fall back on reflection if necessary + // (which at present is for anything defined in the repl session.) + override def follow(id: String) = + if (completions(0) contains id) { + for (clazz <- intp clazzForIdent id) yield { + // XXX The isMemberClass check is a workaround for the crasher described + // in the comments of #3431. The issue as described by iulian is: + // + // Inner classes exist as symbols + // inside their enclosing class, but also inside their package, with a mangled + // name (A$B). The mangled names should never be loaded, and exist only for the + // optimizer, which sometimes cannot get the right symbol, but it doesn't care + // and loads the bytecode anyway. + // + // So this solution is incorrect, but in the short term the simple fix is + // to skip the compiler any time completion is requested on a nested class. + if (clazz.isMemberClass) new InstanceCompletion(clazz) + else (typeOf(clazz.getName) map TypeMemberCompletion.apply) getOrElse new InstanceCompletion(clazz) + } + } + else None + } + + // wildcard imports in the repl like "import global._" or "import String._" + private def imported = intp.wildcardImportedTypes map TypeMemberCompletion.imported + + // literal Ints, Strings, etc. + object literals extends CompletionAware { + def simpleParse(code: String): Tree = { + val unit = new CompilationUnit(new util.BatchSourceFile("<console>", code)) + val scanner = new syntaxAnalyzer.UnitParser(unit) + val tss = scanner.templateStatSeq(false)._2 + + if (tss.size == 1) tss.head else EmptyTree + } + + def completions(verbosity: Int) = Nil + + override def follow(id: String) = simpleParse(id) match { + case x: Literal => Some(new LiteralCompletion(x)) + case _ => None + } + } + + // top level packages + object rootClass extends TypeMemberCompletion(RootClass.tpe) { } + // members of Predef + object predef extends TypeMemberCompletion(PredefModule.tpe) { + override def excludeEndsWith = super.excludeEndsWith ++ List("Wrapper", "ArrayOps") + override def excludeStartsWith = super.excludeStartsWith ++ List("wrap") + override def excludeNames = anyref.methodNames + + override def exclude(name: String) = super.exclude(name) || ( + (name contains "2") + ) + + override def completions(verbosity: Int) = verbosity match { + case 0 => Nil + case _ => super.completions(verbosity) + } + } + // members of scala.* + object scalalang extends PackageCompletion(ScalaPackage.tpe) { + def arityClasses = List("Product", "Tuple", "Function") + def skipArity(name: String) = arityClasses exists (x => name != x && (name startsWith x)) + override def exclude(name: String) = super.exclude(name) || ( + skipArity(name) + ) + + override def completions(verbosity: Int) = verbosity match { + case 0 => filtered(packageNames ++ aliasNames) + case _ => super.completions(verbosity) + } + } + // members of java.lang.* + object javalang extends PackageCompletion(JavaLangPackage.tpe) { + override lazy val excludeEndsWith = super.excludeEndsWith ++ List("Exception", "Error") + override lazy val excludeStartsWith = super.excludeStartsWith ++ List("CharacterData") + + override def completions(verbosity: Int) = verbosity match { + case 0 => filtered(packageNames) + case _ => super.completions(verbosity) + } + } + + // the list of completion aware objects which should be consulted + lazy val topLevelBase: List[CompletionAware] = List(ids, rootClass, predef, scalalang, javalang, literals) + def topLevel = topLevelBase ++ imported + + // the first tier of top level objects (doesn't include file completion) + def topLevelFor(parsed: Parsed) = topLevel flatMap (_ completionsFor parsed) + + // the most recent result + def lastResult = Forwarder(() => ids follow intp.mostRecentVar) + + def lastResultFor(parsed: Parsed) = { + /** The logic is a little tortured right now because normally '.' is + * ignored as a delimiter, but on .<tab> it needs to be propagated. + */ + val xs = lastResult completionsFor parsed + if (parsed.isEmpty) xs map ("." + _) else xs + } + + // chasing down results which won't parse + def execute(line: String): Option[ExecResult] = { + val parsed = Parsed(line) + def noDotOrSlash = line forall (ch => ch != '.' && ch != '/') + + if (noDotOrSlash) None // we defer all unqualified ids to the repl. + else { + (ids executionFor parsed) orElse + (rootClass executionFor parsed) orElse + (FileCompletion executionFor line) + } + } + + // generic interface for querying (e.g. interpreter loop, testing) + def completions(buf: String): List[String] = + topLevelFor(Parsed.dotted(buf + ".", buf.length + 1)) + + def completer() = new JLineCompleterClass + + /** This gets a little bit hairy. It's no small feat delegating everything + * and also keeping track of exactly where the cursor is and where it's supposed + * to end up. The alternatives mechanism is a little hacky: if there is an empty + * string in the list of completions, that means we are expanding a unique + * completion, so don't update the "last" buffer because it'll be wrong. + */ + class JLineCompleterClass extends Instance with Completer { + // For recording the buffer on the last tab hit + private var lastBuf: String = "" + private var lastCursor: Int = -1 + + // Does this represent two consecutive tabs? + def isConsecutiveTabs(buf: String, cursor: Int) = + cursor == lastCursor && buf == lastBuf + + // Longest common prefix + def commonPrefix(xs: List[String]) = + if (xs.isEmpty) "" + else xs.reduceLeft(_ zip _ takeWhile (x => x._1 == x._2) map (_._1) mkString) + + // This is jline's entry point for completion. + override def complete(_buf: String, cursor: Int, candidates: JList[CharSequence]): Int = { + val buf = if (_buf == null) "" else _buf + verbosity = if (isConsecutiveTabs(buf, cursor)) verbosity + 1 else 0 + DBG("\ncomplete(%s, %d) last = (%s, %d), verbosity: %s".format(buf, cursor, lastBuf, lastCursor, verbosity)) + + // we don't try lower priority completions unless higher ones return no results. + def tryCompletion(p: Parsed, completionFunction: Parsed => List[String]): Option[Int] = { + completionFunction(p) match { + case Nil => None + case xs => + // modify in place and return the position + xs foreach (candidates add _) + + // update the last buffer unless this is an alternatives list + if (xs contains "") Some(p.cursor) + else { + val advance = commonPrefix(xs) + lastCursor = p.position + advance.length + lastBuf = (buf take p.position) + advance + + DBG("tryCompletion(%s, _) lastBuf = %s, lastCursor = %s, p.position = %s".format(p, lastBuf, lastCursor, p.position)) + Some(p.position) + } + } + } + + def mkDotted = Parsed.dotted(buf, cursor) withVerbosity verbosity + def mkUndelimited = Parsed.undelimited(buf, cursor) withVerbosity verbosity + + // a single dot is special cased to completion on the previous result + def lastResultCompletion = + if (!looksLikeInvocation(buf)) None + else tryCompletion(Parsed.dotted(buf drop 1, cursor), lastResultFor) + + def regularCompletion = tryCompletion(mkDotted, topLevelFor) + def fileCompletion = + if (!looksLikePath(buf)) None + else tryCompletion(mkUndelimited, FileCompletion completionsFor _.buffer) + + /** This is the kickoff point for all manner of theoretically possible compiler + * unhappiness - fault may be here or elsewhere, but we don't want to crash the + * repl regardless. Hopefully catching Exception is enough, but because the + * compiler still throws some Errors it may not be. + */ + try { + (lastResultCompletion orElse regularCompletion orElse fileCompletion) getOrElse cursor + } + catch { + case ex: Exception => + DBG("Error: complete(%s, %s, _) provoked %s".format(_buf, cursor, ex)) + candidates add " " + candidates add "<completion error>" + cursor + } + } + } +} diff --git a/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala b/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala index 8f42305bcb..d09567eadf 100644 --- a/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala +++ b/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala @@ -7,12 +7,57 @@ package scala.tools.nsc package interpreter import java.io.File +import java.util.{ List => JList } import scala.tools.jline.console.ConsoleReader import scala.tools.jline.console.completer._ +import scala.tools.jline.console.history._ +import scala.tools.jline.console.history.{ FileHistory, PersistentHistory, History => JHistory } +import scala.tools.jline.console.history.History.{ Entry => JEntry } +import scala.tools.jline.console.ConsoleReader +import scala.collection.JavaConverters._ +import Properties.userHome + +/** A wrapper for JLine's History. + */ +class JLineHistory(val jhistory: JHistory) extends History { + def asJavaList = jhistory.entries() + def asStrings = asList map (_.value.toString) + def asList: List[JEntry] = asJavaList.asScala.toList + def index = jhistory.index() + def size = jhistory.size() + + def grep(s: String) = asStrings filter (_ contains s) + def flush() = jhistory match { + case x: PersistentHistory => x.flush() + case _ => () + } +} + +object JLineHistory { + val ScalaHistoryFile = ".scala_history" + + def apply() = new JLineHistory( + try newFile() + catch { case x : Exception => + Console.println("Error creating file history: memory history only. " + x) + newMemory() + } + ) + + def newMemory() = new MemoryHistory() + def newFile() = new FileHistory(new File(userHome, ScalaHistoryFile)) { + // flush after every add to avoid installing a shutdown hook. + // (The shutdown hook approach also loses history when they aren't run.) + override def add(item: CharSequence): Unit = { + super.add(item) + flush() + } + } +} /** Reads from the console using JLine */ class JLineReader(val completion: Completion) extends InteractiveReader { - lazy val history = History() + lazy val history = JLineHistory() def reset() = consoleReader.getTerminal().reset() def init() = consoleReader.getTerminal().init() @@ -24,7 +69,12 @@ class JLineReader(val completion: Completion) extends InteractiveReader { } def argCompletor: ArgumentCompleter = { - val c = new ArgumentCompleter(new JLineDelimiter, completion.completer()) + val wrapped = new Completer { + val cc = completion.completer() + def complete(buffer: String, cursor: Int, candidates: JList[CharSequence]): Int = + cc.complete(buffer, cursor, candidates) + } + val c = new ArgumentCompleter(new JLineDelimiter, wrapped) c setStrict false c } @@ -37,7 +87,7 @@ class JLineReader(val completion: Completion) extends InteractiveReader { if (completion ne Completion.Empty) { r addCompleter argCompletor - r setAutoprintThreshold 250 // max completion candidates without warning + r setAutoprintThreshold 400 // max completion candidates without warning } r @@ -48,3 +98,10 @@ class JLineReader(val completion: Completion) extends InteractiveReader { val interactive = true } +object JLineReader { + def apply(intp: IMain): InteractiveReader = apply(new JLineCompletion(intp)) + def apply(comp: Completion): InteractiveReader = { + try new JLineReader(comp) + catch { case e @ (_: Exception | _: NoClassDefFoundError) => new SimpleReader } + } +} diff --git a/src/compiler/scala/tools/nsc/interpreter/LoopCommands.scala b/src/compiler/scala/tools/nsc/interpreter/LoopCommands.scala new file mode 100644 index 0000000000..24fde9425f --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/LoopCommands.scala @@ -0,0 +1,59 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package interpreter + +trait LoopCommands { + protected def out: java.io.PrintWriter + + // a single interpreter command + sealed abstract class LoopCommand extends (List[String] => Result) { + def name: String + def help: String + def commandError(msg: String) = { + out.println(":" + name + " " + msg + ".") + Result(true, None) + } + def usage(): String + } + case class NoArgs(name: String, help: String, f: () => Result) extends LoopCommand { + def usage(): String = ":" + name + def apply(args: List[String]) = if (args.isEmpty) f() else commandError("accepts no arguments") + } + + case class LineArg(name: String, help: String, f: (String) => Result) extends LoopCommand { + def usage(): String = ":" + name + " " + def apply(args: List[String]) = f(args mkString " ") + } + + case class OneArg(name: String, help: String, f: (String) => Result) extends LoopCommand { + def usage(): String = ":" + name + " " + def apply(args: List[String]) = + if (args.size == 1) f(args.head) + else commandError("requires exactly one argument") + } + + case class VarArgs(name: String, help: String, f: (List[String]) => Result) extends LoopCommand { + def usage(): String = ":" + name + " [arg]" + def apply(args: List[String]) = f(args) + } + + // the result of a single command + case class Result(val keepRunning: Boolean, val lineToRecord: Option[String]) + object Result { + // the default result means "keep running, and don't record that line" + val default = Result(true, None) + + // most commands do not want to micromanage the Result, but they might want + // to print something to the console, so we accomodate Unit and String returns. + implicit def resultFromUnit(x: Unit): Result = default + implicit def resultFromString(msg: String): Result = { + out println msg + default + } + } +} + diff --git a/src/compiler/scala/tools/nsc/interpreter/NamedParam.scala b/src/compiler/scala/tools/nsc/interpreter/NamedParam.scala index 62255b2aaf..878c5b20b1 100644 --- a/src/compiler/scala/tools/nsc/interpreter/NamedParam.scala +++ b/src/compiler/scala/tools/nsc/interpreter/NamedParam.scala @@ -6,26 +6,38 @@ package scala.tools.nsc package interpreter -object NamedParam { - def apply[T: Manifest](name: String, x: T): NamedParam[T] = new NamedParam[T](name, x) - def apply[T: Manifest](x: T): NamedParam[T] = apply(getParamName(), x) +import NamedParam._ - implicit def fromValue[T: Manifest](x: T) = apply(x) - implicit def fromNameAndValue[T: Manifest](name: String, x: T) = apply(name, x) - implicit def fromTuple[T: Manifest](pair: (String, T)) = apply(pair._1, pair._2) +trait NamedParamCreator { + protected def freshName: () => String - private val getParamName = { + def apply[T: Manifest](name: String, x: T): NamedParam = new Typed[T](name, x) + def apply[T: Manifest](x: T): NamedParam = apply(freshName(), x) + + def clazz(name: String, x: Any): NamedParam = new Untyped(name, x) + def clazz(x: Any): NamedParam = clazz(freshName(), x) + + implicit def namedValue[T: Manifest](name: String, x: T): NamedParam = apply(name, x) + implicit def tuple[T: Manifest](pair: (String, T)): NamedParam = apply(pair._1, pair._2) +} + +object NamedParam extends NamedParamCreator { + class Typed[T: Manifest](val name: String, val value: T) extends NamedParam { + val tpe = TypeStrings.fromManifest[T] + } + class Untyped(val name: String, val value: Any) extends NamedParam { + val tpe = TypeStrings.fromValue(value) + } + + protected val freshName = { var counter = 0 () => { counter += 1; "p" + counter } } } -class NamedParam[T: Manifest](val name: String, val value: T) { - val clazz = manifest[T].erasure.getName - val tparams = manifest[T].typeArguments match { - case Nil => "" - case xs => xs.mkString("[", ", ", "]") - } - val tpe = clazz + tparams +trait NamedParam { + def name: String + def tpe: String + def value: Any override def toString = name + ": " + tpe -} +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interpreter/Power.scala b/src/compiler/scala/tools/nsc/interpreter/Power.scala index 9cd6619c2d..6bb70ea02e 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Power.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Power.scala @@ -7,12 +7,14 @@ package scala.tools.nsc package interpreter import scala.collection.{ mutable, immutable } -import mutable.{ HashMap } -import scala.tools.nsc.util.{ NoPosition, BatchSourceFile } +import scala.tools.nsc.util.{ BatchSourceFile } /** A class for methods to be injected into the intp in power mode. */ -class Power(intp: Interpreter) { +class Power(repl: ILoop, intp: IMain) { + def this(repl: ILoop) = this(repl, repl.intp) + def this(intp: IMain) = this(null, intp) + val global: intp.global.type = intp.global import global._ @@ -60,8 +62,13 @@ class Power(intp: Interpreter) { */ def unleash(): Unit = { def f = { - intp.bind[InterpreterLoop]("repl", this) - intp.bind[Interpreter]("intp", intp) + if (repl != null) { + intp.bind[ILoop]("repl", repl) + intp.bind[History]("history", repl.in.history) + intp.bind[Completion]("completion", repl.in.completion) + } + + intp.bind[IMain]("intp", intp) intp.bind[Power]("power", this) init split '\n' foreach interpret } diff --git a/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala index 7b88556f48..0cbac2d0d6 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala @@ -12,7 +12,7 @@ import Modifier.{ isPrivate, isProtected, isStatic } import ReflectionCompletion._ trait ReflectionCompletion extends CompletionAware { - def clazz: Class[_] + def clazz: JClass protected def visibleMembers: List[AccessibleObject] protected def memberCompletions = visibleMembers filter isPublic map reflectName @@ -32,13 +32,13 @@ trait ReflectionCompletion extends CompletionAware { /** Oops, mirror classes don't descend from scalaobject. */ - def isScalaClazz(cl: Class[_]) = { + def isScalaClazz(cl: JClass) = { (allInterfacesFor(cl) exists (_.getName == "scala.ScalaObject")) || (classForName(cl.getName + "$").isDefined) } - def allInterfacesFor(cl: Class[_]): List[Class[_]] = allInterfacesFor(cl, Nil) + def allInterfacesFor(cl: JClass): List[JClass] = allInterfacesFor(cl, Nil) - private def allInterfacesFor(cl: Class[_], acc: List[Class[_]]): List[Class[_]] = { + private def allInterfacesFor(cl: JClass, acc: List[JClass]): List[JClass] = { if (cl == null) acc.distinct else allInterfacesFor(cl.getSuperclass, acc ::: cl.getInterfaces.toList) } @@ -48,7 +48,7 @@ trait ReflectionCompletion extends CompletionAware { * It completes to instance fields and methods, and delegates to another * InstanceCompletion object if it can determine the result type of the element. */ -class InstanceCompletion(val clazz: Class[_]) extends ReflectionCompletion { +class InstanceCompletion(val clazz: JClass) extends ReflectionCompletion { protected def visibleMembers = instanceMethods ::: instanceFields def extras = List("isInstanceOf", "asInstanceOf", "toString") lazy val completions = memberCompletions ::: extras @@ -65,7 +65,7 @@ class InstanceCompletion(val clazz: Class[_]) extends ReflectionCompletion { /** The complementary class to InstanceCompletion. It has logic to deal with * java static members and scala companion object members. */ -class StaticCompletion(val clazz: Class[_]) extends ReflectionCompletion { +class StaticCompletion(val clazz: JClass) extends ReflectionCompletion { protected def visibleMembers = whichMethods ::: whichFields lazy val completions = memberCompletions def completions(verbosity: Int) = completions @@ -102,7 +102,7 @@ object ReflectionCompletion { val flags = STATIC | PRIVATE | PROTECTED (m.getModifiers & flags) == 0 } - private def getAnyClass(x: Any): Class[_] = x.asInstanceOf[AnyRef].getClass + private def getAnyClass(x: Any): JClass = x.asInstanceOf[AnyRef].getClass def methodsOf(target: Any): List[String] = getAnyClass(target).getMethods filter skipModifiers map (_.getName) toList diff --git a/src/compiler/scala/tools/nsc/InterpreterResults.scala b/src/compiler/scala/tools/nsc/interpreter/Results.scala index ed826481fc..f582d47485 100644 --- a/src/compiler/scala/tools/nsc/InterpreterResults.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Results.scala @@ -4,10 +4,10 @@ */ package scala.tools.nsc +package interpreter -object InterpreterResults { - - /** A result from interpreting one line of input. */ +object Results { + /** A result from the Interpreter interpreting one line of input. */ abstract sealed class Result /** The line was interpreted successfully. */ @@ -16,9 +16,7 @@ object InterpreterResults { /** The line was erroneous in some way. */ case object Error extends Result - /** The input was incomplete. The caller should request more - * input. + /** The input was incomplete. The caller should request more input. */ case object Incomplete extends Result - } diff --git a/src/compiler/scala/tools/nsc/interpreter/RichClass.scala b/src/compiler/scala/tools/nsc/interpreter/RichClass.scala new file mode 100644 index 0000000000..cbeee9c056 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/RichClass.scala @@ -0,0 +1,29 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package interpreter + +class RichClass[T](val clazz: Class[T]) { + def toManifest: Manifest[T] = Manifest.classType(clazz) + def toTypeString: String = TypeStrings.fromClazz(clazz) + + /** It's not easy... to be... me... */ + def supermans: List[Manifest[_]] = supers map (_.toManifest) + def superNames: List[String] = supers map (_.getName) + def interfaces: List[JClass] = supers filter (_.isInterface) + + def hasAncestorName(f: String => Boolean) = superNames exists f + def hasAncestor(f: JClass => Boolean) = supers exists f + def hasAncestorInPackage(pkg: String) = hasAncestorName(_ startsWith (pkg + ".")) + + def supers: List[JClass] = { + def loop(x: JClass): List[JClass] = x.getSuperclass match { + case null => List(x) + case sc => x :: (x.getInterfaces.toList flatMap loop) ++ loop(sc) + } + loop(clazz).distinct + } +} diff --git a/src/compiler/scala/tools/nsc/interpreter/Runner.scala b/src/compiler/scala/tools/nsc/interpreter/Runner.scala new file mode 100644 index 0000000000..f9f75da3c6 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/Runner.scala @@ -0,0 +1,11 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package interpreter + +object Runner { + def main(args: Array[String]): Unit = new ILoop process args +} diff --git a/src/compiler/scala/tools/nsc/interpreter/SimpleReader.scala b/src/compiler/scala/tools/nsc/interpreter/SimpleReader.scala index a28fb9c5fe..e4c0bf8307 100644 --- a/src/compiler/scala/tools/nsc/interpreter/SimpleReader.scala +++ b/src/compiler/scala/tools/nsc/interpreter/SimpleReader.scala @@ -7,7 +7,6 @@ package scala.tools.nsc package interpreter import java.io.{ BufferedReader, PrintWriter } -import io.{ Path, File, Directory } /** Reads using standard JDK API */ class SimpleReader( @@ -16,7 +15,7 @@ class SimpleReader( val interactive: Boolean) extends InteractiveReader { def this() = this(Console.in, new PrintWriter(Console.out), true) - def this(in: File, out: PrintWriter, interactive: Boolean) = this(in.bufferedReader(), out, interactive) + def this(in: io.File, out: PrintWriter, interactive: Boolean) = this(in.bufferedReader(), out, interactive) lazy val history = History.Empty lazy val completion = Completion.Empty diff --git a/src/compiler/scala/tools/nsc/interpreter/TypeStrings.scala b/src/compiler/scala/tools/nsc/interpreter/TypeStrings.scala new file mode 100644 index 0000000000..91dee97277 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/TypeStrings.scala @@ -0,0 +1,78 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package interpreter + +import java.lang.{ reflect => r } +import r.TypeVariable +import scala.reflect.NameTransformer + +/** Logic for turning a type into a String. The goal is to be + * able to take some arbitrary object 'x' and obtain the most precise + * String for which an injection of x.asInstanceOf[String] will + * be valid from both the JVM's and scala's perspectives. + * + * "definition" is when you want strings like + */ +trait TypeStrings { + private val ObjectClass = classOf[java.lang.Object] + private val primitives = Set[String]("byte", "char", "short", "int", "long", "float", "double", "boolean") + private def unbox(s: String): String = s.stripPrefix("java.lang.") match { + case "Integer" => "scala.Int" + case "Character" => "scala.Char" + case "Void" => "scala.Unit" + case x @ ("Byte" | "Short" | "Long" | "Float" | "Double" | "Boolean") => "scala." + x + case _ => NameTransformer.decode(s) + } + + def scalaName(s: String): String = { + if (s endsWith "$") (s dropRight 1) + ".type" + else if (primitives(s)) "scala." + s.capitalize + else if (s == "void") "scala.Unit" + else unbox(s) + } + def scalaName(clazz: JClass): String = scalaName(clazz.getName) + def scalaName(m: ClassManifest[_]): String = scalaName(m.erasure) + def anyClass(x: Any): JClass = if (x == null) null else x.asInstanceOf[AnyRef].getClass + + private def tvarString(tvar: TypeVariable[_]): String = tvarString(tvar.getBounds.toList) + private def tvarString(bounds: List[AnyRef]): String = { + val xs = bounds filterNot (_ == ObjectClass) collect { case x: Class[_] => x } + if (xs.isEmpty) "_" + else scalaName(xs.head) + } + private def tparamString(clazz: JClass): String = { + val tps = clazz.getTypeParameters.toList + if (tps.isEmpty) + return "" + + (tps map tvarString).mkString("[", ", ", "]") + } + private def tparamString[T: Manifest] : String = { + val tps = manifest[T].typeArguments + if (tps.isEmpty) + return "" + + tps.map(m => tvarString(List(m.erasure))).mkString("[", ", ", "]") + } + /** Going for an overabundance of caution right now. + */ + def fromTypedValue[T: Manifest](x: T): String = fromManifest[T] + def fromValue(value: Any): String = if (value == null) "Null" else fromClazz(anyClass(value)) + def fromClazz(clazz: JClass): String = scalaName(clazz) + tparamString(clazz) + def fromManifest[T: Manifest] : String = scalaName(manifest[T].erasure) + tparamString[T] + + /** Reducing fully qualified noise for some common packages. + */ + val typeTransforms = List( + "java.lang." -> "", + "scala.collection.immutable." -> "immutable.", + "scala.collection.mutable." -> "mutable.", + "scala.collection.generic." -> "generic." + ) +} + +object TypeStrings extends TypeStrings { }
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interpreter/package.scala b/src/compiler/scala/tools/nsc/interpreter/package.scala index c59cf8228a..3a10547d81 100644 --- a/src/compiler/scala/tools/nsc/interpreter/package.scala +++ b/src/compiler/scala/tools/nsc/interpreter/package.scala @@ -5,12 +5,32 @@ package scala.tools.nsc +/** The main REPL related classes and values are as follows. + * In addition to standard compiler classes Global and Settings, there are: + * + * History: an interface for session history. + * Completion: an interface for tab completion. + * ILoop (formerly InterpreterLoop): The umbrella class for a session. + * IMain (formerly Interpreter): Handles the evolving state of the session + * and handles submitting code to the compiler and handling the output. + * InteractiveReader: how ILoop obtains input. + * History: an interface for session history. + * Completion: an interface for tab completion. + * Power: a repository for more advanced/experimental features. + * + * ILoop contains { in: InteractiveReader, intp: IMain, settings: Settings, power: Power } + * InteractiveReader contains { history: History, completion: Completion } + * IMain contains { global: Global } + */ package object interpreter { private[nsc] val DebugProperty = "scala.repl.debug" private[nsc] val PowerProperty = "scala.repl.power" private[nsc] var _debug = false private[nsc] def isReplDebug = _debug || (sys.props contains DebugProperty) + type JClass = java.lang.Class[_] + private[nsc] implicit def enrichClass[T](clazz: Class[T]) = new RichClass[T](clazz) + /** Debug output */ def repldbg(msg: String) = if (isReplDebug) Console println msg @@ -22,6 +42,9 @@ package object interpreter { x } + private[nsc] def isQuoted(s: String) = + (s.length >= 2) && (s.head == s.last) && ("\"'" contains s.head) + /** Heuristically strip interpreter wrapper prefixes * from an interpreter output string. */ @@ -31,7 +54,7 @@ package object interpreter { } /** Class objects */ - def classForName(name: String): Option[Class[_]] = + def classForName(name: String): Option[JClass] = try Some(Class forName name) catch { case _: ClassNotFoundException | _: SecurityException => None } } diff --git a/src/compiler/scala/tools/nsc/io/Socket.scala b/src/compiler/scala/tools/nsc/io/Socket.scala index ea0a65986d..0be7de3873 100644 --- a/src/compiler/scala/tools/nsc/io/Socket.scala +++ b/src/compiler/scala/tools/nsc/io/Socket.scala @@ -7,7 +7,6 @@ package scala.tools.nsc package io import java.io.{ IOException, InputStreamReader, BufferedReader, PrintWriter } -import java.net.{ URL, MalformedURLException } import java.net.{ InetAddress, Socket => JSocket } import scala.util.control.Exception._ diff --git a/src/compiler/scala/tools/nsc/package.scala b/src/compiler/scala/tools/nsc/package.scala new file mode 100644 index 0000000000..79ced4f05a --- /dev/null +++ b/src/compiler/scala/tools/nsc/package.scala @@ -0,0 +1,13 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools + +package object nsc { + @deprecated("Use a class in the scala.tools.nsc.interpreter package.") + type InterpreterSettings = interpreter.ISettings + @deprecated("Use a class in the scala.tools.nsc.interpreter package.") + val InterpreterResults = interpreter.Results +}
\ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/symtab/SymbolTable.scala b/src/compiler/scala/tools/nsc/symtab/SymbolTable.scala index 60387c8335..d3decb0abc 100644 --- a/src/compiler/scala/tools/nsc/symtab/SymbolTable.scala +++ b/src/compiler/scala/tools/nsc/symtab/SymbolTable.scala @@ -102,7 +102,7 @@ abstract class SymbolTable extends reflect.generic.Universe /** Break into repl debugger if assertion is true */ // def breakIf(assertion: => Boolean, args: Any*): Unit = // if (assertion) - // InterpreterLoop.break(args.toList) + // ILoop.break(args.toList) /** The set of all installed infotransformers */ var infoTransformers = new InfoTransformer { diff --git a/test/files/run/treePrint.scala b/test/files/run/treePrint.scala index 4c5f852582..8a77a3c9dc 100644 --- a/test/files/run/treePrint.scala +++ b/test/files/run/treePrint.scala @@ -34,9 +34,9 @@ object Test { settings.classpath.value = System.getProperty("java.class.path") settings.Ycompacttrees.value = true - val repl = new Interpreter(settings, new PrintWriter(new NullOutputStream)) - val power = new Power(repl) - repl.interpret("""def initialize = "Have to interpret something or we get errors." """) + val intp = new IMain(settings, new PrintWriter(new NullOutputStream)) + val power = new Power(intp) + intp.interpret("""def initialize = "Have to interpret something or we get errors." """) println(power mkTree code) } } |