summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/compiler/scala/tools/nsc/Interpreter.scala1321
-rw-r--r--src/compiler/scala/tools/nsc/InterpreterCommand.scala18
-rw-r--r--src/compiler/scala/tools/nsc/InterpreterLoop.scala710
-rw-r--r--src/compiler/scala/tools/nsc/InterpreterSettings.scala113
-rw-r--r--src/compiler/scala/tools/nsc/MainGenericRunner.scala7
-rw-r--r--src/compiler/scala/tools/nsc/MainInterpreter.scala8
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/AbstractFileClassLoader.scala2
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/ByteCode.scala2
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/CommandLine.scala14
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Completion.scala366
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/CompletionOutput.scala5
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Eval.scala33
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Formatting.scala35
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/History.scala54
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/ILoop.scala651
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/IMain.scala1267
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/ISettings.scala61
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala11
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/JLineCompletion.scala346
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/JLineReader.scala63
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/LoopCommands.scala59
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/NamedParam.scala42
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Power.scala17
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala14
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Results.scala (renamed from src/compiler/scala/tools/nsc/InterpreterResults.scala)10
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/RichClass.scala29
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Runner.scala11
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/SimpleReader.scala3
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/TypeStrings.scala78
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/package.scala25
-rw-r--r--src/compiler/scala/tools/nsc/io/Socket.scala1
-rw-r--r--src/compiler/scala/tools/nsc/package.scala13
-rw-r--r--src/compiler/scala/tools/nsc/symtab/SymbolTable.scala2
33 files changed, 2795 insertions, 2596 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 {