diff options
Diffstat (limited to 'src/compiler')
-rw-r--r-- | src/compiler/scala/tools/nsc/interpreter/ILoop.scala | 8 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/interpreter/Pasted.scala | 93 |
2 files changed, 70 insertions, 31 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 - } } |