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 gitHash = ManifestInfo.attributes.getOrElse("Git-Hash", "unknown")
val version = s".next (pre-alpha, git-hash: $gitHash)"
/** 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 = {
import java.nio.file.{Files, Paths}
import scala.collection.JavaConversions._
try {
val lines = Files.readAllLines(Paths.get(filename)).mkString("\n")
output.println("Loading " + filename + "...")
output.flush
interpreter.interpret(lines)
} catch {
case _: IOException =>
output.println("Error opening file: " + filename)
}
}
/** 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)?)?)?)?)?.*"
val lastOutput = interpreter.lastOutput()
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 = lastOutput match { // don't interpret twice
case Nil => interpretStartingWith(line)
case oldRes =>
oldRes foreach output.print
Some(line)
}
(true, shouldReplay)
}
def silentlyRun(cmds: List[String]): Unit = cmds.foreach { cmd =>
interpreter.beQuietDuring(interpreter.interpret(cmd))
}
def silentlyBind(values: Array[(String, Any)]): Unit = values.foreach { case (id, value) =>
interpreter.beQuietDuring(
interpreter.bind(id, value.asInstanceOf[AnyRef].getClass.getName, value.asInstanceOf[AnyRef]))
}
/** 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()
silentlyRun(config.initialCommands)
silentlyBind(config.boundValues)
repl(in.readLine(prompt))
silentlyRun(config.cleanupCommands)
}
} finally {
closeInterpreter()
}
ctx.reporter
}
}