diff options
author | Martin Odersky <odersky@gmail.com> | 2016-02-14 15:56:22 +0100 |
---|---|---|
committer | Martin Odersky <odersky@gmail.com> | 2016-02-17 18:39:35 +0100 |
commit | 4550b524771af0719846eb906795a5ba83106eb9 (patch) | |
tree | 39d008ec7e26d341beff0041d219c6f99fa35cda /src/dotty/tools/dotc/repl/CompilingInterpreter.scala | |
parent | 37b6df435f1595f8610ec6d5fbe16d079da2eff7 (diff) | |
download | dotty-4550b524771af0719846eb906795a5ba83106eb9.tar.gz dotty-4550b524771af0719846eb906795a5ba83106eb9.tar.bz2 dotty-4550b524771af0719846eb906795a5ba83106eb9.zip |
Revisions to REPL
Changes necessary to make basic REPL functionality work.
Major refactoing: Code of Interpreter is now in CompilingInterpreter.scala.
Interpreter.scala contains just the API.
Diffstat (limited to 'src/dotty/tools/dotc/repl/CompilingInterpreter.scala')
-rw-r--r-- | src/dotty/tools/dotc/repl/CompilingInterpreter.scala | 831 |
1 files changed, 831 insertions, 0 deletions
diff --git a/src/dotty/tools/dotc/repl/CompilingInterpreter.scala b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala new file mode 100644 index 000000000..f60b3c000 --- /dev/null +++ b/src/dotty/tools/dotc/repl/CompilingInterpreter.scala @@ -0,0 +1,831 @@ +package dotty.tools +package dotc +package repl + +import java.io.{File, PrintWriter, StringWriter, Writer} +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 + +/** 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._ + + /** 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 + + /** Temporarily be quiet */ + override def beQuietDuring[T](operation: => T): T = { + val wasPrinting = printResults + try { + printResults = false + operation + } finally { + printResults = wasPrinting + } + } + + /** interpreter settings */ + override val isettings = new InterpreterSettings + + private def newReporter = new ConsoleReporter(Console.in, out) { + override def printMessage(msg: String) = { + out.print(clean(msg) + "\n") + out.flush() + } + } + + /** 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(indentCode(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 (interpreterResultString, succeeded) = req.loadAndRun() + if (printResults || !succeeded) + out.print(clean(interpreterResultString)) + if (succeeded) { + prevRequests += req + Interpreter.Success + } + else Interpreter.Error + } + } + } + + /** A counter used for numbering objects created by <code>bind()</code>. */ + private var binderNum = 0 + + override def bind(name: String, boundType: String, value: Any)(implicit ctx: Context): Interpreter.Result = { + val binderName = "binder" + binderNum + binderNum += 1 + + compileString( + "object " + binderName + + "{ var value: " + boundType + " = _; " + + " def set(x: Any) = value=x.asInstanceOf[" + boundType + "]; }") + + val binderObject = + Class.forName(binderName, true, classLoader) + val setterMethod = + binderObject.getDeclaredMethods.find(_.getName == "set").get + var argsHolder: Array[Any] = null // this roundabout approach is to try and make sure the value is boxed + argsHolder = List(value).toArray + setterMethod.invoke(null, argsHolder.asInstanceOf[Array[AnyRef]]) + + interpret("val " + name + " = " + binderName + ".value") + } + + /** 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(indentCode(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 + } + + /** load and run the code using reflection. + * @return A pair consisting of the run's result as a string, and + * a boolean indicating whether the run succeeded without throwing + * an exception. + */ + def loadAndRun(): (String, Boolean) = { + val interpreterResultObject: Class[_] = + Class.forName(resultObjectName, true, classLoader) + val resultValMethod: java.lang.reflect.Method = + interpreterResultObject.getMethod("result") + try { + (resultValMethod.invoke(interpreterResultObject).toString, 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)), 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.toString + ";\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.toString + "\\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 + } + + /** 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): String = { + val maxpr = isettings.maxPrintString + + if (maxpr <= 0) + return str + + if (str.length <= maxpr) + return str + + val trailer = "..." + if (maxpr >= trailer.length+1) + return str.substring(0, maxpr-3) + trailer + + str.substring(0, maxpr) + } + + /** Clean up a string for output */ + private def clean(str: String) = str + // truncPrintString(stripWrapperGunk(str)) // TODO: enable + + /** Indent some code by the width of the scala> prompt. + * This way, compiler error messages read better. + */ + def indentCode(code: String) = { + val spaces = " " + + stringFrom(str => + for (line <- code.lines) { + str.print(spaces) + str.print(line + "\n") + str.flush() + }) + } +} + +/** 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 + } +} |