diff options
Diffstat (limited to 'src')
10 files changed, 262 insertions, 72 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/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 --------------------------- diff --git a/src/compiler/scala/tools/nsc/interactive/REPL.scala b/src/compiler/scala/tools/nsc/interactive/REPL.scala index 1dcc979255..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,17 +186,22 @@ 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)) + 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) + 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 c79248e1c1..bd1869e1a4 100644 --- a/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala +++ b/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala @@ -30,8 +30,12 @@ trait ScratchPadMaker { self: Global => 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, "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 +96,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 deleted file mode 100644 index 89523df71e..0000000000 --- a/src/compiler/scala/tools/nsc/scratchpad/Executor.scala +++ /dev/null @@ -1,55 +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. - */ - 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 $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/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 */ 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/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 + |