diff options
author | Paul Phillips <paulp@improving.org> | 2011-04-06 18:48:15 +0000 |
---|---|---|
committer | Paul Phillips <paulp@improving.org> | 2011-04-06 18:48:15 +0000 |
commit | f36e7acd02134fed6f27a61e82bf454a68684499 (patch) | |
tree | 17eadf7ddc0c5e19f352d570805b29319a60fe9a | |
parent | 1cf60d304d1a88c4e79ca4636f49d4676f2bfe04 (diff) | |
download | scala-f36e7acd02134fed6f27a61e82bf454a68684499.tar.gz scala-f36e7acd02134fed6f27a61e82bf454a68684499.tar.bz2 scala-f36e7acd02134fed6f27a61e82bf454a68684499.zip |
The beautification of repl pasting had the prob...
The beautification of repl pasting had the problem that the new
beautiful output was not itself pastable. Now I have achieved "paste
idempotence". No review.
-rw-r--r-- | src/compiler/scala/tools/nsc/interpreter/ILoop.scala | 8 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/interpreter/Pasted.scala | 93 | ||||
-rw-r--r-- | test/files/run/repl-paste-2.check | 48 | ||||
-rw-r--r-- | test/files/run/repl-paste-2.scala | 17 | ||||
-rw-r--r-- | test/files/run/repl-transcript.check | 10 |
5 files changed, 112 insertions, 64 deletions
diff --git a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala index 8aa5f1a006..75503be3d1 100644 --- a/src/compiler/scala/tools/nsc/interpreter/ILoop.scala +++ b/src/compiler/scala/tools/nsc/interpreter/ILoop.scala @@ -605,8 +605,14 @@ class ILoop(in0: Option[BufferedReader], protected val out: PrintWriter) out.println("") } - def transcript(start: String) = + def transcript(start: String) = { + // Printing this message doesn't work very well becaues 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. + // + // out.println("// Detected repl transcript paste: ctrl-D to finish.") apply(Iterator(start) ++ readWhile(_.trim != PromptString.trim)) + } } import paste.{ ContinueString, PromptString } diff --git a/src/compiler/scala/tools/nsc/interpreter/Pasted.scala b/src/compiler/scala/tools/nsc/interpreter/Pasted.scala index df106e1f00..83321356a3 100644 --- a/src/compiler/scala/tools/nsc/interpreter/Pasted.scala +++ b/src/compiler/scala/tools/nsc/interpreter/Pasted.scala @@ -9,12 +9,21 @@ 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. + * + * Most of the interesting code in here is due to my goal of + * "paste idempotence" i.e. the transcript resulting from pasting + * a transcript should itself be pasteable and should achieve + * the same result. */ abstract class Pasted { def ContinueString: String def PromptString: String def interpret(line: String): Unit + def matchesPrompt(line: String) = matchesString(line, PromptString) + def matchesContinue(line: String) = matchesString(line, ContinueString) + def running = isRunning + private def matchesString(line: String, target: String): Boolean = ( (line startsWith target) || (line.nonEmpty && " \t".toSet(line.head) && matchesString(line.tail, target)) @@ -23,12 +32,61 @@ abstract class Pasted { case -1 => line case idx => line drop (idx + target.length) } + private var isRunning = false + private val resReference = """(?<!^)(res\d+)""".r + private val resCreation = """^\s*(res\d+):.*""".r + private val resAssign = """^val (res\d+).*""".r - def matchesPrompt(line: String) = matchesString(line, PromptString) - def matchesContinue(line: String) = matchesString(line, ContinueString) + private class PasteAnalyzer(val lines: List[String]) { + val referenced = lines flatMap (resReference findAllIn _.trim.stripPrefix("res")) toSet + val cmds = lines reduceLeft append split PromptString filterNot (_.trim == "") toList - private var isRunning = false - def running = isRunning + /** If it's a prompt or continuation line, strip the formatting bits and + * assemble the code. Otherwise ship it off to be analyzed for res references + * and discarded. + */ + def append(code: String, line: String): String = + if (matchesPrompt(line)) code + "\n" + line + else if (matchesContinue(line)) code + "\n" + stripString(line, ContinueString) + else fixResRefs(code, line) + + /** If the line looks like + * res15: Int + * + * and the additional conditions hold that: + * 1) res15 is referenced from elsewhere in the transcript + * 2) the preceding repl line is not "val res15 = ..." because that + * indicates it has already been "val-ified" on a previous paste + * + * 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. + */ + def fixResRefs(code: String, line: String) = line match { + case resCreation(resName) if referenced(resName) => + code.lastIndexOf(PromptString) match { + case -1 => code + case idx => + val (str1, str2) = code splitAt (idx + PromptString.length) + str2 match { + case resAssign(`resName`) => code + case _ => "%sval %s = { %s }".format(str1, resName, str2) + } + } + case _ => code + } + + def run() { + println("// Replaying %d commands from transcript.\n" format cmds.size) + cmds foreach { cmd => + print(PromptString) + interpret(cmd) + } + } + } /** Commands start on lines beginning with "scala>" and each successive * line which begins with the continuation string is appended to that command. @@ -36,33 +94,8 @@ abstract class Pasted { * 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 + try new PasteAnalyzer(lines.toList) run() finally isRunning = false } - - private def append(code: String, line: String): String = - if (matchesPrompt(line)) code + "\n" + line - else if (matchesContinue(line)) code + "\n" + stripString(line, 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 = """^\s*(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 index 684f20e1d2..435592567d 100644 --- a/test/files/run/repl-paste-2.check +++ b/test/files/run/repl-paste-2.check @@ -3,14 +3,17 @@ Type :help for more information. scala> -scala> scala> 123 -res0: Int = 123 +scala> scala> 0123 +res4: Int = 0123 + +scala> 123 +res5: Int = 123 scala> 567 -res1: Int = 567 +res6: Int = 567 -scala> res0 + res1 -res2: Int = 690 +scala> res5 + res6 +res7: Int = 690 scala> val x = dingus <console>:7: error: not found: value dingus @@ -21,35 +24,38 @@ scala> val x = "dingus" x: java.lang.String = dingus scala> x.length -res3: Int = 6 +res9: Int = 6 + +scala> x.length + res5 +res10: Int = 12 -scala> x.length + res3 -res4: Int = 12 +// Replaying 8 commands from transcript. -// Replaying 7 commands from transcript. +scala> 0123 +res0: Int = 83 -val res0 = { 123 } -res0: Int = 123 +scala> val res5 = { 123 } +res5: Int = 123 -val res1 = { 567 } -res1: Int = 567 +scala> val res6 = { 567 } +res6: Int = 567 -val res2 = { res0 + res1 } -res2: Int = 690 +scala> res5 + res6 +res1: Int = 690 -val x = dingus +scala> val x = dingus <console>:7: error: not found: value dingus val x = dingus ^ -val x = "dingus" +scala> val x = "dingus" x: java.lang.String = dingus -val res3 = { x.length } -res3: Int = 6 +scala> x.length +res2: Int = 6 -val res4 = { x.length + res3 } -res4: Int = 12 +scala> x.length + res5 +res3: Int = 129 scala> diff --git a/test/files/run/repl-paste-2.scala b/test/files/run/repl-paste-2.scala index 802c627701..f62927791d 100644 --- a/test/files/run/repl-paste-2.scala +++ b/test/files/run/repl-paste-2.scala @@ -2,14 +2,17 @@ import scala.tools.partest.ReplTest object Test extends ReplTest { def code = """ +scala> 0123 +res4: Int = 0123 + scala> 123 -res0: Int = 123 +res5: Int = 123 scala> 567 -res1: Int = 567 +res6: Int = 567 -scala> res0 + res1 -res2: Int = 690 +scala> res5 + res6 +res7: Int = 690 scala> val x = dingus <console>:7: error: not found: value dingus @@ -20,9 +23,9 @@ scala> val x = "dingus" x: java.lang.String = dingus scala> x.length -res3: Int = 6 +res9: Int = 6 -scala> x.length + res3 -res4: Int = 12 +scala> x.length + res5 +res10: Int = 12 """ }
\ No newline at end of file diff --git a/test/files/run/repl-transcript.check b/test/files/run/repl-transcript.check index c6563c67a9..03162451b6 100644 --- a/test/files/run/repl-transcript.check +++ b/test/files/run/repl-transcript.check @@ -19,19 +19,19 @@ scala> res6.sum + res5 // Replaying 5 commands from transcript. -class Bippity +scala> class Bippity defined class Bippity -def f = new Bippity +scala> def f = new Bippity f: Bippity -val res5 = { 123 } +scala> val res5 = { 123 } res5: Int = 123 -val res6 = { 1 to 100 map (_ + 1) } +scala> 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 +scala> res6.sum + res5 res0: Int = 5273 |