
scala.tools.nsc.interpreter.shell.Pasted.scala Maven / Gradle / Ivy
/*
* Scala (https://www.scala-lang.org)
*
* Copyright EPFL and Lightbend, Inc.
*
* Licensed under Apache License 2.0
* (http://www.apache.org/licenses/LICENSE-2.0).
*
* See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*/
package scala.tools.nsc.interpreter.shell
import scala.tools.nsc.interpreter.Results.{Result, Incomplete}
/** 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(prompt: String, continuePrompt: String, continueText: String) {
def interpret(line: String): Result
def echo(message: String): Unit
val PromptString = prompt.linesIterator.toList.last
val AltPromptString = "scala> "
val ContinuePrompt = continuePrompt
val ContinueString = continueText // " | "
val anyPrompt = {
import scala.util.matching.Regex.quote
s"""\\s*(?:${quote(PromptString.trim)}|${quote(AltPromptString.trim)})\\s*""".r
}
def isPrompted(line: String) = matchesPrompt(line)
def isPromptOnly(line: String) = line match { case anyPrompt() => true ; case _ => false }
private val testBoth = PromptString != AltPromptString
private val spacey = " \t".toSet
def matchesPrompt(line: String) = matchesString(line, PromptString) || testBoth && matchesString(line, AltPromptString)
def matchesContinue(line: String) = matchesString(line, ContinueString)
def running = isRunning
private def matchesString(line: String, target: String): Boolean =
line.startsWith(target) || (line.nonEmpty && spacey(line.head) && matchesString(line.tail, target))
private def stripString(line: String, target: String) = line indexOf target match {
case -1 => line
case idx => line drop (idx + target.length)
}
private var isRunning = false
private val resReference = """(? resReference.findAllIn(s.trim.stripPrefix("res"))).toSet
val ActualPromptString = lines.find(matchesPrompt).map(s =>
if (matchesString(s, PromptString)) PromptString else AltPromptString).getOrElse(PromptString)
val cmds = lines.reduceLeft(append).split(ActualPromptString).filterNot(_.trim == "").toList
/** 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 as
* val res15 = { }
* 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(ActualPromptString) match {
case -1 => code
case idx =>
val (str1, str2) = code splitAt (idx + ActualPromptString.length)
str2 match {
case resAssign(`resName`) => code
case _ => "%sval %s = { %s }".format(str1, resName, str2)
}
}
case _ => code
}
def interpreted(line: String) = {
echo(line.trim)
val res = interpret(line)
if (res != Incomplete) echo("")
res
}
def incompletely(cmd: String) = {
print(ActualPromptString)
interpreted(cmd) == Incomplete
}
def run(): Option[String] = {
echo(s"// Replaying ${cmds.size} commands from transcript.\n")
cmds find incompletely
}
}
// Run transcript and return incomplete line if any.
def transcript(lines: IterableOnce[String]): Option[String] = {
echo("\n// Detected repl transcript. Paste more, or ctrl-D to finish.\n")
apply(lines)
}
/** 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: IterableOnce[String]): Option[String] = {
isRunning = true
try new PasteAnalyzer(List.from(lines)).run()
finally isRunning = false
}
// used during loop
def unapply(line: String): Boolean = !running && isPrompted(line)
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy