summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2010-01-23 20:30:01 +0000
committerPaul Phillips <paulp@improving.org>2010-01-23 20:30:01 +0000
commita0c0f0949797a563c9583d0f82c0f390a999ec7d (patch)
tree59a21db321f764372f3ad85930c527f7a1571f0e
parentbb6e5958e63e3d70cd1f1a86f3fa0b5f3b670d8a (diff)
downloadscala-a0c0f0949797a563c9583d0f82c0f390a999ec7d.tar.gz
scala-a0c0f0949797a563c9583d0f82c0f390a999ec7d.tar.bz2
scala-a0c0f0949797a563c9583d0f82c0f390a999ec7d.zip
Another big REPL patch.
a proper commit message, I will just say it adds a couple of pretty frabjous features, in addition to cleaning up a whole bunch of questionable code. * Tab-completion now chains through intermediate results on fields and 0-arg methods * You can now define custom Completors which define their own contents. Details and demos to come in a wiki document about the repl.
-rw-r--r--src/compiler/scala/tools/nsc/Interpreter.scala254
-rw-r--r--src/compiler/scala/tools/nsc/InterpreterLoop.scala87
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Completion.scala384
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala84
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/History.scala23
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/IdentCompletion.scala19
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala15
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/JLineReader.scala23
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala129
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Parsed.scala30
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala139
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/XMLCompletion.scala43
-rw-r--r--src/library/scala/collection/Iterator.scala25
-rw-r--r--src/library/scala/xml/NodeSeq.scala3
14 files changed, 753 insertions, 505 deletions
diff --git a/src/compiler/scala/tools/nsc/Interpreter.scala b/src/compiler/scala/tools/nsc/Interpreter.scala
index d8cf729d25..01ed5087ae 100644
--- a/src/compiler/scala/tools/nsc/Interpreter.scala
+++ b/src/compiler/scala/tools/nsc/Interpreter.scala
@@ -12,9 +12,11 @@ import java.net.{ MalformedURLException, URL }
import java.lang.reflect
import reflect.InvocationTargetException
+import scala.PartialFunction.{ cond, condOpt }
import scala.reflect.Manifest
import scala.collection.mutable
-import scala.collection.mutable.{ ListBuffer, HashSet, ArrayBuffer }
+import scala.collection.mutable.{ ListBuffer, HashSet, HashMap, ArrayBuffer }
+import scala.collection.immutable.Set
import scala.tools.nsc.util.ScalaClassLoader
import ScalaClassLoader.URLClassLoader
import scala.util.control.Exception.{ Catcher, catching, ultimately, unwrapping }
@@ -68,8 +70,7 @@ import Interpreter._
* @author Moez A. Abdel-Gawad
* @author Lex Spoon
*/
-class Interpreter(val settings: Settings, out: PrintWriter)
-{
+class Interpreter(val settings: Settings, out: PrintWriter) {
/** directory to save .class files to */
val virtualDirectory = new VirtualDirectory("(memory)", None)
@@ -81,7 +82,7 @@ class Interpreter(val settings: Settings, out: PrintWriter)
import compiler.{
Tree, TermTree, ValOrDefDef, ValDef, DefDef, Assign, ClassDef,
ModuleDef, Ident, Select, TypeDef, Import, MemberDef, DocDef,
- EmptyTree }
+ ImportSelector, EmptyTree }
import compiler.{ nme, newTermName }
import nme.{
INTERPRETER_VAR_PREFIX, INTERPRETER_SYNTHVAR_PREFIX, INTERPRETER_LINE_PREFIX,
@@ -193,11 +194,27 @@ class Interpreter(val settings: Settings, out: PrintWriter)
/** the previous requests this interpreter has processed */
private val prevRequests = new ArrayBuffer[Request]()
- val prevImports = new ListBuffer[Import]()
+ private val prevNameMap = new HashMap[Name, Request]()
+ private val boundNameMap = new HashMap[Name, Request]()
- private def allUsedNames = prevRequests.toList.flatMap(_.usedNames).removeDuplicates
- private def allBoundNames = prevRequests.toList.flatMap(_.boundNames).removeDuplicates
- // private def allImportedNames = prevImports.toList.flatMap(_.importedNames).removeDuplicates
+ 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.usedNames foreach (x => prevNameMap(x) = req)
+ req.boundNames foreach (x => boundNameMap(x) = req)
+
+ // println("\n s1 = %s\n s2 = %s\n s3 = %s".format(
+ // tripart(prevNameMap.keysIterator.toSet, boundNameMap.keysIterator.toSet): _*
+ // ))
+ }
+
+ private def mostRecentHandler = prevRequests.last.handlers.last
+ def allUsedNames = prevNameMap.keysIterator.toList
+ def allBoundNames = prevRequests.toList.flatMap(_.boundNames).removeDuplicates
/** Generates names pre0, pre1, etc. via calls to apply method */
class NameCreator(pre: String) {
@@ -231,6 +248,7 @@ class Interpreter(val settings: Settings, out: PrintWriter)
/** 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
+ def getVarName = varNameCreator()
/** generate a string using a routine that wants to write on a stream */
private def stringFrom(writer: PrintWriter => Unit): String = {
@@ -302,25 +320,26 @@ class Interpreter(val settings: Settings, out: PrintWriter)
* should be taken. Removes requests which cannot contribute
* useful imports for the specified set of wanted names.
*/
- case class ReqAndHandler(req: Request, handler: MemberHandler)
+ case class ReqAndHandler(req: Request, handler: MemberHandler) { }
+
def reqsToUse: List[ReqAndHandler] = {
/** Loop through a list of MemberHandlers and select which ones to keep.
* 'wanted' is the set of names that need to be imported.
*/
def select(reqs: List[ReqAndHandler], wanted: Set[Name]): List[ReqAndHandler] = {
val isWanted = wanted contains _
- def keepHandler(handler: MemberHandler): Boolean = {
- import handler._
- // Single symbol imports might be implicits! See bug #1752. Rather than
- // try to finesse this, we will mimic all imports for now.
- def isImport = handler.isInstanceOf[ImportHandler]
- definesImplicit || isImport || (importedNames ++ boundNames).exists(isWanted)
+ // 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 ++ usedNames -- boundNames -- importedNames
rh :: select(rest, newWanted)
@@ -352,32 +371,33 @@ class Interpreter(val settings: Settings, out: PrintWriter)
// loop through previous requests, adding imports for each one
for (ReqAndHandler(req, handler) <- reqsToUse) {
- import handler._
- // If the user entered an import, then just use it; add an import wrapping
- // level if the import might conflict with some other import
- if (importsWildcard || currentImps.exists(importedNames.contains))
- addWrapper()
-
- if (member.isInstanceOf[Import])
- code append (member.toString + "\n")
-
- // give wildcard imports a import wrapper all to their own
- if (importsWildcard) addWrapper()
- else currentImps ++= importedNames
-
- // For other requests, import each bound variable.
- // import them explicitly instead of with _, so that
- // ambiguity errors will not be generated. Also, quote
- // the name of the variable, so that we don't need to
- // handle quoting keywords separately.
- for (imv <- boundNames) {
- if (currentImps contains imv) addWrapper()
-
- code append ("import " + req.fullPath(imv))
- currentImps += imv
+ 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 " + (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()
@@ -457,7 +477,7 @@ class Interpreter(val settings: Settings, out: PrintWriter)
// 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 =>
+ case _:TermTree | _:Ident | _:Select => // ... but do want these as valdefs.
return requestFromLine("val %s =\n%s".format(varNameCreator(), line))
case _ =>
}
@@ -485,8 +505,8 @@ class Interpreter(val settings: Settings, out: PrintWriter)
case Left(result) => return result
case Right(req) => req
}
-
- // null is a disallowed statement type; otherwise compile and fail if false (implying e.g. a type error)
+ // null indicates a disallowed statement type; otherwise compile and
+ // fail if false (implying e.g. a type error)
if (req == null || !req.compile)
return IR.Error
@@ -495,7 +515,7 @@ class Interpreter(val settings: Settings, out: PrintWriter)
out print clean(result)
if (succeeded) {
- prevRequests += req // book-keeping
+ recordRequest(req) // book-keeping
IR.Success
}
else IR.Error
@@ -513,7 +533,7 @@ class Interpreter(val settings: Settings, out: PrintWriter)
* @return an indication of whether the binding succeeded
*/
def bind(name: String, boundType: String, value: Any): IR.Result = {
- val binderName = newBinder() // "binder" + binderNum()
+ val binderName = newBinder()
compileString("""
| object %s {
@@ -576,13 +596,9 @@ class Interpreter(val settings: Settings, out: PrintWriter)
def boundNames: List[Name] = Nil
def valAndVarNames: List[Name] = Nil
def defNames: List[Name] = Nil
- val importsWildcard = false
- val importedNames: Seq[Name] = Nil
- val definesImplicit = member match {
- case tree: MemberDef => tree.mods hasFlag Flags.IMPLICIT
- case _ => false
+ val definesImplicit = cond(member) {
+ case tree: MemberDef => tree.mods hasFlag Flags.IMPLICIT
}
-
def generatesValue: Option[Name] = None
def extraCodeToEvaluate(req: Request, code: PrintWriter) { }
@@ -681,18 +697,15 @@ class Interpreter(val settings: Settings, out: PrintWriter)
private class ImportHandler(imp: Import) extends MemberHandler(imp) {
/** Whether this import includes a wildcard import */
- override val importsWildcard = imp.selectors.map(_.name) contains USCOREkw
+ val importsWildcard = imp.selectors map (_.name) contains USCOREkw
/** The individual names imported by this statement */
- override val importedNames: Seq[Name] = for {
- sel <- imp.selectors
- if (sel.rename != null && sel.rename != USCOREkw)
- name <- List(sel.rename.toTypeName, sel.rename.toTermName)
- }
- yield name
-
- // record the import
- prevImports += imp
+ val importedNames: List[Name] = (
+ imp.selectors
+ . map (x => x.rename)
+ . filter (x => x != null && x != USCOREkw)
+ . flatMap (x => List(x.toTypeName, x.toTermName))
+ )
override def resultExtractionCode(req: Request, code: PrintWriter) =
code println codegenln(imp.toString)
@@ -700,8 +713,6 @@ class Interpreter(val settings: Settings, out: PrintWriter)
/** One line of code submitted by the user for interpretation */
private class Request(val line: String, val lineName: String, val trees: List[Tree]) {
- // val trees = parse(line) getOrElse Nil
-
/** name to use for the object that will compute "line" */
def objectName = lineName + INTERPRETER_WRAPPER_SUFFIX
@@ -718,7 +729,8 @@ class Interpreter(val settings: Settings, out: PrintWriter)
val usedNames: List[Name] = handlers.flatMap(_.usedNames)
/** Code to import bound names from previous lines - accessPath is code to
- * append to objectName to access anything bound by request. */
+ * append to objectName to access anything bound by request.
+ */
val ComputedImports(importsPreamble, importsTrailer, accessPath) =
importsCode(Set.empty ++ usedNames)
@@ -733,7 +745,6 @@ class Interpreter(val settings: Settings, out: PrintWriter)
/** generate the source code for the object that computes this request */
def objectSourceCode: String = stringFrom { code =>
- // whitespace compatible with interpreter.scala
val preamble = """object %s {
| %s%s
""".stripMargin.format(objectName, importsPreamble, indentCode(toCompute))
@@ -792,17 +803,14 @@ class Interpreter(val settings: Settings, out: PrintWriter)
x.compileSources(List(new BatchSourceFile("<console>", resultObjectSourceCode)))
x
}
+ lazy val loadedResultObject = loadByName(resultObjectName)
- def extractionValue(): Option[AnyRef] = {
+ def extractionValue(): AnyRef = {
// ensure it has run
extractionObjectRun
- catching(classOf[Exception]) opt {
- // load it and retrieve the value
- val result: Class[_] = loadByName(resultObjectName)
-
- result getMethod "scala_repl_value" invoke result
- }
+ // load it and retrieve the value
+ loadedResultObject getMethod "scala_repl_value" invoke loadedResultObject
}
/** Compile the object file. Returns whether the compilation succeeded.
@@ -866,8 +874,7 @@ class Interpreter(val settings: Settings, out: PrintWriter)
/** load and run the code using reflection */
def loadAndRun: (String, Boolean) = {
- val resultObject: Class[_] = loadByName(resultObjectName)
- val resultValMethod: reflect.Method = resultObject getMethod "scala_repl_result"
+ val resultValMethod: reflect.Method = loadedResultObject getMethod "scala_repl_result"
// XXX if wrapperExceptions isn't type-annotated we crash scalac
val wrapperExceptions: List[Class[_ <: Throwable]] =
List(classOf[InvocationTargetException], classOf[ExceptionInInitializerError])
@@ -883,7 +890,7 @@ class Interpreter(val settings: Settings, out: PrintWriter)
catching(onErr) {
unwrapping(wrapperExceptions: _*) {
- (resultValMethod.invoke(resultObject).toString, true)
+ (resultValMethod.invoke(loadedResultObject).toString, true)
}
}
}
@@ -893,10 +900,6 @@ class Interpreter(val settings: Settings, out: PrintWriter)
* The command infrastructure is in InterpreterLoop.
*/
def dumpState(xs: List[String]): String = {
- // println("Imports for " + req + " => " + req.importsPreamble)
- // req.handlers foreach { h => println("Handler " + h + " used names: " + h.usedNames) }
- // req.trees foreach { x => println("Tree: " + x) }
- // xs foreach { x => println("membersOfIdentifier(" + x + ") = " + membersOfIdentifier(x)) }
List(
"allUsedNames = " + allUsedNames,
"allBoundNames = " + allBoundNames,
@@ -927,43 +930,42 @@ class Interpreter(val settings: Settings, out: PrintWriter)
|** mkTree("def f(x: Int, y: Int) = x+y") **""".stripMargin
}
- def nameOfIdent(line: String): Option[Name] = {
- parse(line) match {
- case Some(List(Ident(x))) => Some(x)
- case _ => None
- }
- }
-
/** 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 =
- prevRequests.last.handlers.last.member 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] = {
- for (req <- prevRequests.toList.reverse) {
- if (req.handlers.exists(_.boundNames contains name))
- return Some(req)
- }
- None
+ def mostRecentVar: String = mostRecentHandler.member 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] =
- nameOfIdent(line) flatMap requestForName
+ requestForName(newTermName(line))
+
+ def methodsOf(name: String) =
+ evalExpr[List[String]](methodsCode(name)) map (x => NameTransformer.decode(getOriginalName(x)))
+
+ def completionAware(name: String) =
+ evalExpr[Option[CompletionAware]](asCompletionAwareCode(name))
+
+ def extractionValueForIdent(id: String): AnyRef =
+ requestForIdent(id).get.extractionValue
+
+ def implicitFor[T: Manifest] = evalExpr[T]("""manifest[%s]""".format(manifest[T]))
- private def mkValDef(line: String, name: String = varNameCreator()) =
- (name, "val %s = %s".format(name, line))
+ def clazzForIdent(id: String): Class[_] =
+ extractionValueForIdent(id).getClass
- // private def inCompletion(s: String) = "scala.tools.nsc.interpreter.Completion." + s
- private def inCompletion(s: String) = classOf[Completion].getName + "." + s
- private def methodsCode(name: String) = inCompletion("methodsOf(" + name + ")")
- private def isSpecialCode(name: String) = inCompletion("isSpecial(" + name + ")")
- private def selfDefinedMembersCode(name: String) = inCompletion("selfDefinedMembers(" + name + ")")
+ 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
@@ -996,7 +998,7 @@ class Interpreter(val settings: Settings, out: PrintWriter)
if (req == null || !req.compile || req.handlers.size != 1)
evalError("Eval error.")
- try req.extractionValue.get.asInstanceOf[T] catch {
+ try req.extractionValue.asInstanceOf[T] catch {
case e: Exception => evalError(e.getMessage)
}
}
@@ -1004,33 +1006,12 @@ class Interpreter(val settings: Settings, out: PrintWriter)
def interpretExpr[T: Manifest](code: String): Option[T] = beQuietDuring {
interpret(code) match {
case IR.Success =>
- try Some(prevRequests.last.extractionValue.get.asInstanceOf[T])
+ try Some(prevRequests.last.extractionValue.asInstanceOf[T])
catch { case e: Exception => println(e) ; None }
case _ => None
}
}
- private def memberListFor(name: String): List[String] = {
- import NameTransformer.{ decode, encode } // e.g. $plus$plus => ++
-
- /** Give objects a chance to define their own members. */
- val special = evalExpr[Option[List[String]]](selfDefinedMembersCode(name))
-
- /** Failing that, use reflection. */
- special getOrElse evalExpr[List[String]](methodsCode(name)) map (x => decode(getOriginalName(x)))
- }
-
- /** The main entry point for tab-completion. When the user types x.<tab>
- * this method is called with "x" as an argument, and it discovers the
- * fields and methods of x via reflection and returns their names to jline.
- */
- def membersOfIdentifier(line: String): List[String] =
- beQuietDuring {
- for (name <- nameOfIdent(line) ; req <- requestForName(name)) yield {
- memberListFor(name) filterNot Completion.shouldHide removeDuplicates
- }
- } getOrElse Nil
-
/** Another entry point for tab-completion, ids in scope */
def unqualifiedIds(): List[String] =
allBoundNames map (_.toString) filterNot isSynthVarName
@@ -1042,8 +1023,11 @@ class Interpreter(val settings: Settings, out: PrintWriter)
def aliasForType(path: String) = ByteCode.aliasForType(path)
/** Artificial object */
- class ReplVars extends Completion.Special {
- def tabCompletions() = unqualifiedIds()
+ class ReplVars extends CompletionAware {
+ def completions() = unqualifiedIds()
+ override def follow(s: String) =
+ if (completions contains s) completionAware(s)
+ else None
}
def replVarsObject() = new ReplVars()
@@ -1066,16 +1050,16 @@ class Interpreter(val settings: Settings, out: PrintWriter)
object Interpreter {
object DebugParam {
- implicit def tuple2debugparam[T](x: (String, T))(implicit m: scala.reflect.Manifest[T]): DebugParam[T] =
+ implicit def tuple2debugparam[T](x: (String, T))(implicit m: Manifest[T]): DebugParam[T] =
DebugParam(x._1, x._2)
- implicit def any2debugparam[T](x: T)(implicit m: scala.reflect.Manifest[T]): DebugParam[T] =
+ implicit def any2debugparam[T](x: T)(implicit m: Manifest[T]): DebugParam[T] =
DebugParam("p" + getCount(), x)
private var counter = 0
def getCount() = { counter += 1; counter }
}
- case class DebugParam[T](name: String, param: T)(implicit m: scala.reflect.Manifest[T]) {
+ case class DebugParam[T](name: String, param: T)(implicit m: Manifest[T]) {
val manifest = m
val typeStr = {
val str = manifest.toString
diff --git a/src/compiler/scala/tools/nsc/InterpreterLoop.scala b/src/compiler/scala/tools/nsc/InterpreterLoop.scala
index 9cc372097a..2d8a5b78ee 100644
--- a/src/compiler/scala/tools/nsc/InterpreterLoop.scala
+++ b/src/compiler/scala/tools/nsc/InterpreterLoop.scala
@@ -78,14 +78,12 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) {
/** The input stream from which commands come, set by main() */
var in: InteractiveReader = _
- def historyList = in.history map (_.asList) getOrElse Nil
/** The context class loader at the time this object was created */
protected val originalClassLoader = Thread.currentThread.getContextClassLoader
var settings: Settings = _ // set by main()
var interpreter: Interpreter = _ // set by createInterpreter()
- def isettings = interpreter.isettings
// XXX
var addedClasspath: List[String] = Nil
@@ -119,15 +117,6 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) {
interpreter.setContextClassLoader()
}
- /** Bind the settings so that evaluated code can modify them */
- def bindSettings() {
- isettings
- // interpreter.beQuietDuring {
- // interpreter.compileString(InterpreterSettings.sourceCodeForClass)
- // interpreter.bind("settings", "scala.tools.nsc.InterpreterSettings", isettings)
- // }
- }
-
/** print a friendly help message */
def printHelp() = {
out println "All commands can be abbreviated - for example :he instead of :help.\n"
@@ -159,7 +148,7 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) {
val current = in.history.get.index
val count = try xs.head.toInt catch { case _: Exception => defaultLines }
- val lines = historyList takeRight count
+ val lines = in.historyList takeRight count
val offset = current - lines.size + 1
for ((line, index) <- lines.zipWithIndex)
@@ -174,9 +163,9 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) {
return println("No history available.")
val current = in.history.get.index
- val offset = current - historyList.size + 1
+ val offset = current - in.historyList.size + 1
- for ((line, index) <- historyList.zipWithIndex ; if line.toLowerCase contains cmdline)
+ for ((line, index) <- in.historyList.zipWithIndex ; if line.toLowerCase contains cmdline)
println("%d %s".format(index + offset, line))
}
@@ -249,12 +238,13 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) {
/* For some reason, the first interpreted command always takes
* a second or two. So, wait until the welcome message
- * has been printed before calling bindSettings. That way,
+ * has been printed before calling isettings. That way,
* the user can read the welcome message while this
* command executes.
*/
val futLine = scala.concurrent.ops.future(readOneLine)
- bindSettings()
+ interpreter.isettings // evaluates lazy val
+
if (!processLine(futLine()))
return
@@ -339,7 +329,7 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) {
powerUserOn = true
out println interpreter.powerUser()
if (in.history.isDefined)
- interpreter.quietBind("history", "scala.collection.immutable.List[String]", historyList)
+ interpreter.quietBind("history", "scala.collection.immutable.List[String]", in.historyList)
interpreter.quietBind("repl", "scala.tools.nsc.Interpreter#ReplVars", interpreter.replVarsObject())
}
@@ -384,21 +374,38 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) {
* read, go ahead and interpret it. Return the full string
* to be recorded for replay, if any.
*/
- def interpretStartingWith(code: String): Option[String] =
- if (code startsWith ".") interpretStartingWith(interpreter.mostRecentVar + code)
- else interpreter.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(" | ") match {
- case null => None // end of file
- case line => interpretStartingWith(code + "\n" + line)
- }
+ def interpretStartingWith(code: String): Option[String] = {
+ def reallyInterpret = {
+ interpreter.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(" | ") match {
+ case null => None // end of file
+ case line => interpretStartingWith(code + "\n" + line)
+ }
+ }
+ }
+
+ /** Here we place ourselves between the user and the interpreter and examine
+ * the input they are ostensibly submitting. It may turn out to be the result
+ * of tab-completion, in which case it might be meaningless to scala but
+ * evaluable by the CompletionAware unit which created it.
+ */
+ if (code == "") None
+ else if (code startsWith ".") interpretStartingWith(interpreter.mostRecentVar + code)
+ else {
+ val result = for (comp <- in.completion ; res <- comp execute code) yield res
+ result match {
+ case Some(res) => injectAndName(res) ; None // completion took responsibility, so do not parse
+ case _ => reallyInterpret
+ }
}
+ }
// runs :load <file> on any files passed via -i
def loadFiles(settings: Settings) = settings match {
@@ -425,7 +432,7 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) {
// the interpeter is passed as an argument to expose tab completion info
if (settings.Xnojline.value || emacsShell) new SimpleReader
else if (settings.noCompletion.value) InteractiveReader.createDefault()
- else InteractiveReader.createDefault(interpreter, this)
+ else InteractiveReader.createDefault(interpreter)
}
loadFiles(settings)
@@ -435,20 +442,26 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) {
printWelcome()
repl()
- } finally {
- closeInterpreter()
- }
+ } finally closeInterpreter()
}
+ private def objName(x: Any) = x.asInstanceOf[AnyRef].getClass.getName
+
// injects one value into the repl; returns pair of name and class
def injectOne(name: String, obj: Any): Tuple2[String, String] = {
- val className = obj.asInstanceOf[AnyRef].getClass.getName
+ val className = objName(obj)
interpreter.quietBind(name, className, obj)
(name, className)
}
+ def injectAndName(obj: Any): Tuple2[String, String] = {
+ val name = interpreter.getVarName
+ val className = objName(obj)
+ interpreter.bind(name, className, obj)
+ (name, className)
+ }
// injects list of values into the repl; returns summary string
- def inject(args: List[Any]): String = {
+ def injectDebug(args: List[Any]): String = {
val strs =
for ((arg, i) <- args.zipWithIndex) yield {
val varName = "p" + (i + 1)
diff --git a/src/compiler/scala/tools/nsc/interpreter/Completion.scala b/src/compiler/scala/tools/nsc/interpreter/Completion.scala
index 3e6698b605..dc544ed657 100644
--- a/src/compiler/scala/tools/nsc/interpreter/Completion.scala
+++ b/src/compiler/scala/tools/nsc/interpreter/Completion.scala
@@ -2,7 +2,6 @@
* Copyright 2005-2010 LAMP/EPFL
* @author Paul Phillips
*/
-// $Id$
//
// TODO, if practical:
@@ -11,345 +10,102 @@
// Possible approach: evaluate buffer as if current identifier is
// 2) Implicits: x.<tab> should show not only x's members but those of anything for which
// there is an implicit conversion from x.
-// 3) Chaining: x.foo(bar).<tab> should complete on foo's result type.
// 4) Imports: after import scala.collection.mutable._, HashMap should be among
// my top level identifiers.
-// 5) Caching: it's silly to parse all the jars on every startup, we should have
-// a peristent store somewhere we can write and only check last-mod dates.
-// 6) Security: Are we using the filesystem unnecessarily?
-//
+// 5) Caching: parsing the jars every startup seems wasteful, but experimentally
+// there is little to no gain from caching.
package scala.tools.nsc
package interpreter
import jline._
import java.net.URL
-import java.lang.reflect
import java.util.{ List => JList }
-import java.util.concurrent.ConcurrentHashMap
-import scala.concurrent.DelayedLazyVal
-import scala.collection.mutable.HashSet
-import scala.util.NameTransformer.{ decode, encode }
-
-// REPL completor - queries supplied interpreter for valid completions
-// based on current contents of buffer.
-class Completion(
- val interpreter: Interpreter,
- val intLoop: InterpreterLoop)
-extends Completor {
- def this(interpreter: Interpreter) = this(interpreter, null)
- import Completion._
- import interpreter.compilerClasspath
-
- // it takes a little while to look through the jars so we use a future and a concurrent map
- class CompletionAgent {
- val dottedPaths = new ConcurrentHashMap[String, List[CompletionInfo]]
- val topLevelPackages = new DelayedLazyVal(
- () => enumToList(dottedPaths.keys) filterNot (_ contains '.'),
- getDottedPaths(dottedPaths, interpreter)
- )
+// REPL completor - queries supplied interpreter for valid
+// completions based on current contents of buffer.
+class Completion(repl: Interpreter) extends Completor {
+ private def asURLs(xs: List[String]) = xs map (x => io.File(x).toURL)
+ private def classPath = (
+ // compiler jars, scala-library.jar etc.
+ (repl.compilerClasspath) :::
+ // boot classpath, java.lang.* etc.
+ (asURLs(repl.settings.bootclasspath.value split ':' toList))
+ )
+
+ // the unqualified vals/defs/etc visible in the repl
+ val ids = new IdentCompletion(repl)
+ // the top level packages we know about
+ val pkgs = new PackageCompletion(classPath)
+
+ // TODO - restore top level availability of scala.* java.lang.* scala.Predef
+ // Old code:
+ //
+ // def membersOfPredef() = membersOfId("scala.Predef")
+ //
+ // def javaLangToHide(s: String) = (
+ // (s endsWith "Exception") ||
+ // (s endsWith "Error") ||
+ // (s endsWith "Impl") ||
+ // (s startsWith "CharacterData") ||
+ // !existsAndPublic("java.lang." + s)
+ // )
+ //
+ // def scalaToHide(s: String) =
+ // (List("Tuple", "Product", "Function") exists (x => (x + """\d+""").r findPrefixMatchOf s isDefined)) ||
+ // (List("Exception", "Error") exists (s endsWith _))
+ //
+ // import reflect.Modifier.{ isPrivate, isProtected, isPublic, isStatic }
+ // private def isSingleton(x: Int, isJava: Boolean) = !isJava || isStatic(x)
+ // private def existsAndPublic(s: String): Boolean =
+ // (dottedPaths containsKey s) || {
+ // val clazz =
+ // try Class.forName(s)
+ // catch { case _: ClassNotFoundException | _: SecurityException => return false }
+ //
+ // isPublic(clazz.getModifiers)
+ // }
+
+ // the high level analysis
+ def analyze(_buffer: String, clist: JList[String]): Int = {
+ val parsed = new Parsed(_buffer)
+ import parsed._
+
+ val candidates = List(ids, pkgs) flatMap (_ completionsFor buffer)
+ candidates foreach (clist add _)
+ position
}
- val agent = new CompletionAgent
- import agent._
-
- import reflect.Modifier.{ isPrivate, isProtected, isPublic, isStatic }
- private def isSingleton(x: Int, isJava: Boolean) = !isJava || isStatic(x)
- private def existsAndPublic(s: String): Boolean =
- (dottedPaths containsKey s) || {
- val clazz =
- try Class.forName(s)
- catch { case _: ClassNotFoundException | _: SecurityException => return false }
-
- isPublic(clazz.getModifiers)
- }
-
- // One instance of a command line
- class Buffer(s: String, verbose: Boolean) {
- val buffer = if (s == null) "" else s
- def isEmptyBuffer = buffer == ""
-
- val segments = buffer.split("\\.", -1).toList
- val lastDot = buffer.lastIndexOf('.')
- val hasDot = segments.size > 0 && segments.last == ""
-
- // given foo.bar.baz, path = foo.bar and stub = baz
- val (path, stub) = segments.size match {
- case 0 => ("", "")
- case 1 => (segments.head, "")
- case _ => (segments.init.mkString("."), segments.last)
- }
-
- def filt(xs: List[String]) = xs filter (_ startsWith stub)
-
- case class Result(candidates: List[String], position: Int) {
- def getCandidates() = (candidates map (_.trim) removeDuplicates) sortWith (_ < _)
- }
-
- // work out completion candidates and position
- def analyzeBuffer(clist: JList[String]): Result = {
- lazy val ids = idsStartingWith(path)
- lazy val pkgs = pkgsStartingWith(path)
- lazy val count = (ids ::: pkgs).size
-
- def doSimple(): Result = count match {
- case 0 => Result(Nil, 0)
- case 1 if pkgs.size > 0 => Result(pkgs, 0)
- case 1 if buffer.length < ids.head.length => Result(ids, 0)
- case 1 => Result(ids, 0)
- // XXX for now commented out "dot inference" because it's overcomplicated
- // val members = membersOfId(ids.head) filter (_ startsWith stub)
- // if (members.isEmpty) Result(Nil, 0)
- // else Result(members, path.length + 1)
- case _ => Result(ids ::: pkgs, 0)
- }
-
- def doDotted(): Result = {
- def pkgs = membersOfPath(path)
- def ids = membersOfId(path)
- def idExtras = List("isInstanceOf", "asInstanceOf", "toString")
- def statics = completeStaticMembers(path)
- def pkgMembers = completePackageMembers(path)
-
- def calcList = if (pkgs.isEmpty) ids ::: idExtras ::: statics else pkgs
- def idList = filt(calcList ::: pkgMembers)
-
- Result(idList.removeDuplicates, path.length + 1)
- }
- segments.size match {
- case 0 => Result(Nil, 0)
- case 1 => doSimple()
- case _ => doDotted()
- }
- }
+ // chasing down results which won't parse
+ def execute(line: String): Option[Any] = {
+ val parsed = new Parsed(line)
+ import parsed._
- def isValidId(s: String) = interpreter.unqualifiedIds contains s
- def membersOfId(s: String) = interpreter membersOfIdentifier s
- def membersOfPath(s: String) = {
- val xs =
- if (dottedPaths containsKey s) dottedPaths get s map (_.visibleName)
- else Nil
+ if (!isQualified)
+ return None
- s match {
- case "scala" => xs filterNot scalaToHide
- case "java.lang" => xs filterNot javaLangToHide
- case _ => xs
- }
- }
- def membersOfPredef() = membersOfId("scala.Predef")
+ for (topLevel <- List(ids, pkgs) ; exec <- topLevel executionFor buffer)
+ return Some(exec)
- def javaLangToHide(s: String) = (
- (s endsWith "Exception") ||
- (s endsWith "Error") ||
- (s endsWith "Impl") ||
- (s startsWith "CharacterData") ||
- !existsAndPublic("java.lang." + s)
- )
-
- def scalaToHide(s: String) =
- (List("Tuple", "Product", "Function") exists (x => (x + """\d+""").r findPrefixMatchOf s isDefined)) ||
- (List("Exception", "Error") exists (s endsWith _))
-
- /** Hide all default members not verbose */
- def defaultMembers =
- if (verbose) (List("scala", "java.lang") flatMap membersOfPath) ::: membersOfPredef
- else Nil
-
- def pkgsStartingWith(s: String) = topLevelPackages() filter (_ startsWith s)
- def idsStartingWith(s: String) = {
- // only print res* when verbose
- val unqIds =
- if (verbose) interpreter.unqualifiedIds
- else interpreter.unqualifiedIds filterNot (_ startsWith INTERPRETER_VAR_PREFIX)
-
- (unqIds ::: defaultMembers) filter (_ startsWith s)
- }
-
- def complete(clist: JList[String]): Int = {
- val res = analyzeBuffer(clist)
- res.getCandidates foreach (x => clist add decode(x))
- res.position
- }
+ None
}
- private def getMembers(c: Class[_], isJava: Boolean): List[String] =
- c.getMethods.toList .
- filter (x => isPublic(x.getModifiers)) .
- filter (x => isSingleton(x.getModifiers, isJava)) .
- map (_.getName) .
- filterNot (shouldHide)
-
- private def getClassObject(path: String): Option[Class[_]] =
- (interpreter getClassObject path) orElse
- (interpreter getClassObject ("scala." + path)) orElse
- (interpreter getClassObject ("java.lang." + path))
-
- def lastHistoryItem = Option(intLoop) map (_.historyList.last)
+ // override if history is available
+ def lastCommand: Option[String] = None
// For recording the buffer on the last tab hit
private var lastTab: (String, String) = (null, null)
// Does this represent two consecutive tabs?
- def isConsecutiveTabs(buf: String) = (buf, lastHistoryItem orNull) == lastTab
+ def isConsecutiveTabs(buf: String) = (buf, lastCommand orNull) == lastTab
- // jline's completion comes through here - we ask a Buffer for the candidates.
+ // This is jline's entry point for completion.
override def complete(_buffer: String, cursor: Int, candidates: JList[String]): Int = {
// println("_buffer = %s, cursor = %d".format(_buffer, cursor))
val verbose = isConsecutiveTabs(_buffer)
- lastTab = (_buffer, lastHistoryItem orNull)
-
- new Buffer(_buffer, verbose) complete candidates
- }
-
- def completePackageMembers(path: String): List[String] =
- getClassObject(path + "." + "package") map (getMembers(_, false)) getOrElse Nil
-
- def completeStaticMembers(path: String): List[String] = {
- // java style, static methods
- val js = getClassObject(path) map (getMembers(_, true)) getOrElse Nil
- // scala style, methods on companion object
- // if getClassObject fails, see if there is a type alias
- val clazz = getClassObject(path + "$") orElse {
- (ByteCode aliasForType path) flatMap (x => getClassObject(x + "$"))
- }
- val ss = clazz map (getMembers(_, false)) getOrElse Nil
-
- js ::: ss
- }
-}
-
-object Completion
-{
- import java.io.File
- import java.util.jar.{ JarEntry, JarFile }
- import scala.tools.nsc.io.Streamable
-
- val EXPAND_SEPARATOR_STRING = "$$"
- val ANON_CLASS_NAME = "$anon"
- val TRAIT_SETTER_SEPARATOR_STRING = "$_setter_$"
- val IMPL_CLASS_SUFFIX ="$class"
- val INTERPRETER_VAR_PREFIX = "res"
-
- /** Interface for objects which supply their own completion contents.
- */
- trait Special {
- def tabCompletions(): List[String]
- }
-
- case class CompletionInfo(visibleName: String, className: String, jar: String) {
- lazy val jarfile = new JarFile(jar)
- lazy val entry = jarfile getEntry className
-
- override def hashCode = visibleName.hashCode
- override def equals(other: Any) = other match {
- case x: CompletionInfo => visibleName == x.visibleName
- case _ => false
- }
-
- def getBytes(): Array[Byte] = {
- if (entry == null) Array() else {
- val x = new Streamable.Bytes { def inputStream() = jarfile getInputStream entry }
- x.toByteArray()
- }
- }
- }
-
- def enumToList[T](e: java.util.Enumeration[T]): List[T] = enumToList(e, Nil)
- def enumToList[T](e: java.util.Enumeration[T], xs: List[T]): List[T] =
- if (e == null || !e.hasMoreElements) xs else enumToList(e, e.nextElement :: xs)
-
- // methods to leave out of completion
- val excludeMethods = List("", "hashCode", "equals", "wait", "notify", "notifyAll")
-
- private def exists(path: String) = new File(path) exists
-
- def shouldHide(x: String) =
- (excludeMethods contains x) ||
- (x contains ANON_CLASS_NAME) ||
- (x contains TRAIT_SETTER_SEPARATOR_STRING) ||
- (x endsWith IMPL_CLASS_SUFFIX)
-
- def getClassFiles(path: String): List[String] = {
- if (!exists(path)) return Nil
-
- (enumToList(new JarFile(path).entries) map (_.getName)) .
- partialMap { case x: String if x endsWith ".class" => x dropRight 6 } .
- filterNot { shouldHide }
- }
-
- // all the dotted path to classfiles we can find by poking through the jars
- def getDottedPaths(
- map: ConcurrentHashMap[String, List[CompletionInfo]],
- interpreter: Interpreter): Unit =
- {
- val cp =
- interpreter.compilerClasspath.map(_.getPath) ::: // compiler jars, scala-library.jar etc.
- interpreter.settings.bootclasspath.value.split(':').toList // boot classpath, java.lang.* etc.
-
- val jars = cp.removeDuplicates filter (_ endsWith ".jar")
-
- // for e.g. foo.bar.baz.C, returns (foo -> bar), (foo.bar -> baz), (foo.bar.baz -> C)
- // and scala.Range$BigInt needs to go scala -> Range -> BigInt
- def subpaths(s: String): List[(String, String)] = {
- val segs = decode(s).split("""[/.]""")
- val components = segs dropRight 1
-
- (1 to components.length).toList flatMap { i =>
- val k = components take i mkString "."
- if (segs(i) contains "$") {
- val dollarsegs = segs(i).split("$").toList
- for (j <- 1 to (dollarsegs.length - 1) toList) yield {
- val newk = k + "." + (dollarsegs take j mkString ".")
- (k -> dollarsegs(j))
- }
- }
- else List(k -> segs(i))
- }
- }
-
- def oneJar(jar: String): Unit = {
- val classfiles = Completion getClassFiles jar
-
- for (cl <- classfiles.removeDuplicates ; (k, _v) <- subpaths(cl)) {
- val v = CompletionInfo(_v, cl, jar)
-
- if (map containsKey k) {
- val vs = map.get(k)
- if (vs contains v) ()
- else map.put(k, v :: vs)
- }
- else map.put(k, List(v))
- }
- }
-
- jars foreach oneJar
- }
-
- /** The methods below this point exist to simplify repl-generated code.
- */
-
- // XXX at the moment this is imperfect because scala's protected semantics
- // differ from java's, so protected methods appear public via reflection;
- // yet scala enforces the protection. The result is that protected members
- // appear in completion yet cannot actually be called. Fixing this
- // properly requires a scala.reflect.* API. Fixing it uglily is possible
- // too (cast to structural type!) but I deem poor use of energy.
- private def skipModifiers(m: reflect.Method) = {
- import java.lang.reflect.Modifier._
- val flags = STATIC | PRIVATE | PROTECTED
- (m.getModifiers & flags) == 0
- }
- private def getAnyClass(x: Any): Class[_] = x.asInstanceOf[AnyRef].getClass
-
- def methodsOf(target: Any): List[String] =
- getAnyClass(target).getMethods filter skipModifiers map (_.getName) toList
-
- // getAnyClass(target).getInterfaces exists (_ == specialClazz)
- // private val specialClazz = classOf[Special]
+ lastTab = (_buffer, lastCommand orNull)
- def selfDefinedMembers(target: Any) = target match {
- case x: Special => Some(x.tabCompletions())
- case _ => None
+ // modify the buffer in place and returns the cursor position
+ analyze(_buffer, candidates)
}
}
diff --git a/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala b/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala
new file mode 100644
index 0000000000..c60f402d3d
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala
@@ -0,0 +1,84 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2010 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools.nsc
+package interpreter
+
+import scala.util.NameTransformer
+
+/** An interface for objects which are aware of tab completion and
+ * will supply their own candidates and resolve their own paths.
+ */
+trait CompletionAware {
+ /** The complete list of unqualified Strings to which this
+ * object will complete.
+ */
+ def completions(): List[String]
+ def completions(start: String): List[String] = completions filter (_ startsWith start)
+
+ /** Default filter to apply to completions.
+ */
+ def filterNotFunction(s: String): Boolean = ReflectionCompletion shouldHide s
+
+ /** Default sort.
+ */
+ def sortFunction(s1: String, s2: String): Boolean = s1 < s2
+
+ /** Default map.
+ */
+ def mapFunction(s: String): String = s
+
+ /** The next completor in the chain.
+ */
+ def follow(id: String): Option[CompletionAware] = None
+
+ /** What to return if this completion is given as a command. It
+ * returns None by default, which means to allow the repl to interpret
+ * the line normally. Returning Some(_) means the line will never
+ * reach the scala interpreter.
+ */
+ def execute(id: String): Option[Any] = None
+
+ /** Given string 'buf', return a list of all the strings
+ * to which it can complete. This may involve delegating
+ * to other CompletionAware objects.
+ */
+ def completionsFor(buf: String): List[String] = {
+ val parsed = new Parsed(buf)
+ import parsed._
+
+ (
+ if (isEmpty) completions()
+ else if (isFirstCharDot) Nil // XXX for now
+ else if (isUnqualified && !isLastCharDot) completions(buf)
+ else follow(hd) match {
+ case Some(next) => next completionsFor remainder
+ case _ => Nil
+ }
+ ) filterNot filterNotFunction map mapFunction sortWith (sortFunction _)
+ }
+
+ def executionFor(buf: String): Option[Any] = {
+ val parsed = new Parsed(buf)
+ import parsed._
+
+ if (isUnqualified && !isLastCharDot && (completions contains buf)) execute(buf)
+ else if (!isQualified) None
+ else follow(hd) match {
+ case Some(next) => next executionFor remainder
+ case _ => None
+ }
+ }
+}
+
+object CompletionAware {
+ def unapply(that: Any): Option[CompletionAware] = that match {
+ case x: CompletionAware => Some((x))
+ case _ => None
+ }
+
+ def apply(xs: List[String]) = new CompletionAware { val completions = xs }
+}
+
diff --git a/src/compiler/scala/tools/nsc/interpreter/History.scala b/src/compiler/scala/tools/nsc/interpreter/History.scala
index 4f726ebc55..519d17f9d2 100644
--- a/src/compiler/scala/tools/nsc/interpreter/History.scala
+++ b/src/compiler/scala/tools/nsc/interpreter/History.scala
@@ -6,12 +6,31 @@
package scala.tools.nsc
package interpreter
+import java.io.File
+import jline.{ ConsoleReader, History => JHistory }
import scala.collection.JavaConversions.asBuffer
/** Primarily, a wrapper for JLine's History.
*/
-class History(jhistory: jline.History) {
+class History(val jhistory: JHistory) {
def asJavaList = jhistory.getHistoryList
def asList: List[String] = asBuffer(asJavaList).toList
def index = jhistory.getCurrentIndex
-} \ No newline at end of file
+
+ def grep(s: String) = asList filter (_ contains s)
+}
+
+object History {
+ val ScalaHistoryFile = ".scala_history"
+ def homeDir = System.getProperty("user.home")
+
+ def apply(reader: ConsoleReader): History =
+ if (reader == null) apply()
+ else new History(reader.getHistory)
+
+ def apply(): History = new History(
+ try new JHistory(new File(homeDir, ScalaHistoryFile))
+ // do not store history if error
+ catch { case _: Exception => new JHistory() }
+ )
+}
diff --git a/src/compiler/scala/tools/nsc/interpreter/IdentCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/IdentCompletion.scala
new file mode 100644
index 0000000000..5759abdb97
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/interpreter/IdentCompletion.scala
@@ -0,0 +1,19 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2010 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools.nsc
+package interpreter
+
+/** Top level identifiers visible in the repl. It immediately
+ * delegates to an InstanceCompletion.
+ */
+class IdentCompletion(repl: Interpreter) extends CompletionAware {
+ def completions() = repl.unqualifiedIds
+ override def follow(id: String) =
+ if (completions contains id)
+ repl completionAware id orElse Some(new InstanceCompletion(repl clazzForIdent id))
+ else
+ None
+}
diff --git a/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala b/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala
index 7f89669806..932450b01a 100644
--- a/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala
+++ b/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala
@@ -25,6 +25,10 @@ trait InteractiveReader {
// overide if history is available
def history: Option[History] = None
+ def historyList = history map (_.asList) getOrElse Nil
+
+ // override if completion is available
+ def completion: Option[Completion] = None
// hack necessary for OSX jvm suspension because read calls are not restarted after SIGTSTP
private def restartSystemCall(e: Exception): Boolean =
@@ -41,9 +45,12 @@ object InteractiveReader {
/** Create an interactive reader. Uses <code>JLineReader</code> if the
* library is available, but otherwise uses a <code>SimpleReader</code>.
*/
- def createDefault(interpreter: Interpreter, intLoop: InterpreterLoop = null): InteractiveReader =
- catching(exes: _*)
- . opt (new JLineReader(interpreter, intLoop))
- . getOrElse (new SimpleReader)
+ def createDefault(interpreter: Interpreter): InteractiveReader =
+ try new JLineReader(interpreter)
+ catch {
+ case e @ (_: Exception | _: NoClassDefFoundError) =>
+ // println("Failed to create JLineReader(%s): %s".format(interpreter, e))
+ new SimpleReader
+ }
}
diff --git a/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala b/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala
index c0fe874a37..d9ce962ca3 100644
--- a/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala
+++ b/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala
@@ -11,28 +11,27 @@ import java.io.File
import jline.{ ConsoleReader, ArgumentCompletor, History => JHistory }
/** Reads from the console using JLine */
-class JLineReader(interpreter: Interpreter, intLoop: InterpreterLoop) extends InteractiveReader {
- def this() = this(null, null)
- def this(interpreter: Interpreter) = this(interpreter, null)
- override def history = Some(new History(consoleReader.getHistory))
+class JLineReader(interpreter: Interpreter) extends InteractiveReader {
+ def this() = this(null)
- val consoleReader = {
- val history =
- try new JHistory(new File(System.getProperty("user.home"), ".scala_history"))
- // do not store history if error
- catch { case _: Exception => new JHistory() }
+ override lazy val history = Some(History(consoleReader))
+ override lazy val completion =
+ if (interpreter == null) None
+ else Some(new Completion(interpreter))
+ val consoleReader = {
val r = new jline.ConsoleReader()
- r setHistory history
+ r setHistory (History().jhistory)
r setBellEnabled false
if (interpreter != null) {
// have to specify all delimiters for completion to work nicely
val delims = new ArgumentCompletor.AbstractArgumentDelimiter {
- val delimChars = "(){}[],`;'\" \t".toArray
+ // val delimChars = "(){}[],`;'\" \t".toArray
+ val delimChars = "(){},`; \t".toArray
def isDelimiterChar(s: String, pos: Int) = delimChars contains s.charAt(pos)
}
- val comp = new ArgumentCompletor(new Completion(interpreter, intLoop), delims)
+ val comp = new ArgumentCompletor(completion.get, delims)
comp setStrict false
r addCompletor comp
// XXX make this use a setting
diff --git a/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala
new file mode 100644
index 0000000000..f1c6717b2b
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/interpreter/PackageCompletion.scala
@@ -0,0 +1,129 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2010 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools.nsc
+package interpreter
+
+import java.net.URL
+import java.lang.reflect
+import java.util.concurrent.ConcurrentHashMap
+import scala.concurrent.DelayedLazyVal
+import scala.util.NameTransformer.{ decode, encode }
+import PackageCompletion._
+
+/** Completion among all known packages. It examines the jars in a
+ * separate thread so as not to slow down startup. If it arrives at
+ * an object, it delegates to StaticCompletion for that object.
+ */
+class PackageCompletion(classpath: List[URL]) extends CompletionAware {
+ // it takes a little while to look through the jars so we use a future and a concurrent map
+ class CompletionAgent {
+ val dottedPaths: ConcurrentHashMap[String, List[CompletionInfo]] = new ConcurrentHashMap[String, List[CompletionInfo]]
+ val topLevelPackages = new DelayedLazyVal(
+ () => enumToList(dottedPaths.keys) filterNot (_ contains '.'),
+ getDottedPaths(dottedPaths, classpath)
+ )
+ }
+ val agent = new CompletionAgent
+ import agent._
+
+ def completions() = topLevelPackages()
+ override def follow(id: String) =
+ if (dottedPaths containsKey id) Some(new SubCompletor(id))
+ else None
+
+ class SubCompletor(root: String) extends CompletionAware {
+ private def infos = dottedPaths get root
+ def completions() = infos map (_.visibleName)
+
+ override def follow(segment: String): Option[CompletionAware] = {
+ PackageCompletion.this.follow(root + "." + segment) orElse {
+ for (CompletionInfo(`segment`, className, _) <- infos) {
+ return Some(new StaticCompletion(className))
+ }
+ None
+ }
+ }
+ }
+}
+
+object PackageCompletion {
+ import java.io.File
+ import java.util.jar.{ JarEntry, JarFile }
+ import scala.tools.nsc.io.Streamable
+
+ def enumToList[T](e: java.util.Enumeration[T]): List[T] = enumToListInternal(e, Nil)
+ private def enumToListInternal[T](e: java.util.Enumeration[T], xs: List[T]): List[T] =
+ if (e == null || !e.hasMoreElements) xs else enumToListInternal(e, e.nextElement :: xs)
+
+ def getClassFiles(path: String): List[String] = {
+ def exists(path: String) = { new File(path) exists }
+ if (!exists(path)) return Nil
+
+ (enumToList(new JarFile(path).entries) map (_.getName)) .
+ partialMap { case x: String if x endsWith ".class" => x dropRight 6 } .
+ filterNot { ReflectionCompletion.shouldHide }
+ }
+
+ case class CompletionInfo(visibleName: String, className: String, jar: String) {
+ lazy val jarfile = new JarFile(jar)
+ lazy val entry = jarfile getEntry className
+
+ override def hashCode = visibleName.hashCode
+ override def equals(other: Any) = other match {
+ case x: CompletionInfo => visibleName == x.visibleName
+ case _ => false
+ }
+
+ def getBytes(): Array[Byte] = {
+ if (entry == null) Array() else {
+ val x = new Streamable.Bytes { def inputStream() = jarfile getInputStream entry }
+ x.toByteArray()
+ }
+ }
+ }
+
+ // all the dotted path to classfiles we can find by poking through the jars
+ def getDottedPaths(map: ConcurrentHashMap[String, List[CompletionInfo]], classpath: List[URL]): Unit = {
+ val cp = classpath map (_.getPath)
+ val jars = cp.removeDuplicates filter (_ endsWith ".jar")
+
+ // for e.g. foo.bar.baz.C, returns (foo -> bar), (foo.bar -> baz), (foo.bar.baz -> C)
+ // and scala.Range$BigInt needs to go scala -> Range -> BigInt
+ def subpaths(s: String): List[(String, String)] = {
+ val segs = decode(s).split("""[/.]""")
+ val components = segs dropRight 1
+
+ (1 to components.length).toList flatMap { i =>
+ val k = components take i mkString "."
+ if (segs(i) contains "$") {
+ val dollarsegs = segs(i).split("$").toList
+ for (j <- 1 to (dollarsegs.length - 1) toList) yield {
+ val newk = k + "." + (dollarsegs take j mkString ".")
+ (k -> dollarsegs(j))
+ }
+ }
+ else List(k -> segs(i))
+ }
+ }
+
+ def oneJar(jar: String): Unit = {
+ val classfiles = getClassFiles(jar)
+
+ for (cl <- classfiles.removeDuplicates ; (k, _v) <- subpaths(cl)) {
+ val v = CompletionInfo(_v, cl, jar)
+
+ if (map containsKey k) {
+ val vs = map.get(k)
+ if (vs contains v) ()
+ else map.put(k, v :: vs)
+ }
+ else map.put(k, List(v))
+ }
+ }
+
+ jars foreach oneJar
+ }
+} \ No newline at end of file
diff --git a/src/compiler/scala/tools/nsc/interpreter/Parsed.scala b/src/compiler/scala/tools/nsc/interpreter/Parsed.scala
new file mode 100644
index 0000000000..0c0104b8fa
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/interpreter/Parsed.scala
@@ -0,0 +1,30 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2010 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools.nsc
+package interpreter
+
+/** One instance of a command buffer.
+ */
+class Parsed(_buf: String) {
+ val buffer = if (_buf == null) "" else _buf
+ val segments = (buffer split '.').toList filterNot (_ == "")
+ lazy val hd :: tl = segments
+ def stub = firstDot + hd + "."
+ def remainder = buffer stripPrefix stub
+
+ def isEmpty = segments.size == 0
+ def isUnqualified = segments.size == 1
+ def isQualified = segments.size > 1
+
+ def isFirstCharDot = buffer startsWith "."
+ def isLastCharDot = buffer endsWith "."
+ def firstDot = if (isFirstCharDot) "." else ""
+ def lastDot = if (isLastCharDot) "." else ""
+
+ // sneakily, that is 0 when there is no dot, which is what we want
+ def position = (buffer lastIndexOf '.') + 1
+}
+
diff --git a/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala
new file mode 100644
index 0000000000..e65635dc1d
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/interpreter/ReflectionCompletion.scala
@@ -0,0 +1,139 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2010 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools.nsc
+package interpreter
+
+import java.lang.reflect
+import reflect.Modifier.{ isPrivate, isProtected, isPublic, isStatic }
+import scala.util.NameTransformer
+import scala.collection.mutable.HashMap
+import ReflectionCompletion._
+
+/** A completion aware object representing a single instance of some class.
+ * 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(clazz: Class[_]) extends CompletionAware {
+ def methods = clazz.getMethods.toList filterNot (x => isStatic(x.getModifiers))
+ def fields = clazz.getFields.toList filterNot (x => isStatic(x.getModifiers))
+ val (zeroArg, otherArg) = methods partition (_.getParameterTypes.size == 0)
+
+ lazy val completions = (methods ::: fields) map (_.getName)
+ override def mapFunction(s: String) = NameTransformer decode s
+
+ // TODO
+ // def idExtras = List("isInstanceOf", "asInstanceOf", "toString")
+
+ override def follow(id: String) = {
+ val nextClazz = zeroArg find (m => m.getName == id) map (_.getReturnType)
+ if (nextClazz.isDefined) nextClazz map (x => new InstanceCompletion(x))
+ else fields find (_.getName == id) map (x => new InstanceCompletion(x.getType))
+ }
+}
+
+/** The complementary class to InstanceCompletion. It has logic to deal with
+ * java static members and scala companion object members.
+ */
+class StaticCompletion(jarEntryName: String) extends CompletionAware {
+ def className = jarEntryName.replace('/', '.')
+ def isScalaClazz(cl: Class[_]) = allInterfaces(cl) exists (_.getName == "scala.ScalaObject")
+ def isJava = !isScalaClazz(clazz)
+
+ lazy val clazz = {
+ val cl = Class.forName(className)
+ if (className.last != '$' && isScalaClazz(cl)) {
+ try Class.forName(className + "$")
+ catch { case _: Exception => cl }
+ }
+ else cl
+ }
+
+ def methodFilter: reflect.Method => Boolean =
+ if (isJava) m => isStatic(m.getModifiers) && isPublic(m.getModifiers)
+ else m => isPublic(m.getModifiers)
+
+ def methods = clazz.getMethods.toList filter methodFilter
+ def fields = clazz.getFields.toList
+
+ lazy val completions = (methods ::: fields) map (_.getName)
+ override def mapFunction(s: String) = NameTransformer decode s
+
+ // TODO - old version.
+ //
+ // private def getClassObject(path: String): Option[Class[_]] = {
+ // val cl = clazz.getClassLoader()
+ // try Some(Class.forName(path, true, cl).asInstanceOf[Class[_]])
+ // catch { case _ => None }
+ // }
+ //
+ // def completeStaticMembers(path: String): List[String] = {
+ // // java style, static methods
+ // val js = getClassObject(path) map (getMembers(_, true)) getOrElse Nil
+ // // scala style, methods on companion object
+ // // if getClassObject fails, see if there is a type alias
+ // val clazz = getClassObject(path + "$") orElse {
+ // (ByteCode aliasForType path) flatMap (x => getClassObject(x + "$"))
+ // }
+ // val ss = clazz map (getMembers(_, false)) getOrElse Nil
+ //
+ // js ::: ss
+ // }
+}
+
+// TODO
+class PackageObjectCompletion(packageName: String) extends CompletionAware {
+ def completions() = error("TODO")
+
+ // def completePackageMembers(path: String): List[String] =
+ // getClassObject(path + "." + "package") map (getMembers(_, false)) getOrElse Nil
+}
+
+class ReflectionCompletion { }
+object ReflectionCompletion {
+ import java.io.File
+ import java.util.jar.{ JarEntry, JarFile }
+ import scala.tools.nsc.io.Streamable
+
+ val EXPAND_SEPARATOR_STRING = "$$"
+ val ANON_CLASS_NAME = "$anon"
+ val TRAIT_SETTER_SEPARATOR_STRING = "$_setter_$"
+ val IMPL_CLASS_SUFFIX ="$class"
+ val INTERPRETER_VAR_PREFIX = "res"
+
+ def allInterfaces(clazz: Class[_]): List[Class[_]] = allInterfaces(clazz, Nil)
+ def allInterfaces(clazz: Class[_], acc: List[Class[_]]): List[Class[_]] = {
+ if (clazz == null) acc.removeDuplicates
+ else allInterfaces(clazz.getSuperclass, acc ::: clazz.getInterfaces.toList)
+ }
+
+ // methods to leave out of completion
+ val excludeMethods = List("", "hashCode", "equals", "wait", "notify", "notifyAll")
+
+ def shouldHide(x: String) =
+ (excludeMethods contains x) ||
+ (x contains EXPAND_SEPARATOR_STRING) || // XXX
+ (x contains ANON_CLASS_NAME) ||
+ (x contains TRAIT_SETTER_SEPARATOR_STRING) ||
+ (x endsWith IMPL_CLASS_SUFFIX) ||
+ (x == "MODULE$") ||
+ (x matches """.*\$\d+$""")
+
+ // XXX at the moment this is imperfect because scala's protected semantics
+ // differ from java's, so protected methods appear public via reflection;
+ // yet scala enforces the protection. The result is that protected members
+ // appear in completion yet cannot actually be called. Fixing this
+ // properly requires a scala.reflect.* API. Fixing it uglily is possible
+ // too (cast to structural type!) but I deem poor use of energy.
+ private def skipModifiers(m: reflect.Method) = {
+ import java.lang.reflect.Modifier._
+ val flags = STATIC | PRIVATE | PROTECTED
+ (m.getModifiers & flags) == 0
+ }
+ private def getAnyClass(x: Any): Class[_] = 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/interpreter/XMLCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/XMLCompletion.scala
new file mode 100644
index 0000000000..18bc136ce7
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/interpreter/XMLCompletion.scala
@@ -0,0 +1,43 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2010 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools.nsc
+package interpreter
+
+import xml.{ XML, Group, Node, NodeSeq }
+import XMLCompletion._
+import scala.collection.mutable.HashMap
+
+class XMLCompletion(root: Node) extends CompletionAware {
+ private val nodeCache = new HashMap[String, Node]
+ private def getNode(s: String): Option[Node] = {
+ completions // make sure cache is populated
+ nodeCache get s
+ }
+
+ lazy val completions: List[String] = {
+ def children = root.child.toList
+ def uniqueTags = children groupBy (_.label) filter (_._2.size == 1) map (_._1)
+ val uniqs = uniqueTags.toList
+
+ children.foldLeft(List[String]())((res, node) => {
+ val name = node.label
+ def count = res filter (_ startsWith (name + "[")) size // ]
+ val suffix = if (uniqs contains name) "" else "[%d]" format (count + 1)
+ val s = name + suffix
+
+ nodeCache(s) = node
+
+ s :: res
+ }).sortWith (_ < _)
+ }
+
+ override def execute(id: String) = getNode(id)
+ override def follow(id: String) = getNode(id) map (x => new XMLCompletion(x))
+}
+
+object XMLCompletion {
+ def apply(x: Node) = new XMLCompletion(x)
+}
diff --git a/src/library/scala/collection/Iterator.scala b/src/library/scala/collection/Iterator.scala
index 32525d59a6..7dbc39f5b7 100644
--- a/src/library/scala/collection/Iterator.scala
+++ b/src/library/scala/collection/Iterator.scala
@@ -1121,6 +1121,17 @@ trait Iterator[+A] { self =>
res.toList
}
+ /** Traverses this iterator and returns all produced values in a set.
+ * $willNotTerminateInf
+ *
+ * @return a set which contains all values produced by this iterator.
+ */
+ def toSet[B >: A]: immutable.Set[B] = {
+ val res = new ListBuffer[B]
+ while (hasNext) res += next
+ res.toSet
+ }
+
/** Lazily wraps a Stream around this iterator so its values are memoized.
*
* @return a Stream which can repeatedly produce all the values
@@ -1140,6 +1151,20 @@ trait Iterator[+A] { self =>
buffer
}
+ /** Traverses this iterator and returns all produced values in a map.
+ * $willNotTerminateInf
+ * @see TraversableLike.toMap
+ *
+ * @return a map containing all elements of this iterator.
+ */
+ def toMap[T, U](implicit ev: A <:< (T, U)): immutable.Map[T, U] = {
+ val b = immutable.Map.newBuilder[T, U]
+ while (hasNext)
+ b += next
+
+ b.result
+ }
+
/** Tests if another iterator produces the same valeus as this one.
* $willNotTerminateInf
* @param that the other iterator
diff --git a/src/library/scala/xml/NodeSeq.scala b/src/library/scala/xml/NodeSeq.scala
index 17ea9228f6..02c1b6ece8 100644
--- a/src/library/scala/xml/NodeSeq.scala
+++ b/src/library/scala/xml/NodeSeq.scala
@@ -80,8 +80,8 @@ abstract class NodeSeq extends immutable.Seq[Node] with SeqLike[Node, NodeSeq] {
* @return ...
*/
def \(that: String): NodeSeq = {
+ def fail = throw new IllegalArgumentException(that)
def atResult = {
- def fail = throw new IllegalArgumentException(that)
lazy val y = this(0)
val attr =
if (that.length == 1) fail
@@ -104,6 +104,7 @@ abstract class NodeSeq extends immutable.Seq[Node] with SeqLike[Node, NodeSeq] {
NodeSeq fromSeq (this flatMap (_.child) filter cond)
that match {
+ case "" => fail
case "_" => makeSeq(!_.isAtom)
case _ if (that(0) == '@' && this.length == 1) => atResult
case _ => makeSeq(_.label == that)