mirror of
https://github.com/odin-lang/Odin.git
synced 2026-11-01 16:28:30 -05:00
206 lines
6.5 KiB
Odin
206 lines
6.5 KiB
Odin
#+private
|
|
package diff
|
|
|
|
/*
|
|
The `.Prune` pre-pass. It sets aside the common prefix and suffix, the tokens with no match on the other side,
|
|
and some of the frequent tokens among those. The algorithm then diffs only the tokens kept, and its result is
|
|
mapped back.
|
|
|
|
A token is frequent when its ID occurs on the other side about `sqrt(n)` times or more, for this side's length
|
|
`n`. No token is frequent with `.Minimal`. A frequent token is set aside only from inside a run of tokens that
|
|
are not kept, with tokens that have no match on both sides of it, and only when few of that run are frequent.
|
|
*/
|
|
|
|
// The limit for a frequent token is never higher than this.
|
|
PRUNE_MAX_MATCHES :: 1024
|
|
|
|
// How far to look back and ahead from a frequent token.
|
|
PRUNE_WINDOW :: 100
|
|
|
|
// A frequent token is set aside when fewer than one in this many of the run around it are frequent.
|
|
PRUNE_RUN_FACTOR :: 4
|
|
|
|
// What the other side says about a token of a middle.
|
|
Prune_Class :: enum u8 {
|
|
Discard, // no match: set aside
|
|
Keep, // a few matches: kept
|
|
Frequent, // many matches: decided by the tokens around it
|
|
}
|
|
|
|
/*
|
|
The kept tokens and their `Changes` come first. The counts and classes come next. The algorithm reuses their
|
|
space once they are done.
|
|
*/
|
|
@(require_results)
|
|
prune_scratch_size :: proc "contextless" (n, m: int, id_count: int, opts: Options) -> int {
|
|
kept := scratch_bytes(i32, n) + scratch_bytes(i32, m)
|
|
classes := 2*scratch_bytes(i32, id_count) + scratch_bytes(Prune_Class, n) + scratch_bytes(Prune_Class, m)
|
|
algorithm := scratch_bytes(u64, changes_size(n, m)) + unpruned_scratch_size(n, m, id_count, opts)
|
|
return kept + max(classes, algorithm)
|
|
}
|
|
|
|
// Diffs `a` against `b` into zeroed `changes`, with the tokens that cannot usefully match set aside.
|
|
prune :: proc(a, b: []i32, id_count: int, changes: Changes, s: ^Scratch, opts: Options, loc := #caller_location) {
|
|
// Gives each token of `ids` its class, by the occurrences of its ID in the whole of the other side
|
|
classify :: proc "contextless" (class: []Prune_Class, ids: []i32, other_count: []i32, limit: int) {
|
|
for id, i in ids {
|
|
count := int(other_count[id])
|
|
switch {
|
|
case count == 0: class[i] = .Discard
|
|
case count < limit: class[i] = .Keep
|
|
case: class[i] = .Frequent
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
Counts the tokens that are not kept, from `class[from]` on by `step`, for `PRUNE_WINDOW` tokens at most.
|
|
Stops at the first kept token or at the end of the middle.
|
|
*/
|
|
@(require_results)
|
|
count_run :: proc "contextless" (class: []Prune_Class, from, step: int) -> (discards, frequent: int) {
|
|
frequent = 1
|
|
for r, steps := from, 0; 0 <= r && r < len(class) && steps < PRUNE_WINDOW; r, steps = r + step, steps + 1 {
|
|
switch class[r] {
|
|
case .Keep: return
|
|
case .Discard: discards += 1
|
|
case .Frequent: frequent += 1
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Whether the frequent token at `class[i]` is set aside: discards on both sides, and few frequent tokens
|
|
@(require_results)
|
|
set_aside :: proc "contextless" (class: []Prune_Class, i: int) -> bool {
|
|
back_discards, back_frequent := count_run(class, i - 1, -1)
|
|
if back_discards == 0 {
|
|
return false
|
|
}
|
|
ahead_discards, ahead_frequent := count_run(class, i + 1, +1)
|
|
if ahead_discards == 0 {
|
|
return false
|
|
}
|
|
|
|
frequent := back_frequent + ahead_frequent
|
|
discards := back_discards + ahead_discards
|
|
return frequent*PRUNE_RUN_FACTOR < frequent + discards
|
|
}
|
|
|
|
/*
|
|
Copies the kept tokens of a middle to `kept`, and sets the bits of the others from `offset` in `bits`.
|
|
Each frequent token is decided from the classes alone.
|
|
*/
|
|
@(require_results)
|
|
keep :: proc "contextless" (kept: []i32, ids: []i32, class: []Prune_Class, bits: []u64, offset: int) -> (count: int) {
|
|
for id, i in ids {
|
|
if class[i] == .Keep || (class[i] == .Frequent && !set_aside(class, i)) {
|
|
kept[count] = id
|
|
count += 1
|
|
} else {
|
|
_set_bit_range(bits, offset + i, offset + i + 1)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// Copies the shorter diff's bits, from `kept[kept_lo]` on, to the kept tokens of `bits[lo:hi]`: those still clear
|
|
map_back :: proc "contextless" (bits: []u64, lo, hi: int, kept: []u64, kept_lo: int) {
|
|
i := lo
|
|
k := kept_lo
|
|
for i < hi {
|
|
i += _bit_run(bits, i, hi, true)
|
|
|
|
run := _bit_run(bits, i, hi, false)
|
|
for t := 0; t < run; /**/ {
|
|
t += _bit_run(kept, k + t, k + run, false)
|
|
|
|
changed := _bit_run(kept, k + t, k + run, true)
|
|
_set_bit_range(bits, i + t, i + t + changed)
|
|
|
|
t += changed
|
|
}
|
|
|
|
i += run
|
|
k += run
|
|
}
|
|
}
|
|
|
|
|
|
n, m := len(a), len(b)
|
|
|
|
// The common ends are equal: nothing to mark
|
|
prefix := 0
|
|
for prefix < n && prefix < m && a[prefix] == b[prefix] {
|
|
prefix += 1
|
|
}
|
|
suffix := 0
|
|
for suffix < n - prefix && suffix < m - prefix && a[n-1-suffix] == b[m-1-suffix] {
|
|
suffix += 1
|
|
}
|
|
a_mid := a[prefix:n-suffix]
|
|
b_mid := b[prefix:m-suffix]
|
|
|
|
// With one middle empty, every token of the other is a change whatever it is classed as
|
|
switch {
|
|
case len(a_mid) == 0:
|
|
_mark_inserted(changes, prefix, m - suffix)
|
|
return
|
|
case len(b_mid) == 0:
|
|
_mark_deleted(changes, prefix, n - suffix)
|
|
return
|
|
}
|
|
|
|
start := s.pos
|
|
kept_a := scratch_take(s, i32, len(a_mid), loc)
|
|
kept_b := scratch_take(s, i32, len(b_mid), loc)
|
|
|
|
// The counts and classes are done once the kept tokens are known
|
|
counted := s.pos
|
|
a_count := scratch_take(s, i32, id_count, loc)
|
|
b_count := scratch_take(s, i32, id_count, loc)
|
|
for &x in a_count {
|
|
x = 0
|
|
}
|
|
for &x in b_count {
|
|
x = 0
|
|
}
|
|
for id in a {
|
|
a_count[id] += 1
|
|
}
|
|
for id in b {
|
|
b_count[id] += 1
|
|
}
|
|
|
|
// Each side's limit comes from its own whole length
|
|
a_limit := max(int)
|
|
b_limit := max(int)
|
|
if opts.algorithm != .Minimal {
|
|
a_limit = min(rough_sqrt(n), PRUNE_MAX_MATCHES)
|
|
b_limit = min(rough_sqrt(m), PRUNE_MAX_MATCHES)
|
|
}
|
|
|
|
a_class := scratch_take(s, Prune_Class, len(a_mid), loc)
|
|
b_class := scratch_take(s, Prune_Class, len(b_mid), loc)
|
|
classify(a_class, a_mid, b_count, a_limit)
|
|
classify(b_class, b_mid, a_count, b_limit)
|
|
|
|
a_offset := prefix
|
|
b_offset := _inserted_offset(changes) + prefix
|
|
kept_n := keep(kept_a, a_mid, a_class, changes.bits, a_offset)
|
|
kept_m := keep(kept_b, b_mid, b_class, changes.bits, b_offset)
|
|
s.pos = counted
|
|
|
|
// Diff what is kept, then map it back
|
|
words := scratch_take(s, u64, changes_size(kept_n, kept_m), loc)
|
|
for &w in words {
|
|
w = 0
|
|
}
|
|
kept := changes_from_buffer(kept_n, kept_m, words, loc)
|
|
run_algorithm(kept_a[:kept_n], kept_b[:kept_m], id_count, kept, s, opts, loc)
|
|
|
|
map_back(changes.bits, a_offset, a_offset + len(a_mid), kept.bits, 0)
|
|
map_back(changes.bits, b_offset, b_offset + len(b_mid), kept.bits, _inserted_offset(kept))
|
|
s.pos = start
|
|
}
|