From 8a61ff432543a29234193cd1f7c14abd3f3d31a0 Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Wed, 2 Nov 2016 11:08:28 +0100 Subject: Move compiler and compiler tests to compiler dir --- .../tools/dotc/repl/AbstractFileClassLoader.scala | 31 + .../src/dotty/tools/dotc/repl/AmmoniteReader.scala | 82 ++ .../tools/dotc/repl/CompilingInterpreter.scala | 966 +++++++++++++++++++++ .../src/dotty/tools/dotc/repl/ConsoleWriter.scala | 21 + .../dotty/tools/dotc/repl/InteractiveReader.scala | 20 + .../src/dotty/tools/dotc/repl/Interpreter.scala | 45 + .../dotty/tools/dotc/repl/InterpreterLoop.scala | 210 +++++ compiler/src/dotty/tools/dotc/repl/Main.scala | 28 + .../src/dotty/tools/dotc/repl/ManifestInfo.scala | 20 + .../dotty/tools/dotc/repl/NewLinePrintWriter.scala | 11 + compiler/src/dotty/tools/dotc/repl/REPL.scala | 100 +++ .../src/dotty/tools/dotc/repl/SimpleReader.scala | 24 + .../src/dotty/tools/dotc/repl/ammonite/Ansi.scala | 256 ++++++ .../dotty/tools/dotc/repl/ammonite/Filter.scala | 61 ++ .../tools/dotc/repl/ammonite/FilterTools.scala | 80 ++ .../src/dotty/tools/dotc/repl/ammonite/LICENSE | 25 + .../dotty/tools/dotc/repl/ammonite/Protocol.scala | 30 + .../tools/dotc/repl/ammonite/SpecialKeys.scala | 81 ++ .../dotty/tools/dotc/repl/ammonite/Terminal.scala | 320 +++++++ .../src/dotty/tools/dotc/repl/ammonite/Utils.scala | 169 ++++ .../dotc/repl/ammonite/filters/BasicFilters.scala | 163 ++++ .../repl/ammonite/filters/GUILikeFilters.scala | 170 ++++ .../dotc/repl/ammonite/filters/HistoryFilter.scala | 334 +++++++ .../repl/ammonite/filters/ReadlineFilters.scala | 165 ++++ .../dotc/repl/ammonite/filters/UndoFilter.scala | 157 ++++ 25 files changed, 3569 insertions(+) create mode 100644 compiler/src/dotty/tools/dotc/repl/AbstractFileClassLoader.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/AmmoniteReader.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/CompilingInterpreter.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/ConsoleWriter.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/InteractiveReader.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/Interpreter.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/InterpreterLoop.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/Main.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/ManifestInfo.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/NewLinePrintWriter.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/REPL.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/SimpleReader.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/ammonite/Ansi.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/ammonite/Filter.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/ammonite/FilterTools.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/ammonite/LICENSE create mode 100644 compiler/src/dotty/tools/dotc/repl/ammonite/Protocol.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/ammonite/SpecialKeys.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/ammonite/Terminal.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/ammonite/Utils.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/ammonite/filters/BasicFilters.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/ammonite/filters/GUILikeFilters.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/ammonite/filters/HistoryFilter.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/ammonite/filters/ReadlineFilters.scala create mode 100644 compiler/src/dotty/tools/dotc/repl/ammonite/filters/UndoFilter.scala (limited to 'compiler/src/dotty/tools/dotc/repl') diff --git a/compiler/src/dotty/tools/dotc/repl/AbstractFileClassLoader.scala b/compiler/src/dotty/tools/dotc/repl/AbstractFileClassLoader.scala new file mode 100644 index 000000000..a3a463717 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/repl/AbstractFileClassLoader.scala @@ -0,0 +1,31 @@ +package dotty.tools +package dotc +package repl + +import io.AbstractFile + +/** + * A class loader that loads files from a {@link scala.tools.nsc.io.AbstractFile}. + * + * @author Lex Spoon + */ +class AbstractFileClassLoader(root: AbstractFile, parent: ClassLoader) +extends ClassLoader(parent) +{ + override def findClass(name: String): Class[_] = { + var file: AbstractFile = root + val pathParts = name.split("[./]").toList + for (dirPart <- pathParts.init) { + file = file.lookupName(dirPart, true) + if (file == null) { + throw new ClassNotFoundException(name) + } + } + file = file.lookupName(pathParts.last+".class", false) + if (file == null) { + throw new ClassNotFoundException(name) + } + val bytes = file.toByteArray + defineClass(name, bytes, 0, bytes.length) + } +} diff --git a/compiler/src/dotty/tools/dotc/repl/AmmoniteReader.scala b/compiler/src/dotty/tools/dotc/repl/AmmoniteReader.scala new file mode 100644 index 000000000..f3b68e4b0 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/repl/AmmoniteReader.scala @@ -0,0 +1,82 @@ +package dotty.tools +package dotc +package repl + +import core.Contexts._ +import ammonite.terminal._ +import LazyList._ +import Ansi.Color +import filters._ +import BasicFilters._ +import GUILikeFilters._ +import util.SourceFile +import printing.SyntaxHighlighting + +class AmmoniteReader(val interpreter: Interpreter)(implicit ctx: Context) extends InteractiveReader { + val interactive = true + + def incompleteInput(str: String): Boolean = + interpreter.delayOutputDuring(interpreter.interpret(str)) match { + case Interpreter.Incomplete => true + case _ => false + } + + val reader = new java.io.InputStreamReader(System.in) + val writer = new java.io.OutputStreamWriter(System.out) + val cutPasteFilter = ReadlineFilters.CutPasteFilter() + var history = List.empty[String] + val selectionFilter = GUILikeFilters.SelectionFilter(indent = 2) + val multilineFilter: Filter = Filter("multilineFilter") { + case TermState(lb ~: rest, b, c, _) + if (lb == 10 || lb == 13) && incompleteInput(b.mkString) => + BasicFilters.injectNewLine(b, c, rest, indent = 2) + } + + def readLine(prompt: String): String = { + val historyFilter = new HistoryFilter( + () => history.toVector, + Console.BLUE, + AnsiNav.resetForegroundColor + ) + + val allFilters = Filter.merge( + UndoFilter(), + historyFilter, + selectionFilter, + GUILikeFilters.altFilter, + GUILikeFilters.fnFilter, + ReadlineFilters.navFilter, + cutPasteFilter, + multilineFilter, + BasicFilters.all + ) + + Terminal.readLine( + Console.BLUE + prompt + Console.RESET, + reader, + writer, + allFilters, + displayTransform = (buffer, cursor) => { + val coloredBuffer = + if (ctx.useColors) SyntaxHighlighting(buffer) + else buffer + + val ansiBuffer = Ansi.Str.parse(coloredBuffer.toVector) + val (newBuffer, cursorOffset) = SelectionFilter.mangleBuffer( + selectionFilter, ansiBuffer, cursor, Ansi.Reversed.On + ) + val newNewBuffer = HistoryFilter.mangleBuffer( + historyFilter, newBuffer, cursor, + Ansi.Color.Green + ) + + (newNewBuffer, cursorOffset) + } + ) match { + case Some(res) => + history = res :: history; + res + case None => ":q" + } + } +} diff --git a/compiler/src/dotty/tools/dotc/repl/CompilingInterpreter.scala b/compiler/src/dotty/tools/dotc/repl/CompilingInterpreter.scala new file mode 100644 index 000000000..5b3669d5e --- /dev/null +++ b/compiler/src/dotty/tools/dotc/repl/CompilingInterpreter.scala @@ -0,0 +1,966 @@ +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, + 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: List[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("