diff options
author | Josh Suereth <Joshua.Suereth@gmail.com> | 2012-09-04 12:16:44 -0700 |
---|---|---|
committer | Josh Suereth <Joshua.Suereth@gmail.com> | 2012-09-04 12:16:44 -0700 |
commit | d34e8abaacf7a51e05630557681e8267d7050e3a (patch) | |
tree | a96e186e8ee3c6be67451269f1b94ab81b6d8acc | |
parent | 3041fc5fe3b36e618cd0a4b2d909c219050a5c3f (diff) | |
parent | bcf1d9a5f5c1d0319b51cd3dcce9ecebdeb12feb (diff) | |
download | scala-d34e8abaacf7a51e05630557681e8267d7050e3a.tar.gz scala-d34e8abaacf7a51e05630557681e8267d7050e3a.tar.bz2 scala-d34e8abaacf7a51e05630557681e8267d7050e3a.zip |
Merge pull request #1219 from odersky/topic/worksheet-instrumenter
More worksheet nstrumentation changes
6 files changed, 51 insertions, 211 deletions
diff --git a/src/compiler/scala/tools/nsc/interactive/REPL.scala b/src/compiler/scala/tools/nsc/interactive/REPL.scala index 2d93c77ca4..afac5828e5 100644 --- a/src/compiler/scala/tools/nsc/interactive/REPL.scala +++ b/src/compiler/scala/tools/nsc/interactive/REPL.scala @@ -133,50 +133,6 @@ object REPL { iSourceName } - /** Compile instrumented source file - * @param iSourceName The name of the instrumented source file - * @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, _)) - val virtualDirectoryOpt = - if (arguments contains "-d") - None - else { - val vdir = new VirtualDirectory("(memory)", None) - command.settings.outputDirs setSingleOutput vdir - Some(vdir) - } - val compiler = new scala.tools.nsc.Global(command.settings, reporter) - val run = new compiler.Run() - println("compiling: "+command.files) - run compile command.files - virtualDirectoryOpt - } - - /** Run instrumented bytecode file - * @param vdir Optionally, the virtual directory containing the generated bytecode classes - * @param iFullName The full name of the generated object - * @param stripped The contents original source file without any right hand column comments. - * @return The generated file content containing original source in the left column - * and outputs in the right column - */ - def runInstrumented(vdirOpt: Option[AbstractFile], iFullName: String, stripped: Array[Char]): Array[Char] = { - val defaultClassLoader = getClass.getClassLoader - val classLoader = vdirOpt match { - case Some(vdir) => new AbstractFileClassLoader(vdir, defaultClassLoader) - case None => defaultClassLoader - } - println("running "+iFullName) - val si = new SourceInserter(stripped) - Executor.execute(iFullName, si, classLoader) - println("done") - si.currentContents - } - */ - /** The method for implementing worksheet functionality. * @param arguments a file name, followed by optional command line arguments that are passed * to the compiler that processes the instrumented source. @@ -191,7 +147,7 @@ object REPL { // strip right hand side comment column and any trailing spaces from all lines val strippedContents = SourceInserter.stripRight(source.content) val strippedSource = new BatchSourceFile(source.file, strippedContents) - println("stripped source = "+strippedSource) + println("stripped source = "+strippedSource+":"+strippedContents.mkString) comp.askReload(List(strippedSource), reloadResult) comp.askInstrumented(strippedSource, line, instrumentedResult) using(instrumentedResult) { diff --git a/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala b/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala index 2aed99657e..0080cfd753 100644 --- a/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala +++ b/src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala @@ -4,6 +4,7 @@ package interactive import scala.reflect.internal.util.{SourceFile, BatchSourceFile, RangePosition} import collection.mutable.ArrayBuffer import reflect.internal.Chars.{isLineBreakChar, isWhitespace} +import ast.parser.Tokens._ trait ScratchPadMaker { self: Global => @@ -11,7 +12,7 @@ trait ScratchPadMaker { self: Global => private case class Patch(offset: Int, text: String) - private class Patcher(contents: Array[Char], endOffset: Int) extends Traverser { + private class Patcher(contents: Array[Char], lex: LexicalStructure, endOffset: Int) extends Traverser { var objectName: String = "" private val patches = new ArrayBuffer[Patch] @@ -24,9 +25,13 @@ trait ScratchPadMaker { self: Global => "res$"+resNum } - private def nameType(name: String, tpe: Type): String = name+": "+tpe + private def nameType(name: String, tpe: Type): String = { + // if name ends in symbol character, add a space to separate it from the following ':' + val pad = if (Character.isLetter(name.last) || Character.isDigit(name.last)) "" else " " + name+pad+": "+tpe + } - private def nameType(sym: Symbol): String = nameType(sym.name.toString, sym.tpe) + private def nameType(sym: Symbol): String = nameType(sym.name.decoded, sym.tpe) private def literal(str: String) = "\"\"\""+str+"\"\"\"" @@ -42,19 +47,19 @@ trait ScratchPadMaker { self: Global => /** The position where to insert an instrumentation statement in front of giuven statement. * This is at the latest `stat.pos.start`. But in order not to mess with column numbers - * in position we try to insert it at the end of the preceding line instead. - * To be safe, this can be done only if there's only whitespace between that position and - * statement's start position. + * in position we try to insert it at the end of the previous token instead. + * Furthermore, `(' tokens have to be skipped because they do not show up + * in statement range positions. */ - private def instrumentPos(stat: Tree): Int = { - var start = stat.pos.start - while (start > 0 && isWhitespace(contents(start - 1))) start -= 1 - if (start > 0 && isLineBreakChar(contents(start - 1))) start -= 1 - start + private def instrumentPos(start: Int): Int = { + val (prevToken, prevStart, prevEnd) = lex.locate(start - 1) + if (prevStart >= start) start + else if (prevToken == LPAREN) instrumentPos(prevStart) + else prevEnd } private def addSkip(stat: Tree): Unit = { - val ipos = instrumentPos(stat) + val ipos = instrumentPos(stat.pos.start) if (stat.pos.start > skipped) applyPendingPatches(ipos) if (stat.pos.start >= endOffset) patches += Patch(ipos, ";$stop()") @@ -98,7 +103,8 @@ trait ScratchPadMaker { self: Global => } else { val resName = nextRes() val dispResName = resName filter ('$' != _) - patches += Patch(stat.pos.start, "val " + resName + " = ") + val offset = instrumentPos(stat.pos.start) + patches += Patch(offset, "val " + resName + " = ") addSandbox(stat) toPrint += resultString(nameType(dispResName, stat.tpe), resName) } @@ -146,6 +152,33 @@ trait ScratchPadMaker { self: Global => } } + class LexicalStructure(source: SourceFile) { + val token = new ArrayBuffer[Int] + val startOffset = new ArrayBuffer[Int] + val endOffset = new ArrayBuffer[Int] + private val scanner = new syntaxAnalyzer.UnitScanner(new CompilationUnit(source)) + scanner.init() + while (scanner.token != EOF) { + startOffset += scanner.offset + token += scanner.token + scanner.nextToken + endOffset += scanner.lastOffset + } + + /** @return token that starts before or at offset, its startOffset, its endOffset + */ + def locate(offset: Int): (Int, Int, Int) = { + var lo = 0 + var hi = token.length - 1 + while (lo < hi) { + val mid = (lo + hi + 1) / 2 + if (startOffset(mid) <= offset) lo = mid + else hi = mid - 1 + } + (token(lo), startOffset(lo), endOffset(lo)) + } + } + /** 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. @@ -158,7 +191,7 @@ trait ScratchPadMaker { self: Global => protected def instrument(source: SourceFile, line: Int): (String, Array[Char]) = { val tree = typedTree(source, true) val endOffset = if (line < 0) source.length else source.lineToOffset(line + 1) - val patcher = new Patcher(source.content, endOffset) + val patcher = new Patcher(source.content, new LexicalStructure(source), endOffset) patcher.traverse(tree) (patcher.objectName, patcher.result) } diff --git a/src/compiler/scala/tools/nsc/scratchpad/CommentOutputStream.scala b/src/compiler/scala/tools/nsc/scratchpad/CommentOutputStream.scala deleted file mode 100644 index 92ccd79df9..0000000000 --- a/src/compiler/scala/tools/nsc/scratchpad/CommentOutputStream.scala +++ /dev/null @@ -1,18 +0,0 @@ -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 deleted file mode 100644 index eb8880e437..0000000000 --- a/src/compiler/scala/tools/nsc/scratchpad/CommentWriter.scala +++ /dev/null @@ -1,42 +0,0 @@ -package scala.tools.nsc.scratchpad - -import java.io.Writer -import reflect.internal.Chars._ - - -class CommentWriter(underlying: SourceInserter, startCol: Int = 40, endCol: Int = 152) 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/SourceInserter.scala b/src/compiler/scala/tools/nsc/scratchpad/SourceInserter.scala index 42a35dc642..1c4fad5511 100644 --- a/src/compiler/scala/tools/nsc/scratchpad/SourceInserter.scala +++ b/src/compiler/scala/tools/nsc/scratchpad/SourceInserter.scala @@ -21,92 +21,3 @@ object SourceInserter { (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 = contents.length - - 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) = { -// Console.err.print("["+ch+"]") - buf(offset) = ch - 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() { - } - - 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) { - val ch = currentChar - hilen -= 1 - insertChar(ch) - } - } -} - diff --git a/src/library/scala/runtime/WorksheetSupport.scala b/src/library/scala/runtime/WorksheetSupport.scala index 6f2a4d382d..a003bba034 100644 --- a/src/library/scala/runtime/WorksheetSupport.scala +++ b/src/library/scala/runtime/WorksheetSupport.scala @@ -40,9 +40,9 @@ object WorksheetSupport { write((currentOffset+" ").getBytes) } out.write(c) - col = + col = if (c == '\n') -1 - else if (c == '\t') (col / tabInc) * tabInc + tabInc + else if (c == '\t') (col / tabInc) * tabInc + tabInc else col + 1 if (col >= width) writeOne('\n') } @@ -86,7 +86,7 @@ object WorksheetSupport { def $stop() = throw new StopException - def $show(x: Any): String = stringOf(x, scala.Int.MaxValue) + def $show(x: Any): String = stringOf(x) } class StopException extends Exception |