use std::cell::RefCell;
use std::ops::Range;
use crate::coords::Bias;
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Edit {
pub old: Range<u32>,
pub new: Range<u32>,
}
#[derive(Clone, Default, Debug)]
pub struct Patch(Vec<Edit>);
impl Patch {
#[must_use]
pub fn new() -> Self {
Self(Vec::new())
}
#[must_use]
pub fn single(edit: Edit) -> Self {
Self(vec![edit])
}
#[must_use]
pub fn edits(&self) -> &[Edit] {
&self.0
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn push(&mut self, edit: Edit) {
debug_assert!(edit.old.start <= edit.old.end && edit.new.start <= edit.new.end);
if let Some(last) = self.0.last() {
debug_assert!(
edit.old.start >= last.old.end,
"old ranges must be ascending and disjoint"
);
debug_assert!(
edit.new.start >= last.new.end,
"new ranges must be ascending and disjoint"
);
}
self.0.push(edit);
}
#[must_use]
pub fn map_offset(&self, offset: u32, bias: Bias) -> u32 {
crate::perf::charge(self.0.len() as u64);
let p = offset as i64;
let mut shift: i64 = 0;
for (i, e) in self.0.iter().enumerate() {
let s = e.old.start as i64;
let end = e.old.end as i64;
if p < s {
return (p + shift) as u32;
}
if p <= end {
let k = if bias == Bias::Right { last_at_point(&self.0, i, p) } else { i };
shift += self.0[i..k].iter().map(net_delta).sum::<i64>();
let e = &self.0[k];
let s = i64::from(e.old.start);
let sn = s + shift; return map_within(p, s, i64::from(e.old.end), sn, inserted_len(e), bias) as u32;
}
shift += net_delta(e);
}
(p + shift) as u32
}
#[must_use]
pub fn map_range(
&self,
range: Range<u32>,
start_bias: Bias,
end_bias: Bias,
) -> Range<u32> {
let start = self.map_offset(range.start, start_bias);
let end = self.map_offset(range.end, end_bias);
start.min(end)..end
}
pub fn map_many(&self, queries: &[(u32, Bias)], out: &mut Vec<u32>) {
out.clear();
out.reserve(queries.len());
let edits = &self.0;
if edits.is_empty() {
out.extend(queries.iter().map(|&(p, _)| p));
return;
}
thread_local! {
static PREF: RefCell<Vec<i64>> = const { RefCell::new(Vec::new()) };
}
PREF.with(|cell| {
let pref = &mut *cell.borrow_mut();
pref.clear();
pref.push(0);
for e in edits {
pref.push(pref[pref.len() - 1] + net_delta(e));
}
for &(offset, bias) in queries {
let p = i64::from(offset);
let first = edits.partition_point(|e| i64::from(e.old.end) < p);
let mapped = if first == edits.len() || p < i64::from(edits[first].old.start) {
p + pref[first]
} else {
let k = if bias == Bias::Right { last_at_point(edits, first, p) } else { first };
let e = &edits[k];
let s = i64::from(e.old.start);
map_within(p, s, i64::from(e.old.end), s + pref[k], inserted_len(e), bias)
};
out.push(mapped as u32);
}
});
}
}
fn net_delta(e: &Edit) -> i64 {
inserted_len(e) - i64::from(e.old.end - e.old.start)
}
fn inserted_len(e: &Edit) -> i64 {
i64::from(e.new.end - e.new.start)
}
fn last_at_point(edits: &[Edit], first: usize, p: i64) -> usize {
debug_assert!(i64::from(edits[first].old.start) <= p && p <= i64::from(edits[first].old.end));
first + edits[first..].partition_point(|e| i64::from(e.old.start) <= p) - 1
}
fn map_within(p: i64, s: i64, end: i64, sn: i64, l: i64, bias: Bias) -> i64 {
if p == s {
return if bias == Bias::Right { sn + l } else { sn };
}
if p < end {
let rel = p - s;
return if rel <= l { sn + rel } else { sn + l };
}
sn + l
}
#[cfg(test)]
mod tests {
use super::*;
use crate::coords::Bias::{Left, Right};
fn edit(os: u32, oe: u32, ns: u32, ne: u32) -> Edit {
Edit { old: os..oe, new: ns..ne }
}
#[test]
fn empty_patch_is_identity() {
let p = Patch::new();
for o in 0..10 {
assert_eq!(p.map_offset(o, Left), o);
assert_eq!(p.map_offset(o, Right), o);
}
}
#[test]
fn pure_insertion_biases_at_the_point() {
let p = Patch::single(edit(5, 5, 5, 8));
assert_eq!(p.map_offset(4, Left), 4); assert_eq!(p.map_offset(5, Left), 5); assert_eq!(p.map_offset(5, Right), 8); assert_eq!(p.map_offset(6, Left), 9); }
#[test]
fn pure_deletion_collapses_interior_to_start() {
let p = Patch::single(edit(3, 7, 3, 3));
assert_eq!(p.map_offset(2, Left), 2); assert_eq!(p.map_offset(3, Left), 3); assert_eq!(p.map_offset(5, Left), 3); assert_eq!(p.map_offset(7, Left), 3); assert_eq!(p.map_offset(8, Left), 4); }
#[test]
fn net_growth_replace_preserves_interior_prefix() {
let p = Patch::single(edit(2, 4, 2, 8));
assert_eq!(p.map_offset(3, Left), 3);
assert_eq!(p.map_offset(2, Left), 2);
assert_eq!(p.map_offset(2, Right), 8);
assert_eq!(p.map_offset(4, Left), 8); assert_eq!(p.map_offset(5, Left), 9); }
#[test]
fn net_shrink_replace_clamps_tail_beyond_insert() {
let p = Patch::single(edit(2, 8, 2, 4));
assert_eq!(p.map_offset(3, Left), 3); assert_eq!(p.map_offset(4, Left), 4); assert_eq!(p.map_offset(5, Left), 4); assert_eq!(p.map_offset(8, Left), 4); assert_eq!(p.map_offset(9, Left), 5); }
#[test]
fn multi_edit_accumulates_shift() {
let mut p = Patch::new();
p.push(edit(1, 1, 1, 3)); p.push(edit(5, 6, 7, 7)); assert_eq!(p.map_offset(0, Left), 0); assert_eq!(p.map_offset(1, Right), 3); assert_eq!(p.map_offset(4, Left), 6); assert_eq!(p.map_offset(5, Left), 7); assert_eq!(p.map_offset(6, Left), 7); assert_eq!(p.map_offset(7, Left), 8); }
fn mapped(p: &Patch, offset: u32, bias: Bias) -> u32 {
let single = p.map_offset(offset, bias);
let mut batch = Vec::new();
p.map_many(&[(offset, bias)], &mut batch);
assert_eq!(batch, vec![single], "map_many diverged from map_offset at {offset} {bias:?}");
single
}
#[test]
fn right_bias_passes_a_touching_replace_and_the_insert_after_it() {
let mut p = Patch::new();
p.push(edit(1, 2, 1, 2)); p.push(edit(2, 2, 2, 3)); assert_eq!(mapped(&p, 2, Right), 3, "Right passes X and Y");
assert_eq!(mapped(&p, 2, Left), 2, "Left stays before Y");
}
#[test]
fn right_bias_passes_every_same_offset_insert() {
let mut p = Patch::new();
p.push(edit(2, 2, 2, 3));
p.push(edit(2, 2, 3, 4));
assert_eq!(mapped(&p, 2, Right), 4, "Right passes both inserts");
assert_eq!(mapped(&p, 2, Left), 2, "Left stays before both inserts");
assert_eq!(mapped(&p, 1, Right), 1);
assert_eq!(mapped(&p, 3, Left), 5);
}
#[test]
fn map_range_never_inverts() {
let p = Patch::single(edit(0, 10, 0, 0)); let r = p.map_range(3..7, Right, Left);
assert!(r.start <= r.end, "range inverted: {r:?}");
assert_eq!(r, 0..0);
}
fn patch_from_script(script: &[(u32, u32, u32)]) -> Patch {
let mut p = Patch::new();
let mut old = 0u32;
let mut shift: i64 = 0;
for &(gap, old_len, new_len) in script {
old += gap;
let ns = (i64::from(old) + shift) as u32;
p.push(Edit { old: old..old + old_len, new: ns..ns + new_len });
shift += i64::from(new_len) - i64::from(old_len);
old += old_len;
}
p
}
fn lcg(seed: u64) -> impl FnMut(u32) -> u32 {
let mut state = seed;
move |n| {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
((state >> 33) as u32) % n
}
}
fn shared_point_script(next: &mut impl FnMut(u32) -> u32) -> Vec<(u32, u32, u32)> {
(0..next(6))
.map(|_| {
let gap = if next(2) == 0 { 0 } else { 1 + next(4) };
let old_len = if next(2) == 0 { 0 } else { 1 + next(3) };
(gap, old_len, next(4))
})
.collect()
}
fn shared_point_kind(patch: &Patch, p: u32) -> (bool, bool) {
let starts = patch.edits().iter().filter(|e| e.old.start == p).count();
let ends_here = patch.edits().iter().any(|e| e.old.start < p && e.old.end == p);
(ends_here && starts > 0, starts > 1)
}
fn marker_oracle(old_len: u32, patch: &Patch, p: u32, bias: Bias) -> Option<u32> {
let edits = patch.edits();
let mut toks: Vec<Option<u32>> = Vec::new();
let mut owner: Vec<Option<usize>> = vec![None; old_len as usize];
let mut replacement_end: Vec<usize> = Vec::new(); let mut cursor = 0;
for (k, e) in edits.iter().enumerate() {
toks.extend((cursor..e.old.start).map(Some));
for i in e.old.clone() {
owner[i as usize] = Some(k);
}
toks.extend((e.new.start..e.new.end).map(|_| None));
replacement_end.push(toks.len());
cursor = e.old.end;
}
toks.extend((cursor..old_len).map(Some));
let survivor = |i: u32| toks.iter().position(|&t| t == Some(i)).expect("surviving byte");
let at = match bias {
Left if p == 0 => 0,
Left => match owner[p as usize - 1] {
None => survivor(p - 1) + 1,
Some(k) if edits[k].old.end == p => replacement_end[k],
Some(_) => return None,
},
Right if p == old_len => toks.len(),
Right => match owner[p as usize] {
None => survivor(p),
Some(k) if edits[k].old.start == p => replacement_end[k],
Some(_) => return None,
},
};
Some(at as u32)
}
#[test]
fn bias_tracks_a_real_marker_through_touching_and_same_offset_edits() {
let mut next = lcg(0xD1B5_4A32_D192_ED03);
let (mut touching, mut same_offset) = (0u32, 0u32);
for _ in 0..2000 {
let script = shared_point_script(&mut next);
let patch = patch_from_script(&script);
let old_len = patch.edits().last().map_or(0, |e| e.old.end) + next(3);
for p in 0..=old_len {
let (t, s) = shared_point_kind(&patch, p);
for bias in [Left, Right] {
let Some(want) = marker_oracle(old_len, &patch, p, bias) else {
continue;
};
assert_eq!(
mapped(&patch, p, bias),
want,
"marker at {p} {bias:?} misplaced, script {script:?}"
);
touching += u32::from(t);
same_offset += u32::from(s);
}
}
}
assert!(touching > 500, "only {touching} touching-point checks");
assert!(same_offset > 500, "only {same_offset} same-offset checks");
}
#[test]
fn map_many_matches_map_offset_oracle() {
let mut next = lcg(0x9E37_79B9_7F4A_7C15);
let (mut touching, mut same_offset) = (0u32, 0u32);
for round in 0..800 {
let script: Vec<(u32, u32, u32)> = if round % 2 == 0 {
(0..next(6)).map(|_| (next(5), next(4), next(4))).collect()
} else {
shared_point_script(&mut next)
};
let patch = patch_from_script(&script);
let hi = 40u32;
let mut queries: Vec<(u32, Bias)> = Vec::new();
for o in 0..hi {
queries.push((o, Left));
queries.push((o, Right));
}
queries[..hi as usize].reverse();
let mut got = Vec::new();
patch.map_many(&queries, &mut got);
assert_eq!(got.len(), queries.len());
for (i, &(o, b)) in queries.iter().enumerate() {
assert_eq!(
got[i],
patch.map_offset(o, b),
"map_many diverged at offset {o} bias {b:?}, script {script:?}"
);
let (t, s) = shared_point_kind(&patch, o);
touching += u32::from(t);
same_offset += u32::from(s);
}
}
assert!(touching > 200, "only {touching} touching-point queries");
assert!(same_offset > 200, "only {same_offset} same-offset queries");
}
#[test]
fn map_many_empty_patch_is_identity() {
let p = Patch::new();
let q = vec![(7, Left), (3, Right), (0, Left)];
let mut out = vec![999]; p.map_many(&q, &mut out);
assert_eq!(out, vec![7, 3, 0]);
}
}