diff --git a/project/build/SparkProject.scala b/project/build/SparkProject.scala index 469dd93ef1dd329ef9496fa7592008fd6d6c007e..7ebd92fde6836c6a20f51d3385732cc94cea3a3b 100644 --- a/project/build/SparkProject.scala +++ b/project/build/SparkProject.scala @@ -10,7 +10,7 @@ class SparkProject(info: ProjectInfo) extends ParentProject(info) with IdeaProje lazy val core = project("core", "Spark Core", new CoreProject(_)) -// lazy val repl = project("repl", "Spark REPL", new ReplProject(_), core) + lazy val repl = project("repl", "Spark REPL", new ReplProject(_), core) lazy val examples = project("examples", "Spark Examples", new ExamplesProject(_), core) diff --git a/repl/lib/jline.jar b/repl/lib/jline.jar deleted file mode 100644 index 6ed67faab68b4ba09e264bdaaaa610971479438b..0000000000000000000000000000000000000000 Binary files a/repl/lib/jline.jar and /dev/null differ diff --git a/repl/lib/scala-jline.jar b/repl/lib/scala-jline.jar new file mode 100644 index 0000000000000000000000000000000000000000..2f18c95cdd7aa00e103e80f2c589ecd715030c70 Binary files /dev/null and b/repl/lib/scala-jline.jar differ diff --git a/repl/src/main/scala/spark/repl/Main.scala b/repl/src/main/scala/spark/repl/Main.scala index f00df5aa580b0f6d368af29590dfe7ae2dea5e82..b4a2bb05f9929212f8872c7b81a9c9b1319e8e80 100644 --- a/repl/src/main/scala/spark/repl/Main.scala +++ b/repl/src/main/scala/spark/repl/Main.scala @@ -3,14 +3,14 @@ package spark.repl import scala.collection.mutable.Set object Main { - private var _interp: SparkInterpreterLoop = null + private var _interp: SparkILoop = null def interp = _interp - private[repl] def interp_=(i: SparkInterpreterLoop) { _interp = i } + private[repl] def interp_=(i: SparkILoop) { _interp = i } def main(args: Array[String]) { - _interp = new SparkInterpreterLoop - _interp.main(args) + _interp = new SparkILoop + _interp.process(args) } } diff --git a/repl/src/main/scala/spark/repl/SparkCompletionOutput.scala b/repl/src/main/scala/spark/repl/SparkCompletionOutput.scala deleted file mode 100644 index 5ac46e34124b85d3662729b732bd1601f10c70c6..0000000000000000000000000000000000000000 --- a/repl/src/main/scala/spark/repl/SparkCompletionOutput.scala +++ /dev/null @@ -1,92 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2010 LAMP/EPFL - * @author Paul Phillips - */ - -package spark.repl - -import scala.tools.nsc -import scala.tools.nsc._ -import scala.tools.nsc.interpreter -import scala.tools.nsc.interpreter._ - -/** This has a lot of duplication with other methods in Symbols and Types, - * but repl completion utility is very sensitive to precise output. Best - * thing would be to abstract an interface for how such things are printed, - * as is also in progress with error messages. - */ -trait SparkCompletionOutput { - self: SparkCompletion => - - import global._ - import definitions.{ NothingClass, AnyClass, isTupleType, isFunctionType, isRepeatedParamType } - - /** Reducing fully qualified noise for some common packages. - */ - val typeTransforms = List( - "java.lang." -> "", - "scala.collection.immutable." -> "immutable.", - "scala.collection.mutable." -> "mutable.", - "scala.collection.generic." -> "generic." - ) - - def quietString(tp: String): String = - typeTransforms.foldLeft(tp) { - case (str, (prefix, replacement)) => - if (str startsWith prefix) replacement + (str stripPrefix prefix) - else str - } - - class MethodSymbolOutput(method: Symbol) { - val pkg = method.ownerChain find (_.isPackageClass) map (_.fullName) getOrElse "" - - def relativize(str: String): String = quietString(str stripPrefix (pkg + ".")) - def relativize(tp: Type): String = relativize(tp.normalize.toString) - def relativize(sym: Symbol): String = relativize(sym.info) - - def braceList(tparams: List[String]) = if (tparams.isEmpty) "" else (tparams map relativize).mkString("[", ", ", "]") - def parenList(params: List[Any]) = params.mkString("(", ", ", ")") - - def methodTypeToString(mt: MethodType) = - (mt.paramss map paramsString mkString "") + ": " + relativize(mt.finalResultType) - - def typeToString(tp: Type): String = relativize( - tp match { - case x if isFunctionType(x) => functionString(x) - case x if isTupleType(x) => tupleString(x) - case x if isRepeatedParamType(x) => typeToString(x.typeArgs.head) + "*" - case mt @ MethodType(_, _) => methodTypeToString(mt) - case x => x.toString - } - ) - - def tupleString(tp: Type) = parenList(tp.normalize.typeArgs map relativize) - def functionString(tp: Type) = tp.normalize.typeArgs match { - case List(t, r) => t + " => " + r - case xs => parenList(xs.init) + " => " + xs.last - } - - def tparamsString(tparams: List[Symbol]) = braceList(tparams map (_.defString)) - def paramsString(params: List[Symbol]) = { - def paramNameString(sym: Symbol) = if (sym.isSynthetic) "" else sym.nameString + ": " - def paramString(sym: Symbol) = paramNameString(sym) + typeToString(sym.info.normalize) - - val isImplicit = params.nonEmpty && params.head.isImplicit - val strs = (params map paramString) match { - case x :: xs if isImplicit => ("implicit " + x) :: xs - case xs => xs - } - parenList(strs) - } - - def methodString() = - method.keyString + " " + method.nameString + (method.info.normalize match { - case PolyType(Nil, resType) => ": " + typeToString(resType) // nullary method - case PolyType(tparams, resType) => tparamsString(tparams) + typeToString(resType) - case mt @ MethodType(_, _) => methodTypeToString(mt) - case x => - DBG("methodString(): %s / %s".format(x.getClass, x)) - x.toString - }) - } -} diff --git a/repl/src/main/scala/spark/repl/SparkHelper.scala b/repl/src/main/scala/spark/repl/SparkHelper.scala new file mode 100644 index 0000000000000000000000000000000000000000..d8fb7191b44b6b90f89ab75e79e518172c2680a9 --- /dev/null +++ b/repl/src/main/scala/spark/repl/SparkHelper.scala @@ -0,0 +1,5 @@ +package scala.tools.nsc + +object SparkHelper { + def explicitParentLoader(settings: Settings) = settings.explicitParentLoader +} diff --git a/repl/src/main/scala/spark/repl/SparkILoop.scala b/repl/src/main/scala/spark/repl/SparkILoop.scala new file mode 100644 index 0000000000000000000000000000000000000000..bfbc66ebdd5a86f7932984cd07107f7689948368 --- /dev/null +++ b/repl/src/main/scala/spark/repl/SparkILoop.scala @@ -0,0 +1,990 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Alexander Spoon + */ + +package spark.repl + +import scala.tools.nsc._ +import scala.tools.nsc.interpreter._ + +import Predef.{ println => _, _ } +import java.io.{ BufferedReader, FileReader, PrintWriter } +import scala.sys.process.Process +import session._ +import scala.tools.nsc.interpreter.{ Results => IR } +import scala.tools.util.{ SignalManager, Signallable, Javap } +import scala.annotation.tailrec +import scala.util.control.Exception.{ ignoring } +import scala.collection.mutable.ListBuffer +import scala.concurrent.ops +import util.{ ClassPath, Exceptional, stringFromWriter, stringFromStream } +import interpreter._ +import io.{ File, Sources } + +import spark.SparkContext + +/** The Scala interactive shell. It provides a read-eval-print loop + * around the Interpreter class. + * After instantiation, clients should call the main() method. + * + * If no in0 is specified, then input will come from the console, and + * the class will attempt to provide input editing feature such as + * input history. + * + * @author Moez A. Abdel-Gawad + * @author Lex Spoon + * @version 1.2 + */ +class SparkILoop(in0: Option[BufferedReader], val out: PrintWriter, val master: Option[String]) + extends AnyRef + with LoopCommands +{ + def this(in0: BufferedReader, out: PrintWriter, master: String) = this(Some(in0), out, Some(master)) + def this(in0: BufferedReader, out: PrintWriter) = this(Some(in0), out, None) + def this() = this(None, new PrintWriter(Console.out, true), None) + + var in: InteractiveReader = _ // the input stream from which commands come + var settings: Settings = _ + var intp: SparkIMain = _ + + /* + lazy val power = { + val g = intp.global + Power[g.type](this, g) + } + */ + + // TODO + // object opt extends AestheticSettings + // + @deprecated("Use `intp` instead.", "2.9.0") + def interpreter = intp + + @deprecated("Use `intp` instead.", "2.9.0") + def interpreter_= (i: SparkIMain): Unit = intp = i + + def history = in.history + + /** The context class loader at the time this object was created */ + protected val originalClassLoader = Thread.currentThread.getContextClassLoader + + // Install a signal handler so we can be prodded. + private val signallable = + /*if (isReplDebug) Signallable("Dump repl state.")(dumpCommand()) + else*/ null + + // classpath entries added via :cp + var addedClasspath: String = "" + + /** A reverse list of commands to replay if the user requests a :replay */ + var replayCommandStack: List[String] = Nil + + /** A list of commands to replay if the user requests a :replay */ + def replayCommands = replayCommandStack.reverse + + /** Record a command for replay should the user request a :replay */ + def addReplay(cmd: String) = replayCommandStack ::= cmd + + /** Try to install sigint handler: ignore failure. Signal handler + * will interrupt current line execution if any is in progress. + * + * Attempting to protect the repl from accidental exit, we only honor + * a single ctrl-C if the current buffer is empty: otherwise we look + * for a second one within a short time. + */ + private def installSigIntHandler() { + def onExit() { + Console.println("") // avoiding "shell prompt in middle of line" syndrome + sys.exit(1) + } + ignoring(classOf[Exception]) { + SignalManager("INT") = { + if (intp == null) + onExit() + else if (intp.lineManager.running) + intp.lineManager.cancel() + else if (in.currentLine != "") { + // non-empty buffer, so make them hit ctrl-C a second time + SignalManager("INT") = onExit() + io.timer(5)(installSigIntHandler()) // and restore original handler if they don't + } + else onExit() + } + } + } + + /** Close the interpreter and set the var to null. */ + def closeInterpreter() { + if (intp ne null) { + intp.close + intp = null + Thread.currentThread.setContextClassLoader(originalClassLoader) + } + } + + class SparkILoopInterpreter extends SparkIMain(settings, out) { + override lazy val formatting = new Formatting { + def prompt = SparkILoop.this.prompt + } + override protected def createLineManager() = new Line.Manager { + override def onRunaway(line: Line[_]): Unit = { + val template = """ + |// She's gone rogue, captain! Have to take her out! + |// Calling Thread.stop on runaway %s with offending code: + |// scala> %s""".stripMargin + + echo(template.format(line.thread, line.code)) + // XXX no way to suppress the deprecation warning + line.thread.stop() + in.redrawLine() + } + } + override protected def parentClassLoader = { + SparkHelper.explicitParentLoader(settings).getOrElse( classOf[SparkILoop].getClassLoader ) + } + } + + /** Create a new interpreter. */ + def createInterpreter() { + if (addedClasspath != "") + settings.classpath append addedClasspath + + intp = new SparkILoopInterpreter + intp.setContextClassLoader() + installSigIntHandler() + } + + /** print a friendly help message */ + def helpCommand(line: String): Result = { + if (line == "") helpSummary() + else uniqueCommand(line) match { + case Some(lc) => echo("\n" + lc.longHelp) + case _ => ambiguousError(line) + } + } + private def helpSummary() = { + val usageWidth = commands map (_.usageMsg.length) max + val formatStr = "%-" + usageWidth + "s %s %s" + + echo("All commands can be abbreviated, e.g. :he instead of :help.") + echo("Those marked with a * have more detailed help, e.g. :help imports.\n") + + commands foreach { cmd => + val star = if (cmd.hasLongHelp) "*" else " " + echo(formatStr.format(cmd.usageMsg, star, cmd.help)) + } + } + private def ambiguousError(cmd: String): Result = { + matchingCommands(cmd) match { + case Nil => echo(cmd + ": no such command. Type :help for help.") + case xs => echo(cmd + " is ambiguous: did you mean " + xs.map(":" + _.name).mkString(" or ") + "?") + } + Result(true, None) + } + private def matchingCommands(cmd: String) = commands filter (_.name startsWith cmd) + private def uniqueCommand(cmd: String): Option[LoopCommand] = { + // this lets us add commands willy-nilly and only requires enough command to disambiguate + matchingCommands(cmd) match { + case List(x) => Some(x) + // exact match OK even if otherwise appears ambiguous + case xs => xs find (_.name == cmd) + } + } + + /** Print a welcome message */ + def printWelcome() { + echo("""Welcome to + ____ __ + / __/__ ___ _____/ /__ + _\ \/ _ \/ _ `/ __/ '_/ + /___/ .__/\_,_/_/ /_/\_\ version 0.3 + /_/ +""") + import Properties._ + val welcomeMsg = "Using Scala %s (%s, Java %s)".format( + versionString, javaVmName, javaVersion) + echo(welcomeMsg) + } + + /** Show the history */ + lazy val historyCommand = new LoopCommand("history", "show the history (optional num is commands to show)") { + override def usage = "[num]" + def defaultLines = 20 + + def apply(line: String): Result = { + if (history eq NoHistory) + return "No history available." + + val xs = words(line) + val current = history.index + val count = try xs.head.toInt catch { case _: Exception => defaultLines } + val lines = history.asStrings takeRight count + val offset = current - lines.size + 1 + + for ((line, index) <- lines.zipWithIndex) + echo("%3d %s".format(index + offset, line)) + } + } + + private def echo(msg: String) = { + out println msg + out.flush() + } + private def echoNoNL(msg: String) = { + out print msg + out.flush() + } + + /** Search the history */ + def searchHistory(_cmdline: String) { + val cmdline = _cmdline.toLowerCase + val offset = history.index - history.size + 1 + + for ((line, index) <- history.asStrings.zipWithIndex ; if line.toLowerCase contains cmdline) + echo("%d %s".format(index + offset, line)) + } + + private var currentPrompt = Properties.shellPromptString + def setPrompt(prompt: String) = currentPrompt = prompt + /** Prompt to print when awaiting input */ + def prompt = currentPrompt + + import LoopCommand.{ cmd, nullary } + + /** Standard commands **/ + lazy val standardCommands = List( + cmd("cp", "<path>", "add a jar or directory to the classpath", addClasspath), + cmd("help", "[command]", "print this summary or command-specific help", helpCommand), + historyCommand, + cmd("h?", "<string>", "search the history", searchHistory), + cmd("imports", "[name name ...]", "show import history, identifying sources of names", importsCommand), + cmd("implicits", "[-v]", "show the implicits in scope", implicitsCommand), + cmd("javap", "<path|class>", "disassemble a file or class name", javapCommand), + nullary("keybindings", "show how ctrl-[A-Z] and other keys are bound", keybindingsCommand), + cmd("load", "<path>", "load and interpret a Scala file", loadCommand), + nullary("paste", "enter paste mode: all input up to ctrl-D compiled together", pasteCommand), + //nullary("power", "enable power user mode", powerCmd), + nullary("quit", "exit the interpreter", () => Result(false, None)), + nullary("replay", "reset execution and replay all previous commands", replay), + shCommand, + nullary("silent", "disable/enable automatic printing of results", verbosity), + cmd("type", "<expr>", "display the type of an expression without evaluating it", typeCommand) + ) + + /** Power user commands */ + lazy val powerCommands: List[LoopCommand] = List( + //nullary("dump", "displays a view of the interpreter's internal state", dumpCommand), + //cmd("phase", "<phase>", "set the implicit phase for power commands", phaseCommand), + cmd("wrap", "<method>", "name of method to wrap around each repl line", wrapCommand) withLongHelp (""" + |:wrap + |:wrap clear + |:wrap <method> + | + |Installs a wrapper around each line entered into the repl. + |Currently it must be the simple name of an existing method + |with the specific signature shown in the following example. + | + |def timed[T](body: => T): T = { + | val start = System.nanoTime + | try body + | finally println((System.nanoTime - start) + " nanos elapsed.") + |} + |:wrap timed + | + |If given no argument, :wrap names the wrapper installed. + |An argument of clear will remove the wrapper if any is active. + |Note that wrappers do not compose (a new one replaces the old + |one) and also that the :phase command uses the same machinery, + |so setting :wrap will clear any :phase setting. + """.stripMargin.trim) + ) + + /* + private def dumpCommand(): Result = { + echo("" + power) + history.asStrings takeRight 30 foreach echo + in.redrawLine() + } + */ + + private val typeTransforms = List( + "scala.collection.immutable." -> "immutable.", + "scala.collection.mutable." -> "mutable.", + "scala.collection.generic." -> "generic.", + "java.lang." -> "jl.", + "scala.runtime." -> "runtime." + ) + + private def importsCommand(line: String): Result = { + val tokens = words(line) + val handlers = intp.languageWildcardHandlers ++ intp.importHandlers + val isVerbose = tokens contains "-v" + + handlers.filterNot(_.importedSymbols.isEmpty).zipWithIndex foreach { + case (handler, idx) => + val (types, terms) = handler.importedSymbols partition (_.name.isTypeName) + val imps = handler.implicitSymbols + val found = tokens filter (handler importsSymbolNamed _) + val typeMsg = if (types.isEmpty) "" else types.size + " types" + val termMsg = if (terms.isEmpty) "" else terms.size + " terms" + val implicitMsg = if (imps.isEmpty) "" else imps.size + " are implicit" + val foundMsg = if (found.isEmpty) "" else found.mkString(" // imports: ", ", ", "") + val statsMsg = List(typeMsg, termMsg, implicitMsg) filterNot (_ == "") mkString ("(", ", ", ")") + + intp.reporter.printMessage("%2d) %-30s %s%s".format( + idx + 1, + handler.importString, + statsMsg, + foundMsg + )) + } + } + + private def implicitsCommand(line: String): Result = { + val intp = SparkILoop.this.intp + import intp._ + import global.Symbol + + def p(x: Any) = intp.reporter.printMessage("" + x) + + // If an argument is given, only show a source with that + // in its name somewhere. + val args = line split "\\s+" + val filtered = intp.implicitSymbolsBySource filter { + case (source, syms) => + (args contains "-v") || { + if (line == "") (source.fullName.toString != "scala.Predef") + else (args exists (source.name.toString contains _)) + } + } + + if (filtered.isEmpty) + return "No implicits have been imported other than those in Predef." + + filtered foreach { + case (source, syms) => + p("/* " + syms.size + " implicit members imported from " + source.fullName + " */") + + // This groups the members by where the symbol is defined + val byOwner = syms groupBy (_.owner) + val sortedOwners = byOwner.toList sortBy { case (owner, _) => intp.afterTyper(source.info.baseClasses indexOf owner) } + + sortedOwners foreach { + case (owner, members) => + // Within each owner, we cluster results based on the final result type + // if there are more than a couple, and sort each cluster based on name. + // This is really just trying to make the 100 or so implicits imported + // by default into something readable. + val memberGroups: List[List[Symbol]] = { + val groups = members groupBy (_.tpe.finalResultType) toList + val (big, small) = groups partition (_._2.size > 3) + val xss = ( + (big sortBy (_._1.toString) map (_._2)) :+ + (small flatMap (_._2)) + ) + + xss map (xs => xs sortBy (_.name.toString)) + } + + val ownerMessage = if (owner == source) " defined in " else " inherited from " + p(" /* " + members.size + ownerMessage + owner.fullName + " */") + + memberGroups foreach { group => + group foreach (s => p(" " + intp.symbolDefString(s))) + p("") + } + } + p("") + } + } + + protected def newJavap() = new Javap(intp.classLoader, new SparkIMain.ReplStrippingWriter(intp)) { + override def tryClass(path: String): Array[Byte] = { + // Look for Foo first, then Foo$, but if Foo$ is given explicitly, + // we have to drop the $ to find object Foo, then tack it back onto + // the end of the flattened name. + def className = intp flatName path + def moduleName = (intp flatName path.stripSuffix("$")) + "$" + + val bytes = super.tryClass(className) + if (bytes.nonEmpty) bytes + else super.tryClass(moduleName) + } + } + private lazy val javap = + try newJavap() + catch { case _: Exception => null } + + private def typeCommand(line: String): Result = { + intp.typeOfExpression(line) match { + case Some(tp) => tp.toString + case _ => "Failed to determine type." + } + } + + private def javapCommand(line: String): Result = { + if (javap == null) + return ":javap unavailable on this platform." + if (line == "") + return ":javap [-lcsvp] [path1 path2 ...]" + + javap(words(line)) foreach { res => + if (res.isError) return "Failed: " + res.value + else res.show() + } + } + private def keybindingsCommand(): Result = { + if (in.keyBindings.isEmpty) "Key bindings unavailable." + else { + echo("Reading jline properties for default key bindings.") + echo("Accuracy not guaranteed: treat this as a guideline only.\n") + in.keyBindings foreach (x => echo ("" + x)) + } + } + private def wrapCommand(line: String): Result = { + def failMsg = "Argument to :wrap must be the name of a method with signature [T](=> T): T" + val intp = SparkILoop.this.intp + val g: intp.global.type = intp.global + import g._ + + words(line) match { + case Nil => + intp.executionWrapper match { + case "" => "No execution wrapper is set." + case s => "Current execution wrapper: " + s + } + case "clear" :: Nil => + intp.executionWrapper match { + case "" => "No execution wrapper is set." + case s => intp.clearExecutionWrapper() ; "Cleared execution wrapper." + } + case wrapper :: Nil => + intp.typeOfExpression(wrapper) match { + case Some(PolyType(List(targ), MethodType(List(arg), restpe))) => + intp setExecutionWrapper intp.pathToTerm(wrapper) + "Set wrapper to '" + wrapper + "'" + case Some(x) => + failMsg + "\nFound: " + x + case _ => + failMsg + "\nFound: <unknown>" + } + case _ => failMsg + } + } + + private def pathToPhaseWrapper = intp.pathToTerm("$r") + ".phased.atCurrent" + /* + private def phaseCommand(name: String): Result = { + // This line crashes us in TreeGen: + // + // if (intp.power.phased set name) "..." + // + // Exception in thread "main" java.lang.AssertionError: assertion failed: ._7.type + // at scala.Predef$.assert(Predef.scala:99) + // at scala.tools.nsc.ast.TreeGen.mkAttributedQualifier(TreeGen.scala:69) + // at scala.tools.nsc.ast.TreeGen.mkAttributedQualifier(TreeGen.scala:44) + // at scala.tools.nsc.ast.TreeGen.mkAttributedRef(TreeGen.scala:101) + // at scala.tools.nsc.ast.TreeGen.mkAttributedStableRef(TreeGen.scala:143) + // + // But it works like so, type annotated. + val phased: Phased = power.phased + import phased.NoPhaseName + + if (name == "clear") { + phased.set(NoPhaseName) + intp.clearExecutionWrapper() + "Cleared active phase." + } + else if (name == "") phased.get match { + case NoPhaseName => "Usage: :phase <expr> (e.g. typer, erasure.next, erasure+3)" + case ph => "Active phase is '%s'. (To clear, :phase clear)".format(phased.get) + } + else { + val what = phased.parse(name) + if (what.isEmpty || !phased.set(what)) + "'" + name + "' does not appear to represent a valid phase." + else { + intp.setExecutionWrapper(pathToPhaseWrapper) + val activeMessage = + if (what.toString.length == name.length) "" + what + else "%s (%s)".format(what, name) + + "Active phase is now: " + activeMessage + } + } + } + */ + + /** Available commands */ + def commands: List[LoopCommand] = standardCommands /* ++ ( + if (isReplPower) powerCommands else Nil + )*/ + + val replayQuestionMessage = + """|The repl compiler has crashed spectacularly. Shall I replay your + |session? I can re-run all lines except the last one. + |[y/n] + """.trim.stripMargin + + private val crashRecovery: PartialFunction[Throwable, Unit] = { + case ex: Throwable => + if (settings.YrichExes.value) { + val sources = implicitly[Sources] + echo("\n" + ex.getMessage) + echo( + if (isReplDebug) "[searching " + sources.path + " for exception contexts...]" + else "[searching for exception contexts...]" + ) + echo(Exceptional(ex).force().context()) + } + else { + echo(util.stackTraceString(ex)) + } + ex match { + case _: NoSuchMethodError | _: NoClassDefFoundError => + echo("Unrecoverable error.") + throw ex + case _ => + def fn(): Boolean = in.readYesOrNo(replayQuestionMessage, { echo("\nYou must enter y or n.") ; fn() }) + if (fn()) replay() + else echo("\nAbandoning crashed session.") + } + } + + /** The main read-eval-print loop for the repl. It calls + * command() for each line of input, and stops when + * command() returns false. + */ + def loop() { + def readOneLine() = { + out.flush() + in readLine prompt + } + // return false if repl should exit + def processLine(line: String): Boolean = + if (line eq null) false // assume null means EOF + else command(line) match { + case Result(false, _) => false + case Result(_, Some(finalLine)) => addReplay(finalLine) ; true + case _ => true + } + + while (true) { + try if (!processLine(readOneLine)) return + catch crashRecovery + } + } + + /** interpret all lines from a specified file */ + def interpretAllFrom(file: File) { + val oldIn = in + val oldReplay = replayCommandStack + + try file applyReader { reader => + in = SimpleReader(reader, out, false) + echo("Loading " + file + "...") + loop() + } + finally { + in = oldIn + replayCommandStack = oldReplay + } + } + + /** create a new interpreter and replay all commands so far */ + def replay() { + closeInterpreter() + createInterpreter() + for (cmd <- replayCommands) { + echo("Replaying: " + cmd) // flush because maybe cmd will have its own output + command(cmd) + echo("") + } + } + + /** fork a shell and run a command */ + lazy val shCommand = new LoopCommand("sh", "run a shell command (result is implicitly => List[String])") { + override def usage = "<command line>" + def apply(line: String): Result = line match { + case "" => showUsage() + case _ => + val toRun = classOf[ProcessResult].getName + "(" + string2codeQuoted(line) + ")" + intp interpret toRun + () + } + } + + def withFile(filename: String)(action: File => Unit) { + val f = File(filename) + + if (f.exists) action(f) + else echo("That file does not exist") + } + + def loadCommand(arg: String) = { + var shouldReplay: Option[String] = None + withFile(arg)(f => { + interpretAllFrom(f) + shouldReplay = Some(":load " + arg) + }) + Result(true, shouldReplay) + } + + def addClasspath(arg: String): Unit = { + val f = File(arg).normalize + if (f.exists) { + addedClasspath = ClassPath.join(addedClasspath, f.path) + val totalClasspath = ClassPath.join(settings.classpath.value, addedClasspath) + echo("Added '%s'. Your new classpath is:\n\"%s\"".format(f.path, totalClasspath)) + replay() + } + else echo("The path '" + f + "' doesn't seem to exist.") + } + + def powerCmd(): Result = { + if (isReplPower) "Already in power mode." + else enablePowerMode() + } + def enablePowerMode() = { + //replProps.power setValue true + //power.unleash() + //echo(power.banner) + } + + def verbosity() = { + val old = intp.printResults + intp.printResults = !old + echo("Switched " + (if (old) "off" else "on") + " result printing.") + } + + /** Run one command submitted by the user. Two values are returned: + * (1) whether to keep running, (2) the line to record for replay, + * if any. */ + def command(line: String): Result = { + if (line startsWith ":") { + val cmd = line.tail takeWhile (x => !x.isWhitespace) + uniqueCommand(cmd) match { + case Some(lc) => lc(line.tail stripPrefix cmd dropWhile (_.isWhitespace)) + case _ => ambiguousError(cmd) + } + } + else if (intp.global == null) Result(false, None) // Notice failure to create compiler + else Result(true, interpretStartingWith(line)) + } + + private def readWhile(cond: String => Boolean) = { + Iterator continually in.readLine("") takeWhile (x => x != null && cond(x)) + } + + def pasteCommand(): Result = { + echo("// Entering paste mode (ctrl-D to finish)\n") + val code = readWhile(_ => true) mkString "\n" + echo("\n// Exiting paste mode, now interpreting.\n") + intp interpret code + () + } + + private object paste extends Pasted { + val ContinueString = " | " + val PromptString = "scala> " + + def interpret(line: String): Unit = { + echo(line.trim) + intp interpret line + echo("") + } + + def transcript(start: String) = { + // Printing this message doesn't work very well because it's buried in the + // transcript they just pasted. Todo: a short timer goes off when + // lines stop coming which tells them to hit ctrl-D. + // + // echo("// Detected repl transcript paste: ctrl-D to finish.") + apply(Iterator(start) ++ readWhile(_.trim != PromptString.trim)) + } + } + import paste.{ ContinueString, PromptString } + + /** Interpret expressions starting with the first line. + * Read lines until a complete compilation unit is available + * or until a syntax error has been seen. If a full unit is + * read, go ahead and interpret it. Return the full string + * to be recorded for replay, if any. + */ + def interpretStartingWith(code: String): Option[String] = { + // signal completion non-completion input has been received + in.completion.resetVerbosity() + + def reallyInterpret = { + val reallyResult = intp.interpret(code) + (reallyResult, reallyResult match { + case IR.Error => None + case IR.Success => Some(code) + case IR.Incomplete => + if (in.interactive && code.endsWith("\n\n")) { + echo("You typed two blank lines. Starting a new command.") + None + } + else in.readLine(ContinueString) match { + case null => + // we know compilation is going to fail since we're at EOF and the + // parser thinks the input is still incomplete, but since this is + // a file being read non-interactively we want to fail. So we send + // it straight to the compiler for the nice error message. + intp.compileString(code) + None + + case line => interpretStartingWith(code + "\n" + line) + } + }) + } + + /** Here we place ourselves between the user and the interpreter and examine + * the input they are ostensibly submitting. We intervene in several cases: + * + * 1) If the line starts with "scala> " it is assumed to be an interpreter paste. + * 2) If the line starts with "." (but not ".." or "./") it is treated as an invocation + * on the previous result. + * 3) If the Completion object's execute returns Some(_), we inject that value + * and avoid the interpreter, as it's likely not valid scala code. + */ + if (code == "") None + else if (!paste.running && code.trim.startsWith(PromptString)) { + paste.transcript(code) + None + } + else if (Completion.looksLikeInvocation(code) && intp.mostRecentVar != "") { + interpretStartingWith(intp.mostRecentVar + code) + } + else { + def runCompletion = in.completion execute code map (intp bindValue _) + /** Due to my accidentally letting file completion execution sneak ahead + * of actual parsing this now operates in such a way that the scala + * interpretation always wins. However to avoid losing useful file + * completion I let it fail and then check the others. So if you + * type /tmp it will echo a failure and then give you a Directory object. + * It's not pretty: maybe I'll implement the silence bits I need to avoid + * echoing the failure. + */ + if (intp isParseable code) { + val (code, result) = reallyInterpret + //if (power != null && code == IR.Error) + // runCompletion + + result + } + else runCompletion match { + case Some(_) => None // completion hit: avoid the latent error + case _ => reallyInterpret._2 // trigger the latent error + } + } + } + + // runs :load `file` on any files passed via -i + def loadFiles(settings: Settings) = settings match { + case settings: GenericRunnerSettings => + for (filename <- settings.loadfiles.value) { + val cmd = ":load " + filename + command(cmd) + addReplay(cmd) + echo("") + } + case _ => + } + + /** Tries to create a JLineReader, falling back to SimpleReader: + * unless settings or properties are such that it should start + * with SimpleReader. + */ + def chooseReader(settings: Settings): InteractiveReader = { + if (settings.Xnojline.value || Properties.isEmacsShell) + SimpleReader() + else try JLineReader( + if (settings.noCompletion.value) NoCompletion + else new JLineCompletion(intp) + ) + catch { + case ex @ (_: Exception | _: NoClassDefFoundError) => + echo("Failed to created JLineReader: " + ex + "\nFalling back to SimpleReader.") + SimpleReader() + } + } + + def initializeSpark() { + intp.beQuietDuring { + command(""" + spark.repl.Main.interp.out.println("Creating SparkContext..."); + spark.repl.Main.interp.out.flush(); + @transient val sc = spark.repl.Main.interp.createSparkContext(); + sc.waitForRegister(); + spark.repl.Main.interp.out.println("Spark context available as sc."); + spark.repl.Main.interp.out.flush(); + """) + command("import spark.SparkContext._"); + } + echo("Type in expressions to have them evaluated.") + echo("Type :help for more information.") + } + + var sparkContext: SparkContext = null + + def createSparkContext(): SparkContext = { + val master = this.master match { + case Some(m) => m + case None => { + val prop = System.getenv("MASTER") + if (prop != null) prop else "local" + } + } + sparkContext = new SparkContext(master, "Spark shell") + sparkContext + } + + def process(settings: Settings): Boolean = { + this.settings = settings + createInterpreter() + + // sets in to some kind of reader depending on environmental cues + in = in0 match { + case Some(reader) => SimpleReader(reader, out, true) + case None => chooseReader(settings) + } + + loadFiles(settings) + // it is broken on startup; go ahead and exit + if (intp.reporter.hasErrors) + return false + + printWelcome() + try { + // this is about the illusion of snappiness. We call initialize() + // which spins off a separate thread, then print the prompt and try + // our best to look ready. Ideally the user will spend a + // couple seconds saying "wow, it starts so fast!" and by the time + // they type a command the compiler is ready to roll. + intp.initialize() + initializeSpark() + if (isReplPower) { + echo("Starting in power mode, one moment...\n") + enablePowerMode() + } + loop() + } + finally closeInterpreter() + true + } + + /** process command-line arguments and do as they request */ + def process(args: Array[String]): Boolean = { + val command = new CommandLine(args.toList, msg => echo("scala: " + msg)) + def neededHelp(): String = + (if (command.settings.help.value) command.usageMsg + "\n" else "") + + (if (command.settings.Xhelp.value) command.xusageMsg + "\n" else "") + + // if they asked for no help and command is valid, we call the real main + neededHelp() match { + case "" => command.ok && process(command.settings) + case help => echoNoNL(help) ; true + } + } + + @deprecated("Use `process` instead", "2.9.0") + def main(args: Array[String]): Unit = { + if (isReplDebug) + System.out.println(new java.util.Date) + + process(args) + } + @deprecated("Use `process` instead", "2.9.0") + def main(settings: Settings): Unit = process(settings) +} + +object SparkILoop { + implicit def loopToInterpreter(repl: SparkILoop): SparkIMain = repl.intp + private def echo(msg: String) = Console println msg + + // Designed primarily for use by test code: take a String with a + // bunch of code, and prints out a transcript of what it would look + // like if you'd just typed it into the repl. + def runForTranscript(code: String, settings: Settings): String = { + import java.io.{ BufferedReader, StringReader, OutputStreamWriter } + + stringFromStream { ostream => + Console.withOut(ostream) { + val output = new PrintWriter(new OutputStreamWriter(ostream), true) { + override def write(str: String) = { + // completely skip continuation lines + if (str forall (ch => ch.isWhitespace || ch == '|')) () + // print a newline on empty scala prompts + else if ((str contains '\n') && (str.trim == "scala> ")) super.write("\n") + else super.write(str) + } + } + val input = new BufferedReader(new StringReader(code)) { + override def readLine(): String = { + val s = super.readLine() + // helping out by printing the line being interpreted. + if (s != null) + output.println(s) + s + } + } + val repl = new SparkILoop(input, output) + if (settings.classpath.isDefault) + settings.classpath.value = sys.props("java.class.path") + + repl process settings + } + } + } + + /** Creates an interpreter loop with default settings and feeds + * the given code to it as input. + */ + def run(code: String, sets: Settings = new Settings): String = { + import java.io.{ BufferedReader, StringReader, OutputStreamWriter } + + stringFromStream { ostream => + Console.withOut(ostream) { + val input = new BufferedReader(new StringReader(code)) + val output = new PrintWriter(new OutputStreamWriter(ostream), true) + val repl = new SparkILoop(input, output) + + if (sets.classpath.isDefault) + sets.classpath.value = sys.props("java.class.path") + + repl process sets + } + } + } + def run(lines: List[String]): String = run(lines map (_ + "\n") mkString) + + // provide the enclosing type T + // in order to set up the interpreter's classpath and parent class loader properly + def breakIf[T: Manifest](assertion: => Boolean, args: NamedParam*): Unit = + if (assertion) break[T](args.toList) + + // start a repl, binding supplied args + def break[T: Manifest](args: List[NamedParam]): Unit = { + val msg = if (args.isEmpty) "" else " Binding " + args.size + " value%s.".format( + if (args.size == 1) "" else "s" + ) + echo("Debug repl starting." + msg) + val repl = new SparkILoop { + override def prompt = "\ndebug> " + } + repl.settings = new Settings(echo) + repl.settings.embeddedDefaults[T] + repl.createInterpreter() + repl.in = JLineReader(repl) + + // rebind exit so people don't accidentally call sys.exit by way of predef + repl.quietRun("""def exit = println("Type :quit to resume program execution.")""") + args foreach (p => repl.bind(p.name, p.tpe, p.value)) + repl.loop() + + echo("\nDebug repl exiting.") + repl.closeInterpreter() + } +} diff --git a/repl/src/main/scala/spark/repl/SparkIMain.scala b/repl/src/main/scala/spark/repl/SparkIMain.scala new file mode 100644 index 0000000000000000000000000000000000000000..1fdc4eaf8a4082f2c9d412080ff2354d269b7279 --- /dev/null +++ b/repl/src/main/scala/spark/repl/SparkIMain.scala @@ -0,0 +1,1131 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Martin Odersky + */ + +package spark.repl + +import scala.tools.nsc._ +import scala.tools.nsc.interpreter._ + +import Predef.{ println => _, _ } +import java.io.{ PrintWriter } +import java.lang.reflect +import java.net.URL +import util.{ Set => _, _ } +import io.{ AbstractFile, PlainFile, VirtualDirectory } +import reporters.{ ConsoleReporter, Reporter } +import symtab.{ Flags, Names } +import scala.tools.nsc.interpreter.{ Results => IR } +import scala.tools.util.PathResolver +import scala.tools.nsc.util.{ ScalaClassLoader, Exceptional } +import ScalaClassLoader.URLClassLoader +import Exceptional.unwrap +import scala.collection.{ mutable, immutable } +import scala.PartialFunction.{ cond, condOpt } +import scala.util.control.Exception.{ ultimately } +import scala.reflect.NameTransformer +import SparkIMain._ + +import spark.HttpServer +import spark.Utils + +/** An interpreter for Scala code. + * + * The main public entry points are compile(), interpret(), and bind(). + * The compile() method loads a complete Scala file. The interpret() method + * executes one line of Scala code at the request of the user. The bind() + * method binds an object to a variable that can then be used by later + * interpreted code. + * + * The overall approach is based on compiling the requested code and then + * using a Java classloader and Java reflection to run the code + * and access its results. + * + * In more detail, a single compiler instance is used + * to accumulate all successfully compiled or interpreted Scala code. To + * "interpret" a line of code, the compiler generates a fresh object that + * includes the line of code and which has public member(s) to export + * all variables defined by that code. To extract the result of an + * interpreted line to show the user, a second "result object" is created + * which imports the variables exported by the above object and then + * exports a single member named "$export". To accomodate user expressions + * that read from variables or methods defined in previous statements, "import" + * statements are used. + * + * This interpreter shares the strengths and weaknesses of using the + * full compiler-to-Java. The main strength is that interpreted code + * behaves exactly as does compiled code, including running at full speed. + * The main weakness is that redefining classes and methods is not handled + * properly, because rebinding at the Java level is technically difficult. + * + * @author Moez A. Abdel-Gawad + * @author Lex Spoon + */ +class SparkIMain(val settings: Settings, protected val out: PrintWriter) extends SparkImports { + imain => + + /** construct an interpreter that reports to Console */ + def this(settings: Settings) = this(settings, new NewLinePrintWriter(new ConsoleWriter, true)) + def this() = this(new Settings()) + + /** whether to print out result lines */ + var printResults: Boolean = true + + /** whether to print errors */ + var totalSilence: Boolean = false + + private val RESULT_OBJECT_PREFIX = "RequestResult$" + + lazy val formatting: Formatting = new Formatting { + val prompt = Properties.shellPromptString + } + import formatting._ + + val SPARK_DEBUG_REPL: Boolean = (System.getenv("SPARK_DEBUG_REPL") == "1") + + /** Local directory to save .class files too */ + val outputDir = { + val tmp = System.getProperty("java.io.tmpdir") + val rootDir = System.getProperty("spark.repl.classdir", tmp) + Utils.createTempDir(rootDir) + } + if (SPARK_DEBUG_REPL) { + echo("Output directory: " + outputDir) + } + + /** Scala compiler virtual directory for outputDir */ + val virtualDirectory = new PlainFile(outputDir) + + /** Jetty server that will serve our classes to worker nodes */ + val classServer = new HttpServer(outputDir) + + // Start the classServer and store its URI in a spark system property + // (which will be passed to executors so that they can connect to it) + classServer.start() + System.setProperty("spark.repl.class.uri", classServer.uri) + if (SPARK_DEBUG_REPL) { + echo("Class server started, URI = " + classServer.uri) + } + + /* + // directory to save .class files to + val virtualDirectory = new VirtualDirectory("(memory)", None) { + private def pp(root: io.AbstractFile, indentLevel: Int) { + val spaces = " " * indentLevel + out.println(spaces + root.name) + if (root.isDirectory) + root.toList sortBy (_.name) foreach (x => pp(x, indentLevel + 1)) + } + // print the contents hierarchically + def show() = pp(this, 0) + } + */ + + /** reporter */ + lazy val reporter: ConsoleReporter = new SparkIMain.ReplReporter(this) + import reporter.{ printMessage, withoutTruncating } + + // not sure if we have some motivation to print directly to console + private def echo(msg: String) { Console println msg } + + // protected def defaultImports: List[String] = List("_root_.scala.sys.exit") + + /** We're going to go to some trouble to initialize the compiler asynchronously. + * It's critical that nothing call into it until it's been initialized or we will + * run into unrecoverable issues, but the perceived repl startup time goes + * through the roof if we wait for it. So we initialize it with a future and + * use a lazy val to ensure that any attempt to use the compiler object waits + * on the future. + */ + private val _compiler: Global = newCompiler(settings, reporter) + private var _initializeComplete = false + def isInitializeComplete = _initializeComplete + + private def _initialize(): Boolean = { + val source = """ + |class $repl_$init { + | List(1) map (_ + 1) + |} + |""".stripMargin + + val result = try { + new _compiler.Run() compileSources List(new BatchSourceFile("<init>", source)) + if (isReplDebug || settings.debug.value) { + // Can't use printMessage here, it deadlocks + Console.println("Repl compiler initialized.") + } + // addImports(defaultImports: _*) + true + } + catch { + case x: AbstractMethodError => + printMessage(""" + |Failed to initialize compiler: abstract method error. + |This is most often remedied by a full clean and recompile. + |""".stripMargin + ) + x.printStackTrace() + false + case x: MissingRequirementError => printMessage(""" + |Failed to initialize compiler: %s not found. + |** Note that as of 2.8 scala does not assume use of the java classpath. + |** For the old behavior pass -usejavacp to scala, or if using a Settings + |** object programatically, settings.usejavacp.value = true.""".stripMargin.format(x.req) + ) + false + } + + try result + finally _initializeComplete = result + } + + // set up initialization future + private var _isInitialized: () => Boolean = null + def initialize() = synchronized { + if (_isInitialized == null) + _isInitialized = scala.concurrent.ops future _initialize() + } + + /** the public, go through the future compiler */ + lazy val global: Global = { + initialize() + + // blocks until it is ; false means catastrophic failure + if (_isInitialized()) _compiler + else null + } + @deprecated("Use `global` for access to the compiler instance.", "2.9.0") + lazy val compiler: global.type = global + + import global._ + + object naming extends { + val global: imain.global.type = imain.global + } with Naming { + // make sure we don't overwrite their unwisely named res3 etc. + override def freshUserVarName(): String = { + val name = super.freshUserVarName() + if (definedNameMap contains name) freshUserVarName() + else name + } + } + import naming._ + + // object dossiers extends { + // val intp: imain.type = imain + // } with Dossiers { } + // import dossiers._ + + lazy val memberHandlers = new { + val intp: imain.type = imain + } with SparkMemberHandlers + import memberHandlers._ + + def atPickler[T](op: => T): T = atPhase(currentRun.picklerPhase)(op) + def afterTyper[T](op: => T): T = atPhase(currentRun.typerPhase.next)(op) + + /** Temporarily be quiet */ + def beQuietDuring[T](operation: => T): T = { + val wasPrinting = printResults + ultimately(printResults = wasPrinting) { + if (isReplDebug) echo(">> beQuietDuring") + else printResults = false + + operation + } + } + def beSilentDuring[T](operation: => T): T = { + val saved = totalSilence + totalSilence = true + try operation + finally totalSilence = saved + } + + def quietRun[T](code: String) = beQuietDuring(interpret(code)) + + /** whether to bind the lastException variable */ + private var bindLastException = true + + /** A string representing code to be wrapped around all lines. */ + private var _executionWrapper: String = "" + def executionWrapper = _executionWrapper + def setExecutionWrapper(code: String) = _executionWrapper = code + def clearExecutionWrapper() = _executionWrapper = "" + + /** Temporarily stop binding lastException */ + def withoutBindingLastException[T](operation: => T): T = { + val wasBinding = bindLastException + ultimately(bindLastException = wasBinding) { + bindLastException = false + operation + } + } + + protected def createLineManager(): Line.Manager = new Line.Manager + lazy val lineManager = createLineManager() + + /** interpreter settings */ + lazy val isettings = new SparkISettings(this) + + /** Instantiate a compiler. Subclasses can override this to + * change the compiler class used by this interpreter. */ + protected def newCompiler(settings: Settings, reporter: Reporter) = { + settings.outputDirs setSingleOutput virtualDirectory + settings.exposeEmptyPackage.value = true + new Global(settings, reporter) + } + + /** the compiler's classpath, as URL's */ + lazy val compilerClasspath: List[URL] = new PathResolver(settings) asURLs + + /* A single class loader is used for all commands interpreted by this Interpreter. + It would also be possible to create a new class loader for each command + to interpret. The advantages of the current approach are: + + - Expressions are only evaluated one time. This is especially + significant for I/O, e.g. "val x = Console.readLine" + + The main disadvantage is: + + - Objects, classes, and methods cannot be rebound. Instead, definitions + shadow the old ones, and old code objects refer to the old + definitions. + */ + private var _classLoader: AbstractFileClassLoader = null + def resetClassLoader() = _classLoader = makeClassLoader() + def classLoader: AbstractFileClassLoader = { + if (_classLoader == null) + resetClassLoader() + + _classLoader + } + private def makeClassLoader(): AbstractFileClassLoader = { + val parent = + if (parentClassLoader == null) ScalaClassLoader fromURLs compilerClasspath + else new URLClassLoader(compilerClasspath, parentClassLoader) + + new AbstractFileClassLoader(virtualDirectory, parent) { + /** Overridden here to try translating a simple name to the generated + * class name if the original attempt fails. This method is used by + * getResourceAsStream as well as findClass. + */ + override protected def findAbstractFile(name: String): AbstractFile = { + super.findAbstractFile(name) match { + // deadlocks on startup if we try to translate names too early + case null if isInitializeComplete => generatedName(name) map (x => super.findAbstractFile(x)) orNull + case file => file + } + } + } + } + private def loadByName(s: String): JClass = + (classLoader tryToInitializeClass s) getOrElse sys.error("Failed to load expected class: '" + s + "'") + + protected def parentClassLoader: ClassLoader = + SparkHelper.explicitParentLoader(settings).getOrElse( this.getClass.getClassLoader() ) + + def getInterpreterClassLoader() = classLoader + + // Set the current Java "context" class loader to this interpreter's class loader + def setContextClassLoader() = classLoader.setAsContext() + + /** Given a simple repl-defined name, returns the real name of + * the class representing it, e.g. for "Bippy" it may return + * + * $line19.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$Bippy + */ + def generatedName(simpleName: String): Option[String] = { + if (simpleName endsWith "$") optFlatName(simpleName.init) map (_ + "$") + else optFlatName(simpleName) + } + def flatName(id: String) = optFlatName(id) getOrElse id + def optFlatName(id: String) = requestForIdent(id) map (_ fullFlatName id) + + def allDefinedNames = definedNameMap.keys.toList sortBy (_.toString) + def pathToType(id: String): String = pathToName(newTypeName(id)) + def pathToTerm(id: String): String = pathToName(newTermName(id)) + def pathToName(name: Name): String = { + if (definedNameMap contains name) + definedNameMap(name) fullPath name + else name.toString + } + + /** Most recent tree handled which wasn't wholly synthetic. */ + private def mostRecentlyHandledTree: Option[Tree] = { + prevRequests.reverse foreach { req => + req.handlers.reverse foreach { + case x: MemberDefHandler if x.definesValue && !isInternalVarName(x.name) => return Some(x.member) + case _ => () + } + } + None + } + + /** Stubs for work in progress. */ + def handleTypeRedefinition(name: TypeName, old: Request, req: Request) = { + for (t1 <- old.simpleNameOfType(name) ; t2 <- req.simpleNameOfType(name)) { + DBG("Redefining type '%s'\n %s -> %s".format(name, t1, t2)) + } + } + + def handleTermRedefinition(name: TermName, old: Request, req: Request) = { + for (t1 <- old.compilerTypeOf get name ; t2 <- req.compilerTypeOf get name) { + // Printing the types here has a tendency to cause assertion errors, like + // assertion failed: fatal: <refinement> has owner value x, but a class owner is required + // so DBG is by-name now to keep it in the family. (It also traps the assertion error, + // but we don't want to unnecessarily risk hosing the compiler's internal state.) + DBG("Redefining term '%s'\n %s -> %s".format(name, t1, t2)) + } + } + def recordRequest(req: Request) { + if (req == null || referencedNameMap == null) + return + + prevRequests += req + req.referencedNames foreach (x => referencedNameMap(x) = req) + + // warning about serially defining companions. It'd be easy + // enough to just redefine them together but that may not always + // be what people want so I'm waiting until I can do it better. + if (!settings.nowarnings.value) { + for { + name <- req.definedNames filterNot (x => req.definedNames contains x.companionName) + oldReq <- definedNameMap get name.companionName + newSym <- req.definedSymbols get name + oldSym <- oldReq.definedSymbols get name.companionName + } { + printMessage("warning: previously defined %s is not a companion to %s.".format(oldSym, newSym)) + printMessage("Companions must be defined together; you may wish to use :paste mode for this.") + } + } + + // Updating the defined name map + req.definedNames foreach { name => + if (definedNameMap contains name) { + if (name.isTypeName) handleTypeRedefinition(name.toTypeName, definedNameMap(name), req) + else handleTermRedefinition(name.toTermName, definedNameMap(name), req) + } + definedNameMap(name) = req + } + } + + /** Parse a line into a sequence of trees. Returns None if the input is incomplete. */ + def parse(line: String): Option[List[Tree]] = { + var justNeedsMore = false + reporter.withIncompleteHandler((pos,msg) => {justNeedsMore = true}) { + // simple parse: just parse it, nothing else + def simpleParse(code: String): List[Tree] = { + reporter.reset() + val unit = new CompilationUnit(new BatchSourceFile("<console>", code)) + val scanner = new syntaxAnalyzer.UnitParser(unit) + + scanner.templateStatSeq(false)._2 + } + val trees = simpleParse(line) + + if (reporter.hasErrors) Some(Nil) // the result did not parse, so stop + else if (justNeedsMore) None + else Some(trees) + } + } + + def isParseable(line: String): Boolean = { + beSilentDuring { + parse(line) match { + case Some(xs) => xs.nonEmpty // parses as-is + case None => true // incomplete + } + } + } + + /** Compile an nsc SourceFile. Returns true if there are + * no compilation errors, or false otherwise. + */ + def compileSources(sources: SourceFile*): Boolean = { + reporter.reset() + new Run() compileSources sources.toList + !reporter.hasErrors + } + + /** Compile a string. Returns true if there are no + * compilation errors, or false otherwise. + */ + def compileString(code: String): Boolean = + compileSources(new BatchSourceFile("<script>", code)) + + /** Build a request from the user. `trees` is `line` after being parsed. + */ + private def buildRequest(line: String, trees: List[Tree]): Request = new Request(line, trees) + + private def requestFromLine(line: String, synthetic: Boolean): Either[IR.Result, Request] = { + val trees = parse(indentCode(line)) match { + case None => return Left(IR.Incomplete) + case Some(Nil) => return Left(IR.Error) // parse error or empty input + case Some(trees) => trees + } + + // use synthetic vars to avoid filling up the resXX slots + def varName = if (synthetic) freshInternalVarName() else freshUserVarName() + + // Treat a single bare expression specially. This is necessary due to it being hard to + // modify code at a textual level, and it being hard to submit an AST to the compiler. + if (trees.size == 1) trees.head match { + case _:Assign => // we don't want to include assignments + case _:TermTree | _:Ident | _:Select => // ... but do want these as valdefs. + requestFromLine("val %s =\n%s".format(varName, line), synthetic) match { + case Right(req) => return Right(req withOriginalLine line) + case x => return x + } + case _ => + } + + // figure out what kind of request + Right(buildRequest(line, 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 = interpret(line, false) + def interpret(line: String, synthetic: Boolean): IR.Result = { + def loadAndRunReq(req: Request) = { + val (result, succeeded) = req.loadAndRun + /** To our displeasure, ConsoleReporter offers only printMessage, + * which tacks a newline on the end. Since that breaks all the + * output checking, we have to take one off to balance. + */ + def show() = { + if (result == "") () + else printMessage(result stripSuffix "\n") + } + + if (succeeded) { + if (printResults) + show() + // Book-keeping. Have to record synthetic requests too, + // as they may have been issued for information, e.g. :type + recordRequest(req) + IR.Success + } + else { + // don't truncate stack traces + withoutTruncating(show()) + IR.Error + } + } + + if (global == null) IR.Error + else requestFromLine(line, synthetic) match { + case Left(result) => result + case Right(req) => + // null indicates a disallowed statement type; otherwise compile and + // fail if false (implying e.g. a type error) + if (req == null || !req.compile) IR.Error + else loadAndRunReq(req) + } + } + + /** Bind a specified name to a specified value. The name may + * later be used by expressions passed to interpret. + * + * @param name the variable name to bind + * @param boundType the type of the variable, as a string + * @param value the object value to bind to it + * @return an indication of whether the binding succeeded + */ + def bind(name: String, boundType: String, value: Any): IR.Result = { + val bindRep = new ReadEvalPrint() + val run = bindRep.compile(""" + |object %s { + | var value: %s = _ + | def set(x: Any) = value = x.asInstanceOf[%s] + |} + """.stripMargin.format(bindRep.evalName, boundType, boundType) + ) + bindRep.callOpt("set", value) match { + case Some(_) => interpret("val %s = %s.value".format(name, bindRep.evalPath)) + case _ => DBG("Set failed in bind(%s, %s, %s)".format(name, boundType, value)) ; IR.Error + } + } + def rebind(p: NamedParam): IR.Result = { + val name = p.name + val oldType = typeOfTerm(name) getOrElse { return IR.Error } + val newType = p.tpe + val tempName = freshInternalVarName() + + quietRun("val %s = %s".format(tempName, name)) + quietRun("val %s = %s.asInstanceOf[%s]".format(name, tempName, newType)) + } + def quietImport(ids: String*): IR.Result = beQuietDuring(addImports(ids: _*)) + def addImports(ids: String*): IR.Result = + if (ids.isEmpty) IR.Success + else interpret("import " + ids.mkString(", ")) + + def quietBind(p: NamedParam): IR.Result = beQuietDuring(bind(p)) + def bind(p: NamedParam): IR.Result = bind(p.name, p.tpe, p.value) + def bind[T: Manifest](name: String, value: T): IR.Result = bind((name, value)) + def bindValue(x: Any): IR.Result = bind(freshUserVarName(), TypeStrings.fromValue(x), x) + + /** Reset this interpreter, forgetting all user-specified requests. */ + def reset() { + //virtualDirectory.clear() + virtualDirectory.delete() + virtualDirectory.create() + resetClassLoader() + resetAllCreators() + prevRequests.clear() + } + + /** This instance is no longer needed, so release any resources + * it is using. The reporter's output gets flushed. + */ + def close() { + reporter.flush() + } + + /** Here is where we: + * + * 1) Read some source code, and put it in the "read" object. + * 2) Evaluate the read object, and put the result in the "eval" object. + * 3) Create a String for human consumption, and put it in the "print" object. + * + * Read! Eval! Print! Some of that not yet centralized here. + */ + class ReadEvalPrint(lineId: Int) { + def this() = this(freshLineId()) + + val packageName = "$line" + lineId + val readName = "$read" + val evalName = "$eval" + val printName = "$print" + val valueMethod = "$result" // no-args method giving result + + // TODO: split it out into a package object and a regular + // object and we can do that much less wrapping. + def packageDecl = "package " + packageName + + def pathTo(name: String) = packageName + "." + name + def packaged(code: String) = packageDecl + "\n\n" + code + + def readPath = pathTo(readName) + def evalPath = pathTo(evalName) + def printPath = pathTo(printName) + + def call(name: String, args: Any*): AnyRef = + evalMethod(name).invoke(evalClass, args.map(_.asInstanceOf[AnyRef]): _*) + + def callOpt(name: String, args: Any*): Option[AnyRef] = + try Some(call(name, args: _*)) + catch { case ex: Exception => + quietBind("lastException", ex) + None + } + + lazy val evalClass = loadByName(evalPath) + lazy val evalValue = callOpt(valueMethod) + + def compile(source: String): Boolean = compileAndSaveRun("<console>", source) + def lineAfterTyper[T](op: => T): T = { + assert(lastRun != null, "Internal error: trying to use atPhase, but Run is null." + this) + atPhase(lastRun.typerPhase.next)(op) + } + + /** The innermost object inside the wrapper, found by + * following accessPath into the outer one. + */ + def resolvePathToSymbol(accessPath: String): Symbol = { + val readRoot = definitions.getModule(readPath) // the outermost wrapper + (accessPath split '.').foldLeft(readRoot) { (sym, name) => + if (name == "") sym else + lineAfterTyper(sym.info member newTermName(name)) + } + } + + // def compileAndTypeExpr(expr: String): Option[Typer] = { + // class TyperRun extends Run { + // override def stopPhase(name: String) = name == "superaccessors" + // } + // } + private var lastRun: Run = _ + private def evalMethod(name: String) = { + val methods = evalClass.getMethods filter (_.getName == name) + assert(methods.size == 1, "Internal error - eval object method " + name + " is overloaded: " + methods) + methods.head + } + private def compileAndSaveRun(label: String, code: String) = { + showCodeIfDebugging(code) + reporter.reset() + lastRun = new Run() + lastRun.compileSources(List(new BatchSourceFile(label, packaged(code)))) + !reporter.hasErrors + } + } + + /** One line of code submitted by the user for interpretation */ + // private + class Request(val line: String, val trees: List[Tree]) { + val lineRep = new ReadEvalPrint() + import lineRep.lineAfterTyper + + private var _originalLine: String = null + def withOriginalLine(s: String): this.type = { _originalLine = s ; this } + def originalLine = if (_originalLine == null) line else _originalLine + + /** handlers for each tree in this request */ + val handlers: List[MemberHandler] = trees map (memberHandlers chooseHandler _) + + /** all (public) names defined by these statements */ + val definedNames = handlers flatMap (_.definedNames) + + /** list of names used by this expression */ + val referencedNames: List[Name] = handlers flatMap (_.referencedNames) + + /** def and val names */ + def termNames = handlers flatMap (_.definesTerm) + def typeNames = handlers flatMap (_.definesType) + + /** Code to import bound names from previous lines - accessPath is code to + * append to objectName to access anything bound by request. + */ + val ComputedImports(importsPreamble, importsTrailer, accessPath) = + importsCode(referencedNames.toSet) + + /** Code to access a variable with the specified name */ + def fullPath(vname: String) = ( + lineRep.readPath + accessPath + ".`%s`".format(vname) + ) + /** Same as fullpath, but after it has been flattened, so: + * $line5.$iw.$iw.$iw.Bippy // fullPath + * $line5.$iw$$iw$$iw$Bippy // fullFlatName + */ + def fullFlatName(name: String) = + lineRep.readPath + accessPath.replace('.', '$') + "$" + name + + /** Code to access a variable with the specified name */ + def fullPath(vname: Name): String = fullPath(vname.toString) + + /** the line of code to compute */ + def toCompute = line + + /** generate the source code for the object that computes this request */ + private object ObjectSourceCode extends CodeAssembler[MemberHandler] { + val preamble = """ + |object %s { + | %s%s + """.stripMargin.format(lineRep.readName, importsPreamble, indentCode(toCompute)) + val postamble = importsTrailer + "\n}" + val generate = (m: MemberHandler) => m extraCodeToEvaluate Request.this + } + + private object ResultObjectSourceCode extends CodeAssembler[MemberHandler] { + /** We only want to generate this code when the result + * is a value which can be referred to as-is. + */ + val evalResult = + if (!handlers.last.definesValue) "" + else handlers.last.definesTerm match { + case Some(vname) if typeOf contains vname => + """ + |lazy val $result = { + | $export + | %s + |}""".stripMargin.format(fullPath(vname)) + case _ => "" + } + // first line evaluates object to make sure constructor is run + // initial "" so later code can uniformly be: + etc + val preamble = """ + |object %s { + | %s + | val $export: String = %s { + | %s + | ("" + """.stripMargin.format( + lineRep.evalName, evalResult, executionWrapper, lineRep.readName + accessPath + ) + + val postamble = """ + | ) + | } + |} + """.stripMargin + val generate = (m: MemberHandler) => m resultExtractionCode Request.this + } + + // get it + def getEvalTyped[T] : Option[T] = getEval map (_.asInstanceOf[T]) + def getEval: Option[AnyRef] = { + // ensure it has been compiled + compile + // try to load it and call the value method + lineRep.evalValue filterNot (_ == null) + } + + /** Compile the object file. Returns whether the compilation succeeded. + * If all goes well, the "types" map is computed. */ + lazy val compile: Boolean = { + // error counting is wrong, hence interpreter may overlook failure - so we reset + reporter.reset() + + // compile the object containing the user's code + lineRep.compile(ObjectSourceCode(handlers)) && { + // extract and remember types + typeOf + typesOfDefinedTerms + + // compile the result-extraction object + lineRep compile ResultObjectSourceCode(handlers) + } + } + + lazy val resultSymbol = lineRep.resolvePathToSymbol(accessPath) + def applyToResultMember[T](name: Name, f: Symbol => T) = lineAfterTyper(f(resultSymbol.info.nonPrivateDecl(name))) + + /* typeOf lookup with encoding */ + def lookupTypeOf(name: Name) = typeOf.getOrElse(name, typeOf(global.encode(name.toString))) + def simpleNameOfType(name: TypeName) = (compilerTypeOf get name) map (_.typeSymbol.simpleName) + + private def typeMap[T](f: Type => T): Map[Name, T] = { + def toType(name: Name): T = { + // the types are all =>T; remove the => + val tp1 = lineAfterTyper(resultSymbol.info.nonPrivateDecl(name).tpe match { + case NullaryMethodType(tp) => tp + case tp => tp + }) + // normalize non-public types so we don't see protected aliases like Self + lineAfterTyper(tp1 match { + case TypeRef(_, sym, _) if !sym.isPublic => f(tp1.normalize) + case tp => f(tp) + }) + } + termNames ++ typeNames map (x => x -> toType(x)) toMap + } + /** Types of variables defined by this request. */ + lazy val compilerTypeOf = typeMap[Type](x => x) + /** String representations of same. */ + lazy val typeOf = typeMap[String](_.toString) + + // lazy val definedTypes: Map[Name, Type] = { + // typeNames map (x => x -> afterTyper(resultSymbol.info.nonPrivateDecl(x).tpe)) toMap + // } + lazy val definedSymbols: Map[Name, Symbol] = ( + termNames.map(x => x -> applyToResultMember(x, x => x)) ++ + typeNames.map(x => x -> compilerTypeOf.get(x).map(_.typeSymbol).getOrElse(NoSymbol)) + ).toMap + + lazy val typesOfDefinedTerms: Map[Name, Type] = + termNames map (x => x -> applyToResultMember(x, _.tpe)) toMap + + private def bindExceptionally(t: Throwable) = { + val ex: Exceptional = + if (isettings.showInternalStackTraces) Exceptional(t) + else new Exceptional(t) { + override def spanFn(frame: JavaStackFrame) = !(frame.className startsWith lineRep.evalPath) + override def contextPrelude = super.contextPrelude + "/* The repl internal portion of the stack trace is elided. */\n" + } + + quietBind("lastException", ex) + ex.contextHead + "\n(access lastException for the full trace)" + } + private def bindUnexceptionally(t: Throwable) = { + quietBind("lastException", t) + stackTraceString(t) + } + + /** load and run the code using reflection */ + def loadAndRun: (String, Boolean) = { + import interpreter.Line._ + + def handleException(t: Throwable) = { + /** We turn off the binding to accomodate ticket #2817 */ + withoutBindingLastException { + val message = + if (opt.richExes) bindExceptionally(unwrap(t)) + else bindUnexceptionally(unwrap(t)) + + (message, false) + } + } + + try { + val execution = lineManager.set(originalLine)(lineRep call "$export") + execution.await() + + execution.state match { + case Done => ("" + execution.get(), true) + case Threw => + val ex = execution.caught() + if (isReplDebug) + ex.printStackTrace() + + if (bindLastException) handleException(ex) + else throw ex + case Cancelled => ("Execution interrupted by signal.\n", false) + case Running => ("Execution still running! Seems impossible.", false) + } + } + finally lineManager.clear() + } + + override def toString = "Request(line=%s, %s trees)".format(line, trees.size) + } + + /** Returns the name of the most recent interpreter result. + * Mostly this exists so you can conveniently invoke methods on + * the previous result. + */ + def mostRecentVar: String = + if (mostRecentlyHandledTree.isEmpty) "" + else "" + (mostRecentlyHandledTree.get match { + case x: ValOrDefDef => x.name + case Assign(Ident(name), _) => name + case ModuleDef(_, name, _) => name + case _ => naming.mostRecentVar + }) + + private def requestForName(name: Name): Option[Request] = { + assert(definedNameMap != null, "definedNameMap is null") + definedNameMap get name + } + + private def requestForIdent(line: String): Option[Request] = + requestForName(newTermName(line)) orElse requestForName(newTypeName(line)) + + def safeClass(name: String): Option[Symbol] = { + try Some(definitions.getClass(newTypeName(name))) + catch { case _: MissingRequirementError => None } + } + def safeModule(name: String): Option[Symbol] = { + try Some(definitions.getModule(newTermName(name))) + catch { case _: MissingRequirementError => None } + } + + def definitionForName(name: Name): Option[MemberHandler] = + requestForName(name) flatMap { req => + req.handlers find (_.definedNames contains name) + } + + def valueOfTerm(id: String): Option[AnyRef] = + requestForIdent(id) flatMap (_.getEval) + + def classOfTerm(id: String): Option[JClass] = + valueOfTerm(id) map (_.getClass) + + def typeOfTerm(id: String): Option[Type] = newTermName(id) match { + case nme.ROOTPKG => Some(definitions.RootClass.tpe) + case name => requestForName(name) flatMap (_.compilerTypeOf get name) + } + def symbolOfTerm(id: String): Symbol = + requestForIdent(id) flatMap (_.definedSymbols get newTermName(id)) getOrElse NoSymbol + + def runtimeClassAndTypeOfTerm(id: String): Option[(JClass, Type)] = { + for { + clazz <- classOfTerm(id) + tpe <- runtimeTypeOfTerm(id) + nonAnon <- new RichClass(clazz).supers.find(c => !(new RichClass(c).isScalaAnonymous)) + } yield { + (nonAnon, tpe) + } + } + + def runtimeTypeOfTerm(id: String): Option[Type] = { + for { + tpe <- typeOfTerm(id) + clazz <- classOfTerm(id) + val staticSym = tpe.typeSymbol + runtimeSym <- safeClass(clazz.getName) + if runtimeSym != staticSym + if runtimeSym isSubClass staticSym + } yield { + runtimeSym.info + } + } + + // XXX literals. + // 1) Identifiers defined in the repl. + // 2) A path loadable via getModule. + // 3) Try interpreting it as an expression. + private var typeOfExpressionDepth = 0 + def typeOfExpression(expr: String): Option[Type] = { + DBG("typeOfExpression(" + expr + ")") + if (typeOfExpressionDepth > 2) { + DBG("Terminating typeOfExpression recursion for expression: " + expr) + return None + } + + def asQualifiedImport = { + val name = expr.takeWhile(_ != '.') + importedTermNamed(name) flatMap { sym => + typeOfExpression(sym.fullName + expr.drop(name.length)) + } + } + def asModule = safeModule(expr) map (_.tpe) + def asExpr = beSilentDuring { + val lhs = freshInternalVarName() + val line = "lazy val " + lhs + " = { " + expr + " } " + + interpret(line, true) match { + case IR.Success => typeOfExpression(lhs) + case _ => None + } + } + + typeOfExpressionDepth += 1 + try typeOfTerm(expr) orElse asModule orElse asExpr orElse asQualifiedImport + finally typeOfExpressionDepth -= 1 + } + // def compileAndTypeExpr(expr: String): Option[Typer] = { + // class TyperRun extends Run { + // override def stopPhase(name: String) = name == "superaccessors" + // } + // } + + protected def onlyTerms(xs: List[Name]) = xs collect { case x: TermName => x } + protected def onlyTypes(xs: List[Name]) = xs collect { case x: TypeName => x } + + def definedTerms = onlyTerms(allDefinedNames) filterNot isInternalVarName + def definedTypes = onlyTypes(allDefinedNames) + def definedSymbols = prevRequests.toSet flatMap ((x: Request) => x.definedSymbols.values) + + /** the previous requests this interpreter has processed */ + private lazy val prevRequests = mutable.ListBuffer[Request]() + private lazy val referencedNameMap = mutable.Map[Name, Request]() + private lazy val definedNameMap = mutable.Map[Name, Request]() + protected def prevRequestList = prevRequests.toList + private def allHandlers = prevRequestList flatMap (_.handlers) + def allSeenTypes = prevRequestList flatMap (_.typeOf.values.toList) distinct + def allImplicits = allHandlers filter (_.definesImplicit) flatMap (_.definedNames) + def importHandlers = allHandlers collect { case x: ImportHandler => x } + + def visibleTermNames: List[Name] = definedTerms ++ importedTerms distinct + + /** Another entry point for tab-completion, ids in scope */ + def unqualifiedIds = visibleTermNames map (_.toString) filterNot (_ contains "$") sorted + + /** Parse the ScalaSig to find type aliases */ + def aliasForType(path: String) = ByteCode.aliasForType(path) + + def withoutUnwrapping(op: => Unit): Unit = { + val saved = isettings.unwrapStrings + isettings.unwrapStrings = false + try op + finally isettings.unwrapStrings = saved + } + + def symbolDefString(sym: Symbol) = { + TypeStrings.quieter( + afterTyper(sym.defString), + sym.owner.name + ".this.", + sym.owner.fullName + "." + ) + } + + def showCodeIfDebugging(code: String) { + /** Secret bookcase entrance for repl debuggers: end the line + * with "// show" and see what's going on. + */ + if (code.lines exists (_.trim endsWith "// show")) { + echo(code) + parse(code) foreach (ts => ts foreach (t => withoutUnwrapping(DBG(asCompactString(t))))) + } + } + // debugging + def debugging[T](msg: String)(res: T) = { + DBG(msg + " " + res) + res + } + def DBG(s: => String) = if (isReplDebug) { + //try repldbg(s) + //catch { case x: AssertionError => repldbg("Assertion error printing debug string:\n " + x) } + } +} + +/** Utility methods for the Interpreter. */ +object SparkIMain { + // The two name forms this is catching are the two sides of this assignment: + // + // $line3.$read.$iw.$iw.Bippy = + // $line3.$read$$iw$$iw$Bippy@4a6a00ca + private def removeLineWrapper(s: String) = s.replaceAll("""\$line\d+[./]\$(read|eval|print)[$.]""", "") + private def removeIWPackages(s: String) = s.replaceAll("""\$(iw|read|eval|print)[$.]""", "") + def stripString(s: String) = removeIWPackages(removeLineWrapper(s)) + + trait CodeAssembler[T] { + def preamble: String + def generate: T => String + def postamble: String + + def apply(contributors: List[T]): String = stringFromWriter { code => + code println preamble + contributors map generate foreach (code println _) + code println postamble + } + } + + trait StrippingWriter { + def isStripping: Boolean + def stripImpl(str: String): String + def strip(str: String): String = if (isStripping) stripImpl(str) else str + } + trait TruncatingWriter { + def maxStringLength: Int + def isTruncating: Boolean + def truncate(str: String): String = { + if (isTruncating && str.length > maxStringLength) + (str take maxStringLength - 3) + "..." + else str + } + } + abstract class StrippingTruncatingWriter(out: PrintWriter) + extends PrintWriter(out) + with StrippingWriter + with TruncatingWriter { + self => + + def clean(str: String): String = truncate(strip(str)) + override def write(str: String) = super.write(clean(str)) + } + class ReplStrippingWriter(intp: SparkIMain) extends StrippingTruncatingWriter(intp.out) { + import intp._ + def maxStringLength = isettings.maxPrintString + def isStripping = isettings.unwrapStrings + def isTruncating = reporter.truncationOK + + def stripImpl(str: String): String = { + val cleaned = stripString(str) + var ctrlChars = 0 + cleaned map { ch => + if (ch.isControl && !ch.isWhitespace) { + ctrlChars += 1 + if (ctrlChars > 5) return "[line elided for control chars: possibly a scala signature]" + else '?' + } + else ch + } + } + } + + class ReplReporter(intp: SparkIMain) extends ConsoleReporter(intp.settings, null, new ReplStrippingWriter(intp)) { + override def printMessage(msg: String) { + // Avoiding deadlock when the compiler starts logging before + // the lazy val is done. + if (intp.isInitializeComplete) { + if (intp.totalSilence) () + else super.printMessage(msg) + } + else Console.println(msg) + } + } +} diff --git a/repl/src/main/scala/spark/repl/SparkISettings.scala b/repl/src/main/scala/spark/repl/SparkISettings.scala new file mode 100644 index 0000000000000000000000000000000000000000..8ebb01d14602171c3ab8c38eaf9df79ec629e0a8 --- /dev/null +++ b/repl/src/main/scala/spark/repl/SparkISettings.scala @@ -0,0 +1,63 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Alexander Spoon + */ + +package spark.repl + +import scala.tools.nsc._ +import scala.tools.nsc.interpreter._ + +/** Settings for the interpreter + * + * @version 1.0 + * @author Lex Spoon, 2007/3/24 + **/ +class SparkISettings(intp: SparkIMain) { + /** A list of paths where :load should look */ + var loadPath = List(".") + + /** Set this to true to see repl machinery under -Yrich-exceptions. + */ + var showInternalStackTraces = false + + /** The maximum length of toString to use when printing the result + * of an evaluation. 0 means no maximum. If a printout requires + * more than this number of characters, then the printout is + * truncated. + */ + var maxPrintString = 800 + + /** The maximum number of completion candidates to print for tab + * completion without requiring confirmation. + */ + var maxAutoprintCompletion = 250 + + /** String unwrapping can be disabled if it is causing issues. + * Settings this to false means you will see Strings like "$iw.$iw.". + */ + var unwrapStrings = true + + def deprecation_=(x: Boolean) = { + val old = intp.settings.deprecation.value + intp.settings.deprecation.value = x + if (!old && x) println("Enabled -deprecation output.") + else if (old && !x) println("Disabled -deprecation output.") + } + def deprecation: Boolean = intp.settings.deprecation.value + + def allSettings = Map( + "maxPrintString" -> maxPrintString, + "maxAutoprintCompletion" -> maxAutoprintCompletion, + "unwrapStrings" -> unwrapStrings, + "deprecation" -> deprecation + ) + + private def allSettingsString = + allSettings.toList sortBy (_._1) map { case (k, v) => " " + k + " = " + v + "\n" } mkString + + override def toString = """ + | SparkISettings { + | %s + | }""".stripMargin.format(allSettingsString) +} diff --git a/repl/src/main/scala/spark/repl/SparkImports.scala b/repl/src/main/scala/spark/repl/SparkImports.scala new file mode 100644 index 0000000000000000000000000000000000000000..e62c9245ea390a814b8a43d43bc59e89e109a5cd --- /dev/null +++ b/repl/src/main/scala/spark/repl/SparkImports.scala @@ -0,0 +1,196 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package spark.repl + +import scala.tools.nsc._ +import scala.tools.nsc.interpreter._ + +import scala.collection.{ mutable, immutable } + +trait SparkImports { + self: SparkIMain => + + import global._ + import definitions.{ ScalaPackage, JavaLangPackage, PredefModule } + import memberHandlers._ + + /** Synthetic import handlers for the language defined imports. */ + private def makeWildcardImportHandler(sym: Symbol): ImportHandler = { + val hd :: tl = sym.fullName.split('.').toList map newTermName + val tree = Import( + tl.foldLeft(Ident(hd): Tree)((x, y) => Select(x, y)), + List(ImportSelector(nme.WILDCARD, -1, null, -1)) + ) + tree setSymbol sym + new ImportHandler(tree) + } + + /** Symbols whose contents are language-defined to be imported. */ + def languageWildcardSyms: List[Symbol] = List(JavaLangPackage, ScalaPackage, PredefModule) + def languageWildcards: List[Type] = languageWildcardSyms map (_.tpe) + def languageWildcardHandlers = languageWildcardSyms map makeWildcardImportHandler + + def importedTerms = onlyTerms(importHandlers flatMap (_.importedNames)) + def importedTypes = onlyTypes(importHandlers flatMap (_.importedNames)) + + /** Types which have been wildcard imported, such as: + * val x = "abc" ; import x._ // type java.lang.String + * import java.lang.String._ // object java.lang.String + * + * Used by tab completion. + * + * XXX right now this gets import x._ and import java.lang.String._, + * but doesn't figure out import String._. There's a lot of ad hoc + * scope twiddling which should be swept away in favor of digging + * into the compiler scopes. + */ + def sessionWildcards: List[Type] = { + importHandlers flatMap { + case x if x.importsWildcard => x.targetType + case _ => None + } distinct + } + def wildcardTypes = languageWildcards ++ sessionWildcards + + def languageSymbols = languageWildcardSyms flatMap membersAtPickler + def sessionImportedSymbols = importHandlers flatMap (_.importedSymbols) + def importedSymbols = languageSymbols ++ sessionImportedSymbols + def importedTermSymbols = importedSymbols collect { case x: TermSymbol => x } + def importedTypeSymbols = importedSymbols collect { case x: TypeSymbol => x } + def implicitSymbols = importedSymbols filter (_.isImplicit) + + def importedTermNamed(name: String) = importedTermSymbols find (_.name.toString == name) + + /** Tuples of (source, imported symbols) in the order they were imported. + */ + def importedSymbolsBySource: List[(Symbol, List[Symbol])] = { + val lang = languageWildcardSyms map (sym => (sym, membersAtPickler(sym))) + val session = importHandlers filter (_.targetType.isDefined) map { mh => + (mh.targetType.get.typeSymbol, mh.importedSymbols) + } + + lang ++ session + } + def implicitSymbolsBySource: List[(Symbol, List[Symbol])] = { + importedSymbolsBySource map { + case (k, vs) => (k, vs filter (_.isImplicit)) + } filterNot (_._2.isEmpty) + } + + /** Compute imports that allow definitions from previous + * requests to be visible in a new request. Returns + * three pieces of related code: + * + * 1. An initial code fragment that should go before + * the code of the new request. + * + * 2. A code fragment that should go after the code + * of the new request. + * + * 3. An access path which can be traverested to access + * any bindings inside code wrapped by #1 and #2 . + * + * The argument is a set of Names that need to be imported. + * + * Limitations: This method is not as precise as it could be. + * (1) It does not process wildcard imports to see what exactly + * they import. + * (2) If it imports any names from a request, it imports all + * of them, which is not really necessary. + * (3) It imports multiple same-named implicits, but only the + * last one imported is actually usable. + */ + case class ComputedImports(prepend: String, append: String, access: String) + protected def importsCode(wanted: Set[Name]): ComputedImports = { + /** Narrow down the list of requests from which imports + * should be taken. Removes requests which cannot contribute + * useful imports for the specified set of wanted names. + */ + case class ReqAndHandler(req: Request, handler: MemberHandler) { } + + def reqsToUse: List[ReqAndHandler] = { + /** Loop through a list of MemberHandlers and select which ones to keep. + * 'wanted' is the set of names that need to be imported. + */ + def select(reqs: List[ReqAndHandler], wanted: Set[Name]): List[ReqAndHandler] = { + val isWanted = wanted contains _ + // Single symbol imports might be implicits! See bug #1752. Rather than + // try to finesse this, we will mimic all imports for now. + def keepHandler(handler: MemberHandler) = handler match { + case _: ImportHandler => true + case x => x.definesImplicit || (x.definedNames exists isWanted) + } + + reqs match { + case Nil => Nil + case rh :: rest if !keepHandler(rh.handler) => select(rest, wanted) + case rh :: rest => + import rh.handler._ + val newWanted = wanted ++ referencedNames -- definedNames -- importedNames + rh :: select(rest, newWanted) + } + } + + /** Flatten the handlers out and pair each with the original request */ + select(allReqAndHandlers reverseMap { case (r, h) => ReqAndHandler(r, h) }, wanted).reverse + } + + val code, trailingBraces, accessPath = new StringBuilder + val currentImps = mutable.HashSet[Name]() + + // add code for a new object to hold some imports + def addWrapper() { + val impname = nme.INTERPRETER_IMPORT_WRAPPER + code append "object %s {\n".format(impname) + trailingBraces append "}\n" + accessPath append ("." + impname) + + currentImps.clear + } + + addWrapper() + + // loop through previous requests, adding imports for each one + for (ReqAndHandler(req, handler) <- reqsToUse) { + handler match { + // If the user entered an import, then just use it; add an import wrapping + // level if the import might conflict with some other import + case x: ImportHandler => + if (x.importsWildcard || (currentImps exists (x.importedNames contains _))) + addWrapper() + + code append (x.member + "\n") + + // give wildcard imports a import wrapper all to their own + if (x.importsWildcard) addWrapper() + else currentImps ++= x.importedNames + + // For other requests, import each defined name. + // 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.definedNames) { + if (currentImps contains imv) addWrapper() + + code append ("import %s\n" format (req fullPath imv)) + currentImps += imv + } + } + } + // add one extra wrapper, to prevent warnings in the common case of + // redefining the value bound in the last interpreter request. + addWrapper() + ComputedImports(code.toString, trailingBraces.toString, accessPath.toString) + } + + private def allReqAndHandlers = + prevRequestList flatMap (req => req.handlers map (req -> _)) + + private def membersAtPickler(sym: Symbol): List[Symbol] = + atPickler(sym.info.nonPrivateMembers) +} diff --git a/repl/src/main/scala/spark/repl/SparkInteractiveReader.scala b/repl/src/main/scala/spark/repl/SparkInteractiveReader.scala deleted file mode 100644 index 4f5a0a6fa0f3f6aac1c75850936ffcaf7a492d72..0000000000000000000000000000000000000000 --- a/repl/src/main/scala/spark/repl/SparkInteractiveReader.scala +++ /dev/null @@ -1,60 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2010 LAMP/EPFL - * @author Stepan Koltsov - */ - -package spark.repl - -import scala.tools.nsc -import scala.tools.nsc._ -import scala.tools.nsc.interpreter -import scala.tools.nsc.interpreter._ - -import scala.util.control.Exception._ - -/** Reads lines from an input stream */ -trait SparkInteractiveReader { - import SparkInteractiveReader._ - import java.io.IOException - - protected def readOneLine(prompt: String): String - val interactive: Boolean - - def readLine(prompt: String): String = { - def handler: Catcher[String] = { - case e: IOException if restartSystemCall(e) => readLine(prompt) - } - catching(handler) { readOneLine(prompt) } - } - - // override if history is available - def history: Option[History] = None - def historyList = history map (_.asList) getOrElse Nil - - // override if completion is available - def completion: Option[SparkCompletion] = 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) -} - - -object SparkInteractiveReader { - val msgEINTR = "Interrupted system call" - private val exes = List(classOf[Exception], classOf[NoClassDefFoundError]) - - def createDefault(): SparkInteractiveReader = createDefault(null) - - /** Create an interactive reader. Uses <code>JLineReader</code> if the - * library is available, but otherwise uses a <code>SimpleReader</code>. - */ - def createDefault(interpreter: SparkInterpreter): SparkInteractiveReader = - try new SparkJLineReader(interpreter) - catch { - case e @ (_: Exception | _: NoClassDefFoundError) => - // println("Failed to create SparkJLineReader(%s): %s".format(interpreter, e)) - new SparkSimpleReader - } -} - diff --git a/repl/src/main/scala/spark/repl/SparkInterpreter.scala b/repl/src/main/scala/spark/repl/SparkInterpreter.scala deleted file mode 100644 index 10ea346658cc2150b13cbc27c5318c0598f81f0b..0000000000000000000000000000000000000000 --- a/repl/src/main/scala/spark/repl/SparkInterpreter.scala +++ /dev/null @@ -1,1395 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2010 LAMP/EPFL - * @author Martin Odersky - */ - -package spark.repl - -import scala.tools.nsc -import scala.tools.nsc._ - -import Predef.{ println => _, _ } -import java.io.{ File, IOException, PrintWriter, StringWriter, Writer } -import File.pathSeparator -import java.lang.{ Class, ClassLoader } -import java.net.{ MalformedURLException, URL } -import java.lang.reflect -import reflect.InvocationTargetException -import java.util.UUID - -import scala.PartialFunction.{ cond, condOpt } -import scala.tools.util.PathResolver -import scala.reflect.Manifest -import scala.collection.mutable -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 } - -import io.{ PlainFile, VirtualDirectory } -import reporters.{ ConsoleReporter, Reporter } -import symtab.{ Flags, Names } -import util.{ SourceFile, BatchSourceFile, ScriptSourceFile, ClassPath, Chars, stringFromWriter } -import scala.reflect.NameTransformer -import scala.tools.nsc.{ InterpreterResults => IR } -import interpreter._ -import SparkInterpreter._ - -import spark.HttpServer -import spark.Utils - -/** <p> - * An interpreter for Scala code. - * </p> - * <p> - * The main public entry points are <code>compile()</code>, - * <code>interpret()</code>, and <code>bind()</code>. - * The <code>compile()</code> method loads a - * complete Scala file. The <code>interpret()</code> method executes one - * line of Scala code at the request of the user. The <code>bind()</code> - * method binds an object to a variable that can then be used by later - * interpreted code. - * </p> - * <p> - * The overall approach is based on compiling the requested code and then - * using a Java classloader and Java reflection to run the code - * and access its results. - * </p> - * <p> - * In more detail, a single compiler instance is used - * to accumulate all successfully compiled or interpreted Scala code. To - * "interpret" a line of code, the compiler generates a fresh object that - * includes the line of code and which has public member(s) to export - * all variables defined by that code. To extract the result of an - * interpreted line to show the user, a second "result object" is created - * which imports the variables exported by the above object and then - * exports a single member named "scala_repl_result". To accomodate user expressions - * that read from variables or methods defined in previous statements, "import" - * statements are used. - * </p> - * <p> - * This interpreter shares the strengths and weaknesses of using the - * full compiler-to-Java. The main strength is that interpreted code - * behaves exactly as does compiled code, including running at full speed. - * The main weakness is that redefining classes and methods is not handled - * properly, because rebinding at the Java level is technically difficult. - * </p> - * - * @author Moez A. Abdel-Gawad - * @author Lex Spoon - */ -class SparkInterpreter(val settings: Settings, out: PrintWriter) { - repl => - - def println(x: Any) = { - out.println(x) - out.flush() - } - - /** construct an interpreter that reports to Console */ - def this(settings: Settings) = this(settings, new NewLinePrintWriter(new ConsoleWriter, true)) - def this() = this(new Settings()) - - val SPARK_DEBUG_REPL: Boolean = (System.getenv("SPARK_DEBUG_REPL") == "1") - - /** Local directory to save .class files too */ - val outputDir = { - val tmp = System.getProperty("java.io.tmpdir") - val rootDir = System.getProperty("spark.repl.classdir", tmp) - Utils.createTempDir(rootDir) - } - if (SPARK_DEBUG_REPL) { - println("Output directory: " + outputDir) - } - - /** Scala compiler virtual directory for outputDir */ - //val virtualDirectory = new VirtualDirectory("(memory)", None) - val virtualDirectory = new PlainFile(outputDir) - - /** Jetty server that will serve our classes to worker nodes */ - val classServer = new HttpServer(outputDir) - - // Start the classServer and store its URI in a spark system property - // (which will be passed to executors so that they can connect to it) - classServer.start() - System.setProperty("spark.repl.class.uri", classServer.uri) - if (SPARK_DEBUG_REPL) { - println("Class server started, URI = " + classServer.uri) - } - - /** reporter */ - object reporter extends ConsoleReporter(settings, null, out) { - override def printMessage(msg: String) { - out println clean(msg) - out.flush() - } - } - - /** We're going to go to some trouble to initialize the compiler asynchronously. - * It's critical that nothing call into it until it's been initialized or we will - * run into unrecoverable issues, but the perceived repl startup time goes - * through the roof if we wait for it. So we initialize it with a future and - * use a lazy val to ensure that any attempt to use the compiler object waits - * on the future. - */ - private val _compiler: Global = newCompiler(settings, reporter) - private def _initialize(): Boolean = { - val source = """ - |// this is assembled to force the loading of approximately the - |// classes which will be loaded on the first expression anyway. - |class $repl_$init { - | val x = "abc".reverse.length + (5 max 5) - | scala.runtime.ScalaRunTime.stringOf(x) - |} - |""".stripMargin - - try { - new _compiler.Run() compileSources List(new BatchSourceFile("<init>", source)) - if (isReplDebug || settings.debug.value) - println("Repl compiler initialized.") - true - } - catch { - case MissingRequirementError(msg) => println(""" - |Failed to initialize compiler: %s not found. - |** Note that as of 2.8 scala does not assume use of the java classpath. - |** For the old behavior pass -usejavacp to scala, or if using a Settings - |** object programatically, settings.usejavacp.value = true.""".stripMargin.format(msg) - ) - false - } - } - - // set up initialization future - private var _isInitialized: () => Boolean = null - def initialize() = synchronized { - if (_isInitialized == null) - _isInitialized = scala.concurrent.ops future _initialize() - } - - /** the public, go through the future compiler */ - lazy val compiler: Global = { - initialize() - - // blocks until it is ; false means catastrophic failure - if (_isInitialized()) _compiler - else null - } - - import compiler.{ Traverser, CompilationUnit, Symbol, Name, Type } - import compiler.{ - Tree, TermTree, ValOrDefDef, ValDef, DefDef, Assign, ClassDef, - ModuleDef, Ident, Select, TypeDef, Import, MemberDef, DocDef, - ImportSelector, EmptyTree, NoType } - import compiler.{ nme, newTermName, newTypeName } - import nme.{ - INTERPRETER_VAR_PREFIX, INTERPRETER_SYNTHVAR_PREFIX, INTERPRETER_LINE_PREFIX, - INTERPRETER_IMPORT_WRAPPER, INTERPRETER_WRAPPER_SUFFIX, USCOREkw - } - - import compiler.definitions - import definitions.{ EmptyPackage, getMember } - - /** whether to print out result lines */ - private[repl] var printResults: Boolean = true - - /** Temporarily be quiet */ - def beQuietDuring[T](operation: => T): T = { - val wasPrinting = printResults - ultimately(printResults = wasPrinting) { - printResults = false - operation - } - } - - /** whether to bind the lastException variable */ - private var bindLastException = true - - /** Temporarily stop binding lastException */ - def withoutBindingLastException[T](operation: => T): T = { - val wasBinding = bindLastException - ultimately(bindLastException = wasBinding) { - bindLastException = false - operation - } - } - - /** interpreter settings */ - lazy val isettings = new SparkInterpreterSettings(this) - - /** Instantiate a compiler. Subclasses can override this to - * change the compiler class used by this interpreter. */ - protected def newCompiler(settings: Settings, reporter: Reporter) = { - settings.outputDirs setSingleOutput virtualDirectory - new Global(settings, reporter) - } - - /** the compiler's classpath, as URL's */ - lazy val compilerClasspath: List[URL] = new PathResolver(settings) asURLs - - /* A single class loader is used for all commands interpreted by this Interpreter. - It would also be possible to create a new class loader for each command - to interpret. The advantages of the current approach are: - - - Expressions are only evaluated one time. This is especially - significant for I/O, e.g. "val x = Console.readLine" - - The main disadvantage is: - - - Objects, classes, and methods cannot be rebound. Instead, definitions - shadow the old ones, and old code objects refer to the old - definitions. - */ - private var _classLoader: ClassLoader = null - def resetClassLoader() = _classLoader = makeClassLoader() - def classLoader: ClassLoader = { - if (_classLoader == null) - resetClassLoader() - - _classLoader - } - private def makeClassLoader(): ClassLoader = { - /* - val parent = - if (parentClassLoader == null) ScalaClassLoader fromURLs compilerClasspath - else new URLClassLoader(compilerClasspath, parentClassLoader) - - new AbstractFileClassLoader(virtualDirectory, parent) - */ - val parent = - if (parentClassLoader == null) - new java.net.URLClassLoader(compilerClasspath.toArray) - else - new java.net.URLClassLoader(compilerClasspath.toArray, - parentClassLoader) - val virtualDirUrl = new URL("file://" + virtualDirectory.path + "/") - new java.net.URLClassLoader(Array(virtualDirUrl), parent) - } - - private def loadByName(s: String): Class[_] = // (classLoader tryToInitializeClass s).get - Class.forName(s, true, classLoader) - - private def methodByName(c: Class[_], name: String): reflect.Method = - c.getMethod(name, classOf[Object]) - - protected def parentClassLoader: ClassLoader = this.getClass.getClassLoader() - def getInterpreterClassLoader() = classLoader - - // Set the current Java "context" class loader to this interpreter's class loader - def setContextClassLoader() = Thread.currentThread.setContextClassLoader(classLoader) - - /** the previous requests this interpreter has processed */ - private val prevRequests = new ArrayBuffer[Request]() - private val usedNameMap = new HashMap[Name, Request]() - private val boundNameMap = new HashMap[Name, Request]() - private def allHandlers = prevRequests.toList flatMap (_.handlers) - private def allReqAndHandlers = prevRequests.toList flatMap (req => req.handlers map (req -> _)) - - def printAllTypeOf = { - prevRequests foreach { req => - req.typeOf foreach { case (k, v) => Console.println(k + " => " + v) } - } - } - - /** Most recent tree handled which wasn't wholly synthetic. */ - private def mostRecentlyHandledTree: Option[Tree] = { - for { - req <- prevRequests.reverse - handler <- req.handlers.reverse - name <- handler.generatesValue - if !isSynthVarName(name) - } return Some(handler.member) - - None - } - - 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 => usedNameMap(x) = req) - req.boundNames foreach (x => boundNameMap(x) = req) - - // XXX temporarily putting this here because of tricky initialization order issues - // so right now it's not bound until after you issue a command. - if (prevRequests.size == 1) - quietBind("settings", "spark.repl.SparkInterpreterSettings", isettings) - - // println("\n s1 = %s\n s2 = %s\n s3 = %s".format( - // tripart(usedNameMap.keysIterator.toSet, boundNameMap.keysIterator.toSet): _* - // )) - } - - private def keyList[T](x: collection.Map[T, _]): List[T] = x.keys.toList sortBy (_.toString) - def allUsedNames = keyList(usedNameMap) - def allBoundNames = keyList(boundNameMap) - def allSeenTypes = prevRequests.toList flatMap (_.typeOf.values.toList) distinct - def allValueGeneratingNames = allHandlers flatMap (_.generatesValue) - def allImplicits = partialFlatMap(allHandlers) { - case x: MemberHandler if x.definesImplicit => x.boundNames - } - - /** Generates names pre0, pre1, etc. via calls to apply method */ - class NameCreator(pre: String) { - private var x = -1 - var mostRecent: String = null - - def apply(): String = { - x += 1 - val name = pre + x.toString - // make sure we don't overwrite their unwisely named res3 etc. - mostRecent = - if (allBoundNames exists (_.toString == name)) apply() - else name - - mostRecent - } - def reset(): Unit = x = -1 - def didGenerate(name: String) = - (name startsWith pre) && ((name drop pre.length) forall (_.isDigit)) - } - - /** allocate a fresh line name */ - private lazy val lineNameCreator = new NameCreator(INTERPRETER_LINE_PREFIX) - - /** allocate a fresh var name */ - private lazy val varNameCreator = new NameCreator(INTERPRETER_VAR_PREFIX) - - /** allocate a fresh internal variable name */ - private lazy val synthVarNameCreator = new NameCreator(INTERPRETER_SYNTHVAR_PREFIX) - - /** Check if a name looks like it was generated by varNameCreator */ - private def isGeneratedVarName(name: String): Boolean = varNameCreator didGenerate name - private def isSynthVarName(name: String): Boolean = synthVarNameCreator didGenerate name - private def isSynthVarName(name: Name): Boolean = synthVarNameCreator didGenerate name.toString - - def getVarName = varNameCreator() - def getSynthVarName = synthVarNameCreator() - - /** Truncate a string if it is longer than isettings.maxPrintString */ - private def truncPrintString(str: String): String = { - val maxpr = isettings.maxPrintString - val trailer = "..." - - if (maxpr <= 0 || str.length <= maxpr) str - else str.substring(0, maxpr-3) + trailer - } - - /** Clean up a string for output */ - private def clean(str: String) = truncPrintString( - if (isettings.unwrapStrings && !SPARK_DEBUG_REPL) stripWrapperGunk(str) - else str - ) - - /** Heuristically strip interpreter wrapper prefixes - * from an interpreter output string. - * MATEI: Copied from interpreter package object - */ - def stripWrapperGunk(str: String): String = { - val wrapregex = """(line[0-9]+\$object[$.])?(\$?VAL.?)*(\$iwC?(.this)?[$.])*""" - str.replaceAll(wrapregex, "") - } - - /** Indent some code by the width of the scala> prompt. - * This way, compiler error messages read better. - */ - private final val spaces = List.fill(7)(" ").mkString - def indentCode(code: String) = { - /** Heuristic to avoid indenting and thereby corrupting """-strings and XML literals. */ - val noIndent = (code contains "\n") && (List("\"\"\"", "</", "/>") exists (code contains _)) - stringFromWriter(str => - for (line <- code.lines) { - if (!noIndent) - str.print(spaces) - - str.print(line + "\n") - str.flush() - }) - } - def indentString(s: String) = s split "\n" map (spaces + _ + "\n") mkString - - implicit def name2string(name: Name) = name.toString - - /** Compute imports that allow definitions from previous - * requests to be visible in a new request. Returns - * three pieces of related code: - * - * 1. An initial code fragment that should go before - * the code of the new request. - * - * 2. A code fragment that should go after the code - * of the new request. - * - * 3. An access path which can be traverested to access - * any bindings inside code wrapped by #1 and #2 . - * - * The argument is a set of Names that need to be imported. - * - * Limitations: This method is not as precise as it could be. - * (1) It does not process wildcard imports to see what exactly - * they import. - * (2) If it imports any names from a request, it imports all - * of them, which is not really necessary. - * (3) It imports multiple same-named implicits, but only the - * last one imported is actually usable. - */ - private case class ComputedImports(prepend: String, append: String, access: String) - private def importsCode(wanted: Set[Name]): ComputedImports = { - /** Narrow down the list of requests from which imports - * should be taken. Removes requests which cannot contribute - * useful imports for the specified set of wanted names. - */ - case class ReqAndHandler(req: Request, handler: MemberHandler) { } - - def reqsToUse: List[ReqAndHandler] = { - /** Loop through a list of MemberHandlers and select which ones to keep. - * 'wanted' is the set of names that need to be imported. - */ - def select(reqs: List[ReqAndHandler], wanted: Set[Name]): List[ReqAndHandler] = { - val isWanted = wanted contains _ - // Single symbol imports might be implicits! See bug #1752. Rather than - // try to finesse this, we will mimic all imports for now. - def keepHandler(handler: MemberHandler) = handler match { - case _: ImportHandler => true - case x => x.definesImplicit || (x.boundNames exists isWanted) - } - - reqs match { - case Nil => Nil - case rh :: rest if !keepHandler(rh.handler) => select(rest, wanted) - case rh :: rest => - val importedNames = rh.handler match { case x: ImportHandler => x.importedNames ; case _ => Nil } - import rh.handler._ - val newWanted = wanted ++ usedNames -- boundNames -- importedNames - rh :: select(rest, newWanted) - } - } - - /** Flatten the handlers out and pair each with the original request */ - select(allReqAndHandlers reverseMap { case (r, h) => ReqAndHandler(r, h) }, wanted).reverse - } - - val code, trailingLines, accessPath = new StringBuffer - val currentImps = HashSet[Name]() - - // add code for a new object to hold some imports - def addWrapper() { - /* - val impname = INTERPRETER_IMPORT_WRAPPER - code append "object %s {\n".format(impname) - trailingLines append "}\n" - accessPath append ("." + impname) - currentImps.clear - */ - val impname = INTERPRETER_IMPORT_WRAPPER - code.append("@serializable class " + impname + "C {\n") - trailingLines.append("}\nval " + impname + " = new " + impname + "C;\n") - accessPath.append("." + impname) - currentImps.clear - } - - addWrapper() - - // loop through previous requests, adding imports for each one - for (ReqAndHandler(req, handler) <- reqsToUse) { - handler match { - // If the user entered an import, then just use it; add an import wrapping - // level if the import might conflict with some other import - case x: ImportHandler => - if (x.importsWildcard || (currentImps exists (x.importedNames contains _))) - addWrapper() - - code append (x.member.toString + "\n") - - // give wildcard imports a import wrapper all to their own - if (x.importsWildcard) addWrapper() - else currentImps ++= x.importedNames - - // For other requests, import each bound variable. - // import them explicitly instead of with _, so that - // ambiguity errors will not be generated. Also, quote - // the name of the variable, so that we don't need to - // handle quoting keywords separately. - case x => - for (imv <- x.boundNames) { - // MATEI: Commented this check out because it was messing up for case classes - // (trying to import them twice within the same wrapper), but that is more likely - // due to a miscomputation of names that makes the code think they're unique. - // Need to evaluate whether having so many wrappers is a bad thing. - /*if (currentImps contains imv) */ addWrapper() - - code.append("val " + req.objectName + "$VAL = " + req.objectName + ".INSTANCE;\n") - code.append("import " + req.objectName + "$VAL" + req.accessPath + ".`" + imv + "`;\n") - - //code append ("import %s\n" format (req fullPath imv)) - currentImps += imv - } - } - } - // add one extra wrapper, to prevent warnings in the common case of - // redefining the value bound in the last interpreter request. - addWrapper() - ComputedImports(code.toString, trailingLines.toString, accessPath.toString) - } - - /** Parse a line into a sequence of trees. Returns None if the input is incomplete. */ - private def parse(line: String): Option[List[Tree]] = { - var justNeedsMore = false - reporter.withIncompleteHandler((pos,msg) => {justNeedsMore = true}) { - // simple parse: just parse it, nothing else - def simpleParse(code: String): List[Tree] = { - reporter.reset - val unit = new CompilationUnit(new BatchSourceFile("<console>", code)) - val scanner = new compiler.syntaxAnalyzer.UnitParser(unit) - - scanner.templateStatSeq(false)._2 - } - val trees = simpleParse(line) - - if (reporter.hasErrors) Some(Nil) // the result did not parse, so stop - else if (justNeedsMore) None - else Some(trees) - } - } - - /** Compile an nsc SourceFile. Returns true if there are - * no compilation errors, or false otherwise. - */ - def compileSources(sources: SourceFile*): Boolean = { - reporter.reset - new compiler.Run() compileSources sources.toList - !reporter.hasErrors - } - - /** Compile a string. Returns true if there are no - * compilation errors, or false otherwise. - */ - def compileString(code: String): Boolean = - compileSources(new BatchSourceFile("<script>", code)) - - def compileAndSaveRun(label: String, code: String) = { - if (SPARK_DEBUG_REPL) - println(code) - if (isReplDebug) { - parse(code) match { - case Some(trees) => trees foreach (t => DBG(compiler.asCompactString(t))) - case _ => DBG("Parse error:\n\n" + code) - } - } - val run = new compiler.Run() - run.compileSources(List(new BatchSourceFile(label, code))) - run - } - - /** Build a request from the user. <code>trees</code> is <code>line</code> - * after being parsed. - */ - private def buildRequest(line: String, lineName: String, trees: List[Tree]): Request = - new Request(line, lineName, trees) - - private def chooseHandler(member: Tree): MemberHandler = member match { - case member: DefDef => new DefHandler(member) - case member: ValDef => new ValHandler(member) - case member@Assign(Ident(_), _) => new AssignHandler(member) - case member: ModuleDef => new ModuleHandler(member) - case member: ClassDef => new ClassHandler(member) - case member: TypeDef => new TypeAliasHandler(member) - case member: Import => new ImportHandler(member) - case DocDef(_, documented) => chooseHandler(documented) - case member => new GenericHandler(member) - } - - private def requestFromLine(line: String, synthetic: Boolean): Either[IR.Result, Request] = { - val trees = parse(indentCode(line)) match { - case None => return Left(IR.Incomplete) - case Some(Nil) => return Left(IR.Error) // parse error or empty input - case Some(trees) => trees - } - - // use synthetic vars to avoid filling up the resXX slots - def varName = if (synthetic) getSynthVarName else getVarName - - // Treat a single bare expression specially. This is necessary due to it being hard to - // modify code at a textual level, and it being hard to submit an AST to the compiler. - if (trees.size == 1) trees.head match { - case _:Assign => // we don't want to include assignments - case _:TermTree | _:Ident | _:Select => // ... but do want these as valdefs. - return requestFromLine("val %s =\n%s".format(varName, line), synthetic) - case _ => - } - - // figure out what kind of request - Right(buildRequest(line, lineNameCreator(), trees)) - } - - /** <p> - * Interpret one line of input. All feedback, including parse errors - * and evaluation results, are printed via the supplied compiler's - * reporter. Values defined are available for future interpreted - * strings. - * </p> - * <p> - * The return value is whether the line was interpreter successfully, - * e.g. that there were no parse errors. - * </p> - * - * @param line ... - * @return ... - */ - def interpret(line: String): IR.Result = interpret(line, false) - def interpret(line: String, synthetic: Boolean): IR.Result = { - def loadAndRunReq(req: Request) = { - val (result, succeeded) = req.loadAndRun - if (printResults || !succeeded) - out print clean(result) - - // book-keeping - if (succeeded && !synthetic) - recordRequest(req) - - if (succeeded) IR.Success - else IR.Error - } - - if (compiler == null) IR.Error - else requestFromLine(line, synthetic) match { - case Left(result) => result - case Right(req) => - // null indicates a disallowed statement type; otherwise compile and - // fail if false (implying e.g. a type error) - if (req == null || !req.compile) IR.Error - else loadAndRunReq(req) - } - } - - /** A name creator used for objects created by <code>bind()</code>. */ - private lazy val newBinder = new NameCreator("binder") - - /** Bind a specified name to a specified value. The name may - * later be used by expressions passed to interpret. - * - * @param name the variable name to bind - * @param boundType the type of the variable, as a string - * @param value the object value to bind to it - * @return an indication of whether the binding succeeded - */ - def bind(name: String, boundType: String, value: Any): IR.Result = { - val binderName = newBinder() - - compileString(""" - |object %s { - | var value: %s = _ - | def set(x: Any) = value = x.asInstanceOf[%s] - |} - """.stripMargin.format(binderName, boundType, boundType)) - - val binderObject = loadByName(binderName) - val setterMethod = methodByName(binderObject, "set") - - setterMethod.invoke(null, value.asInstanceOf[AnyRef]) - interpret("val %s = %s.value".format(name, binderName)) - } - - def quietBind(name: String, boundType: String, value: Any): IR.Result = - beQuietDuring { bind(name, boundType, value) } - - /** Reset this interpreter, forgetting all user-specified requests. */ - def reset() { - //virtualDirectory.clear - virtualDirectory.delete - virtualDirectory.create - resetClassLoader() - lineNameCreator.reset() - varNameCreator.reset() - prevRequests.clear - } - - /** <p> - * This instance is no longer needed, so release any resources - * it is using. The reporter's output gets flushed. - * </p> - */ - def close() { - reporter.flush - classServer.stop() - } - - /** A traverser that finds all mentioned identifiers, i.e. things - * that need to be imported. It might return extra names. - */ - private class ImportVarsTraverser extends Traverser { - val importVars = new HashSet[Name]() - - override def traverse(ast: Tree) = ast match { - // XXX this is obviously inadequate but it's going to require some effort - // to get right. - case Ident(name) if !(name.toString startsWith "x$") => importVars += name - case _ => super.traverse(ast) - } - } - - /** Class to handle one member among all the members included - * in a single interpreter request. - */ - private sealed abstract class MemberHandler(val member: Tree) { - lazy val usedNames: List[Name] = { - val ivt = new ImportVarsTraverser() - ivt traverse member - ivt.importVars.toList - } - def boundNames: List[Name] = Nil - val definesImplicit = cond(member) { - case tree: MemberDef => tree.mods hasFlag Flags.IMPLICIT - } - def generatesValue: Option[Name] = None - - def extraCodeToEvaluate(req: Request, code: PrintWriter) { } - def resultExtractionCode(req: Request, code: PrintWriter) { } - - override def toString = "%s(used = %s)".format(this.getClass.toString split '.' last, usedNames) - } - - private class GenericHandler(member: Tree) extends MemberHandler(member) - - private class ValHandler(member: ValDef) extends MemberHandler(member) { - lazy val ValDef(mods, vname, _, _) = member - lazy val prettyName = NameTransformer.decode(vname) - lazy val isLazy = mods hasFlag Flags.LAZY - - override lazy val boundNames = List(vname) - override def generatesValue = Some(vname) - - override def resultExtractionCode(req: Request, code: PrintWriter) { - val isInternal = isGeneratedVarName(vname) && req.typeOfEnc(vname) == "Unit" - if (!mods.isPublic || isInternal) return - - lazy val extractor = "scala.runtime.ScalaRunTime.stringOf(%s)".format(req fullPath vname) - - // if this is a lazy val we avoid evaluating it here - val resultString = if (isLazy) codegenln(false, "<lazy>") else extractor - val codeToPrint = - """ + "%s: %s = " + %s""".format(prettyName, string2code(req typeOf vname), resultString) - - code print codeToPrint - } - } - - private class DefHandler(defDef: DefDef) extends MemberHandler(defDef) { - lazy val DefDef(mods, name, _, vparamss, _, _) = defDef - override lazy val boundNames = List(name) - // true if 0-arity - override def generatesValue = - if (vparamss.isEmpty || vparamss.head.isEmpty) Some(name) - else None - - override def resultExtractionCode(req: Request, code: PrintWriter) = - if (mods.isPublic) code print codegenln(name, ": ", req.typeOf(name)) - } - - private class AssignHandler(member: Assign) extends MemberHandler(member) { - val lhs = member.lhs.asInstanceOf[Ident] // an unfortunate limitation - val helperName = newTermName(synthVarNameCreator()) - override def generatesValue = Some(helperName) - - override def extraCodeToEvaluate(req: Request, code: PrintWriter) = - code println """val %s = %s""".format(helperName, lhs) - - /** Print out lhs instead of the generated varName */ - override def resultExtractionCode(req: Request, code: PrintWriter) { - val lhsType = string2code(req typeOfEnc helperName) - val res = string2code(req fullPath helperName) - val codeToPrint = """ + "%s: %s = " + %s + "\n" """.format(lhs, lhsType, res) - - code println codeToPrint - } - } - - private class ModuleHandler(module: ModuleDef) extends MemberHandler(module) { - lazy val ModuleDef(mods, name, _) = module - override lazy val boundNames = List(name) - override def generatesValue = Some(name) - - override def resultExtractionCode(req: Request, code: PrintWriter) = - code println codegenln("defined module ", name) - } - - private class ClassHandler(classdef: ClassDef) extends MemberHandler(classdef) { - lazy val ClassDef(mods, name, _, _) = classdef - override lazy val boundNames = - name :: (if (mods hasFlag Flags.CASE) List(name.toTermName) else Nil) - - override def resultExtractionCode(req: Request, code: PrintWriter) = - code print codegenln("defined %s %s".format(classdef.keyword, name)) - } - - private class TypeAliasHandler(typeDef: TypeDef) extends MemberHandler(typeDef) { - lazy val TypeDef(mods, name, _, _) = typeDef - def isAlias() = mods.isPublic && compiler.treeInfo.isAliasTypeDef(typeDef) - override lazy val boundNames = if (isAlias) List(name) else Nil - - override def resultExtractionCode(req: Request, code: PrintWriter) = - code println codegenln("defined type alias ", name) - } - - private class ImportHandler(imp: Import) extends MemberHandler(imp) { - lazy val Import(expr, selectors) = imp - def targetType = stringToCompilerType(expr.toString) match { - case NoType => None - case x => Some(x) - } - - private def selectorWild = selectors filter (_.name == USCOREkw) // wildcard imports, e.g. import foo._ - private def selectorMasked = selectors filter (_.rename == USCOREkw) // masking imports, e.g. import foo.{ bar => _ } - private def selectorNames = selectors map (_.name) - private def selectorRenames = selectors map (_.rename) filterNot (_ == null) - - /** Whether this import includes a wildcard import */ - val importsWildcard = selectorWild.nonEmpty - - /** Complete list of names imported by a wildcard */ - def wildcardImportedNames: List[Name] = ( - for (tpe <- targetType ; if importsWildcard) yield - tpe.nonPrivateMembers filter (x => x.isMethod && x.isPublic) map (_.name) distinct - ).toList.flatten - - /** The individual names imported by this statement */ - /** XXX come back to this and see what can be done with wildcards now that - * we know how to enumerate the identifiers. - */ - val importedNames: List[Name] = - selectorRenames filterNot (_ == USCOREkw) flatMap (x => List(x.toTypeName, x.toTermName)) - - override def resultExtractionCode(req: Request, code: PrintWriter) = - code println codegenln(imp.toString) - } - - /** One line of code submitted by the user for interpretation */ - private class Request(val line: String, val lineName: String, val trees: List[Tree]) { - /** name to use for the object that will compute "line" */ - def objectName = lineName + INTERPRETER_WRAPPER_SUFFIX - - /** name of the object that retrieves the result from the above object */ - def resultObjectName = "RequestResult$" + objectName - - /** handlers for each tree in this request */ - val handlers: List[MemberHandler] = trees map chooseHandler - - /** all (public) names defined by these statements */ - val boundNames = handlers flatMap (_.boundNames) - - /** list of names used by this expression */ - val usedNames: List[Name] = handlers flatMap (_.usedNames) - - /** def and val names */ - def defNames = partialFlatMap(handlers) { case x: DefHandler => x.boundNames } - def valueNames = partialFlatMap(handlers) { - case x: AssignHandler => List(x.helperName) - case x: ValHandler => boundNames - case x: ModuleHandler => List(x.name) - } - - /** Code to import bound names from previous lines - accessPath is code to - * append to objectName to access anything bound by request. - */ - val ComputedImports(importsPreamble, importsTrailer, accessPath) = - importsCode(Set.empty ++ usedNames) - - /** Code to access a variable with the specified name */ - def fullPath(vname: String): String = "%s.`%s`".format(objectName + ".INSTANCE" + accessPath, vname) - - /** Code to access a variable with the specified name */ - def fullPath(vname: Name): String = fullPath(vname.toString) - - /** the line of code to compute */ - def toCompute = line - - /** generate the source code for the object that computes this request */ - def objectSourceCode: String = stringFromWriter { code => - val preamble = """ - |@serializable class %s { - | %s%s - """.stripMargin.format(objectName, importsPreamble, indentCode(toCompute)) - val postamble = importsTrailer + "\n}" - - code println preamble - handlers foreach { _.extraCodeToEvaluate(this, code) } - code println postamble - - //create an object - code.println("object " + objectName + " {") - code.println(" val INSTANCE = new " + objectName + "();") - code.println("}") - } - - /** generate source code for the object that retrieves the result - from objectSourceCode */ - def resultObjectSourceCode: String = stringFromWriter { code => - /** We only want to generate this code when the result - * is a value which can be referred to as-is. - */ - val valueExtractor = handlers.last.generatesValue match { - case Some(vname) if typeOf contains vname => - """ - |lazy val scala_repl_value = { - | scala_repl_result - | %s - |}""".stripMargin.format(fullPath(vname)) - case _ => "" - } - - // first line evaluates object to make sure constructor is run - // initial "" so later code can uniformly be: + etc - val preamble = """ - |object %s { - | %s - | val scala_repl_result: String = { - | %s - | ("" - """.stripMargin.format(resultObjectName, valueExtractor, objectName + ".INSTANCE" + accessPath) - - val postamble = """ - | ) - | } - |} - """.stripMargin - - code println preamble - if (printResults) { - handlers foreach { _.resultExtractionCode(this, code) } - } - code println postamble - } - - // compile the object containing the user's code - lazy val objRun = compileAndSaveRun("<console>", objectSourceCode) - - // compile the result-extraction object - lazy val extractionObjectRun = compileAndSaveRun("<console>", resultObjectSourceCode) - - lazy val loadedResultObject = loadByName(resultObjectName) - - def extractionValue(): Option[AnyRef] = { - // ensure it has run - extractionObjectRun - - // load it and retrieve the value - try Some(loadedResultObject getMethod "scala_repl_value" invoke loadedResultObject) - catch { case _: Exception => None } - } - - /** Compile the object file. Returns whether the compilation succeeded. - * If all goes well, the "types" map is computed. */ - def compile(): Boolean = { - // error counting is wrong, hence interpreter may overlook failure - so we reset - reporter.reset - - // compile the main object - objRun - - // bail on error - if (reporter.hasErrors) - return false - - // extract and remember types - typeOf - - // compile the result-extraction object - extractionObjectRun - - // success - !reporter.hasErrors - } - - def atNextPhase[T](op: => T): T = compiler.atPhase(objRun.typerPhase.next)(op) - - /** The outermost wrapper object */ - lazy val outerResObjSym: Symbol = getMember(EmptyPackage, newTermName(objectName).toTypeName) - - /** 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)) - } - - /* 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 => - val cleanedType = rawType match { - case compiler.PolyType(Nil, rt) => rt - case rawType => rawType - } - - map + (name -> atNextPhase(cleanedType.toString)) - } - } - - getTypes(valueNames, nme.getterToLocal(_)) ++ getTypes(defNames, identity) - } - - /** load and run the code using reflection */ - def loadAndRun: (String, Boolean) = { - 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]) - - /** We turn off the binding to accomodate ticket #2817 */ - def onErr: Catcher[(String, Boolean)] = { - case t: Throwable if bindLastException => - withoutBindingLastException { - quietBind("lastException", "java.lang.Throwable", t) - (stringFromWriter(t.printStackTrace(_)), false) - } - } - - catching(onErr) { - unwrapping(wrapperExceptions: _*) { - (resultValMethod.invoke(loadedResultObject).toString, true) - } - } - } - - override def toString = "Request(line=%s, %s trees)".format(line, trees.size) - } - - /** A container class for methods to be injected into the repl - * in power mode. - */ - object power { - lazy val compiler: repl.compiler.type = repl.compiler - import compiler.{ phaseNames, atPhase, currentRun } - - def mkContext(code: String = "") = compiler.analyzer.rootContext(mkUnit(code)) - def mkAlias(name: String, what: String) = interpret("type %s = %s".format(name, what)) - def mkSourceFile(code: String) = new BatchSourceFile("<console>", code) - def mkUnit(code: String) = new CompilationUnit(mkSourceFile(code)) - - def mkTree(code: String): Tree = mkTrees(code).headOption getOrElse EmptyTree - def mkTrees(code: String): List[Tree] = parse(code) getOrElse Nil - def mkTypedTrees(code: String*): List[compiler.Tree] = { - class TyperRun extends compiler.Run { - override def stopPhase(name: String) = name == "superaccessors" - } - - reporter.reset - val run = new TyperRun - run compileSources (code.toList.zipWithIndex map { - case (s, i) => new BatchSourceFile("<console %d>".format(i), s) - }) - run.units.toList map (_.body) - } - def mkTypedTree(code: String) = mkTypedTrees(code).head - def mkType(id: String): compiler.Type = stringToCompilerType(id) - - def dump(): String = ( - ("Names used: " :: allUsedNames) ++ - ("\nIdentifiers: " :: unqualifiedIds) - ) mkString " " - - lazy val allPhases: List[Phase] = phaseNames map (currentRun phaseNamed _) - def atAllPhases[T](op: => T): List[(String, T)] = allPhases map (ph => (ph.name, atPhase(ph)(op))) - def showAtAllPhases(op: => Any): Unit = - atAllPhases(op.toString) foreach { case (ph, op) => Console.println("%15s -> %s".format(ph, op take 240)) } - } - - def unleash(): Unit = beQuietDuring { - interpret("import scala.tools.nsc._") - repl.bind("repl", "spark.repl.SparkInterpreter", this) - interpret("val global: repl.compiler.type = repl.compiler") - interpret("val power: repl.power.type = repl.power") - // interpret("val replVars = repl.replVars") - } - - /** Artificial object demonstrating completion */ - // lazy val replVars = 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)) - // ) - // ) - - /** Returns the name of the most recent interpreter result. - * Mostly this exists so you can conveniently invoke methods on - * the previous result. - */ - def mostRecentVar: String = - if (mostRecentlyHandledTree.isEmpty) "" - else mostRecentlyHandledTree.get match { - case x: ValOrDefDef => x.name - case Assign(Ident(name), _) => name - case ModuleDef(_, name, _) => name - case _ => onull(varNameCreator.mostRecent) - } - - private def requestForName(name: Name): Option[Request] = - prevRequests.reverse find (_.boundNames contains name) - - private def requestForIdent(line: String): Option[Request] = requestForName(newTermName(line)) - - def stringToCompilerType(id: String): compiler.Type = { - // if it's a recognized identifier, the type of that; otherwise treat the - // String like a value (e.g. scala.collection.Map) . - def findType = typeForIdent(id) match { - case Some(x) => definitions.getClass(newTermName(x)).tpe - case _ => definitions.getModule(newTermName(id)).tpe - } - - try findType catch { case _: MissingRequirementError => NoType } - } - - def typeForIdent(id: String): Option[String] = - requestForIdent(id) flatMap (x => x.typeOf get newTermName(id)) - - def methodsOf(name: String) = - evalExpr[List[String]](methodsCode(name)) map (x => NameTransformer.decode(getOriginalName(x))) - - def completionAware(name: String) = { - // XXX working around "object is not a value" crash, i.e. - // import java.util.ArrayList ; ArrayList.<tab> - clazzForIdent(name) flatMap (_ => evalExpr[Option[CompletionAware]](asCompletionAwareCode(name))) - } - - def extractionValueForIdent(id: String): Option[AnyRef] = - requestForIdent(id) flatMap (_.extractionValue) - - /** Executes code looking for a manifest of type T. - */ - def manifestFor[T: Manifest] = - evalExpr[Manifest[T]]("""manifest[%s]""".format(manifest[T])) - - /** Executes code looking for an implicit value of type T. - */ - def implicitFor[T: Manifest] = { - val s = manifest[T].toString - evalExpr[Option[T]]("{ def f(implicit x: %s = null): %s = x ; Option(f) }".format(s, s)) - // We don't use implicitly so as to fail without failing. - // evalExpr[T]("""implicitly[%s]""".format(manifest[T])) - } - /** Executes code looking for an implicit conversion from the type - * of the given identifier to CompletionAware. - */ - def completionAwareImplicit[T](id: String) = { - val f1string = "%s => %s".format(typeForIdent(id).get, classOf[CompletionAware].getName) - val code = """{ - | def f(implicit x: (%s) = null): %s = x - | val f1 = f - | if (f1 == null) None else Some(f1(%s)) - |}""".stripMargin.format(f1string, f1string, id) - - evalExpr[Option[CompletionAware]](code) - } - - def clazzForIdent(id: String): Option[Class[_]] = - extractionValueForIdent(id) flatMap (x => Option(x) map (_.getClass)) - - private def methodsCode(name: String) = - "%s.%s(%s)".format(classOf[ReflectionCompletion].getName, "methodsOf", name) - - private def asCompletionAwareCode(name: String) = - "%s.%s(%s)".format(classOf[CompletionAware].getName, "unapply", name) - - private def getOriginalName(name: String): String = - nme.originalName(newTermName(name)).toString - - case class InterpreterEvalException(msg: String) extends Exception(msg) - def evalError(msg: String) = throw InterpreterEvalException(msg) - - /** The user-facing eval in :power mode wraps an Option. - */ - def eval[T: Manifest](line: String): Option[T] = - try Some(evalExpr[T](line)) - catch { case InterpreterEvalException(msg) => out println indentString(msg) ; None } - - def evalExpr[T: Manifest](line: String): T = { - // Nothing means the type could not be inferred. - if (manifest[T] eq Manifest.Nothing) - evalError("Could not infer type: try 'eval[SomeType](%s)' instead".format(line)) - - val lhs = getSynthVarName - beQuietDuring { interpret("val " + lhs + " = { " + line + " } ") } - - // TODO - can we meaningfully compare the inferred type T with - // the internal compiler Type assigned to lhs? - // def assignedType = prevRequests.last.typeOf(newTermName(lhs)) - - val req = requestFromLine(lhs, true) match { - case Left(result) => evalError(result.toString) - case Right(req) => req - } - if (req == null || !req.compile || req.handlers.size != 1) - evalError("Eval error.") - - try req.extractionValue.get.asInstanceOf[T] catch { - case e: Exception => evalError(e.getMessage) - } - } - - def interpretExpr[T: Manifest](code: String): Option[T] = beQuietDuring { - interpret(code) match { - case IR.Success => - try prevRequests.last.extractionValue map (_.asInstanceOf[T]) - catch { case e: Exception => out println e ; None } - case _ => None - } - } - - /** Another entry point for tab-completion, ids in scope */ - private def unqualifiedIdNames() = partialFlatMap(allHandlers) { - case x: AssignHandler => List(x.helperName) - case x: ValHandler => List(x.vname) - case x: ModuleHandler => List(x.name) - case x: DefHandler => List(x.name) - case x: ImportHandler => x.importedNames - } filterNot isSynthVarName - - /** Types which have been wildcard imported, such as: - * val x = "abc" ; import x._ // type java.lang.String - * import java.lang.String._ // object java.lang.String - * - * Used by tab completion. - * - * XXX right now this gets import x._ and import java.lang.String._, - * but doesn't figure out import String._. There's a lot of ad hoc - * scope twiddling which should be swept away in favor of digging - * into the compiler scopes. - */ - def wildcardImportedTypes(): List[Type] = { - val xs = allHandlers collect { case x: ImportHandler if x.importsWildcard => x.targetType } - xs.flatten.reverse.distinct - } - - /** Another entry point for tab-completion, ids in scope */ - def unqualifiedIds() = (unqualifiedIdNames() map (_.toString)).distinct.sorted - - /** For static/object method completion */ - def getClassObject(path: String): Option[Class[_]] = //classLoader tryToLoadClass path - try { - Some(Class.forName(path, true, classLoader)) - } catch { - case e: Exception => None - } - - /** Parse the ScalaSig to find type aliases */ - def aliasForType(path: String) = ByteCode.aliasForType(path) - - // Coming soon - // implicit def string2liftedcode(s: String): LiftedCode = new LiftedCode(s) - // case class LiftedCode(code: String) { - // val lifted: String = { - // beQuietDuring { interpret(code) } - // eval2[String]("({ " + code + " }).toString") - // } - // def >> : String = lifted - // } - - // debugging - def isReplDebug = settings.Yrepldebug.value - def isCompletionDebug = settings.Ycompletion.value - def DBG(s: String) = if (isReplDebug) out println s else () -} - -/** Utility methods for the Interpreter. */ -object SparkInterpreter { - - import scala.collection.generic.CanBuildFrom - def partialFlatMap[A, B, CC[X] <: Traversable[X]] - (coll: CC[A]) - (pf: PartialFunction[A, CC[B]]) - (implicit bf: CanBuildFrom[CC[A], B, CC[B]]) = - { - val b = bf(coll) - for (x <- coll collect pf) - b ++= x - - b.result - } - - object DebugParam { - 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: 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: Manifest[T]) { - val manifest = m - val typeStr = { - val str = manifest.toString - // I'm sure there are more to be discovered... - val regexp1 = """(.*?)\[(.*)\]""".r - val regexp2str = """.*\.type#""" - val regexp2 = (regexp2str + """(.*)""").r - - (str.replaceAll("""\n""", "")) match { - case regexp1(clazz, typeArgs) => "%s[%s]".format(clazz, typeArgs.replaceAll(regexp2str, "")) - case regexp2(clazz) => clazz - case _ => str - } - } - } - def breakIf(assertion: => Boolean, args: DebugParam[_]*): Unit = - if (assertion) break(args.toList) - - // start a repl, binding supplied args - def break(args: List[DebugParam[_]]): Unit = { - val intLoop = new SparkInterpreterLoop - intLoop.settings = new Settings(Console.println) - // XXX come back to the dot handling - intLoop.settings.classpath.value = "." - intLoop.createInterpreter - intLoop.in = SparkInteractiveReader.createDefault(intLoop.interpreter) - - // rebind exit so people don't accidentally call System.exit by way of predef - intLoop.interpreter.beQuietDuring { - intLoop.interpreter.interpret("""def exit = println("Type :quit to resume program execution.")""") - for (p <- args) { - intLoop.interpreter.bind(p.name, p.typeStr, p.param) - Console println "%s: %s".format(p.name, p.typeStr) - } - } - intLoop.repl() - intLoop.closeInterpreter - } - - def codegenln(leadingPlus: Boolean, xs: String*): String = codegen(leadingPlus, (xs ++ Array("\n")): _*) - def codegenln(xs: String*): String = codegenln(true, xs: _*) - - def codegen(xs: String*): String = codegen(true, xs: _*) - def codegen(leadingPlus: Boolean, xs: String*): String = { - val front = if (leadingPlus) "+ " else "" - front + (xs map string2codeQuoted mkString " + ") - } - - def string2codeQuoted(str: String) = "\"" + string2code(str) + "\"" - - /** Convert a string into code that can recreate the string. - * This requires replacing all special characters by escape - * codes. It does not add the surrounding " marks. */ - def string2code(str: String): String = { - val res = new StringBuilder - for (c <- str) c match { - case '"' | '\'' | '\\' => res += '\\' ; res += c - case _ if c.isControl => res ++= Chars.char2uescape(c) - case _ => res += c - } - res.toString - } -} - diff --git a/repl/src/main/scala/spark/repl/SparkInterpreterLoop.scala b/repl/src/main/scala/spark/repl/SparkInterpreterLoop.scala deleted file mode 100644 index a118abf3cad27d19cc02fcfcccefacbb1a3ae027..0000000000000000000000000000000000000000 --- a/repl/src/main/scala/spark/repl/SparkInterpreterLoop.scala +++ /dev/null @@ -1,662 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2010 LAMP/EPFL - * @author Alexander Spoon - */ - -package spark.repl - -import scala.tools.nsc -import scala.tools.nsc._ - -import Predef.{ println => _, _ } -import java.io.{ BufferedReader, FileReader, PrintWriter } -import java.io.IOException - -import scala.tools.nsc.{ InterpreterResults => IR } -import scala.annotation.tailrec -import scala.collection.mutable.ListBuffer -import scala.concurrent.ops -import util.{ ClassPath } -import interpreter._ -import io.{ File, Process } - -import spark.SparkContext - -// Classes to wrap up interpreter commands and their results -// You can add new commands by adding entries to val commands -// inside InterpreterLoop. -trait InterpreterControl { - self: SparkInterpreterLoop => - - // the default result means "keep running, and don't record that line" - val defaultResult = Result(true, None) - - // a single interpreter command - sealed abstract class Command extends Function1[List[String], Result] { - def name: String - def help: String - def error(msg: String) = { - out.println(":" + name + " " + msg + ".") - Result(true, None) - } - def usage(): String - } - - case class NoArgs(name: String, help: String, f: () => Result) extends Command { - def usage(): String = ":" + name - def apply(args: List[String]) = if (args.isEmpty) f() else error("accepts no arguments") - } - - case class LineArg(name: String, help: String, f: (String) => Result) extends Command { - def usage(): String = ":" + name + " <line>" - def apply(args: List[String]) = f(args mkString " ") - } - - case class OneArg(name: String, help: String, f: (String) => Result) extends Command { - def usage(): String = ":" + name + " <arg>" - def apply(args: List[String]) = - if (args.size == 1) f(args.head) - else error("requires exactly one argument") - } - - case class VarArgs(name: String, help: String, f: (List[String]) => Result) extends Command { - def usage(): String = ":" + name + " [arg]" - def apply(args: List[String]) = f(args) - } - - // the result of a single command - case class Result(keepRunning: Boolean, lineToRecord: Option[String]) -} - -/** The - * <a href="http://scala-lang.org/" target="_top">Scala</a> - * interactive shell. It provides a read-eval-print loop around - * the Interpreter class. - * After instantiation, clients should call the <code>main()</code> method. - * - * <p>If no in0 is specified, then input will come from the console, and - * the class will attempt to provide input editing feature such as - * input history. - * - * @author Moez A. Abdel-Gawad - * @author Lex Spoon - * @version 1.2 - */ -class SparkInterpreterLoop( - in0: Option[BufferedReader], val out: PrintWriter, master: Option[String]) -extends InterpreterControl { - def this(in0: BufferedReader, out: PrintWriter, master: String) = - this(Some(in0), out, Some(master)) - - def this(in0: BufferedReader, out: PrintWriter) = - this(Some(in0), out, None) - - def this() = this(None, new PrintWriter(Console.out), None) - - /** The input stream from which commands come, set by main() */ - var in: SparkInteractiveReader = _ - - /** 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: SparkInterpreter = _ // set by createInterpreter() - - // classpath entries added via :cp - var addedClasspath: String = "" - - /** A reverse list of commands to replay if the user requests a :replay */ - var replayCommandStack: List[String] = Nil - - /** A list of commands to replay if the user requests a :replay */ - def replayCommands = replayCommandStack.reverse - - /** Record a command for replay should the user request a :replay */ - def addReplay(cmd: String) = replayCommandStack ::= cmd - - /** Close the interpreter and set the var to <code>null</code>. */ - def closeInterpreter() { - if (interpreter ne null) { - interpreter.close - interpreter = null - Thread.currentThread.setContextClassLoader(originalClassLoader) - } - } - - /** Create a new interpreter. */ - def createInterpreter() { - if (addedClasspath != "") - settings.classpath append addedClasspath - - interpreter = new SparkInterpreter(settings, out) { - override protected def parentClassLoader = - classOf[SparkInterpreterLoop].getClassLoader - } - interpreter.setContextClassLoader() - // interpreter.quietBind("settings", "spark.repl.SparkInterpreterSettings", interpreter.isettings) - } - - /** print a friendly help message */ - def printHelp() = { - out println "All commands can be abbreviated - for example :he instead of :help.\n" - val cmds = commands map (x => (x.usage, x.help)) - val width: Int = cmds map { case (x, _) => x.length } max - val formatStr = "%-" + width + "s %s" - cmds foreach { case (usage, help) => out println formatStr.format(usage, help) } - } - - /** Print a welcome message */ - def printWelcome() { - plushln("""Welcome to - ____ __ - / __/__ ___ _____/ /__ - _\ \/ _ \/ _ `/ __/ '_/ - /___/ .__/\_,_/_/ /_/\_\ version 0.0 - /_/ -""") - - import Properties._ - val welcomeMsg = "Using Scala %s (%s, Java %s)".format( - versionString, javaVmName, javaVersion) - plushln(welcomeMsg) - } - - /** Show the history */ - def printHistory(xs: List[String]) { - val defaultLines = 20 - - if (in.history.isEmpty) - return println("No history available.") - - val current = in.history.get.index - val count = try xs.head.toInt catch { case _: Exception => defaultLines } - val lines = in.historyList takeRight count - val offset = current - lines.size + 1 - - for ((line, index) <- lines.zipWithIndex) - println("%d %s".format(index + offset, line)) - } - - /** Some print conveniences */ - def println(x: Any) = out println x - def plush(x: Any) = { out print x ; out.flush() } - def plushln(x: Any) = { out println x ; out.flush() } - - /** Search the history */ - def searchHistory(_cmdline: String) { - val cmdline = _cmdline.toLowerCase - - if (in.history.isEmpty) - return println("No history available.") - - val current = in.history.get.index - val offset = current - in.historyList.size + 1 - - for ((line, index) <- in.historyList.zipWithIndex ; if line.toLowerCase contains cmdline) - println("%d %s".format(index + offset, line)) - } - - /** Prompt to print when awaiting input */ - val prompt = Properties.shellPromptString - - // most commands do not want to micromanage the Result, but they might want - // to print something to the console, so we accomodate Unit and String returns. - object CommandImplicits { - implicit def u2ir(x: Unit): Result = defaultResult - implicit def s2ir(s: String): Result = { - out println s - defaultResult - } - } - - /** Standard commands **/ - val standardCommands: List[Command] = { - import CommandImplicits._ - List( - OneArg("cp", "add an entry (jar or directory) to the classpath", addClasspath), - NoArgs("help", "print this help message", printHelp), - VarArgs("history", "show the history (optional arg: lines to show)", printHistory), - LineArg("h?", "search the history", searchHistory), - OneArg("load", "load and interpret a Scala file", load), - NoArgs("power", "enable power user mode", power), - NoArgs("quit", "exit the interpreter", () => Result(false, None)), - NoArgs("replay", "reset execution and replay all previous commands", replay), - LineArg("sh", "fork a shell and run a command", runShellCmd), - NoArgs("silent", "disable/enable automatic printing of results", verbosity) - ) - } - - /** Power user commands */ - var powerUserOn = false - val powerCommands: List[Command] = { - import CommandImplicits._ - List( - OneArg("completions", "generate list of completions for a given String", completions), - NoArgs("dump", "displays a view of the interpreter's internal state", () => interpreter.power.dump()) - - // VarArgs("tree", "displays ASTs for specified identifiers", - // (xs: List[String]) => interpreter dumpTrees xs) - // LineArg("meta", "given code which produces scala code, executes the results", - // (xs: List[String]) => ) - ) - } - - /** Available commands */ - def commands: List[Command] = standardCommands ::: (if (powerUserOn) powerCommands else Nil) - - def initializeSpark() { - interpreter.beQuietDuring { - command(""" - spark.repl.Main.interp.out.println("Registering with Mesos..."); - spark.repl.Main.interp.out.flush(); - @transient val sc = spark.repl.Main.interp.createSparkContext(); - sc.waitForRegister(); - spark.repl.Main.interp.out.println("Spark context available as sc."); - spark.repl.Main.interp.out.flush(); - """) - command("import spark.SparkContext._"); - } - plushln("Type in expressions to have them evaluated.") - plushln("Type :help for more information.") - } - - var sparkContext: SparkContext = null - - def createSparkContext(): SparkContext = { - val master = this.master match { - case Some(m) => m - case None => { - val prop = System.getenv("MASTER") - if (prop != null) prop else "local" - } - } - sparkContext = new SparkContext(master, "Spark shell") - sparkContext - } - - /** The main read-eval-print loop for the interpreter. It calls - * <code>command()</code> for each line of input, and stops when - * <code>command()</code> returns <code>false</code>. - */ - def repl() { - def readOneLine() = { - out.flush - in readLine prompt - } - // return false if repl should exit - def processLine(line: String): Boolean = - if (line eq null) false // assume null means EOF - else command(line) match { - case Result(false, _) => false - case Result(_, Some(finalLine)) => addReplay(finalLine) ; true - case _ => true - } - - while (processLine(readOneLine)) { } - } - - /** interpret all lines from a specified file */ - def interpretAllFrom(file: File) { - val oldIn = in - val oldReplay = replayCommandStack - - try file applyReader { reader => - in = new SparkSimpleReader(reader, out, false) - plushln("Loading " + file + "...") - repl() - } - finally { - in = oldIn - replayCommandStack = oldReplay - } - } - - /** create a new interpreter and replay all commands so far */ - def replay() { - closeInterpreter() - createInterpreter() - for (cmd <- replayCommands) { - plushln("Replaying: " + cmd) // flush because maybe cmd will have its own output - command(cmd) - out.println - } - } - - /** fork a shell and run a command */ - def runShellCmd(line: String) { - // we assume if they're using :sh they'd appreciate being able to pipeline - interpreter.beQuietDuring { - interpreter.interpret("import _root_.scala.tools.nsc.io.Process.Pipe._") - } - val p = Process(line) - // only bind non-empty streams - def add(name: String, it: Iterator[String]) = - if (it.hasNext) interpreter.bind(name, "scala.List[String]", it.toList) - - List(("stdout", p.stdout), ("stderr", p.stderr)) foreach (add _).tupled - } - - def withFile(filename: String)(action: File => Unit) { - val f = File(filename) - - if (f.exists) action(f) - else out.println("That file does not exist") - } - - def load(arg: String) = { - var shouldReplay: Option[String] = None - withFile(arg)(f => { - interpretAllFrom(f) - shouldReplay = Some(":load " + arg) - }) - Result(true, shouldReplay) - } - - def addClasspath(arg: String): Unit = { - val f = File(arg).normalize - if (f.exists) { - addedClasspath = ClassPath.join(addedClasspath, f.path) - val totalClasspath = ClassPath.join(settings.classpath.value, addedClasspath) - println("Added '%s'. Your new classpath is:\n%s".format(f.path, totalClasspath)) - replay() - } - else out.println("The path '" + f + "' doesn't seem to exist.") - } - - def completions(arg: String): Unit = { - val comp = in.completion getOrElse { return println("Completion unavailable.") } - val xs = comp completions arg - - injectAndName(xs) - } - - def power() { - val powerUserBanner = - """** Power User mode enabled - BEEP BOOP ** - |** scala.tools.nsc._ has been imported ** - |** New vals! Try repl, global, power ** - |** New cmds! :help to discover them ** - |** New defs! Type power.<tab> to reveal **""".stripMargin - - powerUserOn = true - interpreter.unleash() - injectOne("history", in.historyList) - in.completion foreach (x => injectOne("completion", x)) - out println powerUserBanner - } - - def verbosity() = { - val old = interpreter.printResults - interpreter.printResults = !old - out.println("Switched " + (if (old) "off" else "on") + " result printing.") - } - - /** Run one command submitted by the user. Two values are returned: - * (1) whether to keep running, (2) the line to record for replay, - * if any. */ - def command(line: String): Result = { - def withError(msg: String) = { - out println msg - Result(true, None) - } - def ambiguous(cmds: List[Command]) = "Ambiguous: did you mean " + cmds.map(":" + _.name).mkString(" or ") + "?" - - // not a command - if (!line.startsWith(":")) { - // Notice failure to create compiler - if (interpreter.compiler == null) return Result(false, None) - else return Result(true, interpretStartingWith(line)) - } - - val tokens = (line drop 1 split """\s+""").toList - if (tokens.isEmpty) - return withError(ambiguous(commands)) - - val (cmd :: args) = tokens - - // this lets us add commands willy-nilly and only requires enough command to disambiguate - commands.filter(_.name startsWith cmd) match { - case List(x) => x(args) - case Nil => withError("Unknown command. Type :help for help.") - case xs => withError(ambiguous(xs)) - } - } - - private val CONTINUATION_STRING = " | " - private val PROMPT_STRING = "scala> " - - /** If it looks like they're pasting in a scala interpreter - * transcript, remove all the formatting we inserted so we - * can make some sense of it. - */ - private var pasteStamp: Long = 0 - - /** Returns true if it's long enough to quit. */ - def updatePasteStamp(): Boolean = { - /* Enough milliseconds between readLines to call it a day. */ - val PASTE_FINISH = 1000 - - val prevStamp = pasteStamp - pasteStamp = System.currentTimeMillis - - (pasteStamp - prevStamp > PASTE_FINISH) - - } - /** TODO - we could look for the usage of resXX variables in the transcript. - * Right now backreferences to auto-named variables will break. - */ - - /** The trailing lines complication was an attempt to work around the introduction - * of newlines in e.g. email messages of repl sessions. It doesn't work because - * an unlucky newline can always leave you with a syntactically valid first line, - * which is executed before the next line is considered. So this doesn't actually - * accomplish anything, but I'm leaving it in case I decide to try harder. - */ - case class PasteCommand(cmd: String, trailing: ListBuffer[String] = ListBuffer[String]()) - - /** Commands start on lines beginning with "scala>" and each successive - * line which begins with the continuation string is appended to that command. - * Everything else is discarded. When the end of the transcript is spotted, - * all the commands are replayed. - */ - @tailrec private def cleanTranscript(lines: List[String], acc: List[PasteCommand]): List[PasteCommand] = lines match { - case Nil => acc.reverse - case x :: xs if x startsWith PROMPT_STRING => - val first = x stripPrefix PROMPT_STRING - val (xs1, xs2) = xs span (_ startsWith CONTINUATION_STRING) - val rest = xs1 map (_ stripPrefix CONTINUATION_STRING) - val result = (first :: rest).mkString("", "\n", "\n") - - cleanTranscript(xs2, PasteCommand(result) :: acc) - - case ln :: lns => - val newacc = acc match { - case Nil => Nil - case PasteCommand(cmd, trailing) :: accrest => - PasteCommand(cmd, trailing :+ ln) :: accrest - } - cleanTranscript(lns, newacc) - } - - /** The timestamp is for safety so it doesn't hang looking for the end - * of a transcript. Ad hoc parsing can't be too demanding. You can - * also use ctrl-D to start it parsing. - */ - @tailrec private def interpretAsPastedTranscript(lines: List[String]) { - val line = in.readLine("") - val finished = updatePasteStamp() - - if (line == null || finished || line.trim == PROMPT_STRING.trim) { - val xs = cleanTranscript(lines.reverse, Nil) - println("Replaying %d commands from interpreter transcript." format xs.size) - for (PasteCommand(cmd, trailing) <- xs) { - out.flush() - def runCode(code: String, extraLines: List[String]) { - (interpreter interpret code) match { - case IR.Incomplete if extraLines.nonEmpty => - runCode(code + "\n" + extraLines.head, extraLines.tail) - case _ => () - } - } - runCode(cmd, trailing.toList) - } - } - else - interpretAsPastedTranscript(line :: lines) - } - - /** Interpret expressions starting with the first line. - * Read lines until a complete compilation unit is available - * or until a syntax error has been seen. If a full unit is - * read, go ahead and interpret it. Return the full string - * to be recorded for replay, if any. - */ - def interpretStartingWith(code: String): Option[String] = { - // signal completion non-completion input has been received - in.completion foreach (_.resetVerbosity()) - - 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(CONTINUATION_STRING) match { - case null => - // we know compilation is going to fail since we're at EOF and the - // parser thinks the input is still incomplete, but since this is - // a file being read non-interactively we want to fail. So we send - // it straight to the compiler for the nice error message. - interpreter.compileString(code) - None - - case line => interpretStartingWith(code + "\n" + line) - } - } - - /** Here we place ourselves between the user and the interpreter and examine - * the input they are ostensibly submitting. We intervene in several cases: - * - * 1) If the line starts with "scala> " it is assumed to be an interpreter paste. - * 2) If the line starts with "." (but not ".." or "./") it is treated as an invocation - * on the previous result. - * 3) If the Completion object's execute returns Some(_), we inject that value - * and avoid the interpreter, as it's likely not valid scala code. - */ - if (code == "") None - else if (code startsWith PROMPT_STRING) { - updatePasteStamp() - interpretAsPastedTranscript(List(code)) - None - } - else if (Completion.looksLikeInvocation(code) && interpreter.mostRecentVar != "") { - 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 { - case settings: GenericRunnerSettings => - for (filename <- settings.loadfiles.value) { - val cmd = ":load " + filename - command(cmd) - addReplay(cmd) - out.println() - } - case _ => - } - - def main(settings: Settings) { - this.settings = settings - createInterpreter() - - // sets in to some kind of reader depending on environmental cues - in = in0 match { - case Some(in0) => new SparkSimpleReader(in0, out, true) - case None => - // the interpreter is passed as an argument to expose tab completion info - if (settings.Xnojline.value || Properties.isEmacsShell) new SparkSimpleReader - else if (settings.noCompletion.value) SparkInteractiveReader.createDefault() - else SparkInteractiveReader.createDefault(interpreter) - } - - loadFiles(settings) - try { - // it is broken on startup; go ahead and exit - if (interpreter.reporter.hasErrors) return - - printWelcome() - - // this is about the illusion of snappiness. We call initialize() - // which spins off a separate thread, then print the prompt and try - // our best to look ready. Ideally the user will spend a - // couple seconds saying "wow, it starts so fast!" and by the time - // they type a command the compiler is ready to roll. - interpreter.initialize() - initializeSpark() - repl() - } - finally closeInterpreter() - } - - private def objClass(x: Any) = x.asInstanceOf[AnyRef].getClass - private def objName(x: Any) = { - val clazz = objClass(x) - val typeParams = clazz.getTypeParameters - val basename = clazz.getName - val tpString = if (typeParams.isEmpty) "" else "[%s]".format(typeParams map (_ => "_") mkString ", ") - - basename + tpString - } - - // injects one value into the repl; returns pair of name and class - def injectOne(name: String, obj: Any): Tuple2[String, String] = { - 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 injectDebug(args: List[Any]): String = { - val strs = - for ((arg, i) <- args.zipWithIndex) yield { - val varName = "p" + (i + 1) - val (vname, vtype) = injectOne(varName, arg) - vname + ": " + vtype - } - - if (strs.size == 0) "Set no variables." - else "Variables set:\n" + strs.mkString("\n") - } - - /** process command-line arguments and do as they request */ - def main(args: Array[String]) { - def error1(msg: String) = out println ("scala: " + msg) - val command = new InterpreterCommand(args.toList, error1) - def neededHelp(): String = - (if (command.settings.help.value) command.usageMsg + "\n" else "") + - (if (command.settings.Xhelp.value) command.xusageMsg + "\n" else "") - - // if they asked for no help and command is valid, we call the real main - neededHelp() match { - case "" => if (command.ok) main(command.settings) // else nothing - case help => plush(help) - } - } -} - diff --git a/repl/src/main/scala/spark/repl/SparkInterpreterSettings.scala b/repl/src/main/scala/spark/repl/SparkInterpreterSettings.scala deleted file mode 100644 index ffa477785b440475dac21e43251cef6823f1c814..0000000000000000000000000000000000000000 --- a/repl/src/main/scala/spark/repl/SparkInterpreterSettings.scala +++ /dev/null @@ -1,112 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2010 LAMP/EPFL - * @author Alexander Spoon - */ - -package spark.repl - -import scala.tools.nsc -import scala.tools.nsc._ - -/** Settings for the interpreter - * - * @version 1.0 - * @author Lex Spoon, 2007/3/24 - **/ -class SparkInterpreterSettings(repl: SparkInterpreter) { - /** A list of paths where :load should look */ - var loadPath = List(".") - - /** The maximum length of toString to use when printing the result - * of an evaluation. 0 means no maximum. If a printout requires - * more than this number of characters, then the printout is - * truncated. - */ - var maxPrintString = 800 - - /** The maximum number of completion candidates to print for tab - * completion without requiring confirmation. - */ - var maxAutoprintCompletion = 250 - - /** String unwrapping can be disabled if it is causing issues. - * Settings this to false means you will see Strings like "$iw.$iw.". - */ - var unwrapStrings = true - - def deprecation_=(x: Boolean) = { - val old = repl.settings.deprecation.value - repl.settings.deprecation.value = x - if (!old && x) println("Enabled -deprecation output.") - else if (old && !x) println("Disabled -deprecation output.") - } - def deprecation: Boolean = repl.settings.deprecation.value - - def allSettings = Map( - "maxPrintString" -> maxPrintString, - "maxAutoprintCompletion" -> maxAutoprintCompletion, - "unwrapStrings" -> unwrapStrings, - "deprecation" -> deprecation - ) - - private def allSettingsString = - allSettings.toList sortBy (_._1) map { case (k, v) => " " + k + " = " + v + "\n" } mkString - - override def toString = """ - | SparkInterpreterSettings { - | %s - | }""".stripMargin.format(allSettingsString) -} - -/* Utilities for the InterpreterSettings class - * - * @version 1.0 - * @author Lex Spoon, 2007/5/24 - */ -object SparkInterpreterSettings { - /** Source code for the InterpreterSettings class. This is - * used so that the interpreter is sure to have the code - * available. - * - * XXX I'm not seeing why this degree of defensiveness is necessary. - * If files are missing the repl's not going to work, it's not as if - * we have string source backups for anything else. - */ - val sourceCodeForClass = -""" -package scala.tools.nsc - -/** Settings for the interpreter - * - * @version 1.0 - * @author Lex Spoon, 2007/3/24 - **/ -class SparkInterpreterSettings(repl: Interpreter) { - /** A list of paths where :load should look */ - var loadPath = List(".") - - /** The maximum length of toString to use when printing the result - * of an evaluation. 0 means no maximum. If a printout requires - * more than this number of characters, then the printout is - * truncated. - */ - var maxPrintString = 2400 - - def deprecation_=(x: Boolean) = { - val old = repl.settings.deprecation.value - repl.settings.deprecation.value = x - if (!old && x) println("Enabled -deprecation output.") - else if (old && !x) println("Disabled -deprecation output.") - } - def deprecation: Boolean = repl.settings.deprecation.value - - override def toString = - "SparkInterpreterSettings {\n" + -// " loadPath = " + loadPath + "\n" + - " maxPrintString = " + maxPrintString + "\n" + - "}" -} - -""" - -} diff --git a/repl/src/main/scala/spark/repl/SparkCompletion.scala b/repl/src/main/scala/spark/repl/SparkJLineCompletion.scala similarity index 60% rename from repl/src/main/scala/spark/repl/SparkCompletion.scala rename to repl/src/main/scala/spark/repl/SparkJLineCompletion.scala index c6ed1860f019e54f9a2dd68831f2991f47a8c1aa..5d12898732866b8d693e06e61df6b9376fa12be1 100644 --- a/repl/src/main/scala/spark/repl/SparkCompletion.scala +++ b/repl/src/main/scala/spark/repl/SparkJLineCompletion.scala @@ -1,62 +1,31 @@ /* NSC -- new Scala compiler - * Copyright 2005-2010 LAMP/EPFL + * Copyright 2005-2011 LAMP/EPFL * @author Paul Phillips */ - package spark.repl -import scala.tools.nsc import scala.tools.nsc._ -import scala.tools.nsc.interpreter import scala.tools.nsc.interpreter._ -import jline._ -import java.util.{ List => JList } -import util.returning - -object SparkCompletion { - def looksLikeInvocation(code: String) = ( - (code != null) - && (code startsWith ".") - && !(code == ".") - && !(code startsWith "./") - && !(code startsWith "..") - ) - - object Forwarder { - def apply(forwardTo: () => Option[CompletionAware]): CompletionAware = new CompletionAware { - def completions(verbosity: Int) = forwardTo() map (_ completions verbosity) getOrElse Nil - override def follow(s: String) = forwardTo() flatMap (_ follow s) - } - } -} -import SparkCompletion._ +import scala.tools.jline._ +import scala.tools.jline.console.completer._ +import Completion._ +import collection.mutable.ListBuffer // REPL completor - queries supplied interpreter for valid // completions based on current contents of buffer. -class SparkCompletion(val repl: SparkInterpreter) extends SparkCompletionOutput { - // verbosity goes up with consecutive tabs - private var verbosity: Int = 0 - def resetVerbosity() = verbosity = 0 - - def isCompletionDebug = repl.isCompletionDebug - def DBG(msg: => Any) = if (isCompletionDebug) println(msg.toString) - def debugging[T](msg: String): T => T = (res: T) => returning[T](res)(x => DBG(msg + x)) - - lazy val global: repl.compiler.type = repl.compiler +class JLineCompletion(val intp: SparkIMain) extends Completion with CompletionOutput { + val global: intp.global.type = intp.global import global._ import definitions.{ PredefModule, RootClass, AnyClass, AnyRefClass, ScalaPackage, JavaLangPackage } - - // XXX not yet used. - lazy val dottedPaths = { - def walk(tp: Type): scala.List[Symbol] = { - val pkgs = tp.nonPrivateMembers filter (_.isPackage) - pkgs ++ (pkgs map (_.tpe) flatMap walk) - } - walk(RootClass.tpe) - } + type ExecResult = Any + import intp.{ DBG, debugging, afterTyper } + // verbosity goes up with consecutive tabs + private var verbosity: Int = 0 + def resetVerbosity() = verbosity = 0 + def getType(name: String, isModule: Boolean) = { val f = if (isModule) definitions.getModule(_: Name) else definitions.getClass(_: Name) try Some(f(name).tpe) @@ -69,53 +38,74 @@ class SparkCompletion(val repl: SparkInterpreter) extends SparkCompletionOutput trait CompilerCompletion { def tp: Type def effectiveTp = tp match { - case MethodType(Nil, resType) => resType - case PolyType(Nil, resType) => resType - case _ => tp + case MethodType(Nil, resType) => resType + case NullaryMethodType(resType) => resType + case _ => tp } // for some reason any's members don't show up in subclasses, which // we need so 5.<tab> offers asInstanceOf etc. private def anyMembers = AnyClass.tpe.nonPrivateMembers - def anyRefMethodsToShow = List("isInstanceOf", "asInstanceOf", "toString") + def anyRefMethodsToShow = Set("isInstanceOf", "asInstanceOf", "toString") def tos(sym: Symbol) = sym.name.decode.toString def memberNamed(s: String) = members find (x => tos(x) == s) def hasMethod(s: String) = methods exists (x => tos(x) == s) - + // XXX we'd like to say "filterNot (_.isDeprecated)" but this causes the // compiler to crash for reasons not yet known. - def members = (effectiveTp.nonPrivateMembers ++ anyMembers) filter (_.isPublic) + def members = afterTyper((effectiveTp.nonPrivateMembers ++ anyMembers) filter (_.isPublic)) def methods = members filter (_.isMethod) def packages = members filter (_.isPackage) def aliases = members filter (_.isAliasType) - + def memberNames = members map tos def methodNames = methods map tos def packageNames = packages map tos def aliasNames = aliases map tos } - + object TypeMemberCompletion { + def apply(tp: Type, runtimeType: Type, param: NamedParam): TypeMemberCompletion = { + new TypeMemberCompletion(tp) { + var upgraded = false + lazy val upgrade = { + intp rebind param + intp.reporter.printMessage("\nRebinding stable value %s from %s to %s".format(param.name, tp, param.tpe)) + upgraded = true + new TypeMemberCompletion(runtimeType) + } + override def completions(verbosity: Int) = { + super.completions(verbosity) ++ ( + if (verbosity == 0) Nil + else upgrade.completions(verbosity) + ) + } + override def follow(s: String) = super.follow(s) orElse { + if (upgraded) upgrade.follow(s) + else None + } + override def alternativesFor(id: String) = super.alternativesFor(id) ++ ( + if (upgraded) upgrade.alternativesFor(id) + else Nil + ) distinct + } + } def apply(tp: Type): TypeMemberCompletion = { if (tp.typeSymbol.isPackageClass) new PackageCompletion(tp) else new TypeMemberCompletion(tp) } def imported(tp: Type) = new ImportCompletion(tp) } - - class TypeMemberCompletion(val tp: Type) extends CompletionAware with CompilerCompletion { + + class TypeMemberCompletion(val tp: Type) extends CompletionAware + with CompilerCompletion { def excludeEndsWith: List[String] = Nil def excludeStartsWith: List[String] = List("<") // <byname>, <repeated>, etc. - def excludeNames: List[String] = anyref.methodNames.filterNot(anyRefMethodsToShow.contains) ++ List("_root_") + def excludeNames: List[String] = (anyref.methodNames filterNot anyRefMethodsToShow) :+ "_root_" def methodSignatureString(sym: Symbol) = { - def asString = new MethodSymbolOutput(sym).methodString() - - if (isCompletionDebug) - repl.power.showAtAllPhases(asString) - - atPhase(currentRun.typerPhase)(asString) + SparkIMain stripString afterTyper(new MethodSymbolOutput(sym).methodString()) } def exclude(name: String): Boolean = ( @@ -139,7 +129,7 @@ class SparkCompletion(val repl: SparkInterpreter) extends SparkCompletionOutput if (alts.nonEmpty) "" :: alts else Nil } - override def toString = "TypeMemberCompletion(%s)".format(tp) + override def toString = "%s (%d members)".format(tp, members.size) } class PackageCompletion(tp: Type) extends TypeMemberCompletion(tp) { @@ -165,32 +155,36 @@ class SparkCompletion(val repl: SparkInterpreter) extends SparkCompletionOutput // the unqualified vals/defs/etc visible in the repl object ids extends CompletionAware { - override def completions(verbosity: Int) = repl.unqualifiedIds ::: List("classOf") - // we try to use the compiler and fall back on reflection if necessary - // (which at present is for anything defined in the repl session.) - override def follow(id: String) = + override def completions(verbosity: Int) = intp.unqualifiedIds ++ List("classOf") //, "_root_") + // now we use the compiler for everything. + override def follow(id: String) = { if (completions(0) contains id) { - for (clazz <- repl clazzForIdent id) yield { - // XXX The isMemberClass check is a workaround for the crasher described - // in the comments of #3431. The issue as described by iulian is: - // - // Inner classes exist as symbols - // inside their enclosing class, but also inside their package, with a mangled - // name (A$B). The mangled names should never be loaded, and exist only for the - // optimizer, which sometimes cannot get the right symbol, but it doesn't care - // and loads the bytecode anyway. - // - // So this solution is incorrect, but in the short term the simple fix is - // to skip the compiler any time completion is requested on a nested class. - if (clazz.isMemberClass) new InstanceCompletion(clazz) - else (typeOf(clazz.getName) map TypeMemberCompletion.apply) getOrElse new InstanceCompletion(clazz) + intp typeOfExpression id map { tpe => + def default = TypeMemberCompletion(tpe) + + // only rebinding vals in power mode for now. + if (!isReplPower) default + else intp runtimeClassAndTypeOfTerm id match { + case Some((clazz, runtimeType)) => + val sym = intp.symbolOfTerm(id) + if (sym.isStable) { + val param = new NamedParam.Untyped(id, intp valueOfTerm id getOrElse null) + TypeMemberCompletion(tpe, runtimeType, param) + } + else default + case _ => + default + } } } - else None + else + None + } + override def toString = "<repl ids> (%s)".format(completions(0).size) } - // wildcard imports in the repl like "import global._" or "import String._" - private def imported = repl.wildcardImportedTypes map TypeMemberCompletion.imported + // user-issued wildcard imports like "import global._" or "import String._" + private def imported = intp.sessionWildcards map TypeMemberCompletion.imported // literal Ints, Strings, etc. object literals extends CompletionAware { @@ -211,7 +205,13 @@ class SparkCompletion(val repl: SparkInterpreter) extends SparkCompletionOutput } // top level packages - object rootClass extends TypeMemberCompletion(RootClass.tpe) { } + object rootClass extends TypeMemberCompletion(RootClass.tpe) { + override def completions(verbosity: Int) = super.completions(verbosity) :+ "_root_" + override def follow(id: String) = id match { + case "_root_" => Some(this) + case _ => super.follow(id) + } + } // members of Predef object predef extends TypeMemberCompletion(PredefModule.tpe) { override def excludeEndsWith = super.excludeEndsWith ++ List("Wrapper", "ArrayOps") @@ -252,14 +252,25 @@ class SparkCompletion(val repl: SparkInterpreter) extends SparkCompletionOutput } // the list of completion aware objects which should be consulted + // for top level unqualified, it's too noisy to let much in. lazy val topLevelBase: List[CompletionAware] = List(ids, rootClass, predef, scalalang, javalang, literals) def topLevel = topLevelBase ++ imported + def topLevelThreshold = 50 // the first tier of top level objects (doesn't include file completion) - def topLevelFor(parsed: Parsed) = topLevel flatMap (_ completionsFor parsed) + def topLevelFor(parsed: Parsed): List[String] = { + val buf = new ListBuffer[String] + topLevel foreach { ca => + buf ++= (ca completionsFor parsed) + + if (buf.size > topLevelThreshold) + return buf.toList.sorted + } + buf.toList + } // the most recent result - def lastResult = Forwarder(() => ids follow repl.mostRecentVar) + def lastResult = Forwarder(() => ids follow intp.mostRecentVar) def lastResultFor(parsed: Parsed) = { /** The logic is a little tortured right now because normally '.' is @@ -268,9 +279,9 @@ class SparkCompletion(val repl: SparkInterpreter) extends SparkCompletionOutput val xs = lastResult completionsFor parsed if (parsed.isEmpty) xs map ("." + _) else xs } - + // chasing down results which won't parse - def execute(line: String): Option[Any] = { + def execute(line: String): Option[ExecResult] = { val parsed = Parsed(line) def noDotOrSlash = line forall (ch => ch != '.' && ch != '/') @@ -286,9 +297,7 @@ class SparkCompletion(val repl: SparkInterpreter) extends SparkCompletionOutput def completions(buf: String): List[String] = topLevelFor(Parsed.dotted(buf + ".", buf.length + 1)) - // jline's entry point - lazy val jline: ArgumentCompletor = - returning(new ArgumentCompletor(new JLineCompletion, new JLineDelimiter))(_ setStrict false) + def completer(): ScalaCompleter = new JLineTabCompletion /** This gets a little bit hairy. It's no small feat delegating everything * and also keeping track of exactly where the cursor is and where it's supposed @@ -296,44 +305,47 @@ class SparkCompletion(val repl: SparkInterpreter) extends SparkCompletionOutput * string in the list of completions, that means we are expanding a unique * completion, so don't update the "last" buffer because it'll be wrong. */ - class JLineCompletion extends Completor { + class JLineTabCompletion extends ScalaCompleter { // For recording the buffer on the last tab hit private var lastBuf: String = "" private var lastCursor: Int = -1 // Does this represent two consecutive tabs? - def isConsecutiveTabs(buf: String, cursor: Int) = cursor == lastCursor && buf == lastBuf - + def isConsecutiveTabs(buf: String, cursor: Int) = + cursor == lastCursor && buf == lastBuf + // Longest common prefix - def commonPrefix(xs: List[String]) = - if (xs.isEmpty) "" - else xs.reduceLeft(_ zip _ takeWhile (x => x._1 == x._2) map (_._1) mkString) + def commonPrefix(xs: List[String]): String = { + if (xs.isEmpty || xs.contains("")) "" + else xs.head.head match { + case ch => + if (xs.tail forall (_.head == ch)) "" + ch + commonPrefix(xs map (_.tail)) + else "" + } + } // This is jline's entry point for completion. - override def complete(_buf: String, cursor: Int, candidates: java.util.List[java.lang.String]): Int = { - val buf = onull(_buf) + override def complete(buf: String, cursor: Int): Candidates = { verbosity = if (isConsecutiveTabs(buf, cursor)) verbosity + 1 else 0 - DBG("complete(%s, %d) last = (%s, %d), verbosity: %s".format(buf, cursor, lastBuf, lastCursor, verbosity)) + DBG("\ncomplete(%s, %d) last = (%s, %d), verbosity: %s".format(buf, cursor, lastBuf, lastCursor, verbosity)) // we don't try lower priority completions unless higher ones return no results. - def tryCompletion(p: Parsed, completionFunction: Parsed => List[String]): Option[Int] = { - completionFunction(p) match { - case Nil => None - case xs => - // modify in place and return the position - xs.foreach(x => candidates.add(x)) - - // update the last buffer unless this is an alternatives list - if (xs contains "") Some(p.cursor) - else { - val advance = commonPrefix(xs) - lastCursor = p.position + advance.length - lastBuf = (buf take p.position) + advance - - DBG("tryCompletion(%s, _) lastBuf = %s, lastCursor = %s, p.position = %s".format(p, lastBuf, lastCursor, p.position)) - Some(p.position) - } - } + def tryCompletion(p: Parsed, completionFunction: Parsed => List[String]): Option[Candidates] = { + val winners = completionFunction(p) + if (winners.isEmpty) + return None + val newCursor = + if (winners contains "") p.cursor + else { + val advance = commonPrefix(winners) + lastCursor = p.position + advance.length + lastBuf = (buf take p.position) + advance + DBG("tryCompletion(%s, _) lastBuf = %s, lastCursor = %s, p.position = %s".format( + p, lastBuf, lastCursor, p.position)) + p.position + } + + Some(Candidates(newCursor, winners)) } def mkDotted = Parsed.dotted(buf, cursor) withVerbosity verbosity @@ -345,9 +357,23 @@ class SparkCompletion(val repl: SparkInterpreter) extends SparkCompletionOutput else tryCompletion(Parsed.dotted(buf drop 1, cursor), lastResultFor) def regularCompletion = tryCompletion(mkDotted, topLevelFor) - def fileCompletion = tryCompletion(mkUndelimited, FileCompletion completionsFor _.buffer) + def fileCompletion = + if (!looksLikePath(buf)) None + else tryCompletion(mkUndelimited, FileCompletion completionsFor _.buffer) - (lastResultCompletion orElse regularCompletion orElse fileCompletion) getOrElse cursor + /** This is the kickoff point for all manner of theoretically possible compiler + * unhappiness - fault may be here or elsewhere, but we don't want to crash the + * repl regardless. Hopefully catching Exception is enough, but because the + * compiler still throws some Errors it may not be. + */ + try { + (lastResultCompletion orElse regularCompletion orElse fileCompletion) getOrElse Candidates(cursor, Nil) + } + catch { + case ex: Exception => + DBG("Error: complete(%s, %s) provoked %s".format(buf, cursor, ex)) + Candidates(cursor, List(" ", "<completion error: " + ex.getMessage + ">")) + } } } } diff --git a/repl/src/main/scala/spark/repl/SparkJLineReader.scala b/repl/src/main/scala/spark/repl/SparkJLineReader.scala index 9d761c06fcc2d6c0111945a565bfdee1b97bb5d2..f7b0261ad02873f856e594d87ecb052a967a7510 100644 --- a/repl/src/main/scala/spark/repl/SparkJLineReader.scala +++ b/repl/src/main/scala/spark/repl/SparkJLineReader.scala @@ -1,38 +1,79 @@ /* NSC -- new Scala compiler - * Copyright 2005-2010 LAMP/EPFL + * Copyright 2005-2011 LAMP/EPFL * @author Stepan Koltsov */ package spark.repl -import scala.tools.nsc import scala.tools.nsc._ -import scala.tools.nsc.interpreter import scala.tools.nsc.interpreter._ -import java.io.File -import jline.{ ConsoleReader, ArgumentCompletor, History => JHistory } +import scala.tools.jline.console.ConsoleReader +import scala.tools.jline.console.completer._ +import session._ +import scala.collection.JavaConverters._ +import Completion._ +import io.Streamable.slurp /** Reads from the console using JLine */ -class SparkJLineReader(interpreter: SparkInterpreter) extends SparkInteractiveReader { - def this() = this(null) - - override lazy val history = Some(History(consoleReader)) - override lazy val completion = Option(interpreter) map (x => new SparkCompletion(x)) +class SparkJLineReader(val completion: Completion) extends InteractiveReader { + val interactive = true + lazy val history: JLineHistory = JLineHistory() + lazy val keyBindings = + try KeyBinding parse slurp(term.getDefaultBindings) + catch { case _: Exception => Nil } + + private def term = consoleReader.getTerminal() + def reset() = term.reset() + def init() = term.init() - val consoleReader = { - val r = new jline.ConsoleReader() - r setHistory (History().jhistory) - r setBellEnabled false - completion foreach { c => - r addCompletor c.jline - r setAutoprintThreshhold 250 + def scalaToJline(tc: ScalaCompleter): Completer = new Completer { + def complete(_buf: String, cursor: Int, candidates: JList[CharSequence]): Int = { + val buf = if (_buf == null) "" else _buf + val Candidates(newCursor, newCandidates) = tc.complete(buf, cursor) + newCandidates foreach (candidates add _) + newCursor + } + } + + class JLineConsoleReader extends ConsoleReader with ConsoleReaderHelper { + // working around protected/trait/java insufficiencies. + def goBack(num: Int): Unit = back(num) + def readOneKey(prompt: String) = { + this.print(prompt) + this.flush() + this.readVirtualKey() + } + def eraseLine() = consoleReader.resetPromptLine("", "", 0) + def redrawLineAndFlush(): Unit = { flush() ; drawLine() ; flush() } + + this setBellEnabled false + if (history ne NoHistory) + this setHistory history + + if (completion ne NoCompletion) { + val argCompletor: ArgumentCompleter = + new ArgumentCompleter(new JLineDelimiter, scalaToJline(completion.completer())) + argCompletor setStrict false + + this addCompleter argCompletor + this setAutoprintThreshold 400 // max completion candidates without warning } - - r } + val consoleReader: JLineConsoleReader = new JLineConsoleReader() + + def currentLine: String = consoleReader.getCursorBuffer.buffer.toString + def redrawLine() = consoleReader.redrawLineAndFlush() + def eraseLine() = { + while (consoleReader.delete()) { } + // consoleReader.eraseLine() + } def readOneLine(prompt: String) = consoleReader readLine prompt - val interactive = true + def readOneKey(prompt: String) = consoleReader readOneKey prompt } +object JLineReader { + def apply(intp: SparkIMain): JLineReader = apply(new JLineCompletion(intp)) + def apply(comp: Completion): JLineReader = new JLineReader(comp) +} diff --git a/repl/src/main/scala/spark/repl/SparkMemberHandlers.scala b/repl/src/main/scala/spark/repl/SparkMemberHandlers.scala new file mode 100644 index 0000000000000000000000000000000000000000..2980dfcd76834652b0a4e205253f99c93d8b6789 --- /dev/null +++ b/repl/src/main/scala/spark/repl/SparkMemberHandlers.scala @@ -0,0 +1,207 @@ +/* NSC -- new Scala compiler + * Copyright 2005-2011 LAMP/EPFL + * @author Martin Odersky + */ + +package spark.repl + +import scala.tools.nsc._ +import scala.tools.nsc.interpreter._ + +import scala.collection.{ mutable, immutable } +import scala.PartialFunction.cond +import scala.reflect.NameTransformer +import util.Chars + +trait SparkMemberHandlers { + val intp: SparkIMain + + import intp.{ Request, global, naming, atPickler } + import global._ + import naming._ + + private def codegenln(leadingPlus: Boolean, xs: String*): String = codegen(leadingPlus, (xs ++ Array("\n")): _*) + private def codegenln(xs: String*): String = codegenln(true, xs: _*) + + private def codegen(xs: String*): String = codegen(true, xs: _*) + private def codegen(leadingPlus: Boolean, xs: String*): String = { + val front = if (leadingPlus) "+ " else "" + front + (xs map string2codeQuoted mkString " + ") + } + private implicit def name2string(name: Name) = name.toString + + /** A traverser that finds all mentioned identifiers, i.e. things + * that need to be imported. It might return extra names. + */ + private class ImportVarsTraverser extends Traverser { + val importVars = new mutable.HashSet[Name]() + + override def traverse(ast: Tree) = ast match { + case Ident(name) => + // XXX this is obviously inadequate but it's going to require some effort + // to get right. + if (name.toString startsWith "x$") () + else importVars += name + case _ => super.traverse(ast) + } + } + private object ImportVarsTraverser { + def apply(member: Tree) = { + val ivt = new ImportVarsTraverser() + ivt traverse member + ivt.importVars.toList + } + } + + def chooseHandler(member: Tree): MemberHandler = member match { + case member: DefDef => new DefHandler(member) + case member: ValDef => new ValHandler(member) + case member@Assign(Ident(_), _) => new AssignHandler(member) + case member: ModuleDef => new ModuleHandler(member) + case member: ClassDef => new ClassHandler(member) + case member: TypeDef => new TypeAliasHandler(member) + case member: Import => new ImportHandler(member) + case DocDef(_, documented) => chooseHandler(documented) + case member => new GenericHandler(member) + } + + sealed abstract class MemberDefHandler(override val member: MemberDef) extends MemberHandler(member) { + def name: Name = member.name + def mods: Modifiers = member.mods + def keyword = member.keyword + def prettyName = NameTransformer.decode(name) + + override def definesImplicit = member.mods.isImplicit + override def definesTerm: Option[TermName] = Some(name.toTermName) filter (_ => name.isTermName) + override def definesType: Option[TypeName] = Some(name.toTypeName) filter (_ => name.isTypeName) + } + + /** Class to handle one member among all the members included + * in a single interpreter request. + */ + sealed abstract class MemberHandler(val member: Tree) { + def definesImplicit = false + def definesValue = false + def isLegalTopLevel = member match { + case _: ModuleDef | _: ClassDef | _: Import => true + case _ => false + } + + def definesTerm = Option.empty[TermName] + def definesType = Option.empty[TypeName] + + lazy val referencedNames = ImportVarsTraverser(member) + def importedNames = List[Name]() + def definedNames = definesTerm.toList ++ definesType.toList + def definedOrImported = definedNames ++ importedNames + + def extraCodeToEvaluate(req: Request): String = "" + def resultExtractionCode(req: Request): String = "" + + private def shortName = this.getClass.toString split '.' last + override def toString = shortName + referencedNames.mkString(" (refs: ", ", ", ")") + } + + class GenericHandler(member: Tree) extends MemberHandler(member) + + class ValHandler(member: ValDef) extends MemberDefHandler(member) { + val maxStringElements = 1000 // no need to mkString billions of elements + override def definesValue = true + + override def resultExtractionCode(req: Request): String = { + val isInternal = isUserVarName(name) && req.lookupTypeOf(name) == "Unit" + if (!mods.isPublic || isInternal) "" + else { + // if this is a lazy val we avoid evaluating it here + val resultString = + if (mods.isLazy) codegenln(false, "<lazy>") + else any2stringOf(req fullPath name, maxStringElements) + + """ + "%s: %s = " + %s""".format(prettyName, string2code(req typeOf name), resultString) + } + } + } + + class DefHandler(member: DefDef) extends MemberDefHandler(member) { + private def vparamss = member.vparamss + // true if 0-arity + override def definesValue = vparamss.isEmpty || vparamss.head.isEmpty + override def resultExtractionCode(req: Request) = + if (mods.isPublic) codegenln(name, ": ", req.typeOf(name)) else "" + } + + class AssignHandler(member: Assign) extends MemberHandler(member) { + val lhs = member.lhs.asInstanceOf[Ident] // an unfortunate limitation + val name = newTermName(freshInternalVarName()) + + override def definesTerm = Some(name) + override def definesValue = true + override def extraCodeToEvaluate(req: Request) = + """val %s = %s""".format(name, lhs) + + /** Print out lhs instead of the generated varName */ + override def resultExtractionCode(req: Request) = { + val lhsType = string2code(req lookupTypeOf name) + val res = string2code(req fullPath name) + + """ + "%s: %s = " + %s + "\n" """.format(lhs, lhsType, res) + "\n" + } + } + + class ModuleHandler(module: ModuleDef) extends MemberDefHandler(module) { + override def definesTerm = Some(name) + override def definesValue = true + + override def resultExtractionCode(req: Request) = codegenln("defined module ", name) + } + + class ClassHandler(member: ClassDef) extends MemberDefHandler(member) { + override def definesType = Some(name.toTypeName) + override def definesTerm = Some(name.toTermName) filter (_ => mods.isCase) + + override def resultExtractionCode(req: Request) = + codegenln("defined %s %s".format(keyword, name)) + } + + class TypeAliasHandler(member: TypeDef) extends MemberDefHandler(member) { + private def isAlias = mods.isPublic && treeInfo.isAliasTypeDef(member) + override def definesType = Some(name.toTypeName) filter (_ => isAlias) + + override def resultExtractionCode(req: Request) = + codegenln("defined type alias ", name) + "\n" + } + + class ImportHandler(imp: Import) extends MemberHandler(imp) { + val Import(expr, selectors) = imp + def targetType = intp.typeOfExpression("" + expr) + + // wildcard imports, e.g. import foo._ + private def selectorWild = selectors filter (_.name == nme.USCOREkw) + // renamed imports, e.g. import foo.{ bar => baz } + private def selectorRenames = selectors map (_.rename) filterNot (_ == null) + + /** Whether this import includes a wildcard import */ + val importsWildcard = selectorWild.nonEmpty + + def implicitSymbols = importedSymbols filter (_.isImplicit) + def importedSymbols = individualSymbols ++ wildcardSymbols + + lazy val individualSymbols: List[Symbol] = + atPickler(targetType.toList flatMap (tp => individualNames map (tp nonPrivateMember _))) + + lazy val wildcardSymbols: List[Symbol] = + if (importsWildcard) atPickler(targetType.toList flatMap (_.nonPrivateMembers)) + else Nil + + /** Complete list of names imported by a wildcard */ + lazy val wildcardNames: List[Name] = wildcardSymbols map (_.name) + lazy val individualNames: List[Name] = selectorRenames filterNot (_ == nme.USCOREkw) flatMap (_.bothNames) + + /** The names imported by this statement */ + override lazy val importedNames: List[Name] = wildcardNames ++ individualNames + lazy val importsSymbolNamed: Set[String] = importedNames map (_.toString) toSet + + def importString = imp.toString + override def resultExtractionCode(req: Request) = codegenln(importString) + "\n" + } +} diff --git a/repl/src/main/scala/spark/repl/SparkSimpleReader.scala b/repl/src/main/scala/spark/repl/SparkSimpleReader.scala deleted file mode 100644 index 2b24c4bf6334f2544a0bd558767bb53104a96218..0000000000000000000000000000000000000000 --- a/repl/src/main/scala/spark/repl/SparkSimpleReader.scala +++ /dev/null @@ -1,33 +0,0 @@ -/* NSC -- new Scala compiler - * Copyright 2005-2010 LAMP/EPFL - * @author Stepan Koltsov - */ - -package spark.repl - -import scala.tools.nsc -import scala.tools.nsc._ -import scala.tools.nsc.interpreter -import scala.tools.nsc.interpreter._ - -import java.io.{ BufferedReader, PrintWriter } -import io.{ Path, File, Directory } - -/** Reads using standard JDK API */ -class SparkSimpleReader( - in: BufferedReader, - out: PrintWriter, - val interactive: Boolean) -extends SparkInteractiveReader { - def this() = this(Console.in, new PrintWriter(Console.out), true) - def this(in: File, out: PrintWriter, interactive: Boolean) = this(in.bufferedReader(), out, interactive) - - def close() = in.close() - def readOneLine(prompt: String): String = { - if (interactive) { - out.print(prompt) - out.flush() - } - in.readLine() - } -} diff --git a/repl/src/test/scala/spark/repl/ReplSuite.scala b/repl/src/test/scala/spark/repl/ReplSuite.scala index 829b1d934eec9f4394ab96123dc646ab60fa4e7c..a9b1bee7e0ac320c4c155b234e469eaa8be3fbec 100644 --- a/repl/src/test/scala/spark/repl/ReplSuite.scala +++ b/repl/src/test/scala/spark/repl/ReplSuite.scala @@ -22,7 +22,7 @@ class ReplSuite extends FunSuite { } } } - val interp = new SparkInterpreterLoop(in, new PrintWriter(out), master) + val interp = new SparkILoop(in, new PrintWriter(out), master) spark.repl.Main.interp = interp val separator = System.getProperty("path.separator") interp.main(Array("-classpath", paths.mkString(separator)))