aboutsummaryrefslogblamecommitdiff
path: root/src/dotty/tools/dotc/repl/InterpreterLoop.scala
blob: 1a7a6d115bad20992ddefd5bbf9ab0bdcb76ddf6 (plain) (tree)




































                                                                            

















































































































































































































                                                                                                            
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

import repl.{InterpreterResults => IR}

/** The
 *  <a href="http://scala-lang.org/" target="_top">Scala</a>
 *  interactive shell.  It provides a read-eval-print loop around
 *  the Interpreter class.
 *  After instantiation, clients should call the <code>main()</code> method.
 *
 *  <p>If no in0 is specified, then input will come from the console, and
 *  the class will attempt to provide input editing feature such as
 *  input history.
 *
 *  @author Moez A. Abdel-Gawad
 *  @author  Lex Spoon
 *  @version 1.2
 */
class InterpreterLoop(
    compiler: Compiler,
    private var in: InteractiveReader,
    out: PrintWriter)(implicit ctx: Context) {

  val interpreter = compiler.asInstanceOf[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 requset 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)
  }

  /** Bind the settings so that evaluated code can modify them */
  def bindSettings(): Unit = {
    interpreter.beQuietDuring {
      interpreter.compileString(InterpreterSettings.sourceCodeForClass)

      interpreter.bind(
        "settings",
        "scala.tools.nsc.InterpreterSettings",
        interpreter.isettings)
    }
  }


  /** print a friendly help message */
  def printHelp(): Unit = {
    printWelcome()
    out.println("Type :load followed by a filename to load a Scala file.")
    out.println("Type :replay to reset execution and replay all previous commands.")
    out.println("Type :quit to exit the interpreter.")
  }

  /** Print a welcome message */
  def printWelcome(): Unit = {
    out.println("Welcome to Scala.next " + " (" +
                System.getProperty("java.vm.name") + ", Java " + System.getProperty("java.version") + ")." )
    out.println("Type in expressions to have them evaluated.")
    out.println("Type :help for more information.")
    out.flush()
  }

  /** Prompt to print when awaiting input */
  val prompt = "scala> "

  /** The first interpreted command always takes a couple of seconds
   *  due to classloading. To bridge the gap, wait until the welcome message
   *  has been printed before calling bindSettings.  That way,
   *  the user can read the welcome message while this
   *  command executes.
   */
  def firstLine(): String = {
    val futLine = Future(in.readLine(prompt))
    bindSettings()
    Await.result(futLine, Duration.Inf)
  }

  /** The main read-eval-print loop for the interpreter.  It calls
   *  <code>command()</code> for each line of input, and stops when
   *  <code>command()</code> returns <code>false</code>.
   */
  @tailrec final def repl(line: String): Unit =
    if (line != null) {
      val (keepGoing, finalLineOpt) = command(line)
      if (keepGoing) {
        finalLineOpt.foreach(addReplay)
        out.flush()
        repl(in.readLine(prompt))
      }
    }

  /** interpret all lines from a specified file */
  def interpretAllFrom(filename: String): Unit = {
    val fileIn = try {
      new FileReader(filename)
    } catch {
      case _:IOException =>
        out.println("Error opening file: " + filename)
        return
    }
    val oldIn = in
    val oldReplay = replayCommandsRev
    try {
      val inFile = new BufferedReader(fileIn)
      in = new SimpleReader(inFile, out, false)
      out.println("Loading " + filename + "...")
      out.flush
      repl(in.readLine(prompt))
    } finally {
      in = oldIn
      replayCommandsRev = oldReplay
      fileIn.close
    }
  }

  /** create a new interpreter and replay all commands so far */
  def replay(): Unit = {
    for (cmd <- replayCommands) {
      out.println("Replaying: " + cmd)
      out.flush()  // because maybe cmd will have its own output
      command(cmd)
      out.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) {
        out.println("That command requires a filename to be specified.")
        return ()
      }
      val filename = command.substring(spaceIdx).trim
      if (! new File(filename).exists) {
        out.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 ":")
      out.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 IR.Success => Some(code)
      case IR.Error => None
      case IR.Incomplete =>
        if (in.interactive && code.endsWith("\n\n")) {
          out.println("You typed two blank lines.  Starting a new command.")
          None
        } else {
          val nextLine = in.readLine("     | ")
          if (nextLine == null)
            None  // end of file
          else
            interpretStartingWith(code + "\n" + nextLine)
        }
    }
  }
/*
  def loadFiles(settings: Settings) {
    settings match {
      case settings: GenericRunnerSettings =>
        for (filename <- settings.loadfiles.value) {
          val cmd = ":load " + filename
          command(cmd)
          replayCommandsRev = cmd :: replayCommandsRev
          out.println()
        }
      case _ =>
    }
  }
*/
  def run(): Reporter = {
    // loadFiles(settings)
    try {
      if (!ctx.reporter.hasErrors) {
        printWelcome()
        repl(firstLine())
      }
    } finally {
      closeInterpreter()
    }
    ctx.reporter
  }
}