/* NSC -- new Scala compiler * Copyright 2005-2006 LAMP/EPFL * @author Martin Odersky */ // $Id$ package scala.tools.nsc import java.io.{File, PrintWriter, StringWriter} import scala.collection.mutable.{ListBuffer, HashSet, ArrayBuffer} import scala.collection.immutable.{Map, ListMap} import ast.parser.SyntaxAnalyzer import io.PlainFile import reporters.{ConsoleReporter, Reporter} import util.SourceFile import symtab.Flags /** An interpreter for Scala code. The main public entry points are compile() and interpret(). The compile() method loads a complete Scala file. The interpret() method executes one line of Scala code at the request of the user. The overall approach is based on compiling the requested code and then using a Java classloader and Java reflection to run the code and access its results. In more detail, a single compiler instance is used to accumulate all successfully compiled or interpreted Scala code. To "interpret" a line of code, the compiler generates a fresh object that includes the line of code and which has public member(s) to export all variables defined by that code. To extract the result of an interpreted line to show the user, a second "result object" is created which imports the variables exported by the above object and then exports a single member named "result". To accomodate user expressions that read from variables or methods defined in previous statements, "import" statements are used. This interpreter shares the strengths and weaknesses of using the full compiler-to-Java. The main strength is that interpreted code behaves exactly as does compiled code, including running at full speed. The main weakness is that redefining classes and methods is not handled properly, because rebinding at the Java level is technically difficult. */ class Interpreter(val settings: Settings, reporter: Reporter, out: PrintWriter) { import symtab.Names import compiler.Traverser import compiler.{Tree, TermTree, ValOrDefDef, ValDef, DefDef, Assign, ClassDef, ModuleDef, Ident, Select, AliasTypeDef, Import} import compiler.CompilationUnit import compiler.Symbol import compiler.Name /** construct an interpreter that reports to Console */ def this(settings: Settings) = this(settings, new ConsoleReporter, new PrintWriter(new ConsoleWriter, true)) /** construct an interpreter that uses the specified in and out streams */ def this(settings: Settings, out: PrintWriter) = this(settings, new ConsoleReporter(null, out), out) /** whether to print out result lines */ private var printResults: Boolean = true /** be quiet; do not print out the results of each submitted command */ def beQuiet = { printResults = false } /** directory to save .class files to */ private val classfilePath = File.createTempFile("scalaint", "") classfilePath.delete // the file is created as a file; make it a directory classfilePath.mkdirs /* set up the compiler's output directory */ settings.outdir.value = classfilePath.getPath /** the compiler to compile expressions with */ val compiler = new Global(settings, reporter) /** class loader used to load compiled code */ /* A single class loader is used for all commands interpreted by this Interpreter. It would also be possible to create a new class loader for each command to interpret. The advantages of the current approach are: - Expressions are only evaluated one time. This is especially significant for I/O, e.g. "val x = Console.readLine" The main disadvantage is: - Objects, classes, and methods cannot be rebound. Instead, definitions shadow the old ones, and old code objects refer to the old definitions. */ private val classLoader = { if (parentClassLoader == null) new java.net.URLClassLoader(Predef.Array(classfilePath.toURL)) else new java.net.URLClassLoader(Predef.Array(classfilePath.toURL), parentClassLoader) } protected def parentClassLoader : ClassLoader = { new java.net.URLClassLoader( compiler.settings.classpath.value.split(File.pathSeparator). map(s => new File(s).toURL), ClassLoader.getSystemClassLoader) } /** the previous requests this interpreter has processed */ private val prevRequests = new ArrayBuffer[Request]() /** look up the request that bound a specified term or type */ private def reqBinding(vname: Name): Option[Request] = { prevRequests.toList.reverse.find(lin => lin.boundNames.contains(vname)) } /** next line number to use */ private var nextLineNo = 0 /** allocate a fresh line name */ private def newLineName = { val num = nextLineNo nextLineNo = nextLineNo + 1 "line" + num } /** import statements that should be used for submitted code */ private def importLines: List[String] = for { val req <- prevRequests.toList req.isInstanceOf[ImportReq] } yield req.line //private var importLinesRev: List[String] = List("import scala.collection.immutable._") /** a string of import code corresponding to all of the current importLines */ private def codeForImports: String = importLines.mkString("", ";\n", ";\n") /** generate a string using a routine that wants to write on a stream */ private def stringFrom(writer: PrintWriter=>Unit): String = { val stringWriter = new StringWriter() val stream = new PrintWriter(stringWriter) writer(stream) stream.close stringWriter.toString } /** parse a line into a sequence of trees */ private def parse(line: String): List[Tree] = { // simple parse: just parse it, nothing else def simpleParse(code: String): List[Tree] = { val unit = new CompilationUnit( new SourceFile("",code.toCharArray())) new compiler.syntaxAnalyzer.Parser(unit).templateStatSeq } // parse the main code along with the imports reporter.reset val trees = simpleParse(codeForImports + line) if(reporter.errors > 0) return Nil // the result did not parse, so stop // parse the imports alone val importTrees = simpleParse(codeForImports) // return just the new trees, not the import trees trees.drop(importTrees.length) } /** Compile one source file */ def compileFile(filename: String): Unit = { val jfile = new File(filename) if(!jfile.exists) { reporter.error(null, "no such file: " + filename) return () } val cr = new compiler.Run cr.compileSources(List(new SourceFile(PlainFile.fromFile(jfile)))) } /** Compile an nsc SourceFile. Returns true if there are * no compilation errors, or false othrewise. */ def compileSources(sources: List[SourceFile]): Boolean = { val cr = new compiler.Run reporter.reset cr.compileSources(sources) return (reporter.errors == 0) } /** Compile a string. Returns true if there are no * compilation errors, or false otherwise. */ def compileString(code: String): Boolean = compileSources(List(new SourceFile("