/* NSC -- new Scala compiler * Copyright 2005-2011 LAMP/EPFL * @author Martin Odersky */ package scala.tools.nsc package interpreter import Predef.{ println => _, _ } import java.io.{ PrintWriter } import java.lang.reflect import java.net.URL import util._ import io.VirtualDirectory import reporters._ import symtab.Flags import scala.reflect.internal.Names import scala.tools.util.PathResolver import scala.tools.nsc.util.{ ScalaClassLoader, Exceptional } import ScalaClassLoader.URLClassLoader import Exceptional.unwrap import scala.collection.{ mutable, immutable } import scala.PartialFunction.{ cond, condOpt } import scala.util.control.Exception.{ ultimately } import scala.reflect.NameTransformer import IMain._ /** 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 "$export". 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 IMain(val settings: Settings, protected val out: PrintWriter) extends Imports { imain => /** construct an interpreter that reports to Console */ def this(settings: Settings) = this(settings, new NewLinePrintWriter(new ConsoleWriter, true)) def this() = this(new Settings()) /** whether to print out result lines */ private[nsc] var printResults: Boolean = true /** whether to print errors */ private[nsc] var totalSilence: Boolean = false private val RESULT_OBJECT_PREFIX = "RequestResult$" lazy val formatting: Formatting = new Formatting { val prompt = Properties.shellPromptString } import formatting._ /** directory to save .class files to */ val virtualDirectory = new VirtualDirectory("(memory)", None) { private def pp(root: io.AbstractFile, indentLevel: Int) { val spaces = " " * indentLevel out.println(spaces + root.name) if (root.isDirectory) root.toList sortBy (_.name) foreach (x => pp(x, indentLevel + 1)) } // print the contents hierarchically def show() = pp(this, 0) } /** reporter */ lazy val reporter: ReplReporter = new ReplReporter(this) import reporter.{ printMessage, withoutTruncating } // not sure if we have some motivation to print directly to console private def echo(msg: String) { Console println msg } // protected def defaultImports: List[String] = List("_root_.scala.sys.exit") /** We're going to go to some trouble to initialize the compiler asynchronously. * It's critical that nothing call into it until it's been initialized or we will * run into unrecoverable issues, but the perceived repl startup time goes * through the roof if we wait for it. So we initialize it with a future and * use a lazy val to ensure that any attempt to use the compiler object waits * on the future. */ private val _compiler: Global = newCompiler(settings, reporter) private var _initializeComplete = false def isInitializeComplete = _initializeComplete private def _initSources = List(new BatchSourceFile("", "class $repl_$init { }")) private def _initialize() = { try { new _compiler.Run() compileSources _initSources _initializeComplete = true true } catch AbstractOrMissingHandler() } // set up initialization future private var _isInitialized: () => Boolean = null // argument is a thunk to execute after init is done def initialize(postInitSignal: => Unit): Unit = synchronized { if (_isInitialized == null) _isInitialized = scala.concurrent.ops future { val result = _initialize() postInitSignal result } } /** the public, go through the future compiler */ lazy val global: Global = { // If init hasn't been called yet you're on your own. if (_isInitialized == null) { repldbg("Warning: compiler accessed before init set up. Assuming no postInit code.") initialize(()) } // blocks until it is ; false means catastrophic failure if (_isInitialized()) _compiler else null } @deprecated("Use `global` for access to the compiler instance.", "2.9.0") lazy val compiler: global.type = global import global._ private implicit def privateTreeOps(t: Tree): List[Tree] = { (new Traversable[Tree] { def foreach[U](f: Tree => U): Unit = t foreach { x => f(x) ; () } }).toList } object naming extends { val global: imain.global.type = imain.global } with Naming { // make sure we don't overwrite their unwisely named res3 etc. override def freshUserVarName(): String = { val name = super.freshUserVarName() if (definedNameMap contains name) freshUserVarName() else name } } import naming._ // object dossiers extends { // val intp: imain.type = imain // } with Dossiers { } // import dossiers._ lazy val memberHandlers = new { val intp: imain.type = imain } with MemberHandlers import memberHandlers._ def atPickler[T](op: => T): T = atPhase(currentRun.picklerPhase)(op) def afterTyper[T](op: => T): T = atPhase(currentRun.typerPhase.next)(op) /** Temporarily be quiet */ def beQuietDuring[T](operation: => T): T = { val wasPrinting = printResults ultimately(printResults = wasPrinting) { if (isReplDebug) echo(">> beQuietDuring") else printResults = false operation } } def beSilentDuring[T](operation: => T): T = { val saved = totalSilence totalSilence = true try operation finally totalSilence = saved } def quietRun[T](code: String) = beQuietDuring(interpret(code)) /** whether to bind the lastException variable */ private var bindLastException = true /** A string representing code to be wrapped around all lines. */ private var _executionWrapper: String = "" def executionWrapper = _executionWrapper def setExecutionWrapper(code: String) = _executionWrapper = code def clearExecutionWrapper() = _executionWrapper = "" /** Temporarily stop binding lastException */ def withoutBindingLastException[T](operation: => T): T = { val wasBinding = bindLastException ultimately(bindLastException = wasBinding) { bindLastException = false operation } } protected def createLineManager(): Line.Manager = new Line.Manager lazy val lineManager = createLineManager() /** interpreter settings */ lazy val isettings = new ISettings(this) /** Instantiate a compiler. Subclasses can override this to * change the compiler class used by this interpreter. */ protected def newCompiler(settings: Settings, reporter: Reporter) = { settings.outputDirs setSingleOutput virtualDirectory settings.exposeEmptyPackage.value = true new Global(settings, reporter) } /** the compiler's classpath, as URL's */ lazy val compilerClasspath = global.classPath.asURLs /* 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 var _classLoader: AbstractFileClassLoader = null def resetClassLoader() = _classLoader = makeClassLoader() def classLoader: AbstractFileClassLoader = { if (_classLoader == null) resetClassLoader() _classLoader } private def makeClassLoader(): AbstractFileClassLoader = { val parent = if (parentClassLoader == null) ScalaClassLoader fromURLs compilerClasspath else new URLClassLoader(compilerClasspath, parentClassLoader) new AbstractFileClassLoader(virtualDirectory, parent) { /** Overridden here to try translating a simple name to the generated * class name if the original attempt fails. This method is used by * getResourceAsStream as well as findClass. */ override protected def findAbstractFile(name: String): AbstractFile = { super.findAbstractFile(name) match { // deadlocks on startup if we try to translate names too early case null if isInitializeComplete => generatedName(name) map (x => super.findAbstractFile(x)) orNull case file => file } } } } private def loadByName(s: String): JClass = (classLoader tryToInitializeClass s) getOrElse sys.error("Failed to load expected class: '" + s + "'") protected def parentClassLoader: ClassLoader = settings.explicitParentLoader.getOrElse( this.getClass.getClassLoader() ) def getInterpreterClassLoader() = classLoader // Set the current Java "context" class loader to this interpreter's class loader def setContextClassLoader() = classLoader.setAsContext() /** Given a simple repl-defined name, returns the real name of * the class representing it, e.g. for "Bippy" it may return * * $line19.$read$$iw$$iw$$iw$$iw$$iw$$iw$$iw$$iw$Bippy */ def generatedName(simpleName: String): Option[String] = { if (simpleName endsWith "$") optFlatName(simpleName.init) map (_ + "$") else optFlatName(simpleName) } def flatName(id: String) = optFlatName(id) getOrElse id def optFlatName(id: String) = requestForIdent(id) map (_ fullFlatName id) def allDefinedNames = definedNameMap.keys.toList sortBy (_.toString) def pathToType(id: String): String = pathToName(newTypeName(id)) def pathToTerm(id: String): String = pathToName(newTermName(id)) def pathToName(name: Name): String = { if (definedNameMap contains name) definedNameMap(name) fullPath name else name.toString } /** Most recent tree handled which wasn't wholly synthetic. */ private def mostRecentlyHandledTree: Option[Tree] = { prevRequests.reverse foreach { req => req.handlers.reverse foreach { case x: MemberDefHandler if x.definesValue && !isInternalVarName(x.name) => return Some(x.member) case _ => () } } None } /** Stubs for work in progress. */ def handleTypeRedefinition(name: TypeName, old: Request, req: Request) = { for (t1 <- old.simpleNameOfType(name) ; t2 <- req.simpleNameOfType(name)) { repldbg("Redefining type '%s'\n %s -> %s".format(name, t1, t2)) } } def handleTermRedefinition(name: TermName, old: Request, req: Request) = { for (t1 <- old.compilerTypeOf get name ; t2 <- req.compilerTypeOf get name) { // Printing the types here has a tendency to cause assertion errors, like // assertion failed: fatal: has owner value x, but a class owner is required // so DBG is by-name now to keep it in the family. (It also traps the assertion error, // but we don't want to unnecessarily risk hosing the compiler's internal state.) repldbg("Redefining term '%s'\n %s -> %s".format(name, t1, t2)) } } def recordRequest(req: Request) { if (req == null || referencedNameMap == null) return prevRequests += req req.referencedNames foreach (x => referencedNameMap(x) = req) // warning about serially defining companions. It'd be easy // enough to just redefine them together but that may not always // be what people want so I'm waiting until I can do it better. for { name <- req.definedNames filterNot (x => req.definedNames contains x.companionName) oldReq <- definedNameMap get name.companionName newSym <- req.definedSymbols get name oldSym <- oldReq.definedSymbols get name.companionName } { replwarn("warning: previously defined %s is not a companion to %s.".format(oldSym, newSym)) replwarn("Companions must be defined together; you may wish to use :paste mode for this.") } // Updating the defined name map req.definedNames foreach { name => if (definedNameMap contains name) { if (name.isTypeName) handleTypeRedefinition(name.toTypeName, definedNameMap(name), req) else handleTermRedefinition(name.toTermName, definedNameMap(name), req) } definedNameMap(name) = req } } private[nsc] def replwarn(msg: => String): Unit = if (!settings.nowarnings.value) printMessage(msg) def isParseable(line: String): Boolean = { beSilentDuring { try parse(line) match { case Some(xs) => xs.nonEmpty // parses as-is case None => true // incomplete } catch { case x: Exception => // crashed the compiler replwarn("Exception in isParseable(\"" + line + "\"): " + x) false } } } /** Compile an nsc SourceFile. Returns true if there are * no compilation errors, or false otherwise. */ def compileSources(sources: SourceFile*): Boolean = { reporter.reset() new Run() compileSources sources.toList !reporter.hasErrors } /** Compile a string. Returns true if there are no * compilation errors, or false otherwise. */ def compileString(code: String): Boolean = compileSources(new BatchSourceFile("