package dotty.tools
package dotc
package repl
import java.io.{BufferedReader, File, FileReader, PrintWriter}
import java.io.IOException
import java.lang.{ClassLoader, System}
import scala.concurrent.{Future, Await}
import scala.concurrent.duration.Duration
import reporting.Reporter
import core._
import Contexts._
import annotation.tailrec
import scala.concurrent.ExecutionContext.Implicits.global
/** The interactive shell. It provides a read-eval-print loop around
* the Interpreter class.
* After instantiation, clients should call the `run` method.
*
* @author Moez A. Abdel-Gawad
* @author Lex Spoon
* @author Martin Odersky
*/
class InterpreterLoop(compiler: Compiler, config: REPL.Config)(implicit ctx: Context) {
import config._
val interpreter = compiler.asInstanceOf[Interpreter]
private var in = input(interpreter)
/** The context class loader at the time this object was created */
protected val originalClassLoader =
Thread.currentThread.getContextClassLoader
/** A reverse list of commands to replay if the user
* requests a :replay */
var replayCommandsRev: List[String] = Nil
/** A list of commands to replay if the user requests a :replay */
def replayCommands = replayCommandsRev.reverse
/** Record a command for replay should the user request a :replay */
def addReplay(cmd: String) =
replayCommandsRev = cmd :: replayCommandsRev
/** Close the interpreter */
def closeInterpreter()(implicit ctx: Context): Unit = {
ctx.reporter.flush()
Thread.currentThread.setContextClassLoader(originalClassLoader)
}
/** print a friendly help message */
def printHelp(): Unit = {
printWelcome()
output.println("Type :load followed by a filename to load a Scala file.")
output.println("Type :replay to reset execution and replay all previous commands.")
output.println("Type :quit to exit the interpreter.")
}
/** Print a welcome message */
def printWelcome(): Unit = {
output.println(s"Welcome to Scala$version " + " (" +
System.getProperty("java.vm.name") + ", Java " + System.getProperty("java.version") + ")." )
output.println("Type in expressions to have them evaluated.")
output.println("Type :help for more information.")
output.flush()
}
val version = ".next (pre-alpha)"
/** The first interpreted command always takes a couple of seconds
* due to classloading. To bridge the gap, we warm up the interpreter
* by letting it interpret a dummy line while waiting for the first
* line of input to be entered.
*/
def firstLine(): String = {
val line = in.readLine(prompt)
interpreter.beQuietDuring(
interpreter.interpret("val theAnswerToLifeInTheUniverseAndEverything = 21 * 2"))
line
}
/** The main read-eval-print loop for the interpreter. It calls
* `command()` for each line of input.
*/
@tailrec final def repl(line: String = in.readLine(prompt)): Unit =
if (line != null) {
val (keepGoing, finalLineOpt) = command(line)
if (keepGoing) {
finalLineOpt.foreach(addReplay)
output.flush()
repl()
}
}
/** interpret all lines from a specified file */
def interpretAllFrom(filename: String): Unit = {
val fileIn = try {
new FileReader(filename)
} catch {
case _: IOException =>
output.println("Error opening file: " + filename)
return
}
val oldIn = in
val oldReplay = replayCommandsRev
try {
val inFile = new BufferedReader(fileIn)
in = new SimpleReader(inFile, output, false)
output.println("Loading " + filename + "...")
output.flush
repl()
} finally {
in = oldIn
replayCommandsRev = oldReplay
fileIn.close
}
}
/** create a new interpreter and replay all commands so far */
def replay(): Unit = {
for (cmd <- replayCommands) {
output.println("Replaying: " + cmd)
output.flush() // because maybe cmd will have its own output
command(cmd)
output.println
}
}
/** Run one command submitted by the user. Three values are returned:
* (1) whether to keep running, (2) the line to record for replay,
* if any. */
def command(line: String): (Boolean, Option[String]) = {
def withFile(command: String)(action: String => Unit): Unit = {
val spaceIdx = command.indexOf(' ')
if (spaceIdx <= 0) {
output.println("That command requires a filename to be specified.")
return
}
val filename = command.substring(spaceIdx).trim
if (!new File(filename).exists) {
output.println("That file does not exist")
return
}
action(filename)
}
val helpRegexp = ":h(e(l(p)?)?)?"
val quitRegexp = ":q(u(i(t)?)?)?"
val loadRegexp = ":l(o(a(d)?)?)?.*"
val replayRegexp = ":r(e(p(l(a(y)?)?)?)?)?.*"
var shouldReplay: Option[String] = None
if (line.matches(helpRegexp))
printHelp()
else if (line.matches(quitRegexp))
return (false, None)
else if (line.matches(loadRegexp)) {
withFile(line)(f => {
interpretAllFrom(f)
shouldReplay = Some(line)
})
}
else if (line matches replayRegexp)
replay()
else if (line startsWith ":")
output.println("Unknown command. Type :help for help.")
else
shouldReplay = interpretStartingWith(line)
(true, shouldReplay)
}
/** Interpret expressions starting with the first line.
* Read lines until a complete compilation unit is available
* or until a syntax error has been seen. If a full unit is
* read, go ahead and interpret it. Return the full string
* to be recorded for replay, if any.
*/
def interpretStartingWith(code: String): Option[String] =
interpreter.interpret(code) match {
case Interpreter.Success => Some(code)
case _ => None
}
/*
def loadFiles(settings: Settings) {
settings match {
case settings: GenericRunnerSettings =>
for (filename <- settings.loadfiles.value) {
val cmd = ":load " + filename
command(cmd)
replayCommandsRev = cmd :: replayCommandsRev
output.println()
}
case _ =>
}
}
*/
def run(): Reporter = {
// loadFiles(settings)
try {
if (!ctx.reporter.hasErrors) { // if there are already errors, no sense to continue
printWelcome()
repl(firstLine())
}
} finally {
closeInterpreter()
}
ctx.reporter
}
}