blob: bc995a7f917d5e3909d55329222ebe1a300cfc4e [file] [log] [blame]
/* 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.Logging
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
with Logging
{
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.5.3-SNAPSHOT
/_/
""")
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 SparkJLineReader(
if (settings.noCompletion.value) NoCompletion
else new SparkJLineCompletion(intp)
)
catch {
case ex @ (_: Exception | _: NoClassDefFoundError) =>
echo("Failed to created SparkJLineReader: " + 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 = {
// Ensure logging is initialized before any Spark threads try to use logs
// (because SLF4J initialization is not thread safe)
initLogging()
printWelcome()
echo("Initializing interpreter...")
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
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 = SparkJLineReader(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()
}
}