summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Odersky <odersky@gmail.com>2011-07-06 10:49:14 +0000
committerMartin Odersky <odersky@gmail.com>2011-07-06 10:49:14 +0000
commit124e2f95aee0085b5df1af899008a63a762bece6 (patch)
tree991728d457c7df09349a3d693afcf07e660c510e
parentf7bca9a7bf1fd16ae3261c9977f236b4f588be3d (diff)
downloadscala-124e2f95aee0085b5df1af899008a63a762bece6.tar.gz
scala-124e2f95aee0085b5df1af899008a63a762bece6.tar.bz2
scala-124e2f95aee0085b5df1af899008a63a762bece6.zip
scratchpad works in interactive REPL; ready to ...
scratchpad works in interactive REPL; ready to be integrate in Eclipse.
-rw-r--r--src/compiler/scala/tools/nsc/interactive/CompilerControl.scala8
-rw-r--r--src/compiler/scala/tools/nsc/interactive/Global.scala4
-rw-r--r--src/compiler/scala/tools/nsc/interactive/REPL.scala58
-rw-r--r--src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala95
-rw-r--r--src/compiler/scala/tools/nsc/scratchpad/CommentWriter.scala2
-rw-r--r--src/compiler/scala/tools/nsc/scratchpad/Executor.scala58
-rw-r--r--src/compiler/scala/tools/nsc/scratchpad/SourceInserter.scala7
7 files changed, 154 insertions, 78 deletions
diff --git a/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala b/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala
index ef5791e6d1..5cb7a1d811 100644
--- a/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala
+++ b/src/compiler/scala/tools/nsc/interactive/CompilerControl.scala
@@ -215,8 +215,8 @@ trait CompilerControl { self: Global =>
* 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))
+ 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).
@@ -328,8 +328,8 @@ trait CompilerControl { self: Global =>
override def toString = "getParsedEntered "+source+", keepLoaded = "+keepLoaded
}
- class AskInstrumentedItem(val source: SourceFile, response: Response[(String, SourceFile)]) extends WorkItem {
- def apply() = self.getInstrumented(source, response)
+ 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
}
}
diff --git a/src/compiler/scala/tools/nsc/interactive/Global.scala b/src/compiler/scala/tools/nsc/interactive/Global.scala
index 4f1f0946ed..9e61e880c3 100644
--- a/src/compiler/scala/tools/nsc/interactive/Global.scala
+++ b/src/compiler/scala/tools/nsc/interactive/Global.scala
@@ -913,9 +913,9 @@ class Global(settings: Settings, reporter: Reporter, projectName: String = "")
}
}
- def getInstrumented(source: SourceFile, response: Response[(String, SourceFile)]) {
+ def getInstrumented(source: SourceFile, line: Int, response: Response[(String, Array[Char])]) {
respond(response) {
- instrument(source)
+ instrument(source, line)
}
}
diff --git a/src/compiler/scala/tools/nsc/interactive/REPL.scala b/src/compiler/scala/tools/nsc/interactive/REPL.scala
index f5b536a3f4..b3ebc4aa1b 100644
--- a/src/compiler/scala/tools/nsc/interactive/REPL.scala
+++ b/src/compiler/scala/tools/nsc/interactive/REPL.scala
@@ -11,6 +11,8 @@ 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 java.io.{File, FileWriter}
/** Interface of interactive compiler to a client such as an IDE
*/
@@ -89,32 +91,62 @@ 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)]
+ val instrumentedResult = new Response[(String, Array[Char])]
def makePos(file: String, off1: String, off2: String) = {
val source = toSourceFile(file)
comp.rangePos(source, off1.toInt, off1.toInt, off2.toInt)
}
+
def doTypeAt(pos: Position) {
comp.askTypeAt(pos, typeatResult)
show(typeatResult)
}
+
def doComplete(pos: Position) {
comp.askTypeCompletion(pos, completeResult)
show(completeResult)
}
+
def doTypedTree(file: String) {
comp.askType(toSourceFile(file), true, typedResult)
show(typedResult)
}
+
def doStructure(file: String) {
comp.askParsedEntered(toSourceFile(file), false, structureResult)
show(structureResult)
}
- def formatInstrumented(r: (String, SourceFile)) = {
- val (name, source) = r
- "toplevel object = "+name+", contents = \n"+source.content.mkString
+ /** This is the method for implement worksheet functionality
+ */
+ def instrument(source: SourceFile, line: Int): Option[Array[Char]] = {
+ // strip right hand side comment column and any trailing spaces from all lines
+ val strippedSource = new BatchSourceFile(source.file, SourceInserter.stripRight(source.content))
+ comp.askReload(List(strippedSource), reloadResult)
+ // todo: Display strippedSource in place of source
+ comp.askInstrumented(strippedSource, line, instrumentedResult)
+ using(instrumentedResult) { case (iFullName, iContents) =>
+ // iFullName: The full name of the first top-level object in source
+ // iContents: An Array[Char] containing the instrumented source
+ // Create a file from iContents so that it can be compiled
+ // The name here is <simple name of object>+"$instrumented.scala", but
+ // it could be anything.
+ val iSimpleName = iFullName drop ((iFullName lastIndexOf '.') + 1)
+ val iSourceName = iSimpleName + "$instrumented.scala"
+ val ifile = new FileWriter(iSourceName)
+ ifile.write(iContents)
+ ifile.close()
+ println("compiling "+iSourceName)
+ // Compile instrumented source
+ scala.tools.nsc.Main.process(Array(iSourceName, "-verbose", "-d", "/classes"))
+ // Run instrumented source, inserting all output into stripped source
+ println("running "+iSimpleName)
+ val si = new SourceInserter(strippedSource.content)
+ Executor.execute(iSimpleName, si)
+ println("done")
+ si.currentContents
+ }
}
loop { line =>
@@ -139,10 +171,9 @@ object REPL {
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)
+ println(instrument(toSourceFile(file), -1).map(_.mkString))
+ case List("instrument", file, line) =>
+ println(instrument(toSourceFile(file), line.toInt).map(_.mkString))
case List("quit") =>
comp.askShutdown()
// deleted sys. as this has to run on 2.8 also
@@ -157,11 +188,14 @@ object REPL {
def toSourceFile(name: String) = new BatchSourceFile(new PlainFile(new java.io.File(name)))
- def show[T](svar: Response[T], transform: T => Any = (x: T) => x) {
- svar.get match {
- case Left(result) => println("==> "+transform(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))
}
diff --git a/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala b/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala
index e284b816fd..1c7a8523eb 100644
--- a/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala
+++ b/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala
@@ -11,7 +11,7 @@ trait ScratchPadMaker { self: Global =>
private case class Patch(offset: Int, text: String)
- private class Patcher(contents: Array[Char]) extends Traverser {
+ private class Patcher(contents: Array[Char], endOffset: Int) extends Traverser {
var objectName: String = ""
private val patches = new ArrayBuffer[Patch]
@@ -28,56 +28,62 @@ trait ScratchPadMaker { self: Global =>
private def nameType(sym: Symbol): String = nameType(sym.name.toString, sym.tpe)
- private def literal(str: String) = "\"\"\"\""+str+"\"\"\"\""
+ private def literal(str: String) = "\"\"\""+str+"\"\"\""
+
+ private def applyPendingPatches(offset: Int) = {
+ if (skipped == 0) patches += Patch(offset, "import scala.tools.nsc.scratchpad.Executor._; ")
+ for (msg <- toPrint) patches += Patch(offset, ";System.out.println("+msg+")")
+ toPrint.clear()
+ }
private def addSkip(stat: Tree): Unit = {
- if (stat.pos.start > skipped) {
- for (msg <- toPrint)
- patches += Patch(stat.pos.start, ";println("+msg+")")
- }
+ if (stat.pos.start > skipped) applyPendingPatches(stat.pos.start)
+ if (stat.pos.start >= endOffset)
+ patches += Patch(stat.pos.start, ";$stop()")
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)+"); ")
+ 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
- }
+ 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(_, _) =>
@@ -86,6 +92,7 @@ trait ScratchPadMaker { self: Global =>
if (objectName.length == 0 /* objectName.isEmpty does not compile on Java 5 due to ambiguous implicit conversions: augmentString, stringToTermName */)
objectName = tree.symbol.fullName
body foreach traverseStat
+ applyPendingPatches(skipped)
case _ =>
}
@@ -116,16 +123,18 @@ trait ScratchPadMaker { self: Global =>
/** 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): (String, SourceFile) = {
+ protected def instrument(source: SourceFile, line: Int): (String, Array[Char]) = {
val tree = typedTree(source, true)
- val patcher = new Patcher(source.content)
+ val endOffset = if (line < 0) source.length else source.lineToOffset(line + 1)
+ val patcher = new Patcher(source.content, endOffset)
patcher.traverse(tree)
- (patcher.objectName, new BatchSourceFile(source.file, patcher.result))
+ (patcher.objectName, patcher.result)
}
}
diff --git a/src/compiler/scala/tools/nsc/scratchpad/CommentWriter.scala b/src/compiler/scala/tools/nsc/scratchpad/CommentWriter.scala
index 607aa97568..eb8880e437 100644
--- a/src/compiler/scala/tools/nsc/scratchpad/CommentWriter.scala
+++ b/src/compiler/scala/tools/nsc/scratchpad/CommentWriter.scala
@@ -4,7 +4,7 @@ import java.io.Writer
import reflect.internal.Chars._
-class CommentWriter(underlying: SourceInserter, startCol: Int = 40, endCol: Int = 124) extends Writer {
+class CommentWriter(underlying: SourceInserter, startCol: Int = 40, endCol: Int = 152) extends Writer {
private def rightCol(marker: String) = {
while (underlying.column < startCol) underlying.write(' ')
diff --git a/src/compiler/scala/tools/nsc/scratchpad/Executor.scala b/src/compiler/scala/tools/nsc/scratchpad/Executor.scala
index 510f6c5a3b..8eead4f296 100644
--- a/src/compiler/scala/tools/nsc/scratchpad/Executor.scala
+++ b/src/compiler/scala/tools/nsc/scratchpad/Executor.scala
@@ -1,28 +1,60 @@
package scala.tools.nsc.scratchpad
import java.io.{PrintStream, OutputStreamWriter, Writer}
+import java.lang.reflect.InvocationTargetException
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)
+ private var currentWriter: CommentWriter = null
+
+ def ultimateCause(ex: Throwable): Throwable = ex match {
+ case ex: InvocationTargetException =>
+ ultimateCause(ex.getCause)
+ case ex: ExceptionInInitializerError =>
+ ultimateCause(ex.getCause)
+ case ex =>
+ ex
+ }
+ /** 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) {
+ 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 {
- Class.forName(name).newInstance()
+ val clazz = Class.forName(name+"$")
+ clazz.getField("$MODULE").get(null)
} catch {
case ex: Throwable =>
- ex.printStackTrace()
+ ultimateCause(ex) match {
+ case _: StopException => ;
+ case cause => cause.printStackTrace()
+ }
} finally {
- cwr.close()
- System.setOut(oldOut)
- System.setErr(oldErr)
+ currentWriter.close()
+ System.setOut(oldSysOut)
+ System.setErr(oldSysErr)
+ Console.setOut(oldConsOut)
+ Console.setErr(oldConsErr)
+ currentWriter = oldCwr
}
}
-} \ No newline at end of file
+
+ def $skip(n: Int) = currentWriter.skip(n)
+
+ def $stop() = throw new StopException
+}
+
+class StopException extends Exception
diff --git a/src/compiler/scala/tools/nsc/scratchpad/SourceInserter.scala b/src/compiler/scala/tools/nsc/scratchpad/SourceInserter.scala
index f6c91838db..68817f2f64 100644
--- a/src/compiler/scala/tools/nsc/scratchpad/SourceInserter.scala
+++ b/src/compiler/scala/tools/nsc/scratchpad/SourceInserter.scala
@@ -26,7 +26,7 @@ class SourceInserter(contents: Array[Char], start: Int = 0, tabInc: Int = 8) ext
private var buf = contents
private var offset = start
- private var hilen = 0
+ private var hilen = contents.length
def length = offset + hilen
@@ -56,6 +56,7 @@ class SourceInserter(contents: Array[Char], start: Int = 0, tabInc: Int = 8) ext
}
private def insertChar(ch: Char) = {
+// Console.err.print("["+ch+"]")
buf(offset) = ch.toChar
offset += 1
ch match {
@@ -76,7 +77,6 @@ class SourceInserter(contents: Array[Char], start: Int = 0, tabInc: Int = 8) ext
}
override def close() {
- buf = null
}
override def flush() {
@@ -104,8 +104,9 @@ class SourceInserter(contents: Array[Char], start: Int = 0, tabInc: Int = 8) ext
def skip(len: Int) = synchronized {
for (i <- 0 until len) {
- insertChar(currentChar)
+ val ch = currentChar
hilen -= 1
+ insertChar(ch)
}
}
}