Skip to content

Commit d024f46

Browse files
committed
Fix #1405: Implement Xprint-diff without external libraries.
1 parent 62348de commit d024f46

File tree

2 files changed

+108
-27
lines changed

2 files changed

+108
-27
lines changed

project/Build.scala

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ object DottyBuild extends Build {
9999
libraryDependencies ++= Seq("org.scala-lang.modules" %% "scala-xml" % "1.0.1",
100100
"org.scala-lang.modules" %% "scala-partest" % "1.0.11" % "test",
101101
"com.novocode" % "junit-interface" % "0.11" % "test",
102-
"com.googlecode.java-diff-utils" % "diffutils" % "1.3.0",
103102
"com.typesafe.sbt" % "sbt-interface" % sbtVersion.value),
104103
// enable improved incremental compilation algorithm
105104
incOptions := incOptions.value.withNameHashing(true),
@@ -177,7 +176,7 @@ object DottyBuild extends Build {
177176
path = file.getAbsolutePath
178177
} yield "-Xbootclasspath/p:" + path
179178
// dotty itself needs to be in the bootclasspath
180-
val fullpath = ("-Xbootclasspath/p:" + "dotty.jar") :: ("-Xbootclasspath/a:" + bin) :: path.toList
179+
val fullpath = ("-Xbootclasspath/a:" + bin) :: path.toList
181180
// System.err.println("BOOTPATH: " + fullpath)
182181

183182
val travis_build = // propagate if this is a travis build
Lines changed: 107 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package dotty.tools.dotc.util
22

33
import scala.annotation.tailrec
4-
import difflib._
4+
5+
import scala.collection.mutable
56

67
object DiffUtil {
78

@@ -13,9 +14,8 @@ object DiffUtil {
1314
private final val ADDITION_COLOR = ANSI_GREEN
1415

1516
def mkColoredCodeDiff(code: String, lastCode: String, printDiffDel: Boolean): String = {
16-
import scala.collection.JavaConversions._
1717

18-
@tailrec def split(str: String, acc: List[String]): List[String] = {
18+
@tailrec def splitTokens(str: String, acc: List[String] = Nil): List[String] = {
1919
if (str == "") {
2020
acc.reverse
2121
} else {
@@ -30,38 +30,120 @@ object DiffUtil {
3030
!Character.isMirrored(c) && !Character.isWhitespace(c)
3131
}
3232
}
33-
split(rest, token :: acc)
33+
splitTokens(rest, token :: acc)
3434
}
3535
}
3636

37-
val lines = split(code, Nil).toArray
38-
val diff = DiffUtils.diff(split(lastCode, Nil), lines.toList)
37+
val tokens = splitTokens(code, Nil).toArray
38+
val lastTokens = splitTokens(lastCode, Nil).toArray
3939

40-
for (delta <- diff.getDeltas) {
41-
val pos = delta.getRevised.getPosition
42-
val endPos = pos + delta.getRevised.getLines.size - 1
40+
val diff = hirschberg(lastTokens, tokens)
4341

44-
delta.getType.toString match { // Issue #1355 forces us to use the toString
45-
case "INSERT" =>
46-
lines(pos) = ADDITION_COLOR + lines(pos)
47-
lines(endPos) = lines(endPos) + ANSI_DEFAULT
42+
diff.collect {
43+
case Unmodified(str) => str
44+
case Inserted(str) => ADDITION_COLOR + str + ANSI_DEFAULT
45+
case Modified(old, str) if printDiffDel => DELETION_COLOR + old + ADDITION_COLOR + str + ANSI_DEFAULT
46+
case Modified(_, str) => ADDITION_COLOR + str + ANSI_DEFAULT
47+
case Deleted(str) if printDiffDel => DELETION_COLOR + str + ANSI_DEFAULT
48+
}.mkString
49+
}
4850

49-
case "CHANGE" =>
50-
val old = if (!printDiffDel) "" else
51-
DELETION_COLOR + delta.getOriginal.getLines.mkString + ANSI_DEFAULT
52-
lines(pos) = old + ADDITION_COLOR + lines(pos)
53-
lines(endPos) = lines(endPos) + ANSI_DEFAULT
51+
private sealed trait Patch
52+
private final case class Unmodified(str: String) extends Patch
53+
private final case class Modified(original: String, str: String) extends Patch
54+
private final case class Deleted(str: String) extends Patch
55+
private final case class Inserted(str: String) extends Patch
5456

55-
case "DELETE" if printDiffDel =>
56-
val deleted = delta.getOriginal.getLines.mkString
57-
if (!deleted.forall(Character.isWhitespace)) {
58-
lines(pos) = DELETION_COLOR + deleted + ANSI_DEFAULT + lines(pos)
59-
}
57+
private def hirschberg(a: Array[String], b: Array[String]): Array[Patch] = {
58+
def build(x: Array[String], y: Array[String], builder: mutable.ArrayBuilder[Patch]): Unit = {
59+
if (x.isEmpty) {
60+
builder += Inserted(y.mkString)
61+
} else if (y.isEmpty) {
62+
builder += Deleted(x.mkString)
63+
} else if (x.length == 1 || y.length == 1) {
64+
needlemanWunsch(x, y, builder)
65+
} else {
66+
val xlen = x.length
67+
val xmid = xlen / 2
68+
val ylen = y.length
69+
70+
val (x1, x2) = x.splitAt(xmid)
71+
val ScoreL = nwScore(x1, y)
72+
val ScoreR = nwScore(x2.reverse, y.reverse)
73+
val scoreSum = (ScoreL zip ScoreR.reverse).map(tup => tup._1 + tup._2)
74+
val max = scoreSum.max
75+
val ymid = scoreSum.indexOf(max)
76+
77+
val (y1, y2) = y.splitAt(ymid)
78+
build(x1, y1, builder)
79+
build(x2, y2, builder)
80+
}
81+
}
82+
val builder = Array.newBuilder[Patch]
83+
build(a, b, builder)
84+
builder.result()
85+
}
86+
87+
private def nwScore(x: Array[String], y: Array[String]): Array[Int] = {
88+
def ins(s: String) = -2
89+
def del(s: String) = -2
90+
def sub(s1: String, s2: String) = if (s1 == s2) 2 else -1
6091

61-
case _ =>
92+
val score = Array.fill(x.length + 1, y.length + 1)(0)
93+
for (j <- 1 to y.length)
94+
score(0)(j) = score(0)(j - 1) + ins(y(j - 1))
95+
for (i <- 1 to x.length) {
96+
score(i)(0) = score(i - 1)(0) + del(x(i - 1))
97+
for (j <- 1 to y.length) {
98+
val scoreSub = score(i - 1)(j - 1) + sub(x(i - 1), y(j - 1))
99+
val scoreDel = score(i - 1)(j) + del(x(i - 1))
100+
val scoreIns = score(i)(j - 1) + ins(y(j - 1))
101+
score(i)(j) = scoreSub max scoreDel max scoreIns
102+
}
103+
}
104+
Array.tabulate(y.length + 1)(j => score(x.length)(j))
105+
}
106+
107+
private def needlemanWunsch(x: Array[String], y: Array[String], builder: mutable.ArrayBuilder[Patch]): Unit = {
108+
def similarity(a: String, b: String) = {
109+
if (a == b) 2
110+
else -1
111+
}
112+
val d = 1
113+
val F = Array.tabulate(x.length + 1, y.length + 1) { (i, j) =>
114+
if (i == 0) d * j
115+
else if (j == 0) d * i
116+
else 0
117+
}
118+
for (i <- 1 to x.length) {
119+
for (j <- 1 to y.length) {
120+
val mtch = F(i - 1)(j - 1) + similarity(x(i - 1), y(j - 1))
121+
val delete = F(i - 1)(j) + d
122+
val insert = F(i)(j - 1) + d
123+
F(i)(j) = mtch max insert max delete
62124
}
63125
}
64126

65-
lines.mkString
127+
var alignment = List.empty[Patch]
128+
var i = x.length
129+
var j = y.length
130+
while (i > 0 || j > 0) {
131+
if (i > 0 && j > 0 && F(i)(j) == F(i - 1)(j - 1) + similarity(x(i - 1), y(j - 1))) {
132+
alignment = {
133+
if (x(i - 1) == y(j - 1)) Unmodified(x(i - 1))
134+
else Modified(x(i - 1), y(j - 1))
135+
} :: alignment
136+
i = i - 1
137+
j = j - 1
138+
} else if (i > 0 && F(i)(j) == F(i - 1)(j) + d) {
139+
alignment = Deleted(x(i - 1)) :: alignment
140+
i = i - 1
141+
} else {
142+
alignment = Inserted(y(j - 1)) :: alignment
143+
j = j - 1
144+
}
145+
}
146+
builder ++= alignment
66147
}
148+
67149
}

0 commit comments

Comments
 (0)