summaryrefslogblamecommitdiff
path: root/src/compiler/scala/tools/nsc/InterpreterLoop.scala
blob: 928034be38b3c7b22145e05255be9756a0259fbc (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._

/** 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 interpreter commands come */
  var in: InteractiveReader = _  //set by main()

  /** 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 = null // 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 requset a :replay */
  def addReplay(cmd: String) =
    replayCommandsRev = cmd :: replayCommandsRev

  /** Close the interpreter, if there is one, and set
    * interpreter to <code>null</code>. */
  def closeInterpreter() {
    if (interpreter ne null) {
      interpreter.close
      interpreter = null
      Thread.currentThread.setContextClassLoader(originalClassLoader)
    }
  }

  /** Create a new interpreter.  Close the old one, if there
    * is one. */
  def createInterpreter() {
    //closeInterpreter()

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

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

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


  /** print a friendly help message */
  def printHelp {
    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() {
    out.println("Welcome to Scala " + Properties.versionString + " (" +
                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 = Properties.shellPromptString

  /** 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() {
    var first = true
    while (true) {
      out.flush()

      val line =
        if (first) {
          /* 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(in.readLine(prompt))

          bindSettings()
          first = false

          futLine()
      } else {
        in.readLine(prompt)
      }

      if (line eq null)
        return ()  // assumes null means EOF

      val (keepGoing, finalLineMaybe) = command(line)

      if (!keepGoing)
        return

      finalLineMaybe match {
        case Some(finalLine) => addReplay(finalLine)
        case None => ()
      }
    }
  }

  /** interpret all lines from a specified file */
  def interpretAllFrom(filename: String) {
    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
    } 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
    }
  }

  /** 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) {
      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 main(settings: Settings) {
    this.settings = settings

    in =
      in0 match {
        case Some(in0) =>
          new SimpleReader(in0, out, true)

        case None =>
          val emacsShell = System.getProperty("env.emacs", "") != ""
          //println("emacsShell="+emacsShell) //debug
          if (settings.Xnojline.value || emacsShell)
            new SimpleReader()
          else
            InteractiveReader.createDefault()
      }

    createInterpreter()

    loadFiles(settings)

    try {
      if (interpreter.reporter.hasErrors) {
        return // it is broken on startup; go ahead and exit
      }
      printWelcome()
      repl()
    } finally {
      closeInterpreter()
    }
  }

  /** 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)

    if (!command.ok || command.settings.help.value || command.settings.Xhelp.value) {
      // either the command line is wrong, or the user
      // explicitly requested a help listing
      if (command.settings.help.value) out.println(command.usageMsg)
      if (command.settings.Xhelp.value) out.println(command.xusageMsg)
      out.flush
    }
    else
      main(command.settings)
  }
}