diff options
Diffstat (limited to 'src/repl/scala/tools/nsc/interpreter/ILoop.scala')
-rw-r--r-- | src/repl/scala/tools/nsc/interpreter/ILoop.scala | 173 |
1 files changed, 115 insertions, 58 deletions
diff --git a/src/repl/scala/tools/nsc/interpreter/ILoop.scala b/src/repl/scala/tools/nsc/interpreter/ILoop.scala index 6e18682494..4fd5768b79 100644 --- a/src/repl/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/repl/scala/tools/nsc/interpreter/ILoop.scala @@ -19,6 +19,7 @@ import scala.reflect.internal.util.{ BatchSourceFile, ScalaClassLoader } import ScalaClassLoader._ import scala.reflect.io.{ File, Directory } import scala.tools.util._ +import io.AbstractFile import scala.collection.generic.Clearable import scala.concurrent.{ ExecutionContext, Await, Future, future } import ExecutionContext.Implicits._ @@ -125,22 +126,18 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) } /** print a friendly help message */ - def helpCommand(line: String): Result = { - if (line == "") helpSummary() - else uniqueCommand(line) match { - case Some(lc) => echo("\n" + lc.help) - case _ => ambiguousError(line) - } + def helpCommand(line: String): Result = line match { + case "" => helpSummary() + case CommandMatch(cmd) => echo(f"%n${cmd.help}") + case _ => ambiguousError(line) } private def helpSummary() = { - val usageWidth = commands map (_.usageMsg.length) max - val formatStr = "%-" + usageWidth + "s %s" + val usageWidth = commands map (_.usageMsg.length) max + val formatStr = s"%-${usageWidth}s %s" - echo("All commands can be abbreviated, e.g. :he instead of :help.") + echo("All commands can be abbreviated, e.g., :he instead of :help.") - commands foreach { cmd => - echo(formatStr.format(cmd.usageMsg, cmd.help)) - } + for (cmd <- commands) echo(formatStr.format(cmd.usageMsg, cmd.help)) } private def ambiguousError(cmd: String): Result = { matchingCommands(cmd) match { @@ -149,14 +146,14 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) } Result(keepRunning = true, None) } + // this lets us add commands willy-nilly and only requires enough command to disambiguate 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) - } + private object CommandMatch { + def unapply(name: String): Option[LoopCommand] = + matchingCommands(name) match { + case x :: Nil => Some(x) + case xs => xs find (_.name == name) // accept an exact match + } } /** Show the history */ @@ -221,7 +218,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) nullary("power", "enable power user mode", powerCmd), nullary("quit", "exit the interpreter", () => Result(keepRunning = false, None)), cmd("replay", "[options]", "reset the repl and replay all previous commands", replayCommand), - //cmd("require", "<path>", "add a jar or directory to the classpath", require), // TODO + cmd("require", "<path>", "add a jar to the classpath", require), cmd("reset", "[options]", "reset the repl to its initial state, forgetting all session entries", resetCommand), cmd("save", "<path>", "save replayable session to a file", saveCommand), shCommand, @@ -392,23 +389,23 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) true } + // after process line, OK continue, ERR break, or EOF all done + object LineResults extends Enumeration { + type LineResult = Value + val EOF, ERR, OK = Value + } + import LineResults.LineResult + // return false if repl should exit def processLine(line: String): Boolean = { import scala.concurrent.duration._ Await.ready(globalFuture, 10.minutes) // Long timeout here to avoid test failures under heavy load. - if (line eq null) { - // SI-4563: this means the console was properly interrupted (Ctrl+D usually) - // so we display the output message (which by default ends with - // a newline so as not to break the user's terminal) - if (in.interactive) out.print(Properties.shellInterruptedString) - - false - } else (command(line) match { + command(line) match { case Result(false, _) => false case Result(_, Some(line)) => addReplay(line) ; true case _ => true - }) + } } private def readOneLine() = { @@ -426,18 +423,22 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) * command() for each line of input, and stops when * command() returns false. */ - @tailrec final def loop() { - if ( try processLine(readOneLine()) catch crashRecovery ) - loop() + @tailrec final def loop(): LineResult = { + import LineResults._ + readOneLine() match { + case null => EOF + case line => if (try processLine(line) catch crashRecovery) loop() else ERR + } } /** interpret all lines from a specified file */ - def interpretAllFrom(file: File) { + def interpretAllFrom(file: File, verbose: Boolean = false) { savingReader { savingReplayStack { file applyReader { reader => - in = SimpleReader(reader, out, interactive = false) - echo("Loading " + file + "...") + in = if (verbose) new SimpleReader(reader, out, interactive = true) with EchoReader + else SimpleReader(reader, out, interactive = false) + echo(s"Loading $file...") loop() } } @@ -592,13 +593,17 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) res } - def loadCommand(arg: String) = { - var shouldReplay: Option[String] = None - withFile(arg)(f => { - interpretAllFrom(f) - shouldReplay = Some(":load " + arg) - }) - Result(keepRunning = true, shouldReplay) + def loadCommand(arg: String): Result = { + def run(file: String, verbose: Boolean) = withFile(file) { f => + interpretAllFrom(f, verbose) + Result recording s":load $arg" + } getOrElse Result.default + + words(arg) match { + case "-v" :: file :: Nil => run(file, verbose = true) + case file :: Nil => run(file, verbose = false) + case _ => echo("usage: :load -v file") ; Result.default + } } def saveCommand(filename: String): Result = ( @@ -612,13 +617,57 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) 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() + intp.addUrlsToClassPath(f.toURI.toURL) + echo("Added '%s' to classpath.".format(f.path, intp.global.classPath.asClassPathString)) + repldbg("Added '%s'. Your new classpath is:\n\"%s\"".format(f.path, intp.global.classPath.asClassPathString)) } else echo("The path '" + f + "' doesn't seem to exist.") } + /** Adds jar file to the current classpath. Jar will only be added if it + * does not contain classes that already exist on the current classpath. + * + * Importantly, `require` adds jars to the classpath ''without'' resetting + * the state of the interpreter. This is in contrast to `replay` which can + * be used to add jars to the classpath and which creates a new instance of + * the interpreter and replays all interpreter expressions. + */ + def require(arg: String): Unit = { + class InfoClassLoader extends java.lang.ClassLoader { + def classOf(arr: Array[Byte]): Class[_] = + super.defineClass(null, arr, 0, arr.length) + } + + val f = File(arg).normalize + + if (f.isDirectory) { + echo("Adding directories to the classpath is not supported. Add a jar instead.") + return + } + + val jarFile = AbstractFile.getDirectory(new java.io.File(arg)) + + def flatten(f: AbstractFile): Iterator[AbstractFile] = + if (f.isClassContainer) f.iterator.flatMap(flatten) + else Iterator(f) + + val entries = flatten(jarFile) + val cloader = new InfoClassLoader + + def classNameOf(classFile: AbstractFile): String = cloader.classOf(classFile.toByteArray).getName + def alreadyDefined(clsName: String) = intp.classLoader.tryToLoadClass(clsName).isDefined + val exists = entries.filter(_.hasExtension("class")).map(classNameOf).exists(alreadyDefined) + + if (!f.exists) echo(s"The path '$f' doesn't seem to exist.") + else if (exists) echo(s"The path '$f' cannot be loaded, because existing classpath entries conflict.") // TODO tell me which one + else { + addedClasspath = ClassPath.join(addedClasspath, f.path) + intp.addUrlsToClassPath(f.toURI.toURL) + echo("Added '%s' to classpath.".format(f.path, intp.global.classPath.asClassPathString)) + repldbg("Added '%s'. Your new classpath is:\n\"%s\"".format(f.path, intp.global.classPath.asClassPathString)) + } + } + def powerCmd(): Result = { if (isReplPower) "Already in power mode." else enablePowerMode(isDuringInit = false) @@ -648,20 +697,23 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) } /** 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. */ + * (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) - } - } + if (line startsWith ":") colonCommand(line.tail) else if (intp.global == null) Result(keepRunning = false, None) // Notice failure to create compiler else Result(keepRunning = true, interpretStartingWith(line)) } + private val commandish = """(\S+)(?:\s+)?(.*)""".r + + private def colonCommand(line: String): Result = line.trim match { + case "" => helpSummary() + case commandish(CommandMatch(cmd), rest) => cmd(rest) + case commandish(name, _) => ambiguousError(name) + case _ => echo("?") + } + private def readWhile(cond: String => Boolean) = { Iterator continually in.readLine("") takeWhile (x => x != null && cond(x)) } @@ -685,13 +737,13 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) } val code = file match { case Some(name) => - withFile(name)(f => { + withFile(name) { f => shouldReplay = Some(s":paste $arg") val s = f.slurp.trim if (s.isEmpty) echo(s"File contains no code: $f") else echo(s"Pasting file $f...") s - }) getOrElse "" + } getOrElse "" case None => echo("// Entering paste mode (ctrl-D to finish)\n") val text = (readWhile(_ => true) mkString "\n").trim @@ -820,7 +872,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) ) catch { case ex @ (_: Exception | _: NoClassDefFoundError) => - echo("Failed to created JLineReader: " + ex + "\nFalling back to SimpleReader.") + echo(f"Failed to created JLineReader: ${ex}%nFalling back to SimpleReader.") SimpleReader() } } @@ -847,6 +899,8 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) case _ => } } + + // start an interpreter with the given settings def process(settings: Settings): Boolean = savingContextLoader { this.settings = settings createInterpreter() @@ -861,7 +915,10 @@ class ILoop(in0: Option[BufferedReader], protected val out: JPrintWriter) loadFiles(settings) printWelcome() - try loop() + try loop() match { + case LineResults.EOF => out print Properties.shellInterruptedString + case _ => + } catch AbstractOrMissingHandler() finally closeInterpreter() |