From e3bfe109732b7c1cce7493b2869614ed145660f6 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Fri, 27 Jul 2012 15:34:39 +0200 Subject: New Worksheet mixing scheme Debug changes --- .../scala/tools/nsc/interactive/REPL.scala | 11 ++- .../tools/nsc/interactive/ScratchPadMaker.scala | 16 +--- .../scala/tools/nsc/scratchpad/Executor.scala | 84 ------------------- .../scala/tools/nsc/scratchpad/Mixer.scala | 98 ++++++++++++++++++++++ src/library/scala/runtime/WorksheetSupport.scala | 87 +++++++++++++++++++ 5 files changed, 196 insertions(+), 100 deletions(-) delete mode 100644 src/compiler/scala/tools/nsc/scratchpad/Executor.scala create mode 100644 src/compiler/scala/tools/nsc/scratchpad/Mixer.scala create mode 100644 src/library/scala/runtime/WorksheetSupport.scala diff --git a/src/compiler/scala/tools/nsc/interactive/REPL.scala b/src/compiler/scala/tools/nsc/interactive/REPL.scala index a19463bee7..6876ea14e0 100644 --- a/src/compiler/scala/tools/nsc/interactive/REPL.scala +++ b/src/compiler/scala/tools/nsc/interactive/REPL.scala @@ -11,7 +11,7 @@ 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 scala.tools.nsc.scratchpad.SourceInserter import scala.tools.nsc.interpreter.AbstractFileClassLoader import java.io.{File, FileWriter} @@ -138,7 +138,6 @@ object REPL { * @param arguments Further argumenrs to pass to the compiler * @return Optionallu, if no -d option is given, the virtual directory * contained the generated bytecode classes - */ def compileInstrumented(iSourceName: String, arguments: List[String]): Option[AbstractFile] = { println("compiling "+iSourceName) val command = new CompilerCommand(iSourceName :: arguments, reporter.error(scala.reflect.internal.util.NoPosition, _)) @@ -176,6 +175,7 @@ object REPL { println("done") si.currentContents } + */ /** The method for implementing worksheet functionality. * @param arguments a file name, followed by optional command line arguments that are passed @@ -186,7 +186,7 @@ object REPL { * and outputs in the right column, or None if the presentation compiler * does not respond to askInstrumented. */ - def instrument(arguments: List[String], line: Int): Option[Array[Char]] = { + def instrument(arguments: List[String], line: Int): Option[String] = { val source = toSourceFile(arguments.head) // strip right hand side comment column and any trailing spaces from all lines val strippedSource = new BatchSourceFile(source.file, SourceInserter.stripRight(source.content)) @@ -197,8 +197,11 @@ object REPL { case (iFullName, iContents) => println(s"instrumented source $iFullName = ${iContents.mkString}") val iSourceName = writeInstrumented(iFullName, iContents) - val vdirOpt = compileInstrumented(iSourceName, arguments.tail) + iSourceName +/* + * val vdirOpt = compileInstrumented(iSourceName, arguments.tail) runInstrumented(vdirOpt, iFullName, strippedSource.content) + */ } } diff --git a/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala b/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala index 45f7c20ba0..bd1869e1a4 100644 --- a/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala +++ b/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala @@ -29,18 +29,10 @@ trait ScratchPadMaker { self: Global => private def nameType(sym: Symbol): String = nameType(sym.name.toString, sym.tpe) private def literal(str: String) = "\"\"\""+str+"\"\"\"" - - private val prologue = "import scala.tools.nsc.scratchpad.Executor._; def main(args: Array[String])=$execute{" - - def stringifiedContents = { - contents flatMap { - case '$' => """$$""" - case '"' => """${'"'}""" - case c => c.toString - } - }.mkString - - private def epilogue = "}(s\"\"\"" + stringifiedContents + "\"\"\")" + + private val prologue = "import scala.runtime.WorksheetSupport._; def main(args: Array[String])=$execute{" + + private val epilogue = "}" private def applyPendingPatches(offset: Int) = { if (skipped == 0) patches += Patch(offset, prologue) diff --git a/src/compiler/scala/tools/nsc/scratchpad/Executor.scala b/src/compiler/scala/tools/nsc/scratchpad/Executor.scala deleted file mode 100644 index d7b93b0031..0000000000 --- a/src/compiler/scala/tools/nsc/scratchpad/Executor.scala +++ /dev/null @@ -1,84 +0,0 @@ -package scala.tools.nsc.scratchpad - -import java.io.{PrintStream, OutputStreamWriter, Writer} - -import scala.runtime.ScalaRunTime.stringOf -import java.lang.reflect.InvocationTargetException -import scala.reflect.runtime.ReflectionUtils._ - -object Executor { - - println("exec started") - - private var currentWriter: CommentWriter = null - - /** Execute module with given name, redirecting all output to given - * source inserter. Catch all exceptions and print stacktrace of underlying causes. - */ - @deprecated("use $execute instead") - def execute(name: String, si: SourceInserter, classLoader: ClassLoader = getClass.getClassLoader) { - 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 { - singletonInstance(classLoader, name) - } catch { - case ex: Throwable => - unwrapThrowable(ex) match { - case _: StopException => ; - case cause => cause.printStackTrace() - } - } finally { - currentWriter.close() - System.setOut(oldSysOut) - System.setErr(oldSysErr) - Console.setOut(oldConsOut) - Console.setErr(oldConsErr) - currentWriter = oldCwr - } - } - - def $execute(op: => Unit)(source: String) = { - val oldSysOut = System.out - val oldSysErr = System.err - val oldConsOut = Console.out - val oldConsErr = Console.err - val si = new SourceInserter(source.toCharArray) - currentWriter = new CommentWriter(si) - val newOut = new PrintStream(new CommentOutputStream(currentWriter)) - System.setOut(newOut) - System.setErr(newOut) - Console.setOut(newOut) - Console.setErr(newOut) - try { - op - } catch { - case ex: StopException => - case ex: Throwable => ex.printStackTrace() - } finally { - currentWriter.close() - System.setOut(oldSysOut) - System.setErr(oldSysErr) - Console.setOut(oldConsOut) - Console.setErr(oldConsErr) - println("done") - println(si.currentContents.mkString) - } - } - - def $skip(n: Int) = currentWriter.skip(n) - - def $stop() = throw new StopException - - def $show(x: Any): String = stringOf(x, scala.Int.MaxValue) -} - -class StopException extends Exception diff --git a/src/compiler/scala/tools/nsc/scratchpad/Mixer.scala b/src/compiler/scala/tools/nsc/scratchpad/Mixer.scala new file mode 100644 index 0000000000..46ccc32097 --- /dev/null +++ b/src/compiler/scala/tools/nsc/scratchpad/Mixer.scala @@ -0,0 +1,98 @@ +package scala.tools.nsc.scratchpad + +import java.io.{FileInputStream, InputStreamReader, IOException} + +import scala.runtime.ScalaRunTime.stringOf +import java.lang.reflect.InvocationTargetException +import scala.reflect.runtime.ReflectionUtils._ +import collection.mutable.ArrayBuffer + +class Mixer { + + protected val stdSeparator = "//> " + protected val ctdSeparator = "//| " + protected val sepColumn = 50 + protected val tabInc = 8 + + type Comments = Seq[(Int, Array[Char])] + + def parseComments(comments: Array[Char]): Iterator[(Int, Array[Char])] = new Iterator[(Int, Array[Char])] { + var idx = 0 + def hasNext = idx < comments.length + def next() = { + val nextSpace = comments indexOf (' ', idx) + var nextNL = comments indexOf ('\n', nextSpace + 1) + if (nextNL < 0) nextNL = comments.length + val result = + (new String(comments.slice(idx, nextSpace)).toInt, comments.slice(nextSpace + 1, nextNL)) + idx = nextNL + 1 + result + } + } + + def mix(source: Array[Char], comments: Array[Char]): Array[Char] = { + val mixed = new ArrayBuffer[Char] + var written = 0 + def align() = { + var idx = mixed.lastIndexOf('\n') + 1 + var col = 0 + while (idx < mixed.length) { + col = + if (mixed(idx) == '\t') (col / tabInc) * tabInc + tabInc + else col + 1 + idx += 1 + } + if (col > sepColumn) { + mixed += '\n' + col = 0 + } + mixed ++= (" " * (sepColumn - col)) + } + for ((offset, cs) <- parseComments(comments)) { + val sep = + if (written < offset) { + for (i <- written until offset) mixed += source(i) + written = offset + stdSeparator + } else { + mixed += '\n' + ctdSeparator + } + align() + mixed ++= sep ++= cs + } + mixed ++= source.view(written, source.length) + mixed.toArray + } + +} + +object Mixer extends Mixer { + + def contents(name: String): Array[Char] = { + val page = new Array[Char](2 << 14) + val buf = new ArrayBuffer[Char] + val in = new FileInputStream(name) + val rdr = new InputStreamReader(in) + var nread = 0 + do { + nread = rdr.read(page, 0, page.length) + buf ++= (if (nread == page.length) page else page.take(nread)) + } while (nread >= 0) + buf.toArray + } + + def main(args: Array[String]) { + val mixer = new Mixer + try { + require(args.length == 2, "required arguments: file1 file2") + val source = contents(args(0)) + val comments = contents(args(1)) + val mixed = mixer.mix(source, comments) + println(mixed.mkString) + } catch { + case ex: IOException => + println("error: "+ ex.getMessage) + } + } +} diff --git a/src/library/scala/runtime/WorksheetSupport.scala b/src/library/scala/runtime/WorksheetSupport.scala new file mode 100644 index 0000000000..db6d6359a3 --- /dev/null +++ b/src/library/scala/runtime/WorksheetSupport.scala @@ -0,0 +1,87 @@ +package scala.runtime +import java.io.{OutputStream, PrintStream} +import scala.runtime.ScalaRunTime.stringOf + +/** A utility object that's needed by the code that executes a worksheet. + */ +object WorksheetSupport { + + /** The offset in the source which should be printed */ + private var currentOffset = 0 + + /** A stream that flushes in regular intervals so that output can be captured + * in real time. The flush interval is determined by the field "flushInterval". + * By default it is 30ms. + */ + private class FlushedOutputStream(out: OutputStream) extends OutputStream { + private var lastFlush: Long = 0L + protected val flushInterval = 30000000L // 30ms + private var lastCh: Int = '\n' + override def write(b: Array[Byte], off: Int, len: Int) = { + for (idx <- off until (off + len min b.length)) writeOne(b(idx)) + flush() + } + override def write(c: Int) { + writeOne(c) + flush() + } + override def flush() { + val current = System.nanoTime + if (current - lastFlush >= flushInterval) { + out.flush() + lastFlush = current + } + } + def writeOne(c: Int) { + if (lastCh == '\n') { + lastCh = 0 + write((currentOffset+" ").getBytes) + } + out.write(c) + lastCh = c + } + def ensureNewLine() = if (lastCh != '\n') writeOne('\n') + } + + private val flushedOut = new FlushedOutputStream(System.out) + private val printOut = new PrintStream(flushedOut) + + private def redirected(op: => Unit) = { + val oldSysOut = System.out + val oldSysErr = System.err + val oldConsOut = Console.out + val oldConsErr = Console.err + System.setOut(printOut) + System.setErr(printOut) + Console.setOut(printOut) + Console.setErr(printOut) + try op + finally { + printOut.close() + System.setOut(oldSysOut) + System.setErr(oldSysErr) + Console.setOut(oldConsOut) + Console.setErr(oldConsErr) + } + } + + def $execute(op: => Unit) = redirected { + try op + catch { + case ex: StopException => ; + case ex => ex.printStackTrace() + } + } + + def $skip(n: Int) = { + flushedOut.ensureNewLine() + currentOffset += n + } + + def $stop() = throw new StopException + + def $show(x: Any): String = stringOf(x, scala.Int.MaxValue) +} + +class StopException extends Exception + -- cgit v1.2.3