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 e in &self.0 {
let s = e.old.start as i64;
let end = e.old.end as i64;
let l = (e.new.end - e.new.start) as i64;
if p < s {
return (p + shift) as u32;
}
if p <= end {
let sn = s + shift; return map_within(p, s, end, sn, l, bias) as u32;
}
shift += l - (end - s);
}
(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 {
let delta =
i64::from(e.new.end - e.new.start) - i64::from(e.old.end - e.old.start);
pref.push(pref[pref.len() - 1] + delta);
}
for &(offset, bias) in queries {
let p = i64::from(offset);
let k = edits.partition_point(|e| i64::from(e.old.end) < p);
let mapped = if k == edits.len() {
p + pref[k]
} else {
let e = &edits[k];
let s = i64::from(e.old.start);
let shift = pref[k];
if p < s {
p + shift } else {
let end = i64::from(e.old.end);
let l = i64::from(e.new.end - e.new.start);
map_within(p, s, end, s + shift, l, bias)
}
};
out.push(mapped as u32);
}
});
}
}
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); }
#[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
}
#[test]
fn map_many_matches_map_offset_oracle() {
let mut state: u64 = 0x9E37_79B9_7F4A_7C15;
let mut next = |n: u32| {
state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
((state >> 33) as u32) % n
};
for _ in 0..400 {
let n_edits = next(6); let script: Vec<(u32, u32, u32)> = (0..n_edits)
.map(|_| (next(5), next(4), next(4))) .collect();
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:?}"
);
}
}
}
#[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]);
}
}