/* NSC -- new Scala compiler * Copyright 2005-2016 LAMP/EPFL * @author Martin Odersky */ package scala package tools.nsc package interpreter import PartialFunction.cond import scala.language.implicitConversions import scala.beans.BeanProperty import scala.collection.mutable import scala.concurrent.{ExecutionContext, Future} import scala.reflect.runtime.{universe => ru} import scala.reflect.{ClassTag, classTag} import scala.reflect.internal.util.{BatchSourceFile, SourceFile} import scala.tools.nsc.io.AbstractFile import scala.tools.nsc.typechecker.{StructuredTypeStrings, TypeStrings} import scala.tools.nsc.util._ import ScalaClassLoader.URLClassLoader import scala.tools.nsc.util.Exceptional.unwrap import java.net.URL import scala.tools.util.PathResolver /** 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 accommodate 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 with PresentationCompilation { imain => def this(initialSettings: Settings) = this(initialSettings, IMain.defaultOut) object replOutput extends ReplOutput(settings.Yreploutdir) { } @deprecated("Use replOutput.dir instead", "2.11.0") def virtualDirectory = replOutput.dir // Used in a test case. def showDirectory() = replOutput.show(out) lazy val isClassBased: Boolean = settings.Yreplclassbased.value 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 var partialInput: String = "" // code accumulated in multi-line REPL input private var label = "" // compilation unit name for reporting /** 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: util.AbstractFileClassLoader = null // active classloader private val _compiler: ReplGlobal = newCompiler(settings, reporter) // our private compiler private var _runtimeClassLoader: URLClassLoader = null // wrapper exposing addURL def compilerClasspath: Seq[java.net.URL] = ( if (isInitializeComplete) global.classPath.asURLs else new PathResolver(settings).resultAsURLs // the compiler's classpath ) def settings = initialSettings // Run the code body with the given boolean settings flipped to true. def withoutWarnings[T](body: => T): T = beQuietDuring { val saved = settings.nowarn.value if (!saved) settings.nowarn.value = true try body finally if (!saved) settings.nowarn.value = false } // Apply a temporary label for compilation (for example, script name) def withLabel[A](temp: String)(body: => A): A = { val saved = label label = temp try body finally label = saved } // the expanded prompt but without color escapes and without leading newline, for purposes of indenting lazy val formatting = Formatting.forPrompt(replProps.promptText) lazy val reporter: ReplReporter = new ReplReporter(this) import formatting.indentCode import reporter.{ printMessage, printUntruncatedMessage } // 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 { // if this crashes, REPL will hang its head in shame val run = new _compiler.Run() assert(run.typerPhase != NoPhase, "REPL requires a typer phase.") run compileSources _initSources _initializeComplete = true true } catch AbstractOrMissingHandler() } private val logScope = scala.sys.props contains "scala.repl.scope" private def scopelog(msg: String) = if (logScope) Console.err.println(msg) // argument is a thunk to execute after init is done def initialize(postInitSignal: => Unit) { synchronized { if (_isInitialized == null) { _isInitialized = Future(try _initialize() finally postInitSignal)(ExecutionContext.global) } } } def initializeSynchronous(): Unit = { if (!isInitializeComplete) { _initialize() assert(global != null, global) } } def isInitializeComplete = _initializeComplete lazy val global: Global = { if (!isInitializeComplete) _initialize() _compiler } import global._ import definitions.{ ObjectClass, termMember, dropNullaryMethod} lazy val runtimeMirror = ru.runtimeMirror(classLoader) private def noFatal(body: => Symbol): Symbol = try body catch { case _: FatalError => NoSymbol } def getClassIfDefined(path: String) = ( noFatal(runtimeMirror staticClass path) orElse noFatal(rootMirror staticClass path) ) def getModuleIfDefined(path: String) = ( noFatal(runtimeMirror staticModule path) orElse noFatal(rootMirror staticModule path) ) implicit class ReplTypeOps(tp: Type) { 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 (replScope containsName name) freshUserTermName() else 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: reporters.Reporter): ReplGlobal = { settings.outputDirs setSingleOutput replOutput.dir settings.exposeEmptyPackage.value = true new Global(settings, reporter) with ReplGlobal { override def toString: String = "" } } /** * Adds all specified jars to the compile and runtime classpaths. * * @note Currently only supports jars, not directories. * @param urls The list of items to add to the compile and runtime classpaths. */ def addUrlsToClassPath(urls: URL*): Unit = { new Run // force some initialization urls.foreach(_runtimeClassLoader.addURL) // Add jars to runtime classloader global.extendCompilerClassPath(urls: _*) // Add jars to compile-time classpath } /** Parent classloader. Overridable. */ protected def parentClassLoader: ClassLoader = { val replClassLoader = this.getClass.getClassLoader() // might be null if we're on the boot classpath settings.explicitParentLoader.orElse(Option(replClassLoader)).getOrElse(ClassLoader.getSystemClassLoader) } /* 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: util.AbstractFileClassLoader = { ensureClassLoader() _classLoader } def backticked(s: String): String = ( (s split '.').toList map { case "_" => "_" case s if nme.keywords(newTermName(s)) => s"`$s`" case s => s } mkString "." ) def readRootPath(readPath: String) = getModuleIfDefined(readPath) abstract class PhaseDependentOps { def shift[T](op: => T): T def path(name: => Name): String = shift(path(symbolOfName(name))) def path(sym: Symbol): String = backticked(shift(sym.fullName)) def sig(sym: Symbol): String = shift(sym.defString) } object typerOp extends PhaseDependentOps { def shift[T](op: => T): T = exitingTyper(op) } object flatOp extends PhaseDependentOps { def shift[T](op: => T): T = exitingFlatten(op) } def originalPath(name: String): String = originalPath(TermName(name)) def originalPath(name: Name): String = translateOriginalPath(typerOp path name) def originalPath(sym: Symbol): String = translateOriginalPath(typerOp path sym) /** For class based repl mode we use an .INSTANCE accessor. */ val readInstanceName = if(isClassBased) ".INSTANCE" else "" def translateOriginalPath(p: String): String = { val readName = java.util.regex.Matcher.quoteReplacement(sessionNames.read) p.replaceFirst(readName, readName + readInstanceName) } def flatPath(sym: Symbol): String = flatOp shift sym.javaClassName def translatePath(path: String) = { val sym = if (path endsWith "$") symbolOfTerm(path.init) else symbolOfIdent(path) sym.toOption map flatPath } /** If path represents a class resource in the default package, * see if the corresponding symbol has a class file that is a REPL artifact * residing at a different resource path. Translate X.class to $line3/$read$$iw$$iw$X.class. */ def translateSimpleResource(path: String): Option[String] = { if (!(path contains '/') && (path endsWith ".class")) { val name = path stripSuffix ".class" val sym = if (name endsWith "$") symbolOfTerm(name.init) else symbolOfIdent(name) def pathOf(s: String) = s"${s.replace('.', '/')}.class" sym.toOption map (s => pathOf(flatPath(s))) } else { None } } def translateEnclosingClass(n: String) = symbolOfTerm(n).enclClass.toOption map flatPath /** If unable to find a resource foo.class, try taking foo as a symbol in scope * and use its java class name as a resource to load. * * $intp.classLoader classBytes "Bippy" or $intp.classLoader getResource "Bippy.class" just work. */ private class TranslatingClassLoader(parent: ClassLoader) extends util.AbstractFileClassLoader(replOutput.dir, parent) { override protected def findAbstractFile(name: String): AbstractFile = super.findAbstractFile(name) match { case null if _initializeComplete => translateSimpleResource(name) map super.findAbstractFile orNull case file => file } } private def makeClassLoader(): util.AbstractFileClassLoader = new TranslatingClassLoader({ _runtimeClassLoader = new URLClassLoader(compilerClasspath, parentClassLoader) _runtimeClassLoader }) // Set the current Java "context" class loader to this interpreter's class loader def setContextClassLoader() = classLoader.setAsContext() def allDefinedNames: List[Name] = exitingTyper(replScope.toList.map(_.name).sorted) def unqualifiedIds: List[String] = allDefinedNames map (_.decode) sorted /** 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 } private def updateReplScope(sym: Symbol, isDefined: Boolean) { def log(what: String) { val mark = if (sym.isType) "t " else "v " val name = exitingTyper(sym.nameString) val info = cleanTypeAfterTyper(sym) val defn = sym defStringSeenAs info scopelog(f"[$mark$what%6s] $name%-25s $defn%s") } if (ObjectClass isSubClass sym.owner) return // unlink previous replScope lookupAll sym.name foreach { sym => log("unlink") replScope unlink sym } val what = if (isDefined) "define" else "import" log(what) replScope enter sym } def recordRequest(req: Request) { if (req == null) return prevRequests += 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. exitingTyper { req.defines filterNot (s => req.defines contains s.companionSymbol) foreach { newSym => val oldSym = replScope lookup newSym.name.companionName if (Seq(oldSym, newSym).permutations exists { case Seq(s1, s2) => s1.isClass && s2.isModule }) { replwarn(s"warning: previously defined $oldSym is not a companion to $newSym.") replwarn("Companions must be defined together; you may wish to use :paste mode for this.") } } } exitingTyper { req.imports foreach (sym => updateReplScope(sym, isDefined = false)) req.defines foreach (sym => updateReplScope(sym, isDefined = true)) } } private[nsc] def replwarn(msg: => String) { if (!settings.nowarnings) printMessage(msg) } def compileSourcesKeepingRun(sources: SourceFile*) = { val run = new Run() assert(run.typerPhase != NoPhase, "REPL requires a typer phase.") 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("