scala.tools.nsc.interpreter.IMain.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scala-compiler Show documentation
Show all versions of scala-compiler Show documentation
Compiler for the Scala Programming Language
The newest version!
/* NSC -- new Scala compiler
* Copyright 2005-2013 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.{ Future, ExecutionContext }
import scala.reflect.runtime.{ universe => ru }
import scala.reflect.{ ClassTag, classTag }
import scala.reflect.internal.util.{ BatchSourceFile, SourceFile }
import scala.tools.util.PathResolver
import scala.tools.nsc.io.AbstractFile
import scala.tools.nsc.typechecker.{ TypeStrings, StructuredTypeStrings }
import scala.tools.nsc.util.{ ScalaClassLoader, stringFromReader, stringFromWriter, StackTraceOps }
import scala.tools.nsc.util.Exceptional.unwrap
import javax.script.{AbstractScriptEngine, Bindings, ScriptContext, ScriptEngine, ScriptEngineFactory, ScriptException, CompiledScript, Compilable}
/** 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(@BeanProperty val factory: ScriptEngineFactory, initialSettings: Settings, protected val out: JPrintWriter) extends AbstractScriptEngine with Compilable with Imports {
imain =>
setBindings(createBindings, ScriptContext.ENGINE_SCOPE)
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)
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: util.AbstractFileClassLoader = null // active classloader
private val _compiler: ReplGlobal = newCompiler(settings, reporter) // our private compiler
def compilerClasspath: Seq[java.net.URL] = (
if (isInitializeComplete) global.classPath.asURLs
else new PathResolver(settings).result.asURLs // 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
}
/** construct an interpreter that reports to Console */
def this(settings: Settings, out: JPrintWriter) = this(null, settings, out)
def this(factory: ScriptEngineFactory, settings: Settings) = this(factory, settings, new NewLinePrintWriter(new ConsoleWriter, true))
def this(settings: Settings) = this(settings, new NewLinePrintWriter(new ConsoleWriter, true))
def this(factory: ScriptEngineFactory) = this(factory, new Settings())
def this() = this(new Settings())
lazy val formatting: Formatting = new Formatting {
val prompt = Properties.shellPromptString
}
lazy val reporter: ReplReporter = new ReplReporter(this)
import formatting._
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 def tquoted(s: String) = "\"\"\"" + s + "\"\"\""
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 = "" }
}
/** 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: 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(name: TermName)
def originalPath(name: Name): String = typerOp path name
def originalPath(sym: Symbol): String = typerOp path sym
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
}
def translateEnclosingClass(n: String) = symbolOfTerm(n).enclClass.toOption map flatPath
private class TranslatingClassLoader(parent: ClassLoader) extends util.AbstractFileClassLoader(replOutput.dir, 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 {
case null if _initializeComplete => translatePath(name) map (super.findAbstractFile(_)) orNull
case file => file
}
}
private def makeClassLoader(): util.AbstractFileClassLoader =
new TranslatingClassLoader(parentClassLoader match {
case null => ScalaClassLoader fromURLs compilerClasspath
case p => new ScalaClassLoader.URLClassLoader(compilerClasspath, p)
})
// 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("