From 1ab0d9ea486254cc2093bba687b7c9b24bcf23da Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Thu, 21 Jan 2010 21:00:08 +0000 Subject: It's a big REPL patch. And it contains: * Eliminated a bug which was causing all repl lines to be parsed twice * Removed reference to JLine from InterpreterLoop which was causing someone trouble in eclipse * Enriched the repl compile/reflect mechanism to allow retrieving the value as well as the String describing it * Utilized said enrichment to write an eval[T] method which is exposed in the repl in :power mode * Added ability to turn off string unwrapping in repl: settings.unwrapStrings = false * Created interface presently called Completion.Special which lets objects define their own contents * As minor demonstration of above, in :power mode variable "repl" implements Special and completes with all repl identifiers * As more interesting demonstration of above, try a repl session like... import scala.tools.nsc.interpreter.Completion.Special import scala.tools.nsc.io.Process val connections = new Special { def tabCompletions() = Process("netstat -p tcp").toList drop 2 map (_ split "\\s+" apply 4) } connections. Review by community! --- src/compiler/scala/tools/nsc/Interpreter.scala | 320 +++++++++++++-------- src/compiler/scala/tools/nsc/InterpreterLoop.scala | 30 +- .../scala/tools/nsc/InterpreterSettings.scala | 29 +- .../scala/tools/nsc/interpreter/Completion.scala | 49 +++- .../scala/tools/nsc/interpreter/History.scala | 17 ++ .../tools/nsc/interpreter/InteractiveReader.scala | 3 + .../scala/tools/nsc/interpreter/JLineReader.scala | 8 +- 7 files changed, 309 insertions(+), 147 deletions(-) create mode 100644 src/compiler/scala/tools/nsc/interpreter/History.scala diff --git a/src/compiler/scala/tools/nsc/Interpreter.scala b/src/compiler/scala/tools/nsc/Interpreter.scala index b2c5bc2415..d8cf729d25 100644 --- a/src/compiler/scala/tools/nsc/Interpreter.scala +++ b/src/compiler/scala/tools/nsc/Interpreter.scala @@ -12,7 +12,7 @@ import java.net.{ MalformedURLException, URL } import java.lang.reflect import reflect.InvocationTargetException -import scala.collection.immutable.ListSet +import scala.reflect.Manifest import scala.collection.mutable import scala.collection.mutable.{ ListBuffer, HashSet, ArrayBuffer } import scala.tools.nsc.util.ScalaClassLoader @@ -77,6 +77,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) val compiler: Global = newCompiler(settings, reporter) import compiler.{ Traverser, CompilationUnit, Symbol, Name, Type } + import compiler.definitions import compiler.{ Tree, TermTree, ValOrDefDef, ValDef, DefDef, Assign, ClassDef, ModuleDef, Ident, Select, TypeDef, Import, MemberDef, DocDef, @@ -87,6 +88,8 @@ class Interpreter(val settings: Settings, out: PrintWriter) INTERPRETER_IMPORT_WRAPPER, INTERPRETER_WRAPPER_SUFFIX, USCOREkw } + import definitions.{ EmptyPackage, getMember } + /** construct an interpreter that reports to Console */ def this(settings: Settings) = this(settings, new NewLinePrintWriter(new ConsoleWriter, true)) @@ -116,7 +119,21 @@ class Interpreter(val settings: Settings, out: PrintWriter) } /** interpreter settings */ - lazy val isettings = new InterpreterSettings(this) + lazy val isettings = { + val x = new InterpreterSettings(this) + quietBind("settings", "scala.tools.nsc.InterpreterSettings", x) + x + } + + /** Heuristically strip interpreter wrapper prefixes + * from an interpreter output string. + */ + def stripWrapperGunk(str: String): String = + if (isettings.unwrapStrings) { + val wrapregex = """(line[0-9]+\$object[$.])?(\$iw[$.])*""" + str.replaceAll(wrapregex, "") + } + else str object reporter extends ConsoleReporter(settings, null, out) { override def printMessage(msg: String) { @@ -252,6 +269,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) str.flush() }) } + def indentString(s: String) = s split "\n" map (spaces + _ + "\n") mkString implicit def name2string(name: Name) = name.toString @@ -409,8 +427,8 @@ class Interpreter(val settings: Settings, out: PrintWriter) /** Build a request from the user. trees is line * after being parsed. */ - private def buildRequest(trees: List[Tree], line: String, lineName: String): Request = - new Request(line, lineName) + 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) @@ -424,28 +442,14 @@ class Interpreter(val settings: Settings, out: PrintWriter) case member => new GenericHandler(member) } - /**

- * 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. - *

- *

- * The return value is whether the line was interpreter successfully, - * e.g. that there were no parse errors. - *

- * - * @param line ... - * @return ... - */ - def interpret(line: String): IR.Result = { + private def requestFromLine(line: String): Either[IR.Result, Request] = { // initialize the compiler if (prevRequests.isEmpty) new compiler.Run() // parse val trees = parse(indentCode(line)) match { - case None => return IR.Incomplete - case Some(Nil) => return IR.Error // parse error or empty input + case None => return Left(IR.Incomplete) + case Some(Nil) => return Left(IR.Error) // parse error or empty input case Some(trees) => trees } @@ -454,12 +458,34 @@ class Interpreter(val settings: Settings, out: PrintWriter) if (trees.size == 1) trees.head match { case _:Assign => // we don't want to include assignments case _:TermTree | _:Ident | _:Select => - return interpret("val %s =\n%s".format(varNameCreator(), line)) + return requestFromLine("val %s =\n%s".format(varNameCreator(), line)) case _ => } // figure out what kind of request - val req = buildRequest(trees, line, lineNameCreator()) + Right(buildRequest(line, lineNameCreator(), trees)) + } + + /**

+ * 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. + *

+ *

+ * The return value is whether the line was interpreter successfully, + * e.g. that there were no parse errors. + *

+ * + * @param line ... + * @return ... + */ + def interpret(line: String): IR.Result = { + val req = requestFromLine(line) match { + 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) if (req == null || !req.compile) return IR.Error @@ -557,8 +583,11 @@ class Interpreter(val settings: Settings, out: PrintWriter) case _ => false } + def generatesValue: Option[Name] = None + def extraCodeToEvaluate(req: Request, code: PrintWriter) { } def resultExtractionCode(req: Request, code: PrintWriter) { } + override def toString = "%s(usedNames = %s)".format(this.getClass, usedNames) } @@ -571,6 +600,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) override lazy val boundNames = List(vname) override def valAndVarNames = boundNames + override def generatesValue = Some(vname) override def resultExtractionCode(req: Request, code: PrintWriter) { val isInternal = isGeneratedVarName(vname) && req.typeOfEnc(vname) == "Unit" @@ -607,6 +637,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) 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) = code println """val %s = %s""".format(helperName, lhs) @@ -624,6 +655,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) 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) @@ -667,8 +699,8 @@ 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 = parse(line) getOrElse Nil + 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 @@ -680,7 +712,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) val handlers: List[MemberHandler] = trees map chooseHandler /** all (public) names defined by these statements */ - val boundNames = (ListSet() ++ handlers.flatMap(_.boundNames)).toList + val boundNames = handlers flatMap (_.boundNames) /** list of names used by this expression */ val usedNames: List[Name] = handlers.flatMap(_.usedNames) @@ -705,10 +737,6 @@ class Interpreter(val settings: Settings, out: PrintWriter) val preamble = """object %s { | %s%s """.stripMargin.format(objectName, importsPreamble, indentCode(toCompute)) - // val preamble = """ - // | object %s { - // | %s %s - // """.stripMargin.format(objectName, importsPreamble, indentCode(toCompute)) val postamble = importsTrailer + "; }" code println preamble @@ -716,20 +744,29 @@ class Interpreter(val settings: Settings, out: PrintWriter) code println postamble } - /** Types of variables defined by this request. They are computed - after compilation of the main object */ - var typeOf: Map[Name, String] = _ - def typeOfEnc(vname: Name) = typeOf(compiler encode vname) - /** generate source code for the object that retrieves the result from objectSourceCode */ def resultObjectSourceCode: String = stringFrom { 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 // make sure that's run + | %s + | }""".stripMargin.format(fullPath(vname)) + case _ => "" + } + val preamble = """ | object %s { + | %s | val scala_repl_result: String = { | %s // evaluate object to make sure constructor is run | ("" // an initial "" so later code can uniformly be: + etc - """.stripMargin.format(resultObjectName, objectName + accessPath) + """.stripMargin.format(resultObjectName, valueExtractor, objectName + accessPath) val postamble = """ | ) @@ -742,6 +779,32 @@ class Interpreter(val settings: Settings, out: PrintWriter) code println postamble } + lazy val objRun = { + val x = new compiler.Run() + // compile the object containing the user's code + x.compileSources(List(new BatchSourceFile("", objectSourceCode))) + x + } + + lazy val extractionObjectRun = { + val x = new compiler.Run() + // compile the result-extraction object + x.compileSources(List(new BatchSourceFile("", resultObjectSourceCode))) + x + } + + def extractionValue(): Option[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 + } + } + /** Compile the object file. Returns whether the compilation succeeded. * If all goes well, the "types" map is computed. */ def compile(): Boolean = { @@ -749,45 +812,43 @@ class Interpreter(val settings: Settings, out: PrintWriter) reporter.reset // compile the main object - val objRun = new compiler.Run() - objRun.compileSources(List(new BatchSourceFile("", objectSourceCode))) + objRun + + // bail on error if (reporter.hasErrors) return false // extract and remember types - typeOf = findTypes(objRun) + typeOf // compile the result-extraction object - new compiler.Run().compileSources(List(new BatchSourceFile("", resultObjectSourceCode))) + extractionObjectRun // success !reporter.hasErrors } - /** Dig the types of all bound variables out of the compiler run. - * - * @param objRun ... - * @return ... - */ - def findTypes(objRun: compiler.Run): Map[Name, String] = { - import compiler.definitions.{ EmptyPackage, getMember } - def valAndVarNames = handlers flatMap { _.valAndVarNames } - def defNames = handlers flatMap { _.defNames } + def valAndVarNames = handlers flatMap { _.valAndVarNames } + def defNames = handlers flatMap { _.defNames } + def atNextPhase[T](op: => T): T = compiler.atPhase(objRun.typerPhase.next)(op) - def getTypes(names: List[Name], nameMap: Name => Name): Map[Name, String] = { - def atNextPhase[T](op: => T): T = compiler.atPhase(objRun.typerPhase.next)(op) + /** The outermost wrapper object */ + lazy val outerResObjSym: Symbol = getMember(EmptyPackage, newTermName(objectName)) - /** the outermost wrapper object */ - 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 + atNextPhase(sym.info member newTermName(name)) + } - /** the innermost object inside the wrapper, found by - * following accessPath into the outer one. */ - val resObjSym = - accessPath.split("\\.").foldLeft(outerResObjSym) { (sym, name) => - if (name == "") sym else - atNextPhase(sym.info member newTermName(name)) - } + /* typeOf lookup with encoding */ + def typeOfEnc(vname: Name) = typeOf(compiler encode vname) + /** Types of variables defined by this request. */ + lazy val typeOf: Map[Name, String] = { + def getTypes(names: List[Name], nameMap: Name => Name): Map[Name, String] = { names.foldLeft(Map.empty[Name, String]) { (map, name) => val rawType = atNextPhase(resObjSym.info.member(name).tpe) // the types are all =>T; remove the => @@ -800,9 +861,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) } } - val names1 = getTypes(valAndVarNames, nme.getterToLocal(_)) - val names2 = getTypes(defNames, identity) - names1 ++ names2 + getTypes(valAndVarNames, nme.getterToLocal(_)) ++ getTypes(defNames, identity) } /** load and run the code using reflection */ @@ -847,10 +906,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) // very simple right now, will get more interesting def dumpTrees(xs: List[String]): String = { - val treestrs = ( - for (x <- xs ; name <- nameOfIdent(x) ; req <- requestForName(name)) - yield req.trees - ).flatten + val treestrs = (xs map requestForIdent).flatten flatMap (_.trees) if (treestrs.isEmpty) "No trees found." else treestrs.map(t => t.toString + " (" + t.getClass.getSimpleName + ")\n").mkString @@ -860,7 +916,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 }""") + interpret("""import interpreter.{ mkType, mkTree, mkTrees, eval }""") } """** Power User mode enabled - BEEP BOOP ** @@ -897,54 +953,83 @@ class Interpreter(val settings: Settings, out: PrintWriter) } None } + private def requestForIdent(line: String): Option[Request] = + nameOfIdent(line) flatMap requestForName - // 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 val filterFlags: Int = { - import java.lang.reflect.Modifier._ - STATIC | PRIVATE | PROTECTED - } - private val methodsCode = """ . - | asInstanceOf[AnyRef].getClass.getMethods . - | filter(x => (x.getModifiers & %d) == 0) . - | map(_.getName) . - | mkString(" ")""".stripMargin.format(filterFlags) + private def mkValDef(line: String, name: String = varNameCreator()) = + (name, "val %s = %s".format(name, line)) + + // 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 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) => 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 = varNameCreator() + 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 { + 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 Some(prevRequests.last.extractionValue.get.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. * 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] = { - import Completion.{ shouldHide } - import NameTransformer.{ decode, encode } // e.g. $plus$plus => ++ - - val res = beQuietDuring { + def membersOfIdentifier(line: String): List[String] = + beQuietDuring { for (name <- nameOfIdent(line) ; req <- requestForName(name)) yield { - if (interpret("val " + synthVarNameCreator() + " = " + name + methodsCode) != IR.Success) Nil - else { - val result = prevRequests.last.resultObjectName - val resultObj = (classLoader tryToInitializeClass result).get - val valMethod = resultObj getMethod "scala_repl_result" - val str = valMethod.invoke(resultObj).toString - - str.substring(str.indexOf('=') + 1).trim . - split(" ").toList . - map(x => decode(getOriginalName(x))) . - filterNot(shouldHide) . - removeDuplicates - } + memberListFor(name) filterNot Completion.shouldHide removeDuplicates } - } - - res getOrElse Nil - } + } getOrElse Nil /** Another entry point for tab-completion, ids in scope */ def unqualifiedIds(): List[String] = @@ -956,6 +1041,22 @@ 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 Completion.Special { + def tabCompletions() = unqualifiedIds() + } + def replVarsObject() = new ReplVars() + + // 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 private var debuggingOutput = false def DBG(s: String) = if (debuggingOutput) out println s else () @@ -963,6 +1064,7 @@ class Interpreter(val settings: Settings, out: PrintWriter) /** Utility methods for the Interpreter. */ object Interpreter { + object DebugParam { implicit def tuple2debugparam[T](x: (String, T))(implicit m: scala.reflect.Manifest[T]): DebugParam[T] = DebugParam(x._1, x._2) @@ -1011,14 +1113,6 @@ object Interpreter { intLoop.closeInterpreter } - /** Heuristically strip interpreter wrapper prefixes - * from an interpreter output string. - */ - def stripWrapperGunk(str: String): String = { - val wrapregex = """(line[0-9]+\$object[$.])?(\$iw[$.])*""" - str.replaceAll(wrapregex, "") - } - 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: _*) diff --git a/src/compiler/scala/tools/nsc/InterpreterLoop.scala b/src/compiler/scala/tools/nsc/InterpreterLoop.scala index 1a941342c4..9cc372097a 100644 --- a/src/compiler/scala/tools/nsc/InterpreterLoop.scala +++ b/src/compiler/scala/tools/nsc/InterpreterLoop.scala @@ -10,7 +10,6 @@ import java.io.{ BufferedReader, File, FileReader, PrintWriter } import java.io.IOException import scala.tools.nsc.{ InterpreterResults => IR } -import scala.collection.JavaConversions.asBuffer import interpreter._ import io.{ Process } @@ -79,12 +78,7 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) { /** The input stream from which commands come, set by main() */ var in: InteractiveReader = _ - def history = in match { - case x: JLineReader => Some(x.history) - case _ => None - } - def historyList: Seq[String] = - history map (x => asBuffer(x.getHistoryList): Seq[String]) getOrElse Nil + 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 @@ -127,10 +121,11 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) { /** Bind the settings so that evaluated code can modify them */ def bindSettings() { - interpreter.beQuietDuring { - interpreter.compileString(InterpreterSettings.sourceCodeForClass) - interpreter.bind("settings", "scala.tools.nsc.InterpreterSettings", isettings) - } + isettings + // interpreter.beQuietDuring { + // interpreter.compileString(InterpreterSettings.sourceCodeForClass) + // interpreter.bind("settings", "scala.tools.nsc.InterpreterSettings", isettings) + // } } /** print a friendly help message */ @@ -159,10 +154,10 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) { def printHistory(xs: List[String]) { val defaultLines = 20 - if (history.isEmpty) + if (in.history.isEmpty) return println("No history available.") - val current = history.get.getCurrentIndex + val current = in.history.get.index val count = try xs.head.toInt catch { case _: Exception => defaultLines } val lines = historyList takeRight count val offset = current - lines.size + 1 @@ -175,10 +170,10 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) { def searchHistory(_cmdline: String) { val cmdline = _cmdline.toLowerCase - if (history.isEmpty) + if (in.history.isEmpty) return println("No history available.") - val current = history.get.getCurrentIndex + val current = in.history.get.index val offset = current - historyList.size + 1 for ((line, index) <- historyList.zipWithIndex ; if line.toLowerCase contains cmdline) @@ -343,7 +338,10 @@ class InterpreterLoop(in0: Option[BufferedReader], out: PrintWriter) { def power() { powerUserOn = true out println interpreter.powerUser() - interpreter.quietBind("history", "scala.collection.immutable.List[String]", historyList.toList) + if (in.history.isDefined) + interpreter.quietBind("history", "scala.collection.immutable.List[String]", historyList) + + interpreter.quietBind("repl", "scala.tools.nsc.Interpreter#ReplVars", interpreter.replVarsObject()) } def verbosity() = { diff --git a/src/compiler/scala/tools/nsc/InterpreterSettings.scala b/src/compiler/scala/tools/nsc/InterpreterSettings.scala index 99159b081d..82609cc2e5 100644 --- a/src/compiler/scala/tools/nsc/InterpreterSettings.scala +++ b/src/compiler/scala/tools/nsc/InterpreterSettings.scala @@ -20,7 +20,12 @@ class InterpreterSettings(repl: Interpreter) { * more than this number of characters, then the printout is * truncated. */ - var maxPrintString = 2400 + var maxPrintString = 800 + + /** 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 @@ -30,14 +35,20 @@ class InterpreterSettings(repl: Interpreter) { } def deprecation: Boolean = repl.settings.deprecation.value - override def toString = - "InterpreterSettings {\n" + -// " loadPath = " + loadPath + "\n" + - " maxPrintString = " + maxPrintString + "\n" + - "}" -} + def allSettings = Map( + "maxPrintString" -> maxPrintString, + "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 * @@ -48,6 +59,10 @@ 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 = """ diff --git a/src/compiler/scala/tools/nsc/interpreter/Completion.scala b/src/compiler/scala/tools/nsc/interpreter/Completion.scala index 2b9538b3fc..3e6698b605 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Completion.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Completion.scala @@ -24,6 +24,8 @@ 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 @@ -38,7 +40,6 @@ extends Completor { def this(interpreter: Interpreter) = this(interpreter, null) import Completion._ - import java.util.{ List => JList } import interpreter.compilerClasspath // it takes a little while to look through the jars so we use a future and a concurrent map @@ -52,7 +53,7 @@ extends Completor { val agent = new CompletionAgent import agent._ - import java.lang.reflect.Modifier.{ isPrivate, isProtected, isPublic, isStatic } + 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) || { @@ -184,17 +185,18 @@ extends Completor { (interpreter getClassObject ("scala." + path)) orElse (interpreter getClassObject ("java.lang." + path)) - def lastHistoryItem = - for (loop <- Option(intLoop) ; h <- loop.history) yield - h.getHistoryList.get(h.size - 1) + def lastHistoryItem = Option(intLoop) map (_.historyList.last) - // Is the buffer the same it was last time they hit tab? + // 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 + // jline's completion comes through here - we ask a Buffer for the candidates. override def complete(_buffer: String, cursor: Int, candidates: JList[String]): Int = { // println("_buffer = %s, cursor = %d".format(_buffer, cursor)) - val verbose = (_buffer, lastHistoryItem orNull) == lastTab + val verbose = isConsecutiveTabs(_buffer) lastTab = (_buffer, lastHistoryItem orNull) new Buffer(_buffer, verbose) complete candidates @@ -229,6 +231,12 @@ object Completion 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 @@ -317,4 +325,31 @@ object Completion 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] + + def selfDefinedMembers(target: Any) = target match { + case x: Special => Some(x.tabCompletions()) + case _ => None + } } diff --git a/src/compiler/scala/tools/nsc/interpreter/History.scala b/src/compiler/scala/tools/nsc/interpreter/History.scala new file mode 100644 index 0000000000..4f726ebc55 --- /dev/null +++ b/src/compiler/scala/tools/nsc/interpreter/History.scala @@ -0,0 +1,17 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2010 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.nsc +package interpreter + +import scala.collection.JavaConversions.asBuffer + +/** Primarily, a wrapper for JLine's History. + */ +class History(jhistory: jline.History) { + def asJavaList = jhistory.getHistoryList + def asList: List[String] = asBuffer(asJavaList).toList + def index = jhistory.getCurrentIndex +} \ No newline at end of file diff --git a/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala b/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala index 500876bf69..7f89669806 100644 --- a/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala +++ b/src/compiler/scala/tools/nsc/interpreter/InteractiveReader.scala @@ -23,6 +23,9 @@ trait InteractiveReader { catching(handler) { readOneLine(prompt) } } + // overide if history is available + def history: Option[History] = None + // hack necessary for OSX jvm suspension because read calls are not restarted after SIGTSTP private def restartSystemCall(e: Exception): Boolean = Properties.isMac && (e.getMessage == msgEINTR) diff --git a/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala b/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala index b13b54a716..c0fe874a37 100644 --- a/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala +++ b/src/compiler/scala/tools/nsc/interpreter/JLineReader.scala @@ -8,19 +8,19 @@ package scala.tools.nsc package interpreter import java.io.File -import jline.{ History, ConsoleReader, ArgumentCompletor } +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) - def history: History = consoleReader.getHistory + override def history = Some(new History(consoleReader.getHistory)) val consoleReader = { val history = - try new History(new File(System.getProperty("user.home"), ".scala_history")) + try new JHistory(new File(System.getProperty("user.home"), ".scala_history")) // do not store history if error - catch { case _: Exception => new History() } + catch { case _: Exception => new JHistory() } val r = new jline.ConsoleReader() r setHistory history -- cgit v1.2.3