package dotty.tools
package dotc
package repl
import java.io.{
File, PrintWriter, PrintStream, StringWriter, Writer, OutputStream,
ByteArrayOutputStream => ByteOutputStream
}
import java.lang.{Class, ClassLoader}
import java.net.{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 scala.reflect.io.{PlainDirectory, Directory}
import reporting.{ConsoleReporter, Reporter}
import core.Flags
import util.{SourceFile, NameTransformer}
import io.ClassPath
import ast.Trees._
import parsing.Parsers._
import core._
import dotty.tools.backend.jvm.GenBCode
import Symbols._, Types._, Contexts._, StdNames._, Names._, NameOps._
import Decorators._
import scala.util.control.NonFatal
import printing.SyntaxHighlighting
/** An interpreter for Scala code which is based on the `dotc` compiler.
*
* 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 definition(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 definition named "result". 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
* @author Martin Odersky
*
* @param out The output to use for diagnostics
* @param ictx The context to use for initialization of the interpreter,
* needed to access the current classpath.
*/
class CompilingInterpreter(out: PrintWriter, ictx: Context) extends Compiler with Interpreter {
import ast.untpd._
import CompilingInterpreter._
ictx.base.initialize()(ictx)
/** directory to save .class files to */
val virtualDirectory =
if (ictx.settings.d.isDefault(ictx)) new VirtualDirectory("(memory)", None)
else new PlainDirectory(new Directory(new java.io.File(ictx.settings.d.value(ictx)))) // for now, to help debugging
/** A GenBCode phase that uses `virtualDirectory` for its output */
private class REPLGenBCode extends GenBCode {
override def outputDir(implicit ctx: Context) = virtualDirectory
}
/** Phases of this compiler use `REPLGenBCode` instead of `GenBCode`. */
override def phases = Phases.replace(
classOf[GenBCode], _ => new REPLGenBCode :: Nil, super.phases)
/** whether to print out result lines */
private var printResults: Boolean = true
private var delayOutput: Boolean = false
var previousOutput: List[String] = Nil
override def lastOutput() = {
val prev = previousOutput
previousOutput = Nil
prev
}
override def delayOutputDuring[T](operation: => T): T = {
val old = delayOutput
try {
delayOutput = true
operation
} finally {
delayOutput = old
}
}
/** Temporarily be quiet */
override def beQuietDuring[T](operation: => T): T = {
val wasPrinting = printResults
try {
printResults = false
operation
} finally {
printResults = wasPrinting
}
}
private def newReporter = new ConsoleReporter(Console.in, out) {
override def printMessage(msg: String) = {
if (!delayOutput) {
out.print(/*clean*/(msg) + "\n")
// Suppress clean for now for compiler messages
// Otherwise we will completely delete all references to
// line$object$ module classes. The previous interpreter did not
// have the project because the module class was written without the final `$'
// and therefore escaped the purge. We can turn this back on once
// we drop the final `$' from module classes.
out.flush()
} else {
previousOutput = (/*clean*/(msg) + "\n") :: previousOutput
}
}
}
/** the previous requests this interpreter has processed */
private val prevRequests = new ArrayBuffer[Request]()
/** the compiler's classpath, as URL's */
val compilerClasspath: List[URL] = ictx.platform.classPath(ictx).asURLs
protected def parentClassLoader: ClassLoader = classOf[Interpreter].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.
*/
/** class loader used to load compiled code */
val classLoader: ClassLoader = {
val parent = new URLClassLoader(compilerClasspath.toArray, parentClassLoader)
new AbstractFileClassLoader(virtualDirectory, parent)
}
// Set the current Java "context" class loader to this interpreter's class loader
Thread.currentThread.setContextClassLoader(classLoader)
/** Parse a line into a sequence of trees. Returns None if the input is incomplete. */
private def parse(line: String)(implicit ctx: Context): Option[List[Tree]] = {
var justNeedsMore = false
val reporter = newReporter
reporter.withIncompleteHandler { _ => _ => justNeedsMore = true } {
// simple parse: just parse it, nothing else
def simpleParse(code: String)(implicit ctx: Context): List[Tree] = {
val source = new SourceFile("<console>", code.toCharArray())
val parser = new Parser(source)
val (selfDef, stats) = parser.templateStatSeq
stats
}
val trees = simpleParse(line)(ctx.fresh.setReporter(reporter))
if (reporter.hasErrors) {
Some(Nil) // the result did not parse, so stop
} else if (justNeedsMore) {
None
} else {
Some(trees)
}
}
}
/** Compile a SourceFile. Returns the root context of the run that compiled the file.
*/
def compileSources(sources: List[SourceFile])(implicit ctx: Context): Context = {
val reporter = newReporter
val run = newRun(ctx.fresh.setReporter(reporter))
run.compileSources(sources)
run.runContext
}
/** Compile a string. Returns true if there are no
* compilation errors, or false otherwise.
*/
def compileString(code: String)(implicit ctx: Context): Boolean = {
val runCtx = compileSources(List(new SourceFile("<script>", code.toCharArray)))
!runCtx.reporter.hasErrors
}
override def interpret(line: String)(implicit ctx: Context): Interpreter.Result = {
// if (prevRequests.isEmpty)
// new Run(this) // initialize the compiler // (not sure this is needed)
// parse
parse(line) match {
case None => Interpreter.Incomplete
case Some(Nil) => Interpreter.Error // parse error or empty input
case Some(tree :: Nil) if tree.isTerm && !tree.isInstanceOf[Assign] =>
interpret(s"val $newVarName =\n$line")
case Some(trees) =>
val req = new Request(line, newLineName)
if (!req.compile())
Interpreter.Error // an error happened during compilation, e.g. a type error
else {
val (resultStrings, succeeded) = req.loadAndRun()
if (delayOutput)
previousOutput = resultStrings.map(clean) ::: previousOutput
else if (printResults || !succeeded)
resultStrings.map(x => out.print(clean(x)))
if (succeeded) {
prevRequests += req
Interpreter.Success
}
else Interpreter.Error
}
}
}
private def loadAndSetValue(objectName: String, value: AnyRef) = {
/** This terrible string is the wrapped class's full name inside the
* classloader:
* lineX$object$$iw$$iw$list$object
*/
val objName: String = List(
currentLineName + INTERPRETER_WRAPPER_SUFFIX,
INTERPRETER_IMPORT_WRAPPER,
INTERPRETER_IMPORT_WRAPPER,
objectName
).mkString("$")
try {
val resObj: Class[_] = Class.forName(objName, true, classLoader)
val setMethod = resObj.getDeclaredMethods.find(_.getName == "set")
setMethod.fold(false) { method =>
method.invoke(resObj, value) == null
}
} catch {
case NonFatal(_) =>
// Unable to set value on object due to exception during reflection
false
}
}
/** This bind is implemented by creating an object with a set method and a
* field `value`. The value is then set via Java reflection.
*
* Example: We want to bind a value `List(1,2,3)` to identifier `list` from
* sbt. The bind method accomplishes this by creating the following:
* {{{
* object ContainerObjectWithUniqueID {
* var value: List[Int] = _
* def set(x: Any) = value = x.asInstanceOf[List[Int]]
* }
* val list = ContainerObjectWithUniqueID.value
* }}}
*
* Between the object being created and the value being assigned, the value
* inside the object is set via reflection.
*/
override def bind(id: String, boundType: String, value: AnyRef)(implicit ctx: Context): Interpreter.Result =
interpret(
"""
|object %s {
| var value: %s = _
| def set(x: Any) = value = x.asInstanceOf[%s]
|}
""".stripMargin.format(id + INTERPRETER_WRAPPER_SUFFIX, boundType, boundType)
) match {
case Interpreter.Success if loadAndSetValue(id + INTERPRETER_WRAPPER_SUFFIX, value) =>
val line = "val %s = %s.value".format(id, id + INTERPRETER_WRAPPER_SUFFIX)
interpret(line)
case Interpreter.Error | Interpreter.Incomplete =>
out.println("Set failed in bind(%s, %s, %s)".format(id, boundType, value))
Interpreter.Error
}
/** Trait collecting info about one of the statements of an interpreter request */
private trait StatementInfo {
/** The statement */
def statement: Tree
/** The names defined previously and referred to in the statement */
def usedNames: List[Name]
/** The names defined in the statement */
val boundNames: List[Name]
/** Statement is an import that contains a wildcard */
val importsWildcard: Boolean
/** The names imported by the statement (if it is an import clause) */
val importedNames: Seq[Name]
/** Statement defines an implicit calue or method */
val definesImplicit: Boolean
}
/** One line of code submitted by the user for interpretation */
private class Request(val line: String, val lineName: String)(implicit ctx: Context) {
private val trees = parse(line) match {
case Some(ts) => ts
case None => Nil
}
/** name to use for the object that will compute "line" */
private def objectName = lineName + INTERPRETER_WRAPPER_SUFFIX
/** name of the object that retrieves the result from the above object */
private def resultObjectName = "RequestResult$" + objectName
private def chooseHandler(stat: Tree): StatementHandler = stat match {
case stat: DefDef => new DefHandler(stat)
case stat: ValDef => new ValHandler(stat)
case stat @ Assign(Ident(_), _) => new AssignHandler(stat)
case stat: ModuleDef => new ModuleHandler(stat)
case stat: TypeDef if stat.isClassDef => new ClassHandler(stat)
case stat: TypeDef => new TypeAliasHandler(stat)
case stat: Import => new ImportHandler(stat)
// case DocDef(_, documented) => chooseHandler(documented)
case stat => new GenericHandler(stat)
}
private val handlers: List[StatementHandler] = trees.map(chooseHandler)
/** all (public) names defined by these statements */
private val boundNames = ListSet(handlers.flatMap(_.boundNames): _*).toList
/** list of names used by this expression */
private val usedNames: List[Name] = handlers.flatMap(_.usedNames)
private val (importsPreamble, importsTrailer, accessPath) =
importsCode(usedNames.toSet)
/** Code to access a variable with the specified name */
private def fullPath(vname: String): String = s"$objectName$accessPath.`$vname`"
/** Code to access a variable with the specified name */
private def fullPath(vname: Name): String = fullPath(vname.toString)
/** the line of code to compute */
private def toCompute = line
/** generate the source code for the object that computes this request
* TODO Reformulate in a functional way
*/
private def objectSourceCode: String =
stringFrom { code =>
// header for the wrapper object
code.println("object " + objectName + " {")
code.print(importsPreamble)
code.println(toCompute)
handlers.foreach(_.extraCodeToEvaluate(this,code))
code.println(importsTrailer)
//end the wrapper object
code.println(";}")
}
/** Types of variables defined by this request. They are computed
after compilation of the main object */
private var typeOf: Map[Name, String] = _
/** generate source code for the object that retrieves the result
from objectSourceCode */
private def resultObjectSourceCode: String =
stringFrom(code => {
code.println("object " + resultObjectName)
code.println("{ val result: String = {")
code.println(objectName + accessPath + ";") // evaluate the object, to make sure its constructor is run
code.print("(\"\"") // print an initial empty string, so later code can
// uniformly be: + morestuff
handlers.foreach(_.resultExtractionCode(this, code))
code.println("\n)}")
code.println(";}")
})
/** Compile the object file. Returns whether the compilation succeeded.
* If all goes well, the "types" map is computed. */
def compile(): Boolean = {
val compileCtx = compileSources(
List(new SourceFile("<console>", objectSourceCode.toCharArray)))
!compileCtx.reporter.hasErrors && {
this.typeOf = findTypes(compileCtx)
val resultCtx = compileSources(
List(new SourceFile("<console>", resultObjectSourceCode.toCharArray)))
!resultCtx.reporter.hasErrors
}
}
/** Dig the types of all bound variables out of the compiler run.
* TODO: Change the interface so that we typecheck, and then transform
* directly. Treating the compiler as less of a blackbox will require
* much less magic here.
*/
private def findTypes(implicit ctx: Context): Map[Name, String] = {
def valAndVarNames = handlers.flatMap(_.valAndVarNames)
def defNames = handlers.flatMap(_.defNames)
def getTypes(names: List[Name], nameMap: Name => Name): Map[Name, String] = {
/** the outermost wrapper object */
val outerResObjSym: Symbol =
defn.EmptyPackageClass.info.decl(objectName.toTermName).symbol
/** the innermost object inside the wrapper, found by
* following accessPath into the outer one. */
val resObjSym =
(accessPath.split("\\.")).foldLeft(outerResObjSym) { (sym,str) =>
if (str.isEmpty) sym
else
ctx.atPhase(ctx.typerPhase.next) { implicit ctx =>
sym.info.member(str.toTermName).symbol
}
}
names.foldLeft(Map.empty[Name,String]) { (map, name) =>
val rawType =
ctx.atPhase(ctx.typerPhase.next) { implicit ctx =>
resObjSym.info.member(name).info
}
// the types are all =>T; remove the =>
val cleanedType = rawType.widenExpr
map + (name ->
ctx.atPhase(ctx.typerPhase.next) { implicit ctx =>
cleanedType.show
})
}
}
val names1 = getTypes(valAndVarNames, n => n.toTermName.fieldName)
val names2 = getTypes(defNames, identity)
names1 ++ names2
}
/** Sets both System.{out,err} and Console.{out,err} to supplied
* `os: OutputStream`
*/
private def withOutput[T](os: ByteOutputStream)(op: ByteOutputStream => T) = {
val ps = new PrintStream(os)
val oldOut = System.out
val oldErr = System.err
System.setOut(ps)
System.setErr(ps)
try {
Console.withOut(os)(Console.withErr(os)(op(os)))
} finally {
System.setOut(oldOut)
System.setErr(oldErr)
}
}
/** load and run the code using reflection.
* @return A pair consisting of the run's result as a `List[String]`, and
* a boolean indicating whether the run succeeded without throwing
* an exception.
*/
def loadAndRun(): (List[String], Boolean) = {
val interpreterResultObject: Class[_] =
Class.forName(resultObjectName, true, classLoader)
val valMethodRes: java.lang.reflect.Method =
interpreterResultObject.getMethod("result")
try {
withOutput(new ByteOutputStream) { ps =>
val rawRes = valMethodRes.invoke(interpreterResultObject).toString
val res =
if (ictx.useColors) new String(SyntaxHighlighting(rawRes).toArray)
else rawRes
val prints = ps.toString("utf-8")
val printList = if (prints != "") prints :: Nil else Nil
if (!delayOutput) out.print(prints)
(printList :+ res, true)
}
} catch {
case NonFatal(ex) =>
def cause(ex: Throwable): Throwable =
if (ex.getCause eq null) ex else cause(ex.getCause)
val orig = cause(ex)
(stringFrom(str => orig.printStackTrace(str)) :: Nil, false)
}
}
/** Compute imports that allow definitions from previous
* requests to be visible in a new request. Returns
* three pieces of related code as strings:
*
* 1. A _preamble_: An initial code fragment that should go before
* the code of the new request.
*
* 2. A _trailer_: A code fragment that should go after the code
* of the new request.
*
* 3. An _access path_ which can be traversed 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, StatementInfo)] = {
/** Loop through a list of StatementHandlers and select
* which ones to keep. 'wanted' is the set of
* names that need to be imported.
*/
def select(reqs: List[(Request, StatementInfo)], wanted: Set[Name]): List[(Request, StatementInfo)] = {
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 preamble = new StringBuffer
val trailingBraces = new StringBuffer
val accessPath = new StringBuffer
val impname = INTERPRETER_IMPORT_WRAPPER
val currentImps = mutable.Set[Name]()
// add code for a new object to hold some imports
def addWrapper(): Unit = {
preamble.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.statement.isInstanceOf[Import])
preamble.append(handler.statement.show + ";\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()
preamble.append("import ")
preamble.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.
(preamble.toString, trailingBraces.toString, accessPath.toString)
}
// ------ Handlers ------------------------------------------
/** Class to handle one statement among all the statements included
* in a single interpreter request.
*/
private sealed abstract class StatementHandler(val statement: Tree) extends StatementInfo {
val usedNames: List[Name] = {
val ivt = new UntypedTreeAccumulator[mutable.Set[Name]] {
override def apply(ns: mutable.Set[Name], tree: Tree)(implicit ctx: Context) =
tree match {
case Ident(name) => ns += name
case _ => foldOver(ns, tree)
}
}
ivt.foldOver(HashSet(), statement).toList
}
val boundNames: List[Name] = Nil
def valAndVarNames: List[Name] = Nil
def defNames: List[Name] = Nil
val importsWildcard = false
val importedNames: Seq[Name] = Nil
val definesImplicit = statement match {
case tree: MemberDef => tree.mods.is(Flags.Implicit)
case _ => false
}
def extraCodeToEvaluate(req: Request, code: PrintWriter) = {}
def resultExtractionCode(req: Request, code: PrintWriter) = {}
}
private class GenericHandler(statement: Tree) extends StatementHandler(statement)
private class ValHandler(statement: ValDef) extends StatementHandler(statement) {
override val boundNames = List(statement.name)
override def valAndVarNames = boundNames
override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
val vname = statement.name
if (!statement.mods.is(Flags.AccessFlags) &&
!(isGeneratedVarName(vname.toString) &&
req.typeOf(vname.encode) == "Unit")) {
val prettyName = vname.decode
code.print(" + \"" + prettyName + ": " +
string2code(req.typeOf(vname)) +
" = \" + " +
" (if(" +
req.fullPath(vname) +
".asInstanceOf[AnyRef] != null) " +
" ((if(" +
req.fullPath(vname) +
".toString().contains('\\n')) " +
" \"\\n\" else \"\") + " +
req.fullPath(vname) + ".toString() + \"\\n\") else \"null\\n\") ")
}
}
}
private class DefHandler(defDef: DefDef) extends StatementHandler(defDef) {
override val boundNames = List(defDef.name)
override def defNames = boundNames
override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
if (!defDef.mods.is(Flags.AccessFlags))
code.print("+\"" + string2code(defDef.name.toString) + ": " +
string2code(req.typeOf(defDef.name)) + "\\n\"")
}
}
private class AssignHandler(statement: Assign) extends StatementHandler(statement) {
val lhs = statement.lhs.asInstanceOf[Ident] // an unfortunate limitation
val helperName = newInternalVarName().toTermName
override val valAndVarNames = List(helperName)
override def extraCodeToEvaluate(req: Request, code: PrintWriter): Unit = {
code.println("val " + helperName + " = " + statement.lhs + ";")
}
/** Print out lhs instead of the generated varName */
override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
code.print(" + \"" + lhs + ": " +
string2code(req.typeOf(helperName.encode)) +
" = \" + " +
string2code(req.fullPath(helperName))
+ " + \"\\n\"")
}
}
private class ModuleHandler(module: ModuleDef) extends StatementHandler(module) {
override val boundNames = List(module.name)
override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
code.println(" + \"defined module " +
string2code(module.name.toString)
+ "\\n\"")
}
}
private class ClassHandler(classdef: TypeDef)
extends StatementHandler(classdef) {
override val boundNames =
List(classdef.name) :::
(if (classdef.mods.is(Flags.Case))
List(classdef.name.toTermName)
else
Nil)
// TODO: MemberDef.keyword does not include "trait";
// otherwise it could be used here
def keyword: String =
if (classdef.mods.is(Flags.Trait)) "trait" else "class"
override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
code.print(
" + \"defined " +
keyword +
" " +
string2code(classdef.name.toString) +
"\\n\"")
}
}
private class TypeAliasHandler(typeDef: TypeDef)
extends StatementHandler(typeDef) {
override val boundNames =
if (!typeDef.mods.is(Flags.AccessFlags) && !typeDef.rhs.isInstanceOf[TypeBoundsTree])
List(typeDef.name)
else
Nil
override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
code.println(" + \"defined type alias " +
string2code(typeDef.name.toString) + "\\n\"")
}
}
private class ImportHandler(imp: Import) extends StatementHandler(imp) {
override def resultExtractionCode(req: Request, code: PrintWriter): Unit = {
code.println("+ \"" + imp.show + "\\n\"")
}
def isWildcardSelector(tree: Tree) = tree match {
case Ident(nme.USCOREkw) => true
case _ => false
}
/** Whether this import includes a wildcard import */
override val importsWildcard = imp.selectors.exists(isWildcardSelector)
/** The individual names imported by this statement */
override val importedNames: Seq[Name] =
imp.selectors.filterNot(isWildcardSelector).flatMap {
case sel: RefTree => List(sel.name.toTypeName, sel.name.toTermName)
case _ => Nil
}
}
} // end Request
// ------- String handling ----------------------------------
/** next line number to use */
private var nextLineNo = 0
/** allocate a fresh line name */
private def newLineName = {
val num = nextLineNo
nextLineNo += 1
INTERPRETER_LINE_PREFIX + num
}
private def currentLineName =
INTERPRETER_LINE_PREFIX + (nextLineNo - 1)
/** 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)(implicit ctx: Context): String = {
val maxpr = ctx.settings.XreplLineWidth.value
if (maxpr <= 0)
return str
if (str.length <= maxpr)
return str
val trailer = "..."
if (maxpr >= trailer.length-1)
str.substring(0, maxpr-3) + trailer + "\n"
else
str.substring(0, maxpr-1)
}
/** Clean up a string for output */
private def clean(str: String)(implicit ctx: Context) =
truncPrintString(stripWrapperGunk(str))
}
/** Utility methods for the Interpreter. */
object CompilingInterpreter {
val INTERPRETER_WRAPPER_SUFFIX = "$object"
val INTERPRETER_LINE_PREFIX = "line"
val INTERPRETER_VAR_PREFIX = "res"
val INTERPRETER_IMPORT_WRAPPER = "$iw"
val INTERPRETER_SYNTHVAR_PREFIX = "synthvar$"
/** Delete a directory tree recursively. Use with care!
*/
private[repl] def deleteRecursively(path: File): Unit = {
path match {
case _ if !path.exists =>
()
case _ if path.isDirectory =>
for (p <- path.listFiles)
deleteRecursively(p)
path.delete
case _ =>
path.delete
}
}
/** Heuristically strip interpreter wrapper prefixes
* from an interpreter output string.
*/
def stripWrapperGunk(str: String): String = {
val wrapregex = "(line[0-9]+\\$object[$.])?(\\$iw[$.])*"
str.replaceAll(wrapregex, "")
}
/** Convert a string into code that can recreate the string.
* This requires replacing all special characters by escape
* codes. It does not add the surrounding " marks. */
def string2code(str: String): String = {
/** Convert a character to a backslash-u escape */
def char2uescape(c: Char): String = {
var rest = c.toInt
val buf = new StringBuilder
for (i <- 1 to 4) {
buf ++= (rest % 16).toHexString
rest = rest / 16
}
"\\" + "u" + buf.toString.reverse
}
val res = new StringBuilder
for (c <- str) {
if ("'\"\\" contains c) {
res += '\\'
res += c
} else if (!c.isControl) {
res += c
} else {
res ++= char2uescape(c)
}
}
res.toString
}
}