/* NSC -- new Scala compiler * Copyright 2005-2011 LAMP/EPFL * @author Martin Odersky */ package scala.tools.nsc package interpreter import Predef.{ println => _, _ } import util.{ Set => _, _ } import java.net.URL import scala.sys.BooleanProp import io.VirtualDirectory import scala.tools.nsc.io.AbstractFile import reporters._ import symtab.Flags import scala.reflect.internal.Names import scala.tools.util.PathResolver import scala.tools.nsc.util.ScalaClassLoader import ScalaClassLoader.URLClassLoader import scala.tools.nsc.util.Exceptional.unwrap import scala.collection.{ mutable, immutable } import scala.util.control.Exception.{ ultimately } import IMain._ import java.util.concurrent.Future import typechecker.Analyzer import language.implicitConversions /** directory to save .class files to */ private class ReplVirtualDirectory(out: JPrintWriter) extends VirtualDirectory("(memory)", None) { private def pp(root: 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) } /** 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 members called "$eval" and "$print". 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(initialSettings: Settings, protected val out: JPrintWriter) extends Imports { imain => /** Leading with the eagerly evaluated. */ val virtualDirectory: VirtualDirectory = new ReplVirtualDirectory(out) // "directory" for classfiles private var currentSettings: Settings = initialSettings private[nsc] var printResults = true // whether to print result lines private[nsc] var totalSilence = false // whether to print anything private var _initializeComplete = false // compiler is initialized private var _isInitialized: Future[Boolean] = null // set up initialization future private var bindExceptions = true // whether to bind the lastException variable private var _executionWrapper = "" // code to be wrapped around all lines /** 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 var _classLoader: AbstractFileClassLoader = null // active classloader private val _compiler: Global = newCompiler(settings, reporter) // our private compiler private val nextReqId = { var counter = 0 () => { counter += 1 ; counter } } def compilerClasspath: Seq[URL] = ( if (isInitializeComplete) global.classPath.asURLs else new PathResolver(settings).result.asURLs // the compiler's classpath ) def settings = currentSettings def savingSettings[T](fn: Settings => Unit)(body: => T): T = { val saved = currentSettings currentSettings = saved.copy() fn(currentSettings) try body finally currentSettings = saved } def mostRecentLine = prevRequestList match { case Nil => "" case req :: _ => req.originalLine } def rerunWith(names: String*) = { savingSettings((ss: Settings) => { import ss._ names flatMap lookupSetting foreach { case s: BooleanSetting => s.value = true case _ => () } })(interpret(mostRecentLine)) } def rerunForWarnings = rerunWith("-deprecation", "-unchecked", "-Xlint") /** construct an interpreter that reports to Console */ def this(settings: Settings) = this(settings, new NewLinePrintWriter(new ConsoleWriter, true)) def this() = this(new Settings()) lazy val repllog: Logger = new Logger { val out: JPrintWriter = imain.out val isInfo: Boolean = BooleanProp keyExists "scala.repl.info" val isDebug: Boolean = BooleanProp keyExists "scala.repl.debug" val isTrace: Boolean = BooleanProp keyExists "scala.repl.trace" } lazy val formatting: Formatting = new Formatting { val prompt = Properties.shellPromptString } lazy val reporter: ReplReporter = new ReplReporter(this) import formatting._ import reporter.{ printMessage, withoutTruncating } // This exists mostly because using the reporter too early leads to deadlock. private def echo(msg: String) { Console println msg } private def _initSources = List(new BatchSourceFile("", "class $repl_$init { }")) private def _initialize() = { try { // [Eugene] todo. if this crashes, REPL will hang new _compiler.Run() compileSources _initSources _initializeComplete = true true } catch AbstractOrMissingHandler() } private def tquoted(s: String) = "\"\"\"" + s + "\"\"\"" // argument is a thunk to execute after init is done def initialize(postInitSignal: => Unit) { synchronized { if (_isInitialized == null) { _isInitialized = io.spawn { try _initialize() finally postInitSignal } } } } def initializeSynchronous(): Unit = { if (!isInitializeComplete) { _initialize() assert(global != null, global) } } def isInitializeComplete = _initializeComplete /** the public, go through the future compiler */ lazy val global: Global = { if (isInitializeComplete) _compiler else { // 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.get()) _compiler else null } } @deprecated("Use `global` for access to the compiler instance.", "2.9.0") lazy val compiler: global.type = global import global._ import definitions.{ ScalaPackage, JavaLangPackage, RootClass, getClassIfDefined, getModuleIfDefined, getRequiredModule, getRequiredClass, termMember, typeMember } private implicit def privateTreeOps(t: Tree): List[Tree] = { (new Traversable[Tree] { def foreach[U](f: Tree => U): Unit = t foreach { x => f(x) ; () } }).toList } implicit class ReplTypeOps(tp: Type) { def orElse(other: => Type): Type = if (tp ne NoType) tp else other def andAlso(fn: Type => Type): Type = if (tp eq NoType) tp else fn(tp) } // TODO: If we try to make naming a lazy val, we run into big time // scalac unhappiness with what look like cycles. It has not been easy to // reduce, but name resolution clearly takes different paths. object naming extends { val global: imain.global.type = imain.global } with Naming { // make sure we don't overwrite their unwisely named res3 etc. def freshUserTermName(): TermName = { val name = newTermName(freshUserVarName()) if (definedNameMap contains name) freshUserTermName() else name } def isUserTermName(name: Name) = isUserVarName("" + name) def isInternalTermName(name: Name) = isInternalVarName("" + name) } import naming._ object deconstruct extends { val global: imain.global.type = imain.global } with StructuredTypeStrings lazy val memberHandlers = new { val intp: imain.type = imain } with MemberHandlers import memberHandlers._ /** Temporarily be quiet */ def beQuietDuring[T](body: => T): T = { val saved = printResults printResults = false try body finally printResults = saved } def beSilentDuring[T](operation: => T): T = { val saved = totalSilence totalSilence = true try operation finally totalSilence = saved } def quietRun[T](code: String) = beQuietDuring(interpret(code)) /** takes AnyRef because it may be binding a Throwable or an Exceptional */ private def withLastExceptionLock[T](body: => T, alt: => T): T = { assert(bindExceptions, "withLastExceptionLock called incorrectly.") bindExceptions = false try beQuietDuring(body) catch logAndDiscard("withLastExceptionLock", alt) finally bindExceptions = true } def executionWrapper = _executionWrapper def setExecutionWrapper(code: String) = _executionWrapper = code def clearExecutionWrapper() = _executionWrapper = "" /** interpreter settings */ lazy val isettings = new ISettings(this) /** Instantiate a compiler. Overridable. */ protected def newCompiler(settings: Settings, reporter: Reporter): ReplGlobal = { settings.outputDirs setSingleOutput virtualDirectory settings.exposeEmptyPackage.value = true new Global(settings, reporter) with ReplGlobal } /** Parent classloader. Overridable. */ protected def parentClassLoader: ClassLoader = settings.explicitParentLoader.getOrElse( this.getClass.getClassLoader() ) /* 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. */ def resetClassLoader() = { repldbg("Setting new classloader: was " + _classLoader) _classLoader = null ensureClassLoader() } final def ensureClassLoader() { if (_classLoader == null) _classLoader = makeClassLoader() } def classLoader: AbstractFileClassLoader = { ensureClassLoader() _classLoader } private class TranslatingClassLoader(parent: ClassLoader) extends 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 makeClassLoader(): AbstractFileClassLoader = new TranslatingClassLoader(parentClassLoader match { case null => ScalaClassLoader fromURLs compilerClasspath case p => new URLClassLoader(compilerClasspath, p) }) def getInterpreterClassLoader() = classLoader // Set the current Java "context" class loader to this interpreter's class loader def setContextClassLoader() = { classLoader.setAsContext() // this is risky, but it's our only possibility to make default reflexive mirror to work with REPL // so far we have only used the default mirror to create a few tags for the compiler // so it shouldn't be in conflict with our classloader, especially since it respects its parent scala.reflect.mirror.classLoader = classLoader } /** 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 nme.MODULE_SUFFIX_STRING) optFlatName(simpleName.init) map (_ + nme.MODULE_SUFFIX_STRING) 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.sorted 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 && !isInternalTermName(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( stripString("" + oldSym), stripString("" + 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) { 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 } } } def compileSourcesKeepingRun(sources: SourceFile*) = { val run = new Run() reporter.reset() run compileSources sources.toList (!reporter.hasErrors, run) } /** Compile an nsc SourceFile. Returns true if there are * no compilation errors, or false otherwise. */ def compileSources(sources: SourceFile*): Boolean = compileSourcesKeepingRun(sources: _*)._1 /** Compile a string. Returns true if there are no * compilation errors, or false otherwise. */ def compileString(code: String): Boolean = compileSources(new BatchSourceFile("