summaryrefslogtreecommitdiff
path: root/src/compiler
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2011-01-29 01:43:10 +0000
committerPaul Phillips <paulp@improving.org>2011-01-29 01:43:10 +0000
commitcf492f472aa6d1154cd7336c37bab0b78125b872 (patch)
tree8735c22a25c019a5e694d8f93a0d66e9b4f47940 /src/compiler
parentb06bfabfa42cc089521e551acb9876a564ec027f (diff)
downloadscala-cf492f472aa6d1154cd7336c37bab0b78125b872.tar.gz
scala-cf492f472aa6d1154cd7336c37bab0b78125b872.tar.bz2
scala-cf492f472aa6d1154cd7336c37bab0b78125b872.zip
There is a lot of housecleaning to be done.
up the stray interpreter files and put them in the interpreter package. Would really love to change the name of that package. Went looking for some consistent divisions of responsibility and consistent naming. Made some progress. There are deprecated versions of most everything I changed so hopefully the carnage will be limited. This isn't completely baked but I just realized I broke the build earlier and this should fix it. I'll keep the oven on. No review.
Diffstat (limited to 'src/compiler')
-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 {