summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2011-03-15 02:56:58 +0000
committerPaul Phillips <paulp@improving.org>2011-03-15 02:56:58 +0000
commite96dba0c9a2467c6cf396c70a48ef0750503c26e (patch)
treeb31f7e00f5d3257dc38b1316ccd71c94e0d1fe9e
parent7a29fc7de3465a064ffc6e0422d5df4bf39b36a3 (diff)
downloadscala-e96dba0c9a2467c6cf396c70a48ef0750503c26e.tar.gz
scala-e96dba0c9a2467c6cf396c70a48ef0750503c26e.tar.bz2
scala-e96dba0c9a2467c6cf396c70a48ef0750503c26e.zip
Various chronic annoyances with the repl addres...
Various chronic annoyances with the repl addressed. Much improved transcript pasting. Now goes back in time to fix the transcript if it contains self-referential "res0, res1" etc. so that it works as it originally did. Shows which commands it is running, and places the commands with their result in a manner suitable for framing. Also, a new :paste command which accepts input up to ctrl-D, so you can enter companions without gyrations, or code from people who write in a repl unfriendly fashion by putting their curly braces on the next line (I'm looking at you mark harrah) or you name it, it's not picky. No review.
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/ILoop.scala144
-rw-r--r--src/compiler/scala/tools/nsc/interpreter/Pasted.scala59
-rw-r--r--test/files/run/repl-paste-2.check31
-rw-r--r--test/files/run/repl-paste-2.scala28
-rw-r--r--test/files/run/repl-paste.check13
-rw-r--r--test/files/run/repl-paste.scala19
-rw-r--r--test/files/run/repl-transcript.check23
-rw-r--r--test/files/run/repl-transcript.scala20
8 files changed, 231 insertions, 106 deletions
diff --git a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala
index 71224b3016..06e3e54c53 100644
--- a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala
+++ b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala
@@ -43,7 +43,8 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter)
var in: InteractiveReader = _ // the input stream from which commands come
var settings: Settings = _
var intp: IMain = _
- var power: Power = _
+
+ lazy val power = new Power(this)
// TODO
// object opt extends AestheticSettings
@@ -109,7 +110,6 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter)
if (intp ne null) {
intp.close
intp = null
- power = null
Thread.currentThread.setContextClassLoader(originalClassLoader)
}
}
@@ -211,6 +211,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter)
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),
@@ -224,7 +225,6 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter)
List(
LineArg("dump", "displays a view of the interpreter's internal state", dumpCommand),
LineArg("phase", "set the implicit phase for power commands", phaseCommand),
- LineArg("symfilter", "change the filter for symbol printing", symfilterCmd),
LineArg("wrap", "code to wrap around all executions", wrapCommand)
)
}
@@ -237,10 +237,10 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter)
private val typeTransforms = List(
"scala.collection.immutable." -> "immutable.",
- "scala.collection.mutable." -> "mutable.",
- "scala.collection.generic." -> "generic.",
- "java.lang." -> "jl.",
- "scala.runtime." -> "runtime."
+ "scala.collection.mutable." -> "mutable.",
+ "scala.collection.generic." -> "generic.",
+ "java.lang." -> "jl.",
+ "scala.runtime." -> "runtime."
)
private def implicitsCommand(line: String): Result = {
@@ -337,17 +337,6 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter)
if (line == "") "Cleared wrapper."
else "Set wrapper to '" + line + "'"
}
-
- private def symfilterCmd(line: String): Result = {
- if (line == "") {
- power.vars.symfilter set "_ => true"
- "Remove symbol filter."
- }
- else {
- power.vars.symfilter set line
- "Set symbol filter to '" + line + "'."
- }
- }
private def pathToPhased = intp.pathToTerm("power") + ".phased"
private def phaseCommand(name: String): Result = {
// This line crashes us in TreeGen:
@@ -391,8 +380,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter)
/** Available commands */
def commands: List[LoopCommand] = standardCommands ++ (
- if (power == null) Nil
- else powerCommands
+ if (isReplPower) powerCommands else Nil
)
val replayQuestionMessage =
@@ -522,13 +510,13 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter)
}
def powerCmd(): Result = {
- if (power != null)
- return "Already in power mode."
-
- power = new Power(this)
+ if (isReplPower) return "Already in power mode."
+ else enablePowerMode()
+ }
+ def enablePowerMode() = {
isReplPower = true
power.unleash()
- power.banner
+ out.println(power.banner)
}
def verbosity() = {
@@ -572,88 +560,33 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter)
}
}
- 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)
-
+ private def readWhile(cond: String => Boolean) = {
+ Iterator continually in.readLine("") takeWhile (x => x != null && cond(x))
}
- /** 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)
+ def pasteCommand(): Result = {
+ out.println("// Entering paste mode (ctrl-D to finish)\n")
+ out.flush()
+ val code = readWhile(_ => true) mkString "\n"
+ out.println("\n// Exiting paste mode, now interpreting.\n")
+ intp interpret code
+ ()
}
- /** 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]) {
- (intp interpret code) match {
- case IR.Incomplete if extraLines.nonEmpty =>
- runCode(code + "\n" + extraLines.head, extraLines.tail)
- case _ => ()
- }
- }
- runCode(cmd, trailing.toList)
- }
+ private object paste extends Pasted {
+ val ContinueString = " | "
+ val PromptString = "scala> "
+
+ def interpret(line: String): Unit = {
+ out.println(line.trim)
+ intp interpret line
+ out.println("")
}
- else
- interpretAsPastedTranscript(line :: lines)
+
+ def transcript(start: String) =
+ 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
@@ -675,7 +608,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter)
out.println("You typed two blank lines. Starting a new command.")
None
}
- else in.readLine(CONTINUATION_STRING) match {
+ 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
@@ -699,9 +632,8 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter)
* 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))
+ else if (!paste.running && code.startsWith(PromptString)) {
+ paste.transcript(code)
None
}
else if (Completion.looksLikeInvocation(code) && intp.mostRecentVar != "") {
@@ -784,7 +716,7 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter)
intp.initialize()
if (isReplPower) {
plushln("Starting in power mode, one moment...\n")
- powerCmd()
+ enablePowerMode()
}
loop()
}
diff --git a/src/compiler/scala/tools/nsc/interpreter/Pasted.scala b/src/compiler/scala/tools/nsc/interpreter/Pasted.scala
new file mode 100644
index 0000000000..e605bb010d
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/interpreter/Pasted.scala
@@ -0,0 +1,59 @@
+/* NSC -- new Scala compiler
+ * Copyright 2005-2011 LAMP/EPFL
+ * @author Paul Phillips
+ */
+
+package scala.tools.nsc
+package interpreter
+
+/** 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.
+ */
+abstract class Pasted {
+ def ContinueString: String
+ def PromptString: String
+ def interpret(line: String): Unit
+
+ private var isRunning = false
+ def running = isRunning
+
+ /** 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.
+ */
+ def apply(lines: TraversableOnce[String]) = {
+ val cmds = lines reduceLeft append split PromptString filterNot (_.trim == "") toList;
+ println("// Replaying %d commands from transcript.\n" format cmds.size)
+
+ isRunning = true
+ try cmds foreach interpret
+ finally isRunning = false
+ }
+
+ private def isPrompted(line: String) = line startsWith PromptString
+ private def isContinuation(line: String) = line startsWith ContinueString
+
+ private def append(code: String, line: String): String =
+ if (isPrompted(line)) code + "\n" + line
+ else if (isContinuation(line)) code + "\n" + line.stripPrefix(ContinueString)
+ else fixResRefs(code, line)
+
+ /** If the line looks like
+ * res15: Int
+ * then we go back in time to the preceding scala> prompt and rewrite
+ * the line containing <expr> as
+ * val res15 = { <expr> }
+ * and the rest as they say is rewritten history.
+ *
+ * In all other cases, discard the line.
+ */
+ private val resRegex = """^(res\d+):.*""".r
+ private def fixResRefs(code: String, line: String) = line match {
+ case resRegex(resName) if code contains PromptString =>
+ val (str1, str2) = code splitAt code.lastIndexOf(PromptString) + PromptString.length
+ "%sval %s = { %s }".format(str1, resName, str2)
+ case _ => code
+ }
+}
diff --git a/test/files/run/repl-paste-2.check b/test/files/run/repl-paste-2.check
new file mode 100644
index 0000000000..fb7a818f1a
--- /dev/null
+++ b/test/files/run/repl-paste-2.check
@@ -0,0 +1,31 @@
+Type in expressions to have them evaluated.
+Type :help for more information.
+
+scala>
+scala> // Replaying 7 commands from transcript.
+
+val res0 = { 123 }
+res0: Int = 123
+
+val res1 = { 567 }
+res1: Int = 567
+
+val res2 = { res0 + res1 }
+res2: Int = 690
+
+val x = dingus
+<console>:7: error: not found: value dingus
+ val x = dingus
+ ^
+
+val x = "dingus"
+x: java.lang.String = dingus
+
+val res3 = { x.length }
+res3: Int = 6
+
+val res4 = { x.length + res3 }
+res4: Int = 12
+
+
+scala>
diff --git a/test/files/run/repl-paste-2.scala b/test/files/run/repl-paste-2.scala
new file mode 100644
index 0000000000..802c627701
--- /dev/null
+++ b/test/files/run/repl-paste-2.scala
@@ -0,0 +1,28 @@
+import scala.tools.partest.ReplTest
+
+object Test extends ReplTest {
+ def code = """
+scala> 123
+res0: Int = 123
+
+scala> 567
+res1: Int = 567
+
+scala> res0 + res1
+res2: Int = 690
+
+scala> val x = dingus
+<console>:7: error: not found: value dingus
+ val x = dingus
+ ^
+
+scala> val x = "dingus"
+x: java.lang.String = dingus
+
+scala> x.length
+res3: Int = 6
+
+scala> x.length + res3
+res4: Int = 12
+ """
+} \ No newline at end of file
diff --git a/test/files/run/repl-paste.check b/test/files/run/repl-paste.check
new file mode 100644
index 0000000000..4c9de85e67
--- /dev/null
+++ b/test/files/run/repl-paste.check
@@ -0,0 +1,13 @@
+Type in expressions to have them evaluated.
+Type :help for more information.
+
+scala> // Entering paste mode (ctrl-D to finish)
+
+
+// Exiting paste mode, now interpreting.
+
+defined class Dingus
+defined module Dingus
+x: Int = 110
+
+scala>
diff --git a/test/files/run/repl-paste.scala b/test/files/run/repl-paste.scala
new file mode 100644
index 0000000000..e2ebab1e45
--- /dev/null
+++ b/test/files/run/repl-paste.scala
@@ -0,0 +1,19 @@
+import scala.tools.partest.ReplTest
+
+object Test extends ReplTest {
+ def code = ":paste\n" + (
+ """
+ class Dingus
+ {
+ private val x = 5
+ def y = Dingus.x * 2
+ }
+ object Dingus
+ {
+ private val x = 55
+ }
+
+ val x = (new Dingus).y
+ """
+ )
+} \ No newline at end of file
diff --git a/test/files/run/repl-transcript.check b/test/files/run/repl-transcript.check
new file mode 100644
index 0000000000..b2a8d2e156
--- /dev/null
+++ b/test/files/run/repl-transcript.check
@@ -0,0 +1,23 @@
+Type in expressions to have them evaluated.
+Type :help for more information.
+
+scala>
+scala> // Replaying 5 commands from transcript.
+
+class Bippity
+defined class Bippity
+
+def f = new Bippity
+f: Bippity
+
+val res5 = { 123 }
+res5: Int = 123
+
+val res6 = { 1 to 100 map (_ + 1) }
+res6: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101)
+
+res6.sum + res5
+res0: Int = 5273
+
+
+scala>
diff --git a/test/files/run/repl-transcript.scala b/test/files/run/repl-transcript.scala
new file mode 100644
index 0000000000..b39279aea3
--- /dev/null
+++ b/test/files/run/repl-transcript.scala
@@ -0,0 +1,20 @@
+import scala.tools.partest.ReplTest
+
+object Test extends ReplTest {
+ def code = """
+scala> class Bippity
+defined class Bippity
+
+scala> def f = new Bippity
+f: Bippity
+
+scala> 123
+res5: Int = 123
+
+scala> 1 to 100 map (_ + 1)
+res6: scala.collection.immutable.IndexedSeq[Int] = Vector(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101)
+
+scala> res6.sum + res5
+ """
+}
+