Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

worksheet support on 2.9

  • Loading branch information...
commit 4c80b61039a11bbfb95af0d12a2aca79835d6674 1 parent e347985
@lrytz lrytz authored
View
23 src/compiler/scala/tools/nsc/interactive/CompilerControl.scala
@@ -208,6 +208,21 @@ trait CompilerControl { self: Global =>
def askParsedEntered(source: SourceFile, keepLoaded: Boolean, response: Response[Tree]) =
postWorkItem(new AskParsedEnteredItem(source, keepLoaded, response))
+ /** Set sync var `response` to 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.
+ *
+ * @param source The source file to be analyzed
+ * @param keepLoaded If set to `true`, source file will be kept as a loaded unit afterwards.
+ * If keepLoaded is `false` the operation is run at low priority, only after
+ * everything is brought up to date in a regular type checker run.
+ * @param response The 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).
*/
@@ -353,6 +368,14 @@ trait CompilerControl { self: Global =>
response raise new MissingResponse
}
+ case 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
+
+ def raiseMissing() =
+ response raise new MissingResponse
+ }
+
/** A do-nothing work scheduler that responds immediately with MissingResponse.
*
* Used during compiler shutdown.
View
13 src/compiler/scala/tools/nsc/interactive/Global.scala
@@ -30,6 +30,7 @@ class Global(settings: Settings, reporter: Reporter, projectName: String = "")
with RangePositions
with ContextTrees
with RichCompilationUnits
+ with ScratchPadMaker
with Picklers {
import definitions._
@@ -718,7 +719,7 @@ class Global(settings: Settings, reporter: Reporter, projectName: String = "")
}
/** A fully attributed tree corresponding to the entire compilation unit */
- private def typedTree(source: SourceFile, forceReload: Boolean): Tree = {
+ private[interactive] def typedTree(source: SourceFile, forceReload: Boolean): Tree = {
informIDE("typedTree " + source + " forceReload: " + forceReload)
val unit = getOrCreateUnitOf(source)
if (forceReload) reset(unit)
@@ -1025,6 +1026,16 @@ class Global(settings: Settings, reporter: Reporter, projectName: String = "")
}
}
+ def getInstrumented(source: SourceFile, line: Int, response: Response[(String, Array[Char])]) =
+ try {
+ interruptsEnabled = false
+ respond(response) {
+ instrument(source, line)
+ }
+ } finally {
+ interruptsEnabled = true
+ }
+
// ---------------- Helper classes ---------------------------
/** A transformer that replaces tree `from` with tree `to` in a given tree */
View
121 src/compiler/scala/tools/nsc/interactive/REPL.scala
@@ -11,6 +11,9 @@ 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.SourceInserter
+import scala.tools.nsc.interpreter.AbstractFileClassLoader
+import java.io.{File, FileWriter}
/** Interface of interactive compiler to a client such as an IDE
*/
@@ -89,6 +92,7 @@ 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, Array[Char])]
def makePos(file: String, off1: String, off2: String) = {
val source = toSourceFile(file)
@@ -111,6 +115,94 @@ object REPL {
show(structureResult)
}
+ /** Write instrumented source file to disk.
+ * @param iFullName The full name of the first top-level object in source
+ * @param iContents An Array[Char] containing the instrumented source
+ * @return The name of the instrumented source file
+ */
+ def writeInstrumented(iFullName: String, suffix: String, iContents: Array[Char]): String = {
+ val iSimpleName = iFullName drop ((iFullName lastIndexOf '.') + 1)
+ val iSourceName = iSimpleName + suffix
+ val ifile = new FileWriter(iSourceName)
+ ifile.write(iContents)
+ ifile.close()
+ iSourceName
+ }
+
+ /** Compile instrumented source file
+ * @param iSourceName The name of the instrumented source file
+ * @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, _))
+ val virtualDirectoryOpt =
+ if (arguments contains "-d")
+ None
+ else {
+ val vdir = new VirtualDirectory("(memory)", None)
+ command.settings.outputDirs setSingleOutput vdir
+ Some(vdir)
+ }
+ val compiler = new scala.tools.nsc.Global(command.settings, reporter)
+ val run = new compiler.Run()
+ println("compiling: "+command.files)
+ run compile command.files
+ virtualDirectoryOpt
+ }
+
+ /** Run instrumented bytecode file
+ * @param vdir Optionally, the virtual directory containing the generated bytecode classes
+ * @param iFullName The full name of the generated object
+ * @param stripped The contents original source file without any right hand column comments.
+ * @return The generated file content containing original source in the left column
+ * and outputs in the right column
+ */
+ def runInstrumented(vdirOpt: Option[AbstractFile], iFullName: String, stripped: Array[Char]): Array[Char] = {
+ val defaultClassLoader = getClass.getClassLoader
+ val classLoader = vdirOpt match {
+ case Some(vdir) => new AbstractFileClassLoader(vdir, defaultClassLoader)
+ case None => defaultClassLoader
+ }
+ println("running "+iFullName)
+ val si = new SourceInserter(stripped)
+ Executor.execute(iFullName, si, classLoader)
+ println("done")
+ si.currentContents
+ }
+ */
+
+ /** The method for implementing worksheet functionality.
+ * @param arguments a file name, followed by optional command line arguments that are passed
+ * to the compiler that processes the instrumented source.
+ * @param line A line number that controls uop to which line results should be produced
+ * If line = -1, results are produced for all expressions in the worksheet.
+ * @return The generated file content containing original source in the left column
+ * 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[(String, String)] = {
+ val source = toSourceFile(arguments.head)
+ // strip right hand side comment column and any trailing spaces from all lines
+ val strippedContents = SourceInserter.stripRight(source.content)
+ val strippedSource = new BatchSourceFile(source.file, strippedContents)
+ println("stripped source = "+strippedSource)
+ comp.askReload(List(strippedSource), reloadResult)
+ comp.askInstrumented(strippedSource, line, instrumentedResult)
+ using(instrumentedResult) {
+ case (iFullName, iContents) =>
+ println("instrumented source "+ iFullName +" = "+ iContents.mkString)
+ val iSourceName = writeInstrumented(iFullName, "$instrumented.scala", iContents)
+ val sSourceName = writeInstrumented(iFullName, "$stripped.scala", strippedContents)
+ (iSourceName, sSourceName)
+/*
+ * val vdirOpt = compileInstrumented(iSourceName, arguments.tail)
+ runInstrumented(vdirOpt, iFullName, strippedSource.content)
+ */
+ }
+ }
+
loop { line =>
(line split " ").toList match {
case "reload" :: args =>
@@ -132,24 +224,43 @@ object REPL {
doComplete(makePos(file, off1, off2))
case List("complete", file, off1) =>
doComplete(makePos(file, off1, off1))
+ case "instrument" :: arguments =>
+ println(instrument(arguments, -1))
+ case "instrumentTo" :: line :: arguments =>
+ println(instrument(arguments, line.toInt))
case List("quit") =>
comp.askShutdown()
sys.exit(1)
case List("structure", file) =>
doStructure(file)
case _ =>
- println("unrecongized command")
+ print("""Available commands:
+ | reload <file_1> ... <file_n>
+ | reloadAndAskType <file> <sleep-ms>
+ | typed <file>
+ | typeat <file> <start-pos> <end-pos>
+ | typeat <file> <pos>
+ | complete <file> <start-pos> <end-pos>
+ | compile <file> <pos>
+ | instrument <file> <arg>*
+ | instrumentTo <line-num> <file> <arg>*
+ | structure <file>
+ | quit
+ |""".stripMargin)
}
}
}
def toSourceFile(name: String) = new BatchSourceFile(new PlainFile(new java.io.File(name)))
- def show[T](svar: Response[T]) {
- svar.get match {
- case Left(result) => println("==> "+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))
}
View
163 src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala
@@ -0,0 +1,163 @@
+package scala.tools.nsc
+package interactive
+
+import util.{SourceFile, BatchSourceFile, RangePosition}
+import collection.mutable.ArrayBuffer
+import util.Chars.{isLineBreakChar, isWhitespace}
+
+trait ScratchPadMaker { self: Global =>
+
+ import definitions._
+
+ private case class Patch(offset: Int, text: String)
+
+ private class Patcher(contents: Array[Char], endOffset: Int) extends Traverser {
+ var objectName: String = ""
+
+ private val patches = new ArrayBuffer[Patch]
+ private val toPrint = new ArrayBuffer[String]
+ private var skipped = 0
+ private var resNum: Int = -1
+
+ private def nextRes(): String = {
+ resNum += 1
+ "res$"+resNum
+ }
+
+ private def nameType(name: String, tpe: Type): String = name+": "+tpe
+
+ private def nameType(sym: Symbol): String = nameType(sym.name.toString, sym.tpe)
+
+ private def literal(str: String) = "\"\"\""+str+"\"\"\""
+
+ 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)
+ for (msg <- toPrint) patches += Patch(offset, ";System.out.println("+msg+")")
+ toPrint.clear()
+ }
+
+ /** The position where to insert an instrumentation statement in front of giuven statement.
+ * This is at the latest `stat.pos.start`. But in order not to mess with column numbers
+ * in position we try to insert it at the end of the preceding line instead.
+ * To be safe, this can be done only if there's only whitespace between that position and
+ * statement's start position.
+ */
+ private def instrumentPos(stat: Tree): Int = {
+ var start = stat.pos.start
+ while (start > 0 && isWhitespace(contents(start - 1))) start -= 1
+ if (start > 0 && isLineBreakChar(contents(start - 1))) start -= 1
+ start
+ }
+
+ private def addSkip(stat: Tree): Unit = {
+ val ipos = instrumentPos(stat)
+ if (stat.pos.start > skipped) applyPendingPatches(ipos)
+ if (stat.pos.start >= endOffset)
+ patches += Patch(ipos, ";$stop()")
+ var end = stat.pos.end
+ if (end > skipped) {
+ while (end < contents.length && !isLineBreakChar(contents(end))) end += 1
+ patches += Patch(ipos, ";$skip("+(end-skipped)+"); ")
+ skipped = end
+ }
+ }
+
+ private def addSandbox(expr: Tree) = {}
+// patches += (Patch(expr.pos.start, "sandbox("), Patch(expr.pos.end, ")"))
+
+ private def resultString(prefix: String, expr: String) =
+ literal(prefix + " = ") + " + $show(" + expr + ")"
+
+ 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) + " = <lazy>")
+ else if (!stat.symbol.isSynthetic) {
+ addSandbox(rhs)
+ toPrint += resultString(nameType(stat.symbol), stat.symbol.name.toString)
+ }
+ 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 += resultString(nameType(dispResName, stat.tpe), resName)
+ }
+ }
+ }
+ }
+
+ override def traverse(tree: Tree): Unit = tree match {
+ case PackageDef(_, _) =>
+ super.traverse(tree)
+ case ModuleDef(_, name, Template(_, _, body)) =>
+ val topLevel = objectName.isEmpty
+ if (topLevel) objectName = tree.symbol.fullName
+ body foreach traverseStat
+ applyPendingPatches(skipped)
+ if (topLevel)
+ patches += Patch(skipped, epilogue)
+ case _ =>
+ }
+
+ /** The patched text.
+ * @require traverse is run first
+ */
+ def result: Array[Char] = {
+ val reslen = contents.length + (patches map (_.text.length)).sum
+ val res = Array.ofDim[Char](reslen)
+ var lastOffset = 0
+ var from = 0
+ var to = 0
+ for (Patch(offset, text) <- patches) {
+ val delta = offset - lastOffset
+ assert(delta >= 0)
+ Array.copy(contents, from, res, to, delta)
+ from += delta
+ to += delta
+ lastOffset = offset
+ text.copyToArray(res, to)
+ to += text.length
+ }
+ assert(contents.length - from == reslen - to)
+ Array.copy(contents, from, res, to, contents.length - from)
+ res
+ }
+ }
+
+ /** 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, line: Int): (String, Array[Char]) = {
+ val tree = typedTree(source, true)
+ val endOffset = if (line < 0) source.length else source.lineToOffset(line + 1)
+ val patcher = new Patcher(source.content, endOffset)
+ patcher.traverse(tree)
+ (patcher.objectName, patcher.result)
+ }
+}
View
18 src/compiler/scala/tools/nsc/scratchpad/CommentOutputStream.scala
@@ -0,0 +1,18 @@
+package scala.tools.nsc.scratchpad
+
+import java.io.OutputStream
+
+class CommentOutputStream(out: CommentWriter, encoding: String = "") extends OutputStream {
+
+ override def write(bs: Array[Byte]) =
+ out.write(if (encoding.isEmpty) new String(bs) else new String(bs, encoding))
+
+ override def write(bs: Array[Byte], off: Int, len: Int) =
+ out.write(if (encoding.isEmpty) new String(bs, off, len) else new String(bs, off, len, encoding))
+
+ override def write(ch: Int) =
+ write(Array(ch.toByte))
+
+ override def close() = out.close()
+ override def flush() = out.flush()
+}
View
42 src/compiler/scala/tools/nsc/scratchpad/CommentWriter.scala
@@ -0,0 +1,42 @@
+package scala.tools.nsc.scratchpad
+
+import java.io.Writer
+import scala.tools.nsc.util.Chars._
+
+
+class CommentWriter(underlying: SourceInserter, startCol: Int = 40, endCol: Int = 152) extends Writer {
+
+ private def rightCol(marker: String) = {
+ while (underlying.column < startCol) underlying.write(' ')
+ underlying.write(marker)
+ }
+
+ private var lastWasNL = false
+
+ private def writeChar(ch: Char) = {
+ if (underlying.column >= endCol) {
+ underlying.write('\n'); rightCol("//| ")
+ }
+ if (underlying.column < startCol) rightCol("//> ")
+ underlying.write(ch)
+ lastWasNL = isLineBreakChar(ch)
+ }
+
+ override def write(chs: Array[Char], off: Int, len: Int) = {
+ for (i <- off until off + len) writeChar(chs(i))
+ flush()
+ }
+
+ def skip(len: Int) {
+ if (lastWasNL) {
+ underlying.backspace()
+ lastWasNL = false
+ }
+ underlying.skip(len)
+ if (underlying.column >= startCol) underlying.write('\n')
+ }
+
+ override def close() = underlying.close()
+ override def flush() = underlying.flush()
+}
+
View
100 src/compiler/scala/tools/nsc/scratchpad/Mixer.scala
@@ -0,0 +1,100 @@
+package scala.tools.nsc.scratchpad
+
+import java.io.{FileInputStream, InputStreamReader, IOException}
+
+import scala.runtime.ScalaRunTime.stringOf
+import java.lang.reflect.InvocationTargetException
+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
+ }
+ while (col < sepColumn) {
+ mixed += ' '
+ col += 1
+ }
+ }
+ 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)
+ }
+ }
+}
View
112 src/compiler/scala/tools/nsc/scratchpad/SourceInserter.scala
@@ -0,0 +1,112 @@
+package scala.tools.nsc
+package scratchpad
+
+import java.io.Writer
+import util.SourceFile
+
+import util.Chars._
+
+object SourceInserter {
+ def stripRight(cs: Array[Char]): Array[Char] = {
+ val lines =
+ new String(cs) split "\n"
+ def leftPart(str: String) =
+ (str split """//>|//\|""").head
+ def isContinuation(str: String) =
+ ((str contains "//>") || (str contains "//|")) && (leftPart(str) forall isWhitespace)
+ def stripTrailingWS(str: String) =
+ str take (str lastIndexWhere (!isWhitespace(_))) + 1
+ val prefixes =
+ lines filterNot isContinuation map leftPart map stripTrailingWS
+ (prefixes mkString "\n").toArray
+ }
+}
+class SourceInserter(contents: Array[Char], start: Int = 0, tabInc: Int = 8) extends Writer {
+
+ private var buf = contents
+ private var offset = start
+ private var hilen = contents.length
+
+ def length = offset + hilen
+
+ private def currentColumn: Int = {
+ var i = offset
+ while (i > 0 && !isLineBreakChar(buf(i - 1))) i -= 1
+ var col = 0
+ while (i < offset) {
+ col = if (buf(i) == '\t') (col + tabInc) / tabInc * tabInc else col + 1
+ i += 1
+ }
+ col
+ }
+
+ private var col = currentColumn
+
+ def column = synchronized { col }
+
+ private def addCapacity(n: Int) = {
+ val newlength = length + n
+ while (newlength > buf.length) {
+ val buf1 = Array.ofDim[Char](buf.length * 2)
+ Array.copy(buf, 0, buf1, 0, offset)
+ Array.copy(buf, buf.length - hilen, buf1, buf1.length - hilen, hilen)
+ buf = buf1
+ }
+ }
+
+ private def insertChar(ch: Char) = {
+// Console.err.print("["+ch+"]")
+ buf(offset) = ch
+ offset += 1
+ ch match {
+ case LF => col = 0
+ case '\t' => col = (col + tabInc) / tabInc * tabInc
+ case _ => col += 1
+ }
+ }
+
+ override def write(ch: Int) = synchronized {
+ addCapacity(1)
+ insertChar(ch.toChar)
+ }
+
+ override def write(chs: Array[Char], off: Int, len: Int) = synchronized {
+ addCapacity(len)
+ for (i <- off until off + len) insertChar(chs(i))
+ }
+
+ override def close() {
+ }
+
+ override def flush() {
+ // signal buffer change
+ }
+
+ def currentContents = synchronized {
+ if (length == buf.length) buf
+ else {
+ val res = Array.ofDim[Char](length)
+ Array.copy(buf, 0, res, 0, offset)
+ Array.copy(buf, buf.length - hilen, res, offset, hilen)
+ res
+ }
+ }
+
+ def backspace() = synchronized {
+ offset -= 1
+ if (offset > 0 && buf(offset) == LF && buf(offset - 1) == CR) offset -=1
+ }
+
+ def currentChar = synchronized {
+ buf(buf.length - hilen)
+ }
+
+ def skip(len: Int) = synchronized {
+ for (i <- 0 until len) {
+ val ch = currentChar
+ hilen -= 1
+ insertChar(ch)
+ }
+ }
+}
+
View
93 src/library/scala/runtime/WorksheetSupport.scala
@@ -0,0 +1,93 @@
+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 {
+ protected def flushInterval = 30000000L // interval between flushes, by default 30ms
+ protected def width = 80 // output width, by default 80 characters
+ protected def tabInc = 8 // tab increment, by default 8 characters
+ private var lastFlush: Long = 0L
+ private var col = -1
+ 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 (col < 0) {
+ col = 0
+ write((currentOffset+" ").getBytes)
+ }
+ out.write(c)
+ col =
+ if (c == '\n') -1
+ else if (c == '\t') (col / tabInc) * tabInc + tabInc
+ else col + 1
+ if (col >= width) writeOne('\n')
+ }
+ def ensureNewLine() = if (col > 0) 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: Throwable => 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
+
Please sign in to comment.
Something went wrong with that request. Please try again.