summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/compiler/scala/reflect/internal/Trees.scala2
-rw-r--r--src/compiler/scala/tools/nsc/interactive/CompilerControl.scala22
-rw-r--r--src/compiler/scala/tools/nsc/interactive/Global.scala9
-rw-r--r--src/compiler/scala/tools/nsc/interactive/REPL.scala15
-rw-r--r--src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala130
-rw-r--r--src/compiler/scala/tools/nsc/scratchpad/CommentOutputStream.scala18
-rw-r--r--src/compiler/scala/tools/nsc/scratchpad/CommentWriter.scala42
-rw-r--r--src/compiler/scala/tools/nsc/scratchpad/Executor.scala28
-rw-r--r--src/compiler/scala/tools/nsc/scratchpad/SourceInserter.scala112
9 files changed, 373 insertions, 5 deletions
diff --git a/src/compiler/scala/reflect/internal/Trees.scala b/src/compiler/scala/reflect/internal/Trees.scala
index 4d202c6c60..5480d90c5a 100644
--- a/src/compiler/scala/reflect/internal/Trees.scala
+++ b/src/compiler/scala/reflect/internal/Trees.scala
@@ -1823,7 +1823,7 @@ trait Trees /*extends reflect.generic.Trees*/ { self: SymbolTable =>
// >: lo <: hi
case ExistentialTypeTree(tpt, whereClauses) => (eliminated by uncurry)
// tpt forSome { whereClauses }
-
+ case SelectFromArray(_, _, _) => (created and eliminated by erasure)
*/
}
diff --git a/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala b/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala
index 747d74f96c..ef5791e6d1 100644
--- a/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala
+++ b/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala
@@ -203,6 +203,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, response: Response[(String, SourceFile)]) =
+ postWorkItem(new AskInstrumentedItem(source, response))
+
/** Cancels current compiler run and start a fresh one where everything will be re-typechecked
* (but not re-loaded).
*/
@@ -211,7 +226,7 @@ trait CompilerControl { self: Global =>
/** Tells the compile server to shutdown, and not to restart again */
def askShutdown() = scheduler raise ShutdownReq
- @deprecated("use parseTree(source) instead") // deleted 2nd parameter, as thius has to run on 2.8 also.
+ @deprecated("use parseTree(source) instead") // deleted 2nd parameter, as this has to run on 2.8 also.
def askParse(source: SourceFile, response: Response[Tree]) = respond(response) {
parseTree(source)
}
@@ -312,6 +327,11 @@ trait CompilerControl { self: Global =>
def apply() = self.getParsedEntered(source, keepLoaded, response, this.onCompilerThread)
override def toString = "getParsedEntered "+source+", keepLoaded = "+keepLoaded
}
+
+ class AskInstrumentedItem(val source: SourceFile, response: Response[(String, SourceFile)]) extends WorkItem {
+ def apply() = self.getInstrumented(source, response)
+ override def toString = "getInstrumented "+source
+ }
}
// ---------------- Interpreted exceptions -------------------
diff --git a/src/compiler/scala/tools/nsc/interactive/Global.scala b/src/compiler/scala/tools/nsc/interactive/Global.scala
index af5b9a9882..4f1f0946ed 100644
--- a/src/compiler/scala/tools/nsc/interactive/Global.scala
+++ b/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._
@@ -630,7 +631,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)
@@ -912,6 +913,12 @@ class Global(settings: Settings, reporter: Reporter, projectName: String = "")
}
}
+ def getInstrumented(source: SourceFile, response: Response[(String, SourceFile)]) {
+ respond(response) {
+ instrument(source)
+ }
+ }
+
// ---------------- Helper classes ---------------------------
/** A transformer that replaces tree `from` with tree `to` in a given tree */
diff --git a/src/compiler/scala/tools/nsc/interactive/REPL.scala b/src/compiler/scala/tools/nsc/interactive/REPL.scala
index 7545a2f714..f5b536a3f4 100644
--- a/src/compiler/scala/tools/nsc/interactive/REPL.scala
+++ b/src/compiler/scala/tools/nsc/interactive/REPL.scala
@@ -89,6 +89,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, SourceFile)]
def makePos(file: String, off1: String, off2: String) = {
val source = toSourceFile(file)
@@ -111,6 +112,11 @@ object REPL {
show(structureResult)
}
+ def formatInstrumented(r: (String, SourceFile)) = {
+ val (name, source) = r
+ "toplevel object = "+name+", contents = \n"+source.content.mkString
+ }
+
loop { line =>
(line split " ").toList match {
case "reload" :: args =>
@@ -132,6 +138,11 @@ object REPL {
doComplete(makePos(file, off1, off2))
case List("complete", file, off1) =>
doComplete(makePos(file, off1, off1))
+ case List("instrument", file) =>
+ val source = toSourceFile(file)
+ comp.askReload(List(source), reloadResult)
+ comp.askInstrumented(source, instrumentedResult)
+ show(instrumentedResult, formatInstrumented)
case List("quit") =>
comp.askShutdown()
// deleted sys. as this has to run on 2.8 also
@@ -146,9 +157,9 @@ object REPL {
def toSourceFile(name: String) = new BatchSourceFile(new PlainFile(new java.io.File(name)))
- def show[T](svar: Response[T]) {
+ def show[T](svar: Response[T], transform: T => Any = (x: T) => x) {
svar.get match {
- case Left(result) => println("==> "+result)
+ case Left(result) => println("==> "+transform(result))
case Right(exc) => exc.printStackTrace; println("ERROR: "+exc)
}
svar.clear()
diff --git a/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala b/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala
new file mode 100644
index 0000000000..a54513c9ab
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala
@@ -0,0 +1,130 @@
+package scala.tools.nsc
+package interactive
+
+import util.{SourceFile, BatchSourceFile, RangePosition}
+import collection.mutable.ArrayBuffer
+import reflect.internal.Chars.isLineBreakChar
+
+trait ScratchPadMaker { self: Global =>
+
+ import definitions._
+
+ private case class Patch(offset: Int, text: String)
+
+ private class Patcher(contents: Array[Char]) 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 def addSkip(stat: Tree): Unit = {
+ if (stat.pos.start > skipped) {
+ for (msg <- toPrint)
+ patches += Patch(stat.pos.start, ";println("+msg+")")
+ }
+ var end = stat.pos.end
+ if (end > skipped) {
+ while (end < contents.length && !(isLineBreakChar(contents(end)))) end += 1
+ patches += Patch(stat.pos.start, ";skip("+(end-skipped)+"); ")
+ skipped = end
+ }
+ }
+
+ private def addSandbox(expr: Tree) =
+ patches += (Patch(expr.pos.start, "sandbox("), Patch(expr.pos.end, ")"))
+
+ 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 += literal(nameType(stat.symbol)+" = ")+" + "+stat.symbol.name
+ }
+ 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 += literal(nameType(dispResName, stat.tpe)+" = ")+" + "+resName
+ }
+ }
+ }
+ }
+
+ override def traverse(tree: Tree): Unit = tree match {
+ case PackageDef(_, _) =>
+ super.traverse(tree)
+ case ModuleDef(_, name, Template(_, _, body)) =>
+ if (objectName.isEmpty) objectName = tree.symbol.fullName
+ body foreach traverseStat
+ 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.
+ * @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): (String, SourceFile) = {
+ val tree = typedTree(source, true)
+ val patcher = new Patcher(source.content)
+ patcher.traverse(tree)
+ (patcher.objectName, new BatchSourceFile(source.file, patcher.result))
+ }
+}
diff --git a/src/compiler/scala/tools/nsc/scratchpad/CommentOutputStream.scala b/src/compiler/scala/tools/nsc/scratchpad/CommentOutputStream.scala
new file mode 100644
index 0000000000..92ccd79df9
--- /dev/null
+++ b/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()
+} \ No newline at end of file
diff --git a/src/compiler/scala/tools/nsc/scratchpad/CommentWriter.scala b/src/compiler/scala/tools/nsc/scratchpad/CommentWriter.scala
new file mode 100644
index 0000000000..607aa97568
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/scratchpad/CommentWriter.scala
@@ -0,0 +1,42 @@
+package scala.tools.nsc.scratchpad
+
+import java.io.Writer
+import reflect.internal.Chars._
+
+
+class CommentWriter(underlying: SourceInserter, startCol: Int = 40, endCol: Int = 124) 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()
+}
+
diff --git a/src/compiler/scala/tools/nsc/scratchpad/Executor.scala b/src/compiler/scala/tools/nsc/scratchpad/Executor.scala
new file mode 100644
index 0000000000..510f6c5a3b
--- /dev/null
+++ b/src/compiler/scala/tools/nsc/scratchpad/Executor.scala
@@ -0,0 +1,28 @@
+package scala.tools.nsc.scratchpad
+
+import java.io.{PrintStream, OutputStreamWriter, Writer}
+
+object Executor {
+
+ println("exec started")
+
+ def execute(name: String, si: SourceInserter) {
+ val oldOut = System.out
+ val oldErr = System.err
+ val cwr = new CommentWriter(si)
+ val newOut = new PrintStream(new CommentOutputStream(cwr))
+ java.lang.System.setOut(newOut)
+ java.lang.System.setErr(newOut)
+
+ try {
+ Class.forName(name).newInstance()
+ } catch {
+ case ex: Throwable =>
+ ex.printStackTrace()
+ } finally {
+ cwr.close()
+ System.setOut(oldOut)
+ System.setErr(oldErr)
+ }
+ }
+} \ No newline at end of file
diff --git a/src/compiler/scala/tools/nsc/scratchpad/SourceInserter.scala b/src/compiler/scala/tools/nsc/scratchpad/SourceInserter.scala
new file mode 100644
index 0000000000..f6c91838db
--- /dev/null
+++ b/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 reflect.internal.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 = 0
+
+ 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) = {
+ buf(offset) = ch.toChar
+ 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() {
+ buf = null
+ }
+
+ 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) {
+ insertChar(currentChar)
+ hilen -= 1
+ }
+ }
+}
+