summaryrefslogblamecommitdiff
path: root/src/compiler/scala/tools/nsc/InterpreterLoop.scala
blob: ea387b39dc1ee909a66729b08df1f283782a4072 (plain) (tree)
1
2
3
4
5
6
7
8
9
                            
                                





                          
                                                              
                          
                                      
 

                                                 
 










































                                                                                               



                                                                 

                                                                            




                                                                         
                      
                
   
                                                                      
                                                                        
                                                       
 

                                                                 
 
                                                                     
                                                                                
 

                                                                   
                                       
 
                                                                            




                                                                    

                                                                           
 
                                                                    
                          
                              

                        
                                                                     
     
   
 
                                  
                           
                                                  
                                                                                        
     
                                       
   
 
                                                                 

                               
                                                                       
                                                                                    


     
                                      


                                                                                             


                                
                      











                                                    

   
                                            
                                           
 








































                                                                                              
                                                                  
                                                                   
                                                        
     
              










                                                                      

       











                                                                  
   
 
                                                  
                                          



                                                                                           
                  
                                     
         

                                               

                                                
          

                
                                   
                  


     
                                                                
                

                       
                                 
                                      
                                                                




                  



















                                                                            

                                                                     



                                       

     


                                                      
 

                                                                                             
 



                                                                                                          
     

   





                                                               
                                                           
                                       


                                       
                                                      
                                                                            
              
         


                                                                
         
     










                                                      

   
                                
                            
                       
 




                                                                     
 




                                                                                        
 
                       
         


                                                   

                    
               
                        


     



















                                                                    
                                                              
                                 









                                                                            
     
   
 
/* NSC -- new Scala compiler
 * Copyright 2005-2009 LAMP/EPFL
 * @author Alexander Spoon
 */
// $Id$

package scala.tools.nsc

import java.io.{BufferedReader, File, FileReader, PrintWriter}
import java.io.IOException
import java.lang.{ClassLoader, System}

import scala.tools.nsc.{InterpreterResults => IR}
import scala.tools.nsc.interpreter._

// Classes to wrap up interpreter commands and their results
// You can add new commands by adding entries to val commands
// inside InterpreterLoop.
object InterpreterControl {
  // the default result means "keep running, and don't record that line"
  val defaultResult = Result(true, None)

  // a single interpreter command
  sealed abstract class Command extends Function1[List[String], Result] {
    val name: String
    val help: String
    def error(msg: String) = {
      println(":" + name + " " + msg + ".")
      Result(true, None)
    }
    def getHelp(): String = ":" + name + " " + help + "."
  }

  case class NoArgs(name: String, help: String, f: () => Result) extends Command {
    def apply(args: List[String]) = if (args.isEmpty) f() else error("accepts no arguments")
  }

  case class LineArg(name: String, help: String, f: (String) => Result) extends Command {
    def apply(args: List[String]) =
      if (args.size == 1) f(args.head)
      else error("requires a line of input")
  }

  case class OneArg(name: String, help: String, f: (String) => Result) extends Command {
    def apply(args: List[String]) =
      if (args.size == 1) f(args.head)
      else error("requires exactly one argument")
  }

  case class VarArgs(name: String, help: String, f: (List[String]) => Result) extends Command {
    def apply(args: List[String]) = f(args)
  }

  // the result of a single command
  case class Result(keepRunning: Boolean, lineToRecord: Option[String])
}
import InterpreterControl._

/** 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(in0: Option[BufferedReader], out: PrintWriter) {
  def this(in0: BufferedReader, out: PrintWriter) = this(Some(in0), out)
  def this() = this(None, new PrintWriter(Console.out))

  /** The input stream from which commands come, set by main() */
  var in: InteractiveReader = _

  /** The context class loader at the time this object was created */
  protected val originalClassLoader = Thread.currentThread.getContextClassLoader

  var settings: Settings = _          // set by main()
  var interpreter: Interpreter = _    // set by createInterpreter()
  def isettings = interpreter.isettings

  /** 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 and set the var to <code>null</code>. */
  def closeInterpreter() {
    if (interpreter ne null) {
      interpreter.close
      interpreter = null
      Thread.currentThread.setContextClassLoader(originalClassLoader)
    }
  }

  /** Create a new interpreter. */
  def createInterpreter() {
    interpreter = new Interpreter(settings, out) {
      override protected def parentClassLoader = classOf[InterpreterLoop].getClassLoader
    }
    interpreter.setContextClassLoader()
  }

  /** Bind the settings so that evaluated code can modify them */
  def bindSettings() {
    interpreter.beQuietDuring {
      interpreter.compileString(InterpreterSettings.sourceCodeForClass)
      interpreter.bind("settings", "scala.tools.nsc.InterpreterSettings", isettings)
    }
  }

  /** print a friendly help message */
  def printHelp() = {
    out println "All commands can be abbreviated - for example :h or :he instead of :help.\n"
    commands foreach { c => out println c.getHelp }
  }

  /** Print a welcome message */
  def printWelcome() {
    val welcomeMsg =
     """|Welcome to Scala %s (%s, Java %s).
        |Type in expressions to have them evaluated.
        |Type :help for more information.""" .
    stripMargin.format(
      Properties.versionString,
      System.getProperty("java.vm.name"),
      System.getProperty("java.version")
    )

    out println welcomeMsg
    out.flush
  }

  /** Prompt to print when awaiting input */
  val prompt = Properties.shellPromptString

  // most commands do not want to micromanage the Result, but they might want
  // to print something to the console, so we accomodate Unit and String returns.
  object CommandImplicits {
    implicit def u2ir(x: Unit): Result = defaultResult
    implicit def s2ir(s: String): Result = {
      out println s
      defaultResult
    }
  }

  /** Standard commands **/
  val standardCommands: List[Command] = {
    import CommandImplicits._
    List(
       NoArgs("help", "prints this help message", printHelp),
       OneArg("load", "followed by a filename loads a Scala file", load),
       NoArgs("replay", "resets execution and replays all previous commands", replay),
       NoArgs("quit", "exits the interpreter", () => Result(false, None)),
       NoArgs("power", "enable power user mode", power)
    )
  }

  /** Power user commands */
  // XXX - why does a third argument like "interpreter dumpState(_)" throw an NPE
  // while the version below works?
  var powerUserOn = false
  val powerCommands: List[Command] = {
    import CommandImplicits._
    List(
      VarArgs("dump", "displays a view of the interpreter's internal state",
        (xs: List[String]) => interpreter dumpState xs),
      VarArgs("tree", "displays ASTs for specified identifiers",
        (xs: List[String]) => interpreter dumpTrees xs)
      // LineArg("meta", "given code which produces scala code, executes the results",
      //   (xs: List[String]) => )
    )
  }

  /** Available commands */
  def commands: List[Command] = standardCommands ::: (if (powerUserOn) powerCommands else Nil)

  /** 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>.
   */
  def repl() {
    def readOneLine() = {
      out.flush
      in readLine prompt
    }
    // return false if repl should exit
    def processLine(line: String): Boolean =
      if (line eq null) false               // assume null means EOF
      else command(line) match {
        case Result(false, _)           => false
        case Result(_, Some(finalLine)) => addReplay(finalLine) ; true
        case _                          => true
      }

    /* For some reason, the first interpreted command always takes
     * a second or two.  So, wait until the welcome message
     * has been printed before calling bindSettings.  That way,
     * the user can read the welcome message while this
     * command executes.
     */
    val futLine = scala.concurrent.ops.future(readOneLine)
    bindSettings()
    processLine(futLine())

    // loops until false, then returns
    while (processLine(readOneLine)) { }
  }

  /** interpret all lines from a specified file */
  def interpretAllFrom(filename: String) {
    val fileIn =
      try   { new FileReader(filename) }
      catch { case _:IOException => return out.println("Error opening file: " + filename) }

    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
    } finally {
      in = oldIn
      replayCommandsRev = oldReplay
      fileIn.close
    }
  }

  /** create a new interpreter and replay all commands so far */
  def replay() {
    closeInterpreter()
    createInterpreter()
    for (cmd <- replayCommands) {
      out.println("Replaying: " + cmd)
      out.flush()  // because maybe cmd will have its own output
      command(cmd)
      out.println
    }
  }

  def withFile(filename: String)(action: String => Unit) {
    if (! new File(filename).exists) out.println("That file does not exist")
    else action(filename)
  }

  def load(arg: String) = {
    var shouldReplay: Option[String] = None
    withFile(arg)(f => {
      interpretAllFrom(f)
      shouldReplay = Some(":load " + arg)
    })
    Result(true, shouldReplay)
  }

  def power() = {
    powerUserOn = true
    interpreter.powerUser()
  }

  /** Run one command submitted by the user.  Two values are returned:
    * (1) whether to keep running, (2) the line to record for replay,
    * if any. */
  def command(line: String): Result = {
    def withError(msg: String) = {
      out println msg
      Result(true, None)
    }

    // not a command
    if (!line.startsWith(":"))
      return Result(true, interpretStartingWith(line))

    // this lets us add commands willy-nilly and only requires enough command to disambiguate
    val (x :: args) = line.substring(1).split("""\s+""").toList

    commands.filter(_.name startsWith x) match {
      case List(x)  => x(args)
      case Nil      => withError("Unknown command.  Type :help for help.")
      case xs       => withError("Ambiguous: did you mean " + xs.map(":" + _.name).mkString(" or ") + "?")
    }
  }

  /** 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.Error       => None
      case IR.Success     => Some(code)
      case IR.Incomplete  =>
        if (in.interactive && code.endsWith("\n\n")) {
          out.println("You typed two blank lines.  Starting a new command.")
          None
        }
        else in.readLine("     | ") match {
          case null => None         // end of file
          case line => interpretStartingWith(code + "\n" + line)
        }
    }

  // runs :load <file> on any files passed via -i
  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 main(settings: Settings) {
    this.settings = settings
    createInterpreter()

    // sets in to some kind of reader depending on environmental cues
    in = in0 match {
      case Some(in0)  => new SimpleReader(in0, out, true)
      case None       =>
        val emacsShell = System.getProperty("env.emacs", "") != ""

        // tab completion off by default for now
        if (settings.Xnojline.value || emacsShell) new SimpleReader
        else if (settings.completion.value) InteractiveReader.createDefault(interpreter)
        else InteractiveReader.createDefault()
    }

    loadFiles(settings)
    try {
      // it is broken on startup; go ahead and exit
      if (interpreter.reporter.hasErrors) return

      printWelcome()
      repl()
    } finally {
      closeInterpreter()
    }
  }

  // injects one value into the repl; returns pair of name and class
  def injectOne(name: String, obj: Any): Tuple2[String, String] = {
    val className = obj.asInstanceOf[AnyRef].getClass.getName
    interpreter.bind(name, className, obj)
    (name, className)
  }

  // injects list of values into the repl; returns summary string
  def inject(args: List[Any]): String = {
    val strs =
      for ((arg, i) <- args.zipWithIndex) yield {
        val varName = "p" + (i + 1)
        val (vname, vtype) = injectOne(varName, arg)
        vname + ": " + vtype
      }

    if (strs.size == 0) "Set no variables."
    else "Variables set:\n" + strs.mkString("\n")
  }

  /** process command-line arguments and do as they request */
  def main(args: Array[String]) {
    def error1(msg: String) = out println ("scala: " + msg)
    val command = new InterpreterCommand(List fromArray args, error1)
    def neededHelp(): String =
      (if (command.settings.help.value) command.usageMsg + "\n" else "") +
      (if (command.settings.Xhelp.value) command.xusageMsg + "\n" else "")

    // if they asked for no help and command is valid, we call the real main
    neededHelp() match {
      case ""     => if (command.ok) main(command.settings) // else nothing
      case help   => out print help ; out flush
    }
  }
}