From 4415640dc47397f58639532ca0d7ca3c3414bcd7 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Tue, 5 Apr 2011 23:19:26 +0000 Subject: Some accumulated cleanups around the interprete... Some accumulated cleanups around the interpreter loop commands as I attempt to make my way all the way to the promised land of documenting it. No review. --- .../scala/tools/nsc/interpreter/ILoop.scala | 136 +++++++++++---------- .../scala/tools/nsc/interpreter/LoopCommands.scala | 75 +++++++++--- .../scala/tools/nsc/interpreter/package.scala | 3 + 3 files changed, 130 insertions(+), 84 deletions(-) (limited to 'src') diff --git a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala index ccec5c0e22..8aa5f1a006 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala @@ -44,6 +44,8 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) var settings: Settings = _ var intp: IMain = _ + def codeQuoted(s: String) = intp.memberHandlers.string2codeQuoted(s) + lazy val power = { val g = intp.global Power[g.type](this, g) @@ -65,7 +67,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) // Install a signal handler so we can be prodded. private val signallable = - if (isReplDebug) Signallable("Dump repl state.")(dumpCommand("")) + if (isReplDebug) Signallable("Dump repl state.")(dumpCommand()) else null // classpath entries added via :cp @@ -149,12 +151,14 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) } /** print a friendly help message */ - def printHelp() = { + def helpCommand() = { + val usageWidth = commands map (_.usageMsg.length) max + val cmds = commands map (x => (x.usageMsg, x.help)) + val formatStr = "%-" + usageWidth + "s %s" + 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) } + for ((use, help) <- cmds) + out println formatStr.format(use, help) } /** Print a welcome message */ @@ -171,18 +175,23 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) } /** Show the history */ - def printHistory(xs: List[String]): Result = { - if (history eq NoHistory) - return "No history available." + lazy val historyCommand = new LoopCommand("history", "show the history (optional num is commands to show)") { + override def usage = "[num]" + def defaultLines = 20 - val defaultLines = 20 - 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 + def apply(line: String): Result = { + if (history eq NoHistory) + return "No history available." - for ((line, index) <- lines.zipWithIndex) - println("%3d %s".format(index + offset, line)) + 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) + println("%3d %s".format(index + offset, line)) + } } /** Some print conveniences */ @@ -204,37 +213,35 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) /** Prompt to print when awaiting input */ def prompt = currentPrompt + import LoopCommand.{ cmd, nullary } + /** Standard commands **/ - val standardCommands: List[LoopCommand] = { - List( - LineArg("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), - LineArg("implicits", "show the implicits in scope (-v to include Predef)", implicitsCommand), - LineArg("javap", "disassemble a file or class name", javapCommand), - LineArg("keybindings", "show how ctrl-[A-Z] and other keys are bound", keybindingsCommand), - OneArg("load", "load and interpret a Scala file", load), - NoArgs("paste", "enter paste mode: all input up to ctrl-D compiled together", pasteCommand), - NoArgs("power", "enable power user mode", powerCmd), - 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", shCommand), - NoArgs("silent", "disable/enable automatic printing of results", verbosity), - LineArg("type", "display the type of an expression without evaluating it", typeCommand) - ) - } + val standardCommands = List( + cmd("cp", "", "add an entry (jar or directory) to the classpath", addClasspath), + nullary("help", "print this help message", helpCommand), + historyCommand, + cmd("h?", "", "search the history", searchHistory), + cmd("implicits", "[-v]", "show the implicits in scope", implicitsCommand), + cmd("javap", "", "disassemble a file or class name", javapCommand), + nullary("keybindings", "show how ctrl-[A-Z] and other keys are bound", keybindingsCommand), + cmd("load", "", "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", "", "display the type of an expression without evaluating it", typeCommand) + ) /** Power user commands */ - val powerCommands: List[LoopCommand] = { - List( - LineArg("dump", "displays a view of the interpreter's internal state", dumpCommand), - LineArg("phase", "set the implicit phase for power commands", phaseCommand), - LineArg("wrap", "code to wrap around all executions", wrapCommand) - ) - } + val powerCommands: List[LoopCommand] = List( + nullary("dump", "displays a view of the interpreter's internal state", dumpCommand), + cmd("phase", "", "set the implicit phase for power commands", phaseCommand), + cmd("wrap", "", "code to wrap around all executions", wrapCommand) + ) - private def dumpCommand(line: String): Result = { + private def dumpCommand(): Result = { println(power) history.asStrings takeRight 30 foreach println in.redrawLine() @@ -341,7 +348,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) else res.show() } } - private def keybindingsCommand(line: String): Result = { + private def keybindingsCommand(): Result = { if (in.keyBindings.isEmpty) "Key bindings unavailable." else { println("Reading jline properties for default key bindings.") @@ -486,16 +493,15 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) } /** fork a shell and run a command */ - def shCommand(cmd: String): Result = { - if (cmd == "") - return "Usage: sh " - - intp quietRun "import _root_.scala.sys.process._" - val pb = Process(cmd) - intp.bind("builder", pb) - val stdout = Process(cmd).lines - intp.bind("stdout", stdout) - () + lazy val shCommand = new LoopCommand("sh", "run a shell command (result is implicitly => List[String])") { + override def usage = "" + def apply(line: String): Result = line match { + case "" => showUsage() + case _ => + val toRun = classOf[ProcessResult].getName + "(" + codeQuoted(line) + ")" + intp interpret toRun + () + } } def withFile(filename: String)(action: File => Unit) { @@ -505,7 +511,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) else out.println("That file does not exist") } - def load(arg: String) = { + def loadCommand(arg: String) = { var shouldReplay: Option[String] = None withFile(arg)(f => { interpretAllFrom(f) @@ -558,20 +564,20 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) 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 + val line2 = line.tail + val (cmd, rest) = line2 indexWhere (_.isWhitespace) match { + case -1 => (line2, "") + case idx => (line2 take idx, line2 drop idx dropWhile (_.isWhitespace)) + } // 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 List(f) => f(rest) case Nil => withError("Unknown command. Type :help for help.") - case xs => - xs find (_.name == cmd) match { - case Some(exact) => exact(args) - case _ => withError(ambiguous(xs)) + case fs => + fs find (_.name == cmd) match { + case Some(exact) => exact(rest) + case _ => withError(ambiguous(fs)) } } } diff --git a/src/compiler/scala/tools/nsc/interpreter/LoopCommands.scala b/src/compiler/scala/tools/nsc/interpreter/LoopCommands.scala index 24fde9425f..2fa7f1501b 100644 --- a/src/compiler/scala/tools/nsc/interpreter/LoopCommands.scala +++ b/src/compiler/scala/tools/nsc/interpreter/LoopCommands.scala @@ -6,43 +6,80 @@ package scala.tools.nsc package interpreter +import collection.{ mutable, immutable } +import mutable.ListBuffer + +class ProcessResult(val line: String) { + import sys.process._ + private val buffer = new ListBuffer[String] + + val builder = Process(line) + val logger = ProcessLogger(buffer += _) + val exitCode = builder ! logger + def lines = buffer.toList + + def show() = lines foreach println + override def toString = "`%s` (%d lines, exit %d)".format(line, buffer.size, exitCode) +} +object ProcessResult { + implicit def processResultToOutputLines(pr: ProcessResult): List[String] = pr.lines + def apply(line: String): ProcessResult = new ProcessResult(line) +} + trait LoopCommands { protected def out: java.io.PrintWriter // a single interpreter command - sealed abstract class LoopCommand extends (List[String] => Result) { - def name: String - def help: String - def commandError(msg: String) = { - out.println(":" + name + " " + msg + ".") + abstract class LoopCommand(val name: String, val help: String) extends (String => Result) { + def usage: String = "" + def usageMsg: String = ":" + name + ( + if (usage == "") "" else " " + usage + ) + def apply(line: String): Result + protected def words(line: String): List[String] = line split "\\s+" toList + + // called if no args are given + def showUsage(): Result = { + "usage is " + usageMsg Result(true, None) } - def usage(): String + + def onError(msg: String) = { + out.println("error: " + msg) + showUsage() + } } - case class NoArgs(name: String, help: String, f: () => Result) extends LoopCommand { - def usage(): String = ":" + name - def apply(args: List[String]) = if (args.isEmpty) f() else commandError("accepts no arguments") + object LoopCommand { + def nullary(name: String, help: String, f: () => Result): LoopCommand = + new NullaryCmd(name, help, _ => f()) + + def cmd(name: String, usage: String, help: String, f: String => Result): LoopCommand = + if (usage == "") new NullaryCmd(name, help, f) + else new LineCmd(name, usage, help, f) + + def varargs(name: String, usage: String, help: String, f: List[String] => Result): LoopCommand = + new VarArgsCmd(name, usage, help, f) } - case class LineArg(name: String, help: String, f: (String) => Result) extends LoopCommand { - def usage(): String = ":" + name + " " - def apply(args: List[String]) = f(args mkString " ") + class NullaryCmd(name: String, help: String, f: String => Result) extends LoopCommand(name, help) { + def apply(line: String): Result = f(line) } - case class OneArg(name: String, help: String, f: (String) => Result) extends LoopCommand { - def usage(): String = ":" + name + " " - def apply(args: List[String]) = - if (args.size == 1) f(args.head) - else commandError("requires exactly one argument") + class LineCmd(name: String, argWord: String, help: String, f: String => Result) extends LoopCommand(name, help) { + override def usage = argWord + def apply(line: String): Result = f(line) } - case class VarArgs(name: String, help: String, f: (List[String]) => Result) extends LoopCommand { - def usage(): String = ":" + name + " [arg]" + class VarArgsCmd(name: String, argWord: String, help: String, f: List[String] => Result) + extends LoopCommand(name, help) { + override def usage = argWord + def apply(line: String): Result = apply(words(line)) def apply(args: List[String]) = f(args) } // the result of a single command case class Result(val keepRunning: Boolean, val lineToRecord: Option[String]) + object Result { // the default result means "keep running, and don't record that line" val default = Result(true, None) diff --git a/src/compiler/scala/tools/nsc/interpreter/package.scala b/src/compiler/scala/tools/nsc/interpreter/package.scala index b5ba2d9b8e..322224ed36 100644 --- a/src/compiler/scala/tools/nsc/interpreter/package.scala +++ b/src/compiler/scala/tools/nsc/interpreter/package.scala @@ -29,6 +29,9 @@ package object interpreter { type InputStream = java.io.InputStream type OutputStream = java.io.OutputStream + private[nsc] val JLineDebug = "scala.tools.jline.internal.Log.debug" + private[nsc] val JLineTrace = "scala.tools.jline.internal.Log.trace" + private[nsc] val DebugProperty = "scala.repl.debug" private[nsc] val TraceProperty = "scala.repl.trace" private[nsc] val PowerProperty = "scala.repl.power" -- cgit v1.2.3