Source file src/simd/archsimd/_gen/gentools/diff.go
1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Copied literally from src/internal/diff/diff.go so gentools does not 6 // import internal/diff (which is forbidden when built outside the std module 7 // using a release Go toolchain). 8 9 package gentools 10 11 import ( 12 "bytes" 13 "fmt" 14 "sort" 15 "strings" 16 ) 17 18 // A pair is a pair of values tracked for both the x and y side of a diff. 19 // It is typically a pair of line indexes. 20 type pair struct{ x, y int } 21 22 // Diff returns an anchored diff of the two texts old and new 23 // in the “unified diff” format. If old and new are identical, 24 // Diff returns a nil slice (no output). 25 // 26 // Unix diff implementations typically look for a diff with 27 // the smallest number of lines inserted and removed, 28 // which can in the worst case take time quadratic in the 29 // number of lines in the texts. As a result, many implementations 30 // either can be made to run for a long time or cut off the search 31 // after a predetermined amount of work. 32 // 33 // In contrast, this implementation looks for a diff with the 34 // smallest number of “unique” lines inserted and removed, 35 // where unique means a line that appears just once in both old and new. 36 // We call this an “anchored diff” because the unique lines anchor 37 // the chosen matching regions. An anchored diff is usually clearer 38 // than a standard diff, because the algorithm does not try to 39 // reuse unrelated blank lines or closing braces. 40 // The algorithm also guarantees to run in O(n log n) time 41 // instead of the standard O(n²) time. 42 // 43 // Some systems call this approach a “patience diff,” named for 44 // the “patience sorting” algorithm, itself named for a solitaire card game. 45 // We avoid that name for two reasons. First, the name has been used 46 // for a few different variants of the algorithm, so it is imprecise. 47 // Second, the name is frequently interpreted as meaning that you have 48 // to wait longer (to be patient) for the diff, meaning that it is a slower algorithm, 49 // when in fact the algorithm is faster than the standard one. 50 func Diff(oldName string, old []byte, newName string, new []byte) []byte { 51 if bytes.Equal(old, new) { 52 return nil 53 } 54 x := lines(old) 55 y := lines(new) 56 57 // Print diff header. 58 var out bytes.Buffer 59 fmt.Fprintf(&out, "diff %s %s\n", oldName, newName) 60 fmt.Fprintf(&out, "--- %s\n", oldName) 61 fmt.Fprintf(&out, "+++ %s\n", newName) 62 63 // Loop over matches to consider, 64 // expanding each match to include surrounding lines, 65 // and then printing diff chunks. 66 // To avoid setup/teardown cases outside the loop, 67 // tgs returns a leading {0,0} and trailing {len(x), len(y)} pair 68 // in the sequence of matches. 69 var ( 70 done pair // printed up to x[:done.x] and y[:done.y] 71 chunk pair // start lines of current chunk 72 count pair // number of lines from each side in current chunk 73 ctext []string // lines for current chunk 74 ) 75 for _, m := range tgs(x, y) { 76 if m.x < done.x { 77 // Already handled scanning forward from earlier match. 78 continue 79 } 80 81 // Expand matching lines as far as possible, 82 // establishing that x[start.x:end.x] == y[start.y:end.y]. 83 // Note that on the first (or last) iteration we may (or definitely do) 84 // have an empty match: start.x==end.x and start.y==end.y. 85 start := m 86 for start.x > done.x && start.y > done.y && x[start.x-1] == y[start.y-1] { 87 start.x-- 88 start.y-- 89 } 90 end := m 91 for end.x < len(x) && end.y < len(y) && x[end.x] == y[end.y] { 92 end.x++ 93 end.y++ 94 } 95 96 // Emit the mismatched lines before start into this chunk. 97 // (No effect on first sentinel iteration, when start = {0,0}.) 98 for _, s := range x[done.x:start.x] { 99 ctext = append(ctext, "-"+s) 100 count.x++ 101 } 102 for _, s := range y[done.y:start.y] { 103 ctext = append(ctext, "+"+s) 104 count.y++ 105 } 106 107 // If we're not at EOF and have too few common lines, 108 // the chunk includes all the common lines and continues. 109 const C = 3 // number of context lines 110 if (end.x < len(x) || end.y < len(y)) && 111 (end.x-start.x < C || (len(ctext) > 0 && end.x-start.x < 2*C)) { 112 for _, s := range x[start.x:end.x] { 113 ctext = append(ctext, " "+s) 114 count.x++ 115 count.y++ 116 } 117 done = end 118 continue 119 } 120 121 // End chunk with common lines for context. 122 if len(ctext) > 0 { 123 n := end.x - start.x 124 if n > C { 125 n = C 126 } 127 for _, s := range x[start.x : start.x+n] { 128 ctext = append(ctext, " "+s) 129 count.x++ 130 count.y++ 131 } 132 done = pair{start.x + n, start.y + n} 133 134 // Format and emit chunk. 135 // Convert line numbers to 1-indexed. 136 // Special case: empty file shows up as 0,0 not 1,0. 137 if count.x > 0 { 138 chunk.x++ 139 } 140 if count.y > 0 { 141 chunk.y++ 142 } 143 fmt.Fprintf(&out, "@@ -%d,%d +%d,%d @@\n", chunk.x, count.x, chunk.y, count.y) 144 for _, s := range ctext { 145 out.WriteString(s) 146 } 147 count.x = 0 148 count.y = 0 149 ctext = ctext[:0] 150 } 151 152 // If we reached EOF, we're done. 153 if end.x >= len(x) && end.y >= len(y) { 154 break 155 } 156 157 // Otherwise start a new chunk. 158 chunk = pair{end.x - C, end.y - C} 159 for _, s := range x[chunk.x:end.x] { 160 ctext = append(ctext, " "+s) 161 count.x++ 162 count.y++ 163 } 164 done = end 165 } 166 167 return out.Bytes() 168 } 169 170 // lines returns the lines in the file x, including newlines. 171 // If the file does not end in a newline, one is supplied 172 // along with a warning about the missing newline. 173 func lines(x []byte) []string { 174 l := strings.SplitAfter(string(x), "\n") 175 if l[len(l)-1] == "" { 176 l = l[:len(l)-1] 177 } else { 178 // Treat last line as having a message about the missing newline attached, 179 // using the same text as BSD/GNU diff (including the leading backslash). 180 l[len(l)-1] += "\n\\ No newline at end of file\n" 181 } 182 return l 183 } 184 185 // tgs returns the pairs of indexes of the longest common subsequence 186 // of unique lines in x and y, where a unique line is one that appears 187 // once in x and once in y. 188 // 189 // The longest common subsequence algorithm is as described in 190 // Thomas G. Szymanski, “A Special Case of the Maximal Common 191 // Subsequence Problem,” Princeton TR #170 (January 1975), 192 // available at https://research.swtch.com/tgs170.pdf. 193 func tgs(x, y []string) []pair { 194 // Count the number of times each string appears in a and b. 195 // We only care about 0, 1, many, counted as 0, -1, -2 196 // for the x side and 0, -4, -8 for the y side. 197 // Using negative numbers now lets us distinguish positive line numbers later. 198 m := make(map[string]int) 199 for _, s := range x { 200 if c := m[s]; c > -2 { 201 m[s] = c - 1 202 } 203 } 204 for _, s := range y { 205 if c := m[s]; c > -8 { 206 m[s] = c - 4 207 } 208 } 209 210 // Now unique strings can be identified by m[s] = -1+-4. 211 // 212 // Gather the indexes of those strings in x and y, building: 213 // xi[i] = increasing indexes of unique strings in x. 214 // yi[i] = increasing indexes of unique strings in y. 215 // inv[i] = index j such that x[xi[i]] = y[yi[j]]. 216 var xi, yi, inv []int 217 for i, s := range y { 218 if m[s] == -1+-4 { 219 m[s] = len(yi) 220 yi = append(yi, i) 221 } 222 } 223 for i, s := range x { 224 if j, ok := m[s]; ok && j >= 0 { 225 xi = append(xi, i) 226 inv = append(inv, j) 227 } 228 } 229 230 // Apply Algorithm A from Szymanski's paper. 231 // In those terms, A = J = inv and B = [0, n). 232 // We add sentinel pairs {0,0}, and {len(x),len(y)} 233 // to the returned sequence, to help the processing loop. 234 J := inv 235 n := len(xi) 236 T := make([]int, n) 237 L := make([]int, n) 238 for i := range T { 239 T[i] = n + 1 240 } 241 for i := 0; i < n; i++ { 242 k := sort.Search(n, func(k int) bool { 243 return T[k] >= J[i] 244 }) 245 T[k] = J[i] 246 L[i] = k + 1 247 } 248 k := 0 249 for _, v := range L { 250 if k < v { 251 k = v 252 } 253 } 254 seq := make([]pair, 2+k) 255 seq[1+k] = pair{len(x), len(y)} // sentinel at end 256 lastj := n 257 for i := n - 1; i >= 0; i-- { 258 if L[i] == k && J[i] < lastj { 259 seq[k] = pair{xi[i], yi[J[i]]} 260 k-- 261 } 262 } 263 seq[0] = pair{0, 0} // sentinel at start 264 return seq 265 } 266