Skip to content

Commit 9775279

Browse files
committed
Fix scala#1312: Improve Xprint
* Do not reprint a tree that has not changed. * Highlight changes with yellow and insertions in green. * -Xprint-diff-del: Inserts the deleted parts of the tree in red and the parts that where changed in magenta.
1 parent 07fd8a3 commit 9775279

File tree

3 files changed

+94
-7
lines changed

3 files changed

+94
-7
lines changed

project/Build.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ object DottyBuild extends Build {
100100
"org.scala-lang.modules" %% "scala-partest" % "1.0.11" % "test",
101101
"com.novocode" % "junit-interface" % "0.11" % "test",
102102
"jline" % "jline" % "2.12",
103+
"com.googlecode.java-diff-utils" % "diffutils" % "1.3.0",
103104
"com.typesafe.sbt" % "sbt-interface" % sbtVersion.value),
104105
// enable improved incremental compilation algorithm
105106
incOptions := incOptions.value.withNameHashing(true),

src/dotty/tools/dotc/Run.scala

Lines changed: 92 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,20 @@ import reporting.Reporter
1010
import transform.TreeChecker
1111
import rewrite.Rewrites
1212
import java.io.{BufferedWriter, OutputStreamWriter}
13+
14+
import scala.annotation.tailrec
1315
import scala.reflect.io.VirtualFile
1416
import scala.util.control.NonFatal
1517

1618
/** A compiler run. Exports various methods to compile source files */
1719
class Run(comp: Compiler)(implicit ctx: Context) {
1820

21+
private final val ANSI_DEFAULT = "\u001B[0m"
22+
private final val ANSI_RED = "\u001B[31m"
23+
private final val ANSI_GREEN = "\u001B[32m"
24+
private final val ANSI_YELLOW = "\u001B[33m"
25+
private final val ANSI_MAGENTA = "\u001B[35m"
26+
1927
assert(comp.phases.last.last.id <= Periods.MaxPossiblePhaseId)
2028
assert(ctx.runId <= Periods.MaxPossibleRunId)
2129

@@ -56,26 +64,103 @@ class Run(comp: Compiler)(implicit ctx: Context) {
5664
val phases = ctx.squashPhases(ctx.phasePlan,
5765
ctx.settings.Yskip.value, ctx.settings.YstopBefore.value, ctx.settings.YstopAfter.value, ctx.settings.Ycheck.value)
5866
ctx.usePhases(phases)
67+
var lastPrintedTree: PrintedTree = NoPrintedTree
5968
for (phase <- ctx.allPhases)
6069
if (!ctx.reporter.hasErrors) {
6170
val start = System.currentTimeMillis
6271
units = phase.runOn(units)
63-
def foreachUnit(op: Context => Unit)(implicit ctx: Context): Unit =
64-
for (unit <- units) op(ctx.fresh.setPhase(phase.next).setCompilationUnit(unit))
65-
if (ctx.settings.Xprint.value.containsPhase(phase))
66-
foreachUnit(printTree)
72+
if (ctx.settings.Xprint.value.containsPhase(phase)) {
73+
for (unit <- units) {
74+
lastPrintedTree =
75+
printTree(ctx.fresh.setPhase(phase.next).setCompilationUnit(unit), lastPrintedTree)
76+
}
77+
}
6778
ctx.informTime(s"$phase ", start)
6879
}
6980
if (!ctx.reporter.hasErrors) Rewrites.writeBack()
7081
}
7182

72-
private def printTree(ctx: Context) = {
83+
private sealed trait PrintedTree
84+
private final case class SomePrintedTree(phase: String, tree: String) extends PrintedTree
85+
private object NoPrintedTree extends PrintedTree
86+
87+
private def printTree(ctx: Context, last: PrintedTree): PrintedTree = {
7388
val unit = ctx.compilationUnit
7489
val prevPhase = ctx.phase.prev // can be a mini-phase
7590
val squashedPhase = ctx.squashed(prevPhase)
91+
val treeString = unit.tpdTree.show(ctx)
92+
93+
ctx.echo(s"result of $unit after $squashedPhase:")
94+
95+
last match {
96+
case SomePrintedTree(phase, lastTreeSting) if lastTreeSting != treeString =>
97+
ctx.echo(mkColoredDiffTree(treeString, lastTreeSting))
98+
SomePrintedTree(squashedPhase.toString, treeString)
99+
100+
case SomePrintedTree(phase, lastTreeSting) =>
101+
ctx.echo(" Unchanged since " + phase)
102+
last
103+
104+
case NoPrintedTree =>
105+
ctx.echo(treeString)
106+
SomePrintedTree(squashedPhase.toString, treeString)
107+
}
108+
}
109+
110+
private def mkColoredDiffTree(treeString: String, lastTreeSting: String): String = {
111+
import difflib._
112+
import scala.collection.JavaConversions._
113+
114+
@tailrec def split(str: String, acc: List[String]): List[String] = {
115+
if (str == "") {
116+
acc.reverse
117+
} else {
118+
val head = str.charAt(0)
119+
val (token, rest) = if (Character.isAlphabetic(head) || Character.isDigit(head)) {
120+
str.span(c => Character.isAlphabetic(c) || Character.isDigit(c))
121+
} else if (Character.isMirrored(head) || Character.isWhitespace(head)) {
122+
str.splitAt(1)
123+
} else {
124+
str.span { c =>
125+
!Character.isAlphabetic(c) && !Character.isDigit(c) &&
126+
!Character.isMirrored(c) && !Character.isWhitespace(c)
127+
}
128+
}
129+
split(rest, token :: acc)
130+
}
131+
}
132+
133+
val lines = split(treeString, Nil).toArray
134+
135+
val printDiffDel = ctx.settings.XprintDiffDel.value
136+
val diff = DiffUtils.diff(split(lastTreeSting, Nil), lines.toList)
137+
138+
for (delta <- diff.getDeltas) {
139+
val pos = delta.getRevised.getPosition
140+
val endPos = pos + delta.getRevised.getLines.size - 1
141+
142+
delta.getType.toString match { // Issue #1355 forces us to use the toString
143+
case "INSERT" =>
144+
lines(pos) = ANSI_GREEN + lines(pos)
145+
lines(endPos) = lines(endPos) + ANSI_DEFAULT
146+
147+
case "CHANGE" =>
148+
val old = if (!printDiffDel) "" else
149+
ANSI_MAGENTA + delta.getOriginal.getLines.mkString + ANSI_DEFAULT
150+
lines(pos) = old + ANSI_YELLOW + lines(pos)
151+
lines(endPos) = lines(endPos) + ANSI_DEFAULT
152+
153+
case "DELETE" if printDiffDel =>
154+
val deleted = delta.getOriginal.getLines.mkString
155+
if (!deleted.forall(Character.isWhitespace)) {
156+
lines(pos) = ANSI_RED + deleted + ANSI_DEFAULT + lines(pos)
157+
}
158+
159+
case _ =>
160+
}
161+
}
76162

77-
ctx.echo(s"result of $unit after ${squashedPhase}:")
78-
ctx.echo(unit.tpdTree.show(ctx))
163+
lines.mkString
79164
}
80165

81166
def compile(sourceCode: String): Unit = {

src/dotty/tools/dotc/config/ScalaSettings.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ class ScalaSettings extends Settings.SettingGroup {
8686
val writeICode = PhasesSetting("-Xprint-icode", "Log internal icode to *.icode files after", "icode")
8787
val Xprintpos = BooleanSetting("-Xprint-pos", "Print tree positions, as offsets.")
8888
val printtypes = BooleanSetting("-Xprint-types", "Print tree types (debugging option).")
89+
val XprintDiffDel = BooleanSetting("-Xprint-diff-del", "Print deleted parts of the tree since last print.")
8990
val prompt = BooleanSetting("-Xprompt", "Display a prompt after each error (debugging option).")
9091
val script = StringSetting("-Xscript", "object", "Treat the source file as a script and wrap it in a main method.", "")
9192
val mainClass = StringSetting("-Xmain-class", "path", "Class for manifest's Main-Class entry (only useful with -d <jar>)", "")

0 commit comments

Comments
 (0)