From 124e2f95aee0085b5df1af899008a63a762bece6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 6 Jul 2011 10:49:14 +0000 Subject: scratchpad works in interactive REPL; ready to ... scratchpad works in interactive REPL; ready to be integrate in Eclipse. --- .../tools/nsc/interactive/CompilerControl.scala | 8 +- .../scala/tools/nsc/interactive/Global.scala | 4 +- .../scala/tools/nsc/interactive/REPL.scala | 58 ++++++++++--- .../tools/nsc/interactive/ScratchPadMaker.scala | 95 ++++++++++++---------- .../scala/tools/nsc/scratchpad/CommentWriter.scala | 2 +- .../scala/tools/nsc/scratchpad/Executor.scala | 58 ++++++++++--- .../tools/nsc/scratchpad/SourceInserter.scala | 7 +- 7 files changed, 154 insertions(+), 78 deletions(-) diff --git a/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala b/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala index ef5791e6d1..5cb7a1d811 100644 --- a/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala +++ b/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala @@ -215,8 +215,8 @@ trait CompilerControl { self: Global => * everything is brought up to date in a regular type checker run. * @param response The response. */ - def askInstrumented(source: SourceFile, response: Response[(String, SourceFile)]) = - postWorkItem(new AskInstrumentedItem(source, response)) + def askInstrumented(source: SourceFile, line: Int, response: Response[(String, Array[Char])]) = + postWorkItem(new AskInstrumentedItem(source, line, response)) /** Cancels current compiler run and start a fresh one where everything will be re-typechecked * (but not re-loaded). @@ -328,8 +328,8 @@ trait CompilerControl { self: Global => override def toString = "getParsedEntered "+source+", keepLoaded = "+keepLoaded } - class AskInstrumentedItem(val source: SourceFile, response: Response[(String, SourceFile)]) extends WorkItem { - def apply() = self.getInstrumented(source, response) + class AskInstrumentedItem(val source: SourceFile, line: Int, response: Response[(String, Array[Char])]) extends WorkItem { + def apply() = self.getInstrumented(source, line, response) override def toString = "getInstrumented "+source } } diff --git a/src/compiler/scala/tools/nsc/interactive/Global.scala b/src/compiler/scala/tools/nsc/interactive/Global.scala index 4f1f0946ed..9e61e880c3 100644 --- a/src/compiler/scala/tools/nsc/interactive/Global.scala +++ b/src/compiler/scala/tools/nsc/interactive/Global.scala @@ -913,9 +913,9 @@ class Global(settings: Settings, reporter: Reporter, projectName: String = "") } } - def getInstrumented(source: SourceFile, response: Response[(String, SourceFile)]) { + def getInstrumented(source: SourceFile, line: Int, response: Response[(String, Array[Char])]) { respond(response) { - instrument(source) + instrument(source, line) } } diff --git a/src/compiler/scala/tools/nsc/interactive/REPL.scala b/src/compiler/scala/tools/nsc/interactive/REPL.scala index f5b536a3f4..b3ebc4aa1b 100644 --- a/src/compiler/scala/tools/nsc/interactive/REPL.scala +++ b/src/compiler/scala/tools/nsc/interactive/REPL.scala @@ -11,6 +11,8 @@ import scala.tools.nsc.symtab._ import scala.tools.nsc.ast._ import scala.tools.nsc.reporters._ import scala.tools.nsc.io._ +import scala.tools.nsc.scratchpad.{Executor, SourceInserter} +import java.io.{File, FileWriter} /** Interface of interactive compiler to a client such as an IDE */ @@ -89,32 +91,62 @@ object REPL { val completeResult = new Response[List[comp.Member]] val typedResult = new Response[comp.Tree] val structureResult = new Response[comp.Tree] - val instrumentedResult = new Response[(String, SourceFile)] + val instrumentedResult = new Response[(String, Array[Char])] def makePos(file: String, off1: String, off2: String) = { val source = toSourceFile(file) comp.rangePos(source, off1.toInt, off1.toInt, off2.toInt) } + def doTypeAt(pos: Position) { comp.askTypeAt(pos, typeatResult) show(typeatResult) } + def doComplete(pos: Position) { comp.askTypeCompletion(pos, completeResult) show(completeResult) } + def doTypedTree(file: String) { comp.askType(toSourceFile(file), true, typedResult) show(typedResult) } + def doStructure(file: String) { comp.askParsedEntered(toSourceFile(file), false, structureResult) show(structureResult) } - def formatInstrumented(r: (String, SourceFile)) = { - val (name, source) = r - "toplevel object = "+name+", contents = \n"+source.content.mkString + /** This is the method for implement worksheet functionality + */ + def instrument(source: SourceFile, line: Int): Option[Array[Char]] = { + // strip right hand side comment column and any trailing spaces from all lines + val strippedSource = new BatchSourceFile(source.file, SourceInserter.stripRight(source.content)) + comp.askReload(List(strippedSource), reloadResult) + // todo: Display strippedSource in place of source + comp.askInstrumented(strippedSource, line, instrumentedResult) + using(instrumentedResult) { case (iFullName, iContents) => + // iFullName: The full name of the first top-level object in source + // iContents: An Array[Char] containing the instrumented source + // Create a file from iContents so that it can be compiled + // The name here is +"$instrumented.scala", but + // it could be anything. + val iSimpleName = iFullName drop ((iFullName lastIndexOf '.') + 1) + val iSourceName = iSimpleName + "$instrumented.scala" + val ifile = new FileWriter(iSourceName) + ifile.write(iContents) + ifile.close() + println("compiling "+iSourceName) + // Compile instrumented source + scala.tools.nsc.Main.process(Array(iSourceName, "-verbose", "-d", "/classes")) + // Run instrumented source, inserting all output into stripped source + println("running "+iSimpleName) + val si = new SourceInserter(strippedSource.content) + Executor.execute(iSimpleName, si) + println("done") + si.currentContents + } } loop { line => @@ -139,10 +171,9 @@ object REPL { case List("complete", file, off1) => doComplete(makePos(file, off1, off1)) case List("instrument", file) => - val source = toSourceFile(file) - comp.askReload(List(source), reloadResult) - comp.askInstrumented(source, instrumentedResult) - show(instrumentedResult, formatInstrumented) + println(instrument(toSourceFile(file), -1).map(_.mkString)) + case List("instrument", file, line) => + println(instrument(toSourceFile(file), line.toInt).map(_.mkString)) case List("quit") => comp.askShutdown() // deleted sys. as this has to run on 2.8 also @@ -157,11 +188,14 @@ object REPL { def toSourceFile(name: String) = new BatchSourceFile(new PlainFile(new java.io.File(name))) - def show[T](svar: Response[T], transform: T => Any = (x: T) => x) { - svar.get match { - case Left(result) => println("==> "+transform(result)) - case Right(exc) => exc.printStackTrace; println("ERROR: "+exc) + def using[T, U](svar: Response[T])(op: T => U): Option[U] = { + val res = svar.get match { + case Left(result) => Some(op(result)) + case Right(exc) => exc.printStackTrace; println("ERROR: "+exc); None } svar.clear() + res } + + def show[T](svar: Response[T]) = using(svar)(res => println("==> "+res)) } diff --git a/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala b/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala index e284b816fd..1c7a8523eb 100644 --- a/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala +++ b/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala @@ -11,7 +11,7 @@ trait ScratchPadMaker { self: Global => private case class Patch(offset: Int, text: String) - private class Patcher(contents: Array[Char]) extends Traverser { + private class Patcher(contents: Array[Char], endOffset: Int) extends Traverser { var objectName: String = "" private val patches = new ArrayBuffer[Patch] @@ -28,56 +28,62 @@ trait ScratchPadMaker { self: Global => private def nameType(sym: Symbol): String = nameType(sym.name.toString, sym.tpe) - private def literal(str: String) = "\"\"\"\""+str+"\"\"\"\"" + private def literal(str: String) = "\"\"\""+str+"\"\"\"" + + private def applyPendingPatches(offset: Int) = { + if (skipped == 0) patches += Patch(offset, "import scala.tools.nsc.scratchpad.Executor._; ") + for (msg <- toPrint) patches += Patch(offset, ";System.out.println("+msg+")") + toPrint.clear() + } private def addSkip(stat: Tree): Unit = { - if (stat.pos.start > skipped) { - for (msg <- toPrint) - patches += Patch(stat.pos.start, ";println("+msg+")") - } + if (stat.pos.start > skipped) applyPendingPatches(stat.pos.start) + if (stat.pos.start >= endOffset) + patches += Patch(stat.pos.start, ";$stop()") var end = stat.pos.end if (end > skipped) { while (end < contents.length && !(isLineBreakChar(contents(end)))) end += 1 - patches += Patch(stat.pos.start, ";skip("+(end-skipped)+"); ") + patches += Patch(stat.pos.start, ";$skip("+(end-skipped)+"); ") skipped = end } } - private def addSandbox(expr: Tree) = - patches += (Patch(expr.pos.start, "sandbox("), Patch(expr.pos.end, ")")) - - private def traverseStat(stat: Tree) = if (stat.pos.isInstanceOf[RangePosition]) { - stat match { - case ValDef(_, _, _, rhs) => - addSkip(stat) - if (stat.symbol.isLazy) - toPrint += literal(nameType(stat.symbol)+" = ") - else if (!stat.symbol.isSynthetic) { - addSandbox(rhs) - toPrint += literal(nameType(stat.symbol)+" = ")+" + "+stat.symbol.name - } - case DefDef(_, _, _, _, _, _) => - addSkip(stat) - toPrint += literal(nameType(stat.symbol)) - case Annotated(_, arg) => - traverse(arg) - case DocDef(_, defn) => - traverse(defn) - case _ => - if (stat.isTerm) { - addSkip(stat) - if (stat.tpe.typeSymbol == UnitClass) { - addSandbox(stat) - } else { - val resName = nextRes() - val dispResName = resName filter ('$' !=) - patches += Patch(stat.pos.start, "val "+resName+" = ") - addSandbox(stat) - toPrint += literal(nameType(dispResName, stat.tpe)+" = ")+" + "+resName - } + private def addSandbox(expr: Tree) = {} +// patches += (Patch(expr.pos.start, "sandbox("), Patch(expr.pos.end, ")")) + + private def traverseStat(stat: Tree) = + if (stat.pos.isInstanceOf[RangePosition]) { + stat match { + case ValDef(_, _, _, rhs) => + addSkip(stat) + if (stat.symbol.isLazy) + toPrint += literal(nameType(stat.symbol) + " = ") + else if (!stat.symbol.isSynthetic) { + addSandbox(rhs) + toPrint += literal(nameType(stat.symbol) + " = ") + " + " + stat.symbol.name + } + case DefDef(_, _, _, _, _, _) => + addSkip(stat) + toPrint += literal(nameType(stat.symbol)) + case Annotated(_, arg) => + traverse(arg) + case DocDef(_, defn) => + traverse(defn) + case _ => + if (stat.isTerm) { + addSkip(stat) + if (stat.tpe.typeSymbol == UnitClass) { + addSandbox(stat) + } else { + val resName = nextRes() + val dispResName = resName filter ('$' !=) + patches += Patch(stat.pos.start, "val " + resName + " = ") + addSandbox(stat) + toPrint += literal(nameType(dispResName, stat.tpe) + " = ") + " + " + resName + } + } } } - } override def traverse(tree: Tree): Unit = tree match { case PackageDef(_, _) => @@ -86,6 +92,7 @@ trait ScratchPadMaker { self: Global => if (objectName.length == 0 /* objectName.isEmpty does not compile on Java 5 due to ambiguous implicit conversions: augmentString, stringToTermName */) objectName = tree.symbol.fullName body foreach traverseStat + applyPendingPatches(skipped) case _ => } @@ -116,16 +123,18 @@ trait ScratchPadMaker { self: Global => /** Compute an instrumented version of a sourcefile. * @param source The given sourcefile. + * @param line The line up to which results should be printed, -1 = whole document. * @return A pair consisting of * - the fully qualified name of the first top-level object definition in the file. * or "" if there are no object definitions. * - the text of the instrumented program which, when run, * prints its output and all defined values in a comment column. */ - protected def instrument(source: SourceFile): (String, SourceFile) = { + protected def instrument(source: SourceFile, line: Int): (String, Array[Char]) = { val tree = typedTree(source, true) - val patcher = new Patcher(source.content) + val endOffset = if (line < 0) source.length else source.lineToOffset(line + 1) + val patcher = new Patcher(source.content, endOffset) patcher.traverse(tree) - (patcher.objectName, new BatchSourceFile(source.file, patcher.result)) + (patcher.objectName, patcher.result) } } diff --git a/src/compiler/scala/tools/nsc/scratchpad/CommentWriter.scala b/src/compiler/scala/tools/nsc/scratchpad/CommentWriter.scala index 607aa97568..eb8880e437 100644 --- a/src/compiler/scala/tools/nsc/scratchpad/CommentWriter.scala +++ b/src/compiler/scala/tools/nsc/scratchpad/CommentWriter.scala @@ -4,7 +4,7 @@ import java.io.Writer import reflect.internal.Chars._ -class CommentWriter(underlying: SourceInserter, startCol: Int = 40, endCol: Int = 124) extends Writer { +class CommentWriter(underlying: SourceInserter, startCol: Int = 40, endCol: Int = 152) extends Writer { private def rightCol(marker: String) = { while (underlying.column < startCol) underlying.write(' ') diff --git a/src/compiler/scala/tools/nsc/scratchpad/Executor.scala b/src/compiler/scala/tools/nsc/scratchpad/Executor.scala index 510f6c5a3b..8eead4f296 100644 --- a/src/compiler/scala/tools/nsc/scratchpad/Executor.scala +++ b/src/compiler/scala/tools/nsc/scratchpad/Executor.scala @@ -1,28 +1,60 @@ package scala.tools.nsc.scratchpad import java.io.{PrintStream, OutputStreamWriter, Writer} +import java.lang.reflect.InvocationTargetException object Executor { println("exec started") - def execute(name: String, si: SourceInserter) { - val oldOut = System.out - val oldErr = System.err - val cwr = new CommentWriter(si) - val newOut = new PrintStream(new CommentOutputStream(cwr)) - java.lang.System.setOut(newOut) - java.lang.System.setErr(newOut) + private var currentWriter: CommentWriter = null + + def ultimateCause(ex: Throwable): Throwable = ex match { + case ex: InvocationTargetException => + ultimateCause(ex.getCause) + case ex: ExceptionInInitializerError => + ultimateCause(ex.getCause) + case ex => + ex + } + /** Execute module with given name, redirecting all output to given + * source inserter. Catch all exceptions and print stacktrace of underlying causes. + */ + def execute(name: String, si: SourceInserter) { + val oldSysOut = System.out + val oldSysErr = System.err + val oldConsOut = Console.out + val oldConsErr = Console.err + val oldCwr = currentWriter + currentWriter = new CommentWriter(si) + val newOut = new PrintStream(new CommentOutputStream(currentWriter)) + System.setOut(newOut) + System.setErr(newOut) + Console.setOut(newOut) + Console.setErr(newOut) try { - Class.forName(name).newInstance() + val clazz = Class.forName(name+"$") + clazz.getField("$MODULE").get(null) } catch { case ex: Throwable => - ex.printStackTrace() + ultimateCause(ex) match { + case _: StopException => ; + case cause => cause.printStackTrace() + } } finally { - cwr.close() - System.setOut(oldOut) - System.setErr(oldErr) + currentWriter.close() + System.setOut(oldSysOut) + System.setErr(oldSysErr) + Console.setOut(oldConsOut) + Console.setErr(oldConsErr) + currentWriter = oldCwr } } -} \ No newline at end of file + + def $skip(n: Int) = currentWriter.skip(n) + + def $stop() = throw new StopException +} + +class StopException extends Exception diff --git a/src/compiler/scala/tools/nsc/scratchpad/SourceInserter.scala b/src/compiler/scala/tools/nsc/scratchpad/SourceInserter.scala index f6c91838db..68817f2f64 100644 --- a/src/compiler/scala/tools/nsc/scratchpad/SourceInserter.scala +++ b/src/compiler/scala/tools/nsc/scratchpad/SourceInserter.scala @@ -26,7 +26,7 @@ class SourceInserter(contents: Array[Char], start: Int = 0, tabInc: Int = 8) ext private var buf = contents private var offset = start - private var hilen = 0 + private var hilen = contents.length def length = offset + hilen @@ -56,6 +56,7 @@ class SourceInserter(contents: Array[Char], start: Int = 0, tabInc: Int = 8) ext } private def insertChar(ch: Char) = { +// Console.err.print("["+ch+"]") buf(offset) = ch.toChar offset += 1 ch match { @@ -76,7 +77,6 @@ class SourceInserter(contents: Array[Char], start: Int = 0, tabInc: Int = 8) ext } override def close() { - buf = null } override def flush() { @@ -104,8 +104,9 @@ class SourceInserter(contents: Array[Char], start: Int = 0, tabInc: Int = 8) ext def skip(len: Int) = synchronized { for (i <- 0 until len) { - insertChar(currentChar) + val ch = currentChar hilen -= 1 + insertChar(ch) } } } -- cgit v1.2.3