From d1877e1ff89b318d16ad0637bcd923e540080140 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 13 Feb 2016 22:29:32 +0100 Subject: [PATCH 01/18] Make output directory overridable The interpreter needs to install a virtual directory as output directory. This is not supported with the -d option in ScalaSettings. The solution is to make the output directory overridable in the GenBCode phase. --- src/dotty/tools/backend/jvm/DottyBackendInterface.scala | 4 ++-- src/dotty/tools/backend/jvm/GenBCode.scala | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index b6adba85a28b..5776cc8e234f 100644 --- a/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -32,7 +32,7 @@ import NameOps._ import StdNames.nme import NameOps._ -class DottyBackendInterface()(implicit ctx: Context) extends BackendInterface{ +class DottyBackendInterface(outputDirectory: AbstractFile)(implicit ctx: Context) extends BackendInterface{ type Symbol = Symbols.Symbol type Type = Types.Type type Tree = tpd.Tree @@ -734,7 +734,7 @@ class DottyBackendInterface()(implicit ctx: Context) extends BackendInterface{ def setter(clz: Symbol): Symbol = decorateSymbol(sym).setter def moduleSuffix: String = "" // todo: validate that names already have $ suffix - def outputDirectory: AbstractFile = new PlainDirectory(new Directory(new JFile(ctx.settings.d.value))) + def outputDirectory: AbstractFile = DottyBackendInterface.this.outputDirectory def pos: Position = sym.pos def throwsAnnotations: List[Symbol] = Nil diff --git a/src/dotty/tools/backend/jvm/GenBCode.scala b/src/dotty/tools/backend/jvm/GenBCode.scala index e8d196ce73c4..2d444d3beb9e 100644 --- a/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/src/dotty/tools/backend/jvm/GenBCode.scala @@ -29,6 +29,7 @@ import scala.tools.asm.tree._ import dotty.tools.dotc.util.{Positions, DotClass} import tpd._ import StdNames._ +import scala.reflect.io.{Directory, PlainDirectory, AbstractFile} import scala.tools.nsc.backend.jvm.opt.LocalOpt @@ -37,9 +38,11 @@ class GenBCode extends Phase { private val entryPoints = new mutable.HashSet[Symbol]() def registerEntryPoint(sym: Symbol) = entryPoints += sym + def outputDir(implicit ctx: Context): AbstractFile = + new PlainDirectory(new Directory(new JFile(ctx.settings.d.value))) def run(implicit ctx: Context): Unit = { - new GenBCodePipeline(entryPoints.toList, new DottyBackendInterface()(ctx))(ctx).run(ctx.compilationUnit.tpdTree) + new GenBCodePipeline(entryPoints.toList, new DottyBackendInterface(outputDir)(ctx))(ctx).run(ctx.compilationUnit.tpdTree) entryPoints.clear() } } From 5f5eca9ee9367c57da8138f2618759dfc86ffb71 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 13 Feb 2016 22:34:39 +0100 Subject: [PATCH 02/18] Utility method for phase replacement Allows to replace existing phase by sequence of new phases. --- src/dotty/tools/dotc/core/Phases.scala | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/dotty/tools/dotc/core/Phases.scala b/src/dotty/tools/dotc/core/Phases.scala index 970a9297ac2e..83ac64d536e9 100644 --- a/src/dotty/tools/dotc/core/Phases.scala +++ b/src/dotty/tools/dotc/core/Phases.scala @@ -347,6 +347,13 @@ object Phases { override def toString = phaseName } + /** Replace all instances of `oldPhaseClass` in `current` phases + * by the result of `newPhases` applied to the old phase. + */ + def replace(oldPhaseClass: Class[_ <: Phase], newPhases: Phase => List[Phase], current: List[List[Phase]]): List[List[Phase]] = + current.map(_.flatMap(phase => + if (oldPhaseClass.isInstance(phase)) newPhases(phase) else phase :: Nil)) + /** Dotty deviation: getClass yields Class[_], instead of [Class <: ]. * We can get back the old behavior using this decorator. We should also use the same * trick for standard getClass. From 6ecdc8a69db1a808269b1c288284a4a430ce865e Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Sat, 13 Feb 2016 22:35:57 +0100 Subject: [PATCH 03/18] First PoC of REPL Adaptation of REPL by Spoon from ca 2007. Compiles OK, but not yet tested. --- src/dotty/tools/dotc/REPL.scala | 47 + .../dotc/repl/AbstractFileClassLoader.scala | 31 + src/dotty/tools/dotc/repl/ConsoleWriter.scala | 21 + .../tools/dotc/repl/InteractiveReader.scala | 28 + src/dotty/tools/dotc/repl/Interpreter.scala | 920 ++++++++++++++++++ .../tools/dotc/repl/InterpreterLoop.scala | 248 +++++ .../tools/dotc/repl/InterpreterResults.scala | 19 + .../tools/dotc/repl/InterpreterSettings.scala | 69 ++ .../tools/dotc/repl/NewLinePrintWriter.scala | 11 + src/dotty/tools/dotc/repl/SimpleReader.scala | 23 + 10 files changed, 1417 insertions(+) create mode 100644 src/dotty/tools/dotc/REPL.scala create mode 100644 src/dotty/tools/dotc/repl/AbstractFileClassLoader.scala create mode 100644 src/dotty/tools/dotc/repl/ConsoleWriter.scala create mode 100644 src/dotty/tools/dotc/repl/InteractiveReader.scala create mode 100644 src/dotty/tools/dotc/repl/Interpreter.scala create mode 100644 src/dotty/tools/dotc/repl/InterpreterLoop.scala create mode 100644 src/dotty/tools/dotc/repl/InterpreterResults.scala create mode 100644 src/dotty/tools/dotc/repl/InterpreterSettings.scala create mode 100644 src/dotty/tools/dotc/repl/NewLinePrintWriter.scala create mode 100644 src/dotty/tools/dotc/repl/SimpleReader.scala diff --git a/src/dotty/tools/dotc/REPL.scala b/src/dotty/tools/dotc/REPL.scala new file mode 100644 index 000000000000..a0255efa67e6 --- /dev/null +++ b/src/dotty/tools/dotc/REPL.scala @@ -0,0 +1,47 @@ +package dotty.tools +package dotc + +import core.Phases +import core.Contexts.Context +import reporting.Reporter +import java.io.EOFException +import scala.annotation.tailrec +import io.VirtualDirectory +import java.io.{BufferedReader, File, FileReader, PrintWriter} +import repl._ + +/** A compiler which stays resident between runs. + * Usage: + * + * > scala dotty.tools.dotc.Resident + * + * dotc> "more options and files to compile" + * + * ... + * + * dotc> :reset // reset all options to the ones passed on the command line + * + * ... + * + * dotc> :q // quit + */ +class REPL extends Driver { + + def input(implicit ctx: Context): InteractiveReader = { + val emacsShell = System.getProperty("env.emacs", "") != "" + //println("emacsShell="+emacsShell) //debug + if (ctx.settings.Xnojline.value || emacsShell) new SimpleReader() + else InteractiveReader.createDefault() + } + + def output: PrintWriter = new NewLinePrintWriter(new ConsoleWriter, true) + + override def newCompiler(): Compiler = new repl.Interpreter(output) + + override def sourcesRequired = false + + override def doCompile(compiler: Compiler, fileNames: List[String])(implicit ctx: Context): Reporter = { + new InterpreterLoop(compiler, input, output).run() + ctx.reporter + } +} diff --git a/src/dotty/tools/dotc/repl/AbstractFileClassLoader.scala b/src/dotty/tools/dotc/repl/AbstractFileClassLoader.scala new file mode 100644 index 000000000000..a3a463717311 --- /dev/null +++ b/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/src/dotty/tools/dotc/repl/ConsoleWriter.scala b/src/dotty/tools/dotc/repl/ConsoleWriter.scala new file mode 100644 index 000000000000..9387f366a25f --- /dev/null +++ b/src/dotty/tools/dotc/repl/ConsoleWriter.scala @@ -0,0 +1,21 @@ +package dotty.tools +package dotc +package repl +import java.io.Writer + +/** A Writer that writes onto the Scala Console. + * + * @author Lex Spoon + * @version 1.0 + */ +class ConsoleWriter extends Writer { + def close = flush + + def flush = Console.flush + + def write(cbuf: Array[Char], off: Int, len: Int): Unit = + if (len > 0) + write(new String(cbuf, off, len)) + + override def write(str: String): Unit = Console.print(str) +} diff --git a/src/dotty/tools/dotc/repl/InteractiveReader.scala b/src/dotty/tools/dotc/repl/InteractiveReader.scala new file mode 100644 index 000000000000..55f7de0b2afb --- /dev/null +++ b/src/dotty/tools/dotc/repl/InteractiveReader.scala @@ -0,0 +1,28 @@ +package dotty.tools +package dotc +package repl + +/** Reads lines from an input stream */ +trait InteractiveReader { + def readLine(prompt: String): String + val interactive: Boolean +} + +object InteractiveReader { + /** Create an interactive reader. Uses JLine if the + * library is available, but otherwise uses a + * SimpleReader. */ + def createDefault(): InteractiveReader = new SimpleReader() + /* + { + try { + new JLineReader + } catch { + case e => + //out.println("jline is not available: " + e) //debug + new SimpleReader() + } + } +*/ + +} diff --git a/src/dotty/tools/dotc/repl/Interpreter.scala b/src/dotty/tools/dotc/repl/Interpreter.scala new file mode 100644 index 000000000000..b9853e18be61 --- /dev/null +++ b/src/dotty/tools/dotc/repl/Interpreter.scala @@ -0,0 +1,920 @@ +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 reporting.{ConsoleReporter, Reporter} +import core.Flags +import util.{SourceFile, NameTransformer} +import io.ClassPath +import repl.{InterpreterResults=>IR} +import ast.Trees._ +import parsing.Parsers._ +import core._ +import dotty.tools.backend.jvm.GenBCode +import Symbols._, Types._, Contexts._, StdNames._, Names._, NameOps._ +import Decorators._ +import Interpreter._ + +/**

+ * An interpreter for Scala code. + *

+ *

+ * The main public entry points are compile() and + * interpret(). The compile() method loads a + * complete Scala file. The interpret() method executes one + * line of Scala code at the request of the user. + *

+ *

+ * 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 a single member named "result". 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 Interpreter(out: PrintWriter) extends Compiler { + + import ast.untpd._ + import Interpreter._ + + /** directory to save .class files to */ + val virtualDirectory = new VirtualDirectory("(memory)", None) + + class REPLGenBCode extends GenBCode { + override def outputDir(implicit ctx: Context) = virtualDirectory + } + + override def phases = Phases.replace( + classOf[GenBCode], _ => new REPLGenBCode :: Nil, super.phases) + + /** whether to print out result lines */ + private var printResults: Boolean = true + + /** Be quiet. Do not print out the results of each + * submitted command unless an exception is thrown. */ + def beQuiet = { printResults = false } + + /** Temporarily be quiet */ + def beQuietDuring[T](operation: => T): T = { + val wasPrinting = printResults + try { + printResults = false + operation + } finally { + printResults = wasPrinting + } + } + + /** interpreter settings */ + val isettings = new InterpreterSettings + + def newReporter = new ConsoleReporter(Console.in, writer = out) { + //override def printMessage(msg: String) { out.println(clean(msg)) } + 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 */ + var compilerClasspath: List[URL] = _ + + /* 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 */ + var classLoader: ClassLoader = _ + + protected def parentClassLoader: ClassLoader = classOf[Interpreter].getClassLoader + + def init()(implicit ctx: Context) = { + compilerClasspath = ctx.platform.classPath.asURLs + 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 + */ + def setContextClassLoader(): Unit = { + 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): List[Tree] = { + val source = new SourceFile("", code.toCharArray()) + val parser = new Parser(source) + val (selfDef, stats) = parser.templateStatSeq + stats + } + val trees = simpleParse(line) + if (reporter.hasErrors) { + Some(Nil) // the result did not parse, so stop + } else if (justNeedsMore) { + None + } else { + Some(trees) + } + } + } + + /** Compile a SourceFile. Returns true if there are + * no compilation errors, or false othrewise. + */ + def compileSources(sources: List[SourceFile])(implicit ctx: Context): Boolean = { + val reporter = newReporter + val run = new Run(this)(ctx.fresh.setReporter(reporter)) + run.compileSources(sources) + !reporter.hasErrors + } + + /** Compile a string. Returns true if there are no + * compilation errors, or false otherwise. + */ + def compileString(code: String)(implicit ctx: Context): Boolean = + compileSources(List(new SourceFile("