summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorodersky <odersky@gmail.com>2012-07-30 05:40:57 -0700
committerodersky <odersky@gmail.com>2012-07-30 05:40:57 -0700
commite77dbfc774b929df78aa408e86bce7eeec1271ee (patch)
treef4e9a90c0a4c1bd9e488da865852040081e35fbf
parent1ad6e501997d332e2bc0ccc8dd40e3909e728930 (diff)
parente3bfe109732b7c1cce7493b2869614ed145660f6 (diff)
downloadscala-e77dbfc774b929df78aa408e86bce7eeec1271ee.tar.gz
scala-e77dbfc774b929df78aa408e86bce7eeec1271ee.tar.bz2
scala-e77dbfc774b929df78aa408e86bce7eeec1271ee.zip
Merge pull request #1005 from odersky/topic/worksheet
IDE improvements, with particular focus on making worksheets work.
-rw-r--r--src/compiler/scala/tools/nsc/interactive/CompilerControl.scala15
-rw-r--r--src/compiler/scala/tools/nsc/interactive/Global.scala12
-rw-r--r--src/compiler/scala/tools/nsc/interactive/REPL.scala13
-rw-r--r--src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala12
-rw-r--r--src/compiler/scala/tools/nsc/scratchpad/Executor.scala55
-rw-r--r--src/compiler/scala/tools/nsc/scratchpad/Mixer.scala98
-rw-r--r--src/compiler/scala/tools/nsc/util/InterruptReq.scala11
-rw-r--r--src/compiler/scala/tools/nsc/util/WorkScheduler.scala7
-rw-r--r--src/library/scala/StringContext.scala24
-rw-r--r--src/library/scala/runtime/WorksheetSupport.scala87
-rw-r--r--test/files/run/rawstrings.check1
-rw-r--r--test/files/run/rawstrings.scala3
12 files changed, 266 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
+
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'${'"'}$$]")
+}