diff options
author | Paul Phillips <paulp@improving.org> | 2010-01-24 00:31:38 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2010-01-24 00:31:38 +0000 |
commit | 895c271ead5bfe42a19610871037bfb5c90503e9 (patch) | |
tree | d869fc1bb5dc87e701317cc9b14ce7ba5b91fbe7 | |
parent | a0c0f0949797a563c9583d0f82c0f390a999ec7d (diff) | |
download | scala-895c271ead5bfe42a19610871037bfb5c90503e9.tar.gz scala-895c271ead5bfe42a19610871037bfb5c90503e9.tar.bz2 scala-895c271ead5bfe42a19610871037bfb5c90503e9.zip |
Some minor polishing to the previous repl compl...
Some minor polishing to the previous repl completion patch, plus a few
new features and improvements.
-rw-r--r-- | src/compiler/scala/tools/nsc/Interpreter.scala | 152 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/InterpreterLoop.scala | 2 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala | 19 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/interpreter/IdentCompletion.scala | 10 | ||||
-rw-r--r-- | test/files/jvm/interpreter.check | 2 |
5 files changed, 133 insertions, 52 deletions
diff --git a/src/compiler/scala/tools/nsc/Interpreter.scala b/src/compiler/scala/tools/nsc/Interpreter.scala index 01ed5087ae..94aed81b61 100644 --- a/src/compiler/scala/tools/nsc/Interpreter.scala +++ b/src/compiler/scala/tools/nsc/Interpreter.scala @@ -194,8 +194,10 @@ class Interpreter(val settings: Settings, out: PrintWriter) { /** the previous requests this interpreter has processed */ private val prevRequests = new ArrayBuffer[Request]() - private val prevNameMap = new HashMap[Name, Request]() + private val usedNameMap = new HashMap[Name, Request]() private val boundNameMap = new HashMap[Name, Request]() + private def allHandlers = prevRequests.toList flatMap (_.handlers) + private def mostRecentHandler = prevRequests.last.handlers.last def recordRequest(req: Request) { def tripart[T](set1: Set[T], set2: Set[T]) = { @@ -204,17 +206,22 @@ class Interpreter(val settings: Settings, out: PrintWriter) { } prevRequests += req - req.usedNames foreach (x => prevNameMap(x) = req) + req.usedNames foreach (x => usedNameMap(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): _* + // tripart(usedNameMap.keysIterator.toSet, boundNameMap.keysIterator.toSet): _* // )) } - private def mostRecentHandler = prevRequests.last.handlers.last - def allUsedNames = prevNameMap.keysIterator.toList - def allBoundNames = prevRequests.toList.flatMap(_.boundNames).removeDuplicates + private def keyList[T](x: collection.Map[T, _]): List[T] = x.keysIterator.toList sortBy (_.toString) + def allUsedNames = keyList(usedNameMap) + def allBoundNames = keyList(boundNameMap) + def allSeenTypes = prevRequests.toList flatMap (_.typeOf.valuesIterator.toList) removeDuplicates + 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) { @@ -248,7 +255,10 @@ 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 + private def isSynthVarName(name: Name): Boolean = synthVarNameCreator didGenerate name.toString + def getVarName = varNameCreator() + def getSynthVarName = synthVarNameCreator() /** generate a string using a routine that wants to write on a stream */ private def stringFrom(writer: PrintWriter => Unit): String = { @@ -346,16 +356,15 @@ class Interpreter(val settings: Settings, out: PrintWriter) { } } - val rhpairs = for { - req <- prevRequests.toList.reverse - handler <- req.handlers - } yield ReqAndHandler(req, handler) - + /** Flatten the handlers out and pair each with the original request */ + val rhpairs = prevRequests.reverse.toList flatMap { req => + req.handlers map (ReqAndHandler(req, _)) + } select(rhpairs, wanted).reverse } val code, trailingBraces, accessPath = new StringBuffer - val currentImps = mutable.Set.empty[Name] + val currentImps = HashSet[Name]() // add code for a new object to hold some imports def addWrapper() { @@ -462,7 +471,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) { case member => new GenericHandler(member) } - private def requestFromLine(line: String): Either[IR.Result, Request] = { + private def requestFromLine(line: String, synthetic: Boolean): Either[IR.Result, Request] = { // initialize the compiler if (prevRequests.isEmpty) new compiler.Run() @@ -473,12 +482,15 @@ class Interpreter(val settings: Settings, out: PrintWriter) { 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. - return requestFromLine("val %s =\n%s".format(varNameCreator(), line)) + return requestFromLine("val %s =\n%s".format(varName, line), synthetic) case _ => } @@ -500,8 +512,9 @@ class Interpreter(val settings: Settings, out: PrintWriter) { * @param line ... * @return ... */ - def interpret(line: String): IR.Result = { - val req = requestFromLine(line) match { + def interpret(line: String): IR.Result = interpret(line, false) + def interpret(line: String, synthetic: Boolean): IR.Result = { + val req = requestFromLine(line, synthetic) match { case Left(result) => return result case Right(req) => req } @@ -515,7 +528,9 @@ class Interpreter(val settings: Settings, out: PrintWriter) { out print clean(result) if (succeeded) { - recordRequest(req) // book-keeping + if (!synthetic) + recordRequest(req) // book-keeping + IR.Success } else IR.Error @@ -594,8 +609,6 @@ class Interpreter(val settings: Settings, out: PrintWriter) { ivt.importVars.toList } def boundNames: List[Name] = Nil - def valAndVarNames: List[Name] = Nil - def defNames: List[Name] = Nil val definesImplicit = cond(member) { case tree: MemberDef => tree.mods hasFlag Flags.IMPLICIT } @@ -615,7 +628,6 @@ class Interpreter(val settings: Settings, out: PrintWriter) { lazy val isLazy = mods hasFlag Flags.LAZY override lazy val boundNames = List(vname) - override def valAndVarNames = boundNames override def generatesValue = Some(vname) override def resultExtractionCode(req: Request, code: PrintWriter) { @@ -643,7 +655,6 @@ class Interpreter(val settings: Settings, out: PrintWriter) { private class DefHandler(defDef: DefDef) extends MemberHandler(defDef) { lazy val DefDef(mods, name, _, _, _, _) = defDef override lazy val boundNames = List(name) - override def defNames = boundNames override def resultExtractionCode(req: Request, code: PrintWriter) = if (mods.isPublic) code print codegenln(name, ": ", req.typeOf(name)) @@ -652,7 +663,6 @@ class Interpreter(val settings: Settings, out: PrintWriter) { private class AssignHandler(member: Assign) extends MemberHandler(member) { val lhs = member.lhs.asInstanceOf[Ident] // an unfortunate limitation val helperName = newTermName(synthVarNameCreator()) - override val valAndVarNames = List(helperName) override def generatesValue = Some(helperName) override def extraCodeToEvaluate(req: Request, code: PrintWriter) = @@ -726,7 +736,14 @@ class Interpreter(val settings: Settings, out: PrintWriter) { val boundNames = handlers flatMap (_.boundNames) /** list of names used by this expression */ - val usedNames: List[Name] = handlers.flatMap(_.usedNames) + val usedNames: List[Name] = handlers flatMap (_.usedNames) + + /** def and val names */ + def defNames = partialFlatMap(handlers) { case x: DefHandler => x.boundNames } + def valAndVarNames = partialFlatMap(handlers) { + case x: AssignHandler => List(x.helperName) + case x: ValHandler => boundNames + } /** Code to import bound names from previous lines - accessPath is code to * append to objectName to access anything bound by request. @@ -836,8 +853,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) { !reporter.hasErrors } - def valAndVarNames = handlers flatMap { _.valAndVarNames } - def defNames = handlers flatMap { _.defNames } + def atNextPhase[T](op: => T): T = compiler.atPhase(objRun.typerPhase.next)(op) /** The outermost wrapper object */ @@ -899,13 +915,17 @@ class Interpreter(val settings: Settings, out: PrintWriter) { /** These methods are exposed so REPL commands can access them. * The command infrastructure is in InterpreterLoop. */ - def dumpState(xs: List[String]): String = { - List( - "allUsedNames = " + allUsedNames, - "allBoundNames = " + allBoundNames, - prevRequests.toList.map(req => " \"" + req.line + "\" => " + req.objectSourceCode) - ).mkString("", "\n", "\n") - } + def dumpState(xs: List[String]): String = """ + | Names used: %s + | + | Identifiers: %s + | + | synthvars: %d + """.stripMargin.format( + allUsedNames mkString " ", + unqualifiedIds mkString " ", + allBoundNames filter isSynthVarName size + ) // very simple right now, will get more interesting def dumpTrees(xs: List[String]): String = { @@ -919,7 +939,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) { beQuietDuring { this.bind("interpreter", "scala.tools.nsc.Interpreter", this) this.bind("global", "scala.tools.nsc.Global", compiler) - interpret("""import interpreter.{ mkType, mkTree, mkTrees, eval }""") + interpret("""import interpreter.{ mkType, mkTree, mkTrees, eval }""", true) } """** Power User mode enabled - BEEP BOOP ** @@ -947,6 +967,9 @@ class Interpreter(val settings: Settings, out: PrintWriter) { private def requestForIdent(line: String): Option[Request] = requestForName(newTermName(line)) + private def typeForIdent(id: String): Option[String] = + requestForIdent(id) map (_ typeOf newTermName(id)) + def methodsOf(name: String) = evalExpr[List[String]](methodsCode(name)) map (x => NameTransformer.decode(getOriginalName(x))) @@ -956,7 +979,32 @@ class Interpreter(val settings: Settings, out: PrintWriter) { def extractionValueForIdent(id: String): AnyRef = requestForIdent(id).get.extractionValue - def implicitFor[T: Manifest] = evalExpr[T]("""manifest[%s]""".format(manifest[T])) + /** 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): Class[_] = extractionValueForIdent(id).getClass @@ -984,14 +1032,14 @@ class Interpreter(val settings: Settings, out: PrintWriter) { if (manifest[T] eq Manifest.Nothing) evalError("Could not infer type: try 'eval[SomeType](%s)' instead".format(line)) - val lhs = varNameCreator() + 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) match { + val req = requestFromLine(lhs, true) match { case Left(result) => evalError(result.toString) case Right(req) => req } @@ -1014,7 +1062,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) { /** Another entry point for tab-completion, ids in scope */ def unqualifiedIds(): List[String] = - allBoundNames map (_.toString) filterNot isSynthVarName + allValueGeneratingNames map (_.toString) sortWith (_ < _) filterNot isSynthVarName /** For static/object method completion */ def getClassObject(path: String): Option[Class[_]] = classLoader tryToLoadClass path @@ -1022,14 +1070,15 @@ class Interpreter(val settings: Settings, out: PrintWriter) { /** Parse the ScalaSig to find type aliases */ def aliasForType(path: String) = ByteCode.aliasForType(path) - /** Artificial object */ - class ReplVars extends CompletionAware { - def completions() = unqualifiedIds() - override def follow(s: String) = - if (completions contains s) completionAware(s) - else None - } - def replVarsObject() = new ReplVars() + /** Artificial object demonstrating completion */ + def replVarsObject() = CompletionAware( + Map[String, CompletionAware]( + "ids" -> CompletionAware(() => unqualifiedIds, completionAware _), + "synthVars" -> CompletionAware(() => allBoundNames filter isSynthVarName map (_.toString)), + "types" -> CompletionAware(() => allSeenTypes map (_.toString)), + "implicits" -> CompletionAware(() => allImplicits map (_.toString)) + ) + ) // Coming soon // implicit def string2liftedcode(s: String): LiftedCode = new LiftedCode(s) @@ -1049,6 +1098,19 @@ class Interpreter(val settings: Settings, out: PrintWriter) { /** 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 partialMap pf) + b ++= x + + b.result + } + object DebugParam { implicit def tuple2debugparam[T](x: (String, T))(implicit m: Manifest[T]): DebugParam[T] = DebugParam(x._1, x._2) diff --git a/src/compiler/scala/tools/nsc/InterpreterLoop.scala b/src/compiler/scala/tools/nsc/InterpreterLoop.scala index 2d8a5b78ee..02d0393590 100644 --- a/src/compiler/scala/tools/nsc/InterpreterLoop.scala +++ b/src/compiler/scala/tools/nsc/InterpreterLoop.scala @@ -331,7 +331,7 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) { if (in.history.isDefined) interpreter.quietBind("history", "scala.collection.immutable.List[String]", in.historyList) - interpreter.quietBind("repl", "scala.tools.nsc.Interpreter#ReplVars", interpreter.replVarsObject()) + interpreter.quietBind("repl", "scala.tools.nsc.interpreter.CompletionAware", interpreter.replVarsObject()) } def verbosity() = { diff --git a/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala b/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala index c60f402d3d..df110333dc 100644 --- a/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala +++ b/src/compiler/scala/tools/nsc/interpreter/CompletionAware.scala @@ -60,6 +60,8 @@ trait CompletionAware { ) filterNot filterNotFunction map mapFunction sortWith (sortFunction _) } + /** TODO - unify this and completionsFor under a common traverser. + */ def executionFor(buf: String): Option[Any] = { val parsed = new Parsed(buf) import parsed._ @@ -79,6 +81,21 @@ object CompletionAware { case _ => None } - def apply(xs: List[String]) = new CompletionAware { val completions = xs } + /** Create a CompletionAware object from the given functions. + * The first should generate the list of completions whenever queried, + * and the second should return Some(CompletionAware) object if + * subcompletions are possible. + */ + def apply(terms: () => List[String], followFunction: String => Option[CompletionAware]): CompletionAware = + new CompletionAware { + def completions = terms() + override def follow(id: String) = followFunction(id) + } + + /** Convenience factories. + */ + def apply(terms: () => List[String]): CompletionAware = apply(terms, _ => None) + def apply(map: collection.Map[String, CompletionAware]): CompletionAware = + apply(() => map.keysIterator.toList, map.get _) } diff --git a/src/compiler/scala/tools/nsc/interpreter/IdentCompletion.scala b/src/compiler/scala/tools/nsc/interpreter/IdentCompletion.scala index 5759abdb97..47a0cb80a0 100644 --- a/src/compiler/scala/tools/nsc/interpreter/IdentCompletion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/IdentCompletion.scala @@ -12,8 +12,10 @@ package interpreter 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 + if (completions contains id) { + (repl completionAware id) orElse + (repl completionAwareImplicit id) orElse + Some(new InstanceCompletion(repl clazzForIdent id)) + } + else None } diff --git a/test/files/jvm/interpreter.check b/test/files/jvm/interpreter.check index 6c89318470..f9f0d07c04 100644 --- a/test/files/jvm/interpreter.check +++ b/test/files/jvm/interpreter.check @@ -229,7 +229,7 @@ scala> scala> plusOne: (x: Int)Int res0: Int = 6 -res0: java.lang.String = after reset +res1: java.lang.String = after reset <console>:5: error: not found: value plusOne plusOne(5) // should be undefined now ^ |