/* NSC -- new Scala compiler * Copyright 2005-2008 LAMP/EPFL * @author Martin Odersky */ // $Id$ package scala.tools.nsc import java.io.{File, PrintWriter, StringWriter, Writer} import java.lang.{Class, ClassLoader} import java.net.{MalformedURLException, URL, URLClassLoader} import scala.collection.immutable.ListSet import scala.collection.mutable import scala.collection.mutable.{ListBuffer, HashSet, ArrayBuffer} //import ast.parser.SyntaxAnalyzer import io.{PlainFile, VirtualDirectory} import reporters.{ConsoleReporter, Reporter} import symtab.Flags import util.{SourceFile,BatchSourceFile,ClassPath,NameTransformer} import nsc.{InterpreterResults=>IR} import scala.tools.nsc.interpreter._ /**

* An interpreter for Scala code. *

*

* The main public entry points are compile(), * interpret(), and bind(). * The compile() method loads a * complete Scala file. The interpret() method executes one * line of Scala code at the request of the user. The bind() * method binds an object to a variable that can then be used by later * interpreted code. *

*

* 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. *

* * @author Moez A. Abdel-Gawad * @author Lex Spoon */ class Interpreter(val settings: Settings, out: PrintWriter) { import symtab.Names /* If the interpreter is running on pre-jvm-1.5 JVM, it is necessary to force the target setting to jvm-1.4 */ private val major = System.getProperty("java.class.version").split("\\.")(0) if (major.toInt < 49) { this.settings.target.value = "jvm-1.4" } /** directory to save .class files to */ val virtualDirectory = new VirtualDirectory("(memory)", None) /** the compiler to compile expressions with */ val compiler: scala.tools.nsc.Global = newCompiler(settings, reporter) import compiler.Traverser import compiler.{Tree, TermTree, ValOrDefDef, ValDef, DefDef, Assign, ClassDef, ModuleDef, Ident, Select, TypeDef, Import, MemberDef, DocDef} import compiler.CompilationUnit import compiler.{Symbol,Name,Type} import compiler.nme import compiler.newTermName import compiler.nme.{INTERPRETER_VAR_PREFIX, INTERPRETER_SYNTHVAR_PREFIX} import Interpreter.string2code /** construct an interpreter that reports to Console */ def this(settings: Settings) = this(settings, new NewLinePrintWriter(new ConsoleWriter, true)) /** whether to print out result lines */ private var printResults: Boolean = true /** Be quiet. Do not print out the results of each * submitted command unless an exception is thrown. */ def beQuiet = { printResults = false } /** Temporarily be quiet */ def beQuietDuring[T](operation: => T): T = { val wasPrinting = printResults try { printResults = false operation } finally { printResults = wasPrinting } } /** interpreter settings */ lazy val isettings = new InterpreterSettings object reporter extends ConsoleReporter(settings, null, out) { //override def printMessage(msg: String) { out.println(clean(msg)) } override def printMessage(msg: String) { out.print(clean(msg) + "\n"); out.flush() } } /** Instantiate a compiler. Subclasses can override this to * change the compiler class used by this interpreter. */ protected def newCompiler(settings: Settings, reporter: Reporter) = { val comp = new scala.tools.nsc.Global(settings, reporter) comp.genJVM.outputDir = virtualDirectory comp } /** the compiler's classpath, as URL's */ val compilerClasspath: List[URL] = { val classpathPart = (ClassPath.expandPath(compiler.settings.classpath.value). map(s => new File(s).toURL)) def parseURL(s: String): Option[URL] = try { Some(new URL(s)) } catch { case _:MalformedURLException => None } val codebasePart = (compiler.settings.Xcodebase.value.split(" ")).toList.flatMap(parseURL) classpathPart ::: codebasePart } /* 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. */ /** class loader used to load compiled code */ private val classLoader = { val parent = if (parentClassLoader == null) new URLClassLoader(compilerClasspath.toArray) else new URLClassLoader(compilerClasspath.toArray, parentClassLoader) new AbstractFileClassLoader(virtualDirectory, parent) } /** Set the current Java "context" class loader to this * interpreter's class loader */ def setContextClassLoader() { Thread.currentThread.setContextClassLoader(classLoader) } protected def parentClassLoader: ClassLoader = this.getClass.getClassLoader() /** the previous requests this interpreter has processed */ private val prevRequests = new ArrayBuffer[Request]() /** next line number to use */ private var nextLineNo = 0 /** allocate a fresh line name */ private def newLineName = { val num = nextLineNo nextLineNo += 1 compiler.nme.INTERPRETER_LINE_PREFIX + num } /** next result variable number to use */ private var nextVarNameNo = 0 /** allocate a fresh variable name */ private def newVarName() = { val num = nextVarNameNo nextVarNameNo += 1 INTERPRETER_VAR_PREFIX + num } /** next internal variable number to use */ private var nextInternalVarNo = 0 /** allocate a fresh internal variable name */ private def newInternalVarName() = { val num = nextVarNameNo nextVarNameNo += 1 INTERPRETER_SYNTHVAR_PREFIX + num } /** Check if a name looks like it was generated by newVarName */ private def isGeneratedVarName(name: String): Boolean = name.startsWith(INTERPRETER_VAR_PREFIX) && { val suffix = name.drop(INTERPRETER_VAR_PREFIX.length) suffix.forall(_.isDigit) } /** 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 NewLinePrintWriter(stringWriter) writer(stream) stream.close stringWriter.toString } /** Truncate a string if it is longer than settings.maxPrintString */ private def truncPrintString(str: String): String = { val maxpr = isettings.maxPrintString if (maxpr <= 0) return str if (str.length <= maxpr) return str val trailer = "..." if (maxpr >= trailer.length+1) return str.substring(0, maxpr-3) + trailer str.substring(0, maxpr) } /** Clean up a string for output */ private def clean(str: String) = truncPrintString(Interpreter.stripWrapperGunk(str)) /** Indent some code by the width of the scala> prompt. * This way, compiler error messages read beettr. */ def indentCode(code: String) = { val spaces = " " stringFrom(str => for (line <- code.lines) { str.print(spaces) str.print(line + "\n") str.flush() }) } implicit def name2string(name: Name) = name.toString /** Compute imports that allow definitions from previous * requests to be visible in a new request. Returns * three pieces of related code: * * 1. An initial code fragment that should go before * the code of the new request. * * 2. A code fragment that should go after the code * of the new request. * * 3. An access path which can be traverested to access * any bindings inside code wrapped by #1 and #2 . * * The argument is a set of Names that need to be imported. * * Limitations: This method is not as precise as it could be. * (1) It does not process wildcard imports to see what exactly * they import. * (2) If it imports any names from a request, it imports all * of them, which is not really necessary. * (3) It imports multiple same-named implicits, but only the * last one imported is actually usable. */ private def importsCode(wanted: Set[Name]): (String, String, String) = { /** Narrow down the list of requests from which imports * should be taken. Removes requests which cannot contribute * useful imports for the specified set of wanted names. */ def reqsToUse: List[(Request,MemberHandler)] = { /** Loop through a list of MemberHandlers and select * which ones to keep. 'wanted' is the set of * names that need to be imported, and * 'shadowed' is the list of names useless to import * because a later request will re-import it anyway. */ def select(reqs: List[(Request,MemberHandler)], wanted: Set[Name]): List[(Request,MemberHandler)] = { reqs match { case Nil => Nil case (req,handler)::rest => val keepit = (handler.definesImplicit || handler.importsWildcard || handler.importedNames.exists(wanted.contains(_)) || handler.boundNames.exists(wanted.contains(_))) val newWanted = if (keepit) { (wanted ++ handler.usedNames -- handler.boundNames -- handler.importedNames) } else { wanted } val restToKeep = select(rest, newWanted) if(keepit) (req,handler) :: restToKeep else restToKeep } } val rhpairs = for { req <- prevRequests.toList.reverse handler <- req.handlers } yield (req, handler) select(rhpairs, wanted).reverse } val code = new StringBuffer val trailingBraces = new StringBuffer val accessPath = new StringBuffer val impname = compiler.nme.INTERPRETER_IMPORT_WRAPPER val currentImps = mutable.Set.empty[Name] // add code for a new object to hold some imports def addWrapper() { code.append("object " + impname + "{\n") trailingBraces.append("}\n") accessPath.append("." + impname) currentImps.clear } addWrapper() // loop through previous requests, adding imports // for each one for ((req,handler) <- reqsToUse) { // If the user entered an import, then just use it // add an import wrapping level if the import might // conflict with some other import if(handler.importsWildcard || currentImps.exists(handler.importedNames.contains)) if(!currentImps.isEmpty) addWrapper() if (handler.member.isInstanceOf[Import]) code.append(handler.member.toString + ";\n") // give wildcard imports a import wrapper all to their own if(handler.importsWildcard) addWrapper() else currentImps ++= handler.importedNames // For other requests, import each bound variable. // import them explicitly instead of with _, so that // ambiguity errors will not be generated. Also, quote // the name of the variable, so that we don't need to // handle quoting keywords separately. for (imv <- handler.boundNames) { if (currentImps.contains(imv)) addWrapper() code.append("import ") code.append(req.objectName + req.accessPath + ".`" + imv + "`;\n") currentImps += imv } } addWrapper() // Add one extra wrapper, to prevent warnings // in the frequent case of redefining // the value bound in the last interpreter // request. (code.toString, trailingBraces.toString, accessPath.toString) } /** Parse a line into a sequence of trees. Returns None if the input * is incomplete. */ private def parse(line: String): Option[List[Tree]] = { var justNeedsMore = false reporter.withIncompleteHandler((pos,msg) => {justNeedsMore = true}) { // simple parse: just parse it, nothing else def simpleParse(code: String): List[Tree] = { reporter.reset val unit = new CompilationUnit( new BatchSourceFile("", code.toCharArray())) val scanner = new compiler.syntaxAnalyzer.UnitParser(unit); val xxx = scanner.templateStatSeq(false); (xxx._2) } val (trees) = simpleParse(line) if (reporter.hasErrors) { Some(Nil) // the result did not parse, so stop } else if (justNeedsMore) { None } else { Some(trees) } } } /** 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) !reporter.hasErrors } /** Compile a string. Returns true if there are no * compilation errors, or false otherwise. */ def compileString(code: String): Boolean = compileSources(List(new BatchSourceFile("