Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Made instrumenter more robust by looking at tokens

Made instrumenter more robust by taking into account the positions of the tokens in the source. This allows us to reliably put instrumentation code at the end of the previous token, which tends to put it at end of lines. Furthermore, we now skip left parents. Previously, the instrumenter got confused for a statement like

( x + 1 )

because it thought that the statement started at the `x` and not at the `(`.

Another small change is that we now use decoded names in the worksheet. So ??? will show as ??? not $qmark$qmark$qmark.
(cherry picked from commit b2ecb93)

Conflicts:

	src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala
  • Loading branch information...
commit 321d3f5689278762174ed66ea11110e87f7aed55 1 parent a082aa2
@odersky odersky authored dragos committed
View
61 src/compiler/scala/tools/nsc/interactive/ScratchPadMaker.scala
@@ -4,6 +4,7 @@ package interactive
import util.{SourceFile, BatchSourceFile, RangePosition}
import collection.mutable.ArrayBuffer
import util.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.decode, 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)
}
View
6 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

0 comments on commit 321d3f5

Please sign in to comment.
Something went wrong with that request. Please try again.