aboutsummaryrefslogblamecommitdiff
path: root/compiler/src/dotty/tools/dotc/repl/InterpreterLoop.scala
blob: cfe8d892d55ac385f17f49821e1c87bcd85c6e09 (plain) (tree)
1
2
3
4
5
6
7





                                                              
                                              







                                                         
                                                                     
                          
                                                              

                               
                     
                          
   


                                                                                       
                                                      
 

                                     










                                                                     
                                                                      








                                                                   


                                      


                                                                                       



                                
                                                        
                                                                                                            


                                                                 

   

                                                                        
 
                                                                  
                                         
     
                                                                     



                                                   
                      
              




                                                  

                                             
         
                                                                        

                                                   



                                                         





                                                                

                                                                   
                  
                    









                                                                        
                                                                           
              

                                                     
                                       
                                                  
              







                                                  
                                                













                                           
              
                                 
                                                              
        
                                                                


                                               
                    
       



                        



                                                                   




                                                                                                    





                                                               
                                                           
                                       
                                            
                    
     







                                                      
                          







                          
                                                                                         
                      
                                           
                                        

                                           






                        
package dotty.tools
package dotc
package repl

import java.io.{BufferedReader, File, FileReader, PrintWriter}
import java.io.IOException
import java.lang.{ClassLoader, System, Thread}
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
  }
}