From 923d5e02298d67f84c13210cd2a20c038b8f3e46 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 25 Jul 2012 14:00:18 +0200 Subject: New Executor. To try it out: run "instrument worksheet" as usual in the REPL. Worksheet needs to have enclosing object definition. Say it is named "Obj". This will generate a file Obj$instrumented. You can compile that file separately and run it using Java. It should reproduce itself. --- .../scala/tools/nsc/interactive/REPL.scala | 2 ++ .../tools/nsc/interactive/ScratchPadMaker.scala | 20 +++++++++++--- .../scala/tools/nsc/scratchpad/Executor.scala | 31 +++++++++++++++++++++- 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/compiler/scala/tools/nsc/interactive/REPL.scala b/src/compiler/scala/tools/nsc/interactive/REPL.scala index 1dcc979255..a19463bee7 100644 --- a/src/compiler/scala/tools/nsc/interactive/REPL.scala +++ b/src/compiler/scala/tools/nsc/interactive/REPL.scala @@ -190,10 +190,12 @@ object REPL { 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)) + println("stripped source = "+strippedSource) comp.askReload(List(strippedSource), reloadResult) comp.askInstrumented(strippedSource, line, instrumentedResult) using(instrumentedResult) { case (iFullName, iContents) => + println(s"instrumented source $iFullName = ${iContents.mkString}") val iSourceName = writeInstrumented(iFullName, iContents) 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 c79248e1c1..45f7c20ba0 100644 --- a/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala +++ b/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala @@ -29,9 +29,21 @@ 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 def applyPendingPatches(offset: Int) = { - if (skipped == 0) patches += Patch(offset, "import scala.tools.nsc.scratchpad.Executor._; ") + if (skipped == 0) patches += Patch(offset, prologue) for (msg <- toPrint) patches += Patch(offset, ";System.out.println("+msg+")") toPrint.clear() } @@ -92,10 +104,12 @@ trait ScratchPadMaker { self: Global => case PackageDef(_, _) => super.traverse(tree) case ModuleDef(_, name, Template(_, _, body)) => - if (objectName.length == 0) - objectName = tree.symbol.fullName + val topLevel = objectName.isEmpty + if (topLevel) objectName = tree.symbol.fullName body foreach traverseStat applyPendingPatches(skipped) + if (topLevel) + patches += Patch(skipped, epilogue) case _ => } diff --git a/src/compiler/scala/tools/nsc/scratchpad/Executor.scala b/src/compiler/scala/tools/nsc/scratchpad/Executor.scala index 89523df71e..d7b93b0031 100644 --- a/src/compiler/scala/tools/nsc/scratchpad/Executor.scala +++ b/src/compiler/scala/tools/nsc/scratchpad/Executor.scala @@ -15,6 +15,7 @@ object Executor { /** 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 @@ -29,7 +30,7 @@ object Executor { Console.setErr(newOut) try { singletonInstance(classLoader, name) - } catch { + } catch { case ex: Throwable => unwrapThrowable(ex) match { case _: StopException => ; @@ -44,6 +45,34 @@ object Executor { 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) -- cgit v1.2.3 From 50903959efc2dcc4093f3d7c0b4b9b9def51cc04 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 25 Jul 2012 15:55:33 +0200 Subject: Disable interrupts during code instrumentation Code instrumentation needs to see a consistent snapshot of a source file. To achieve that we disable interrupts during getInstrumented. --- src/compiler/scala/tools/nsc/interactive/Global.scala | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/compiler/scala/tools/nsc/interactive/Global.scala b/src/compiler/scala/tools/nsc/interactive/Global.scala index 2a435aa6f6..27b6cae2a6 100644 --- a/src/compiler/scala/tools/nsc/interactive/Global.scala +++ b/src/compiler/scala/tools/nsc/interactive/Global.scala @@ -1030,11 +1030,15 @@ class Global(settings: Settings, _reporter: Reporter, projectName: String = "") } } - def getInstrumented(source: SourceFile, line: Int, response: Response[(String, Array[Char])]) { - respond(response) { - instrument(source, line) + def getInstrumented(source: SourceFile, line: Int, response: Response[(String, Array[Char])]) = + try { + interruptsEnabled = false + respond(response) { + instrument(source, line) + } + } finally { + interruptsEnabled = true } - } // ---------------- Helper classes --------------------------- -- cgit v1.2.3 From e896c0ab2d36740207f91086262e9feb4fc1c32d Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Wed, 25 Jul 2012 15:58:45 +0200 Subject: Adds method askForResponse Adds method askForResponse which returns a response immediately instead of waiting for a result. That way, one can wait for an ask's result using a timeout. --- .../scala/tools/nsc/interactive/CompilerControl.scala | 15 ++++++++++++++- src/compiler/scala/tools/nsc/util/InterruptReq.scala | 11 +++++++++++ src/compiler/scala/tools/nsc/util/WorkScheduler.scala | 7 ++++++- 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala b/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala index 801b4ad22b..aab1c8fb7e 100644 --- a/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala +++ b/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala @@ -252,6 +252,19 @@ trait CompilerControl { self: Global => /** Asks for a computation to be done quickly on the presentation compiler thread */ def ask[A](op: () => A): A = if (self.onCompilerThread) op() else scheduler doQuickly op + /** Asks for a computation to be done on presentation compiler thread, returning + * a response with the result or an exception + */ + def askForResponse[A](op: () => A): Response[A] = { + val r = new Response[A] + val ir = scheduler askDoQuickly op + ir onComplete { + case Left(result) => r set result + case Right(exc) => r raise exc + } + r + } + def onCompilerThread = Thread.currentThread == compileRunner /** Info given for every member found by completion @@ -390,7 +403,7 @@ trait CompilerControl { self: Global => case _ => println("don't know what to do with this " + action.getClass) } } - + override def doQuickly[A](op: () => A): A = { throw new FailedInterrupt(new Exception("Posted a work item to a compiler that's shutting down")) } diff --git a/src/compiler/scala/tools/nsc/util/InterruptReq.scala b/src/compiler/scala/tools/nsc/util/InterruptReq.scala index 61aaa1bdcb..816d16f767 100644 --- a/src/compiler/scala/tools/nsc/util/InterruptReq.scala +++ b/src/compiler/scala/tools/nsc/util/InterruptReq.scala @@ -2,6 +2,7 @@ package scala.tools.nsc package util /** A class of work items to be used in interrupt requests. + * Todo: we should replace the Eithers by Futures or Try's. */ abstract class InterruptReq { /** The result type of the operation @@ -11,9 +12,14 @@ abstract class InterruptReq { /** The operation to be performed */ protected val todo: () => R + type Continuation = Either[R, Throwable] => Unit + /** The result provided */ private var result: Option[Either[R, Throwable]] = None + /** The continuations waiting asynchronously on a provided result */ + private var waiting: List[Continuation] = Nil + /** To be called from interrupted server to execute demanded task */ def execute(): Unit = synchronized { try { @@ -22,6 +28,7 @@ abstract class InterruptReq { case t: Throwable => result = Some(Right(t)) } finally { notify() + for (k <- waiting.reverse) k(result.get) } } @@ -38,6 +45,10 @@ abstract class InterruptReq { case Right(t) => throw new FailedInterrupt(t) } } + + def onComplete(k: Continuation) = synchronized { + waiting = k :: waiting + } } class FailedInterrupt(cause: Throwable) extends Exception("Compiler exception during call to 'ask'", cause) diff --git a/src/compiler/scala/tools/nsc/util/WorkScheduler.scala b/src/compiler/scala/tools/nsc/util/WorkScheduler.scala index 8c037cbda5..b1f4696d3e 100644 --- a/src/compiler/scala/tools/nsc/util/WorkScheduler.scala +++ b/src/compiler/scala/tools/nsc/util/WorkScheduler.scala @@ -54,6 +54,11 @@ class WorkScheduler { /** Called from client: have interrupt executed by server and return result */ def doQuickly[A](op: () => A): A = { + val ir = askDoQuickly(op) + ir.getResult() + } + + def askDoQuickly[A](op: () => A): InterruptReq { type R = A } = { val ir = new InterruptReq { type R = A val todo = op @@ -62,7 +67,7 @@ class WorkScheduler { interruptReqs enqueue ir notify() } - ir.getResult() + ir } /** Called from client: have action executed by server */ -- cgit v1.2.3 From b964bac5e295df92d304e97657d01481d63f2d62 Mon Sep 17 00:00:00 2001 From: Martin Odersky Date: Thu, 26 Jul 2012 16:01:27 +0200 Subject: Raw string interpolator Adds a raw string interpolator raw"..." which does not do any escape sequence processing. --- src/library/scala/StringContext.scala | 24 ++++++++++++++++++++---- test/files/run/rawstrings.check | 1 + test/files/run/rawstrings.scala | 3 +++ 3 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 test/files/run/rawstrings.check create mode 100644 test/files/run/rawstrings.scala diff --git a/src/library/scala/StringContext.scala b/src/library/scala/StringContext.scala index f11dfb72ae..7d37fa4aa1 100644 --- a/src/library/scala/StringContext.scala +++ b/src/library/scala/StringContext.scala @@ -26,7 +26,7 @@ case class StringContext(parts: String*) { * @param `args` The arguments to be checked. * @throws An `IllegalArgumentException` if this is not the case. */ - def checkLengths(args: Any*): Unit = + def checkLengths(args: Seq[Any]): Unit = if (parts.length != args.length + 1) throw new IllegalArgumentException("wrong number of arguments for interpolated string") @@ -42,11 +42,27 @@ case class StringContext(parts: String*) { * @throws A `StringContext.InvalidEscapeException` if if a `parts` string contains a backslash (`\`) character * that does not start a valid escape sequence. */ - def s(args: Any*): String = { - checkLengths(args: _*) + def s(args: Any*): String = standardInterpolator(treatEscapes, args) + + /** The raw string interpolator. + * + * It inserts its arguments between corresponding parts of the string context. + * As opposed to the simple string interpolator `s`, this one does not treat + * standard escape sequences as defined in the Scala specification. + * @param `args` The arguments to be inserted into the resulting string. + * @throws An `IllegalArgumentException` + * if the number of `parts` in the enclosing `StringContext` does not exceed + * the number of arguments `arg` by exactly 1. + * @throws A `StringContext.InvalidEscapeException` if if a `parts` string contains a backslash (`\`) character + * that does not start a valid escape sequence. + */ + def raw(args: Any*): String = standardInterpolator(identity, args) + + def standardInterpolator(process: String => String, args: Seq[Any]): String = { + checkLengths(args) val pi = parts.iterator val ai = args.iterator - val bldr = new java.lang.StringBuilder(treatEscapes(pi.next())) + val bldr = new java.lang.StringBuilder(process(pi.next())) while (ai.hasNext) { bldr append ai.next bldr append treatEscapes(pi.next()) diff --git a/test/files/run/rawstrings.check b/test/files/run/rawstrings.check new file mode 100644 index 0000000000..36e63594df --- /dev/null +++ b/test/files/run/rawstrings.check @@ -0,0 +1 @@ +[\n\t'"$] diff --git a/test/files/run/rawstrings.scala b/test/files/run/rawstrings.scala new file mode 100644 index 0000000000..9df64f6625 --- /dev/null +++ b/test/files/run/rawstrings.scala @@ -0,0 +1,3 @@ +object Test extends App { + println(raw"[\n\t'${'"'}$$]") +} -- cgit v1.2.3 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