diff options
-rw-r--r-- | src/compiler/scala/tools/nsc/interpreter/ILoop.scala | 144 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/interpreter/Pasted.scala | 59 | ||||
-rw-r--r-- | test/files/run/repl-paste-2.check | 31 | ||||
-rw-r--r-- | test/files/run/repl-paste-2.scala | 28 | ||||
-rw-r--r-- | test/files/run/repl-paste.check | 13 | ||||
-rw-r--r-- | test/files/run/repl-paste.scala | 19 | ||||
-rw-r--r-- | test/files/run/repl-transcript.check | 23 | ||||
-rw-r--r-- | test/files/run/repl-transcript.scala | 20 |
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 + """ +} + |