package dotty.tools package dotc package repl import java.io.{ File, PrintWriter, PrintStream, StringWriter, Writer, OutputStream, ByteArrayOutputStream => ByteOutputStream } import java.lang.{Class, ClassLoader, Thread, System, StringBuffer} 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, parentClassLoader: Option[ClassLoader] ) 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 val previousOutput = ListBuffer.empty[String] override def lastOutput() = { val prev = previousOutput.toList previousOutput.clear() 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") } } /** the previous requests this interpreter has processed */ private val prevRequests = new ArrayBuffer[Request]() /** the compiler's classpath, as URL's */ val compilerClasspath: Seq[URL] = ictx.platform.classPath(ictx).asURLs /* 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 = { lazy val parent = new URLClassLoader(compilerClasspath.toArray, classOf[Interpreter].getClassLoader) new AbstractFileClassLoader(virtualDirectory, parentClassLoader.getOrElse(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("", 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("