use std::collections::HashMap;
use crate::parse::{Attr, Child, Element, File, Value};
pub const MAX_EDITS: usize = 256;
pub fn matched(mut new: File, last: &File) -> File {
let mut next = last.next_id;
if new.root.name == last.root.name {
pair(&mut new.root, &last.root, &mut next);
} else {
fresh(&mut new.root, &mut next);
}
new.next_id = next;
new
}
pub fn key(el: &Element) -> Option<&str> {
el.attrs.iter().find_map(|a| match (&*a.name, &a.value) {
("key", Value::Str(s, _)) => Some(s.as_str()),
_ => None,
})
}
fn fresh(el: &mut Element, next: &mut u32) {
el.id = *next;
*next += 1;
for child in &mut el.children {
if let Child::Element(e) = child {
fresh(e, next);
}
}
}
fn pair(new: &mut Element, old: &Element, next: &mut u32) {
new.id = old.id;
let olds: Vec<&Element> = elements(&old.children).collect();
let mut news: Vec<&mut Element> = new
.children
.iter_mut()
.filter_map(|c| match c {
Child::Element(e) => Some(e),
Child::Text(..) => None,
})
.collect();
let matches = siblings(&news.iter().map(|e| &**e).collect::<Vec<_>>(), &olds);
for (n, el) in news.iter_mut().enumerate() {
match matches[n] {
Some(o) => pair(el, olds[o], next),
None => fresh(el, next),
}
}
}
fn elements(children: &[Child]) -> impl Iterator<Item = &Element> {
children.iter().filter_map(|c| match c {
Child::Element(e) => Some(e),
Child::Text(..) => None,
})
}
fn siblings(news: &[&Element], olds: &[&Element]) -> Vec<Option<usize>> {
let mut out = vec![None; news.len()];
let mut taken = vec![false; olds.len()];
let mut keyed: HashMap<(&str, &str), usize> = HashMap::new();
for (o, el) in olds.iter().enumerate() {
if let Some(k) = key(el) {
keyed.entry((&el.name, k)).or_insert(o);
}
}
for (n, el) in news.iter().enumerate() {
if let Some(k) = key(el)
&& let Some(&o) = keyed.get(&(el.name.as_str(), k))
&& !taken[o]
{
out[n] = Some(o);
taken[o] = true;
}
}
let free_new: Vec<usize> = (0..news.len())
.filter(|&n| out[n].is_none() && key(news[n]).is_none())
.collect();
let free_old: Vec<usize> = (0..olds.len())
.filter(|&o| !taken[o] && key(olds[o]).is_none())
.collect();
let shapes_new: Vec<u64> = free_new.iter().map(|&n| shape(news[n])).collect();
let shapes_old: Vec<u64> = free_old.iter().map(|&o| shape(olds[o])).collect();
let same = |n: usize, o: usize| {
shapes_new[n] == shapes_old[o] && same_own(news[free_new[n]], olds[free_old[o]])
};
let anchors = common(free_new.len(), free_old.len(), &same);
let mut last = (0, 0);
let ends = anchors
.iter()
.copied()
.chain([(free_new.len(), free_old.len())]);
for (an, ao) in ends {
let mut olds_left: Vec<usize> = (last.1..ao).collect();
for n in last.0..an {
let name = &news[free_new[n]].name;
if let Some(at) = olds_left
.iter()
.position(|&o| olds[free_old[o]].name == *name)
{
let o = olds_left.remove(at);
out[free_new[n]] = Some(free_old[o]);
olds_left.drain(..at);
}
}
if an < free_new.len() {
out[free_new[an]] = Some(free_old[ao]);
}
last = (an + 1, ao + 1);
}
out
}
fn common(n: usize, m: usize, same: &dyn Fn(usize, usize) -> bool) -> Vec<(usize, usize)> {
let mut pre = 0;
while pre < n && pre < m && same(pre, pre) {
pre += 1;
}
let mut suf = 0;
while suf < n - pre && suf < m - pre && same(n - 1 - suf, m - 1 - suf) {
suf += 1;
}
let mut out: Vec<(usize, usize)> = (0..pre).map(|i| (i, i)).collect();
let (a, b) = (n - pre - suf, m - pre - suf);
let mid = myers(a, b, &|i, j| same(pre + i, pre + j));
out.extend(mid.into_iter().map(|(i, j)| (pre + i, pre + j)));
out.extend((0..suf).map(|i| (n - suf + i, m - suf + i)));
out
}
fn myers(n: usize, m: usize, same: &dyn Fn(usize, usize) -> bool) -> Vec<(usize, usize)> {
if n == 0 || m == 0 {
return Vec::new();
}
let max = (n + m).min(MAX_EDITS);
let off = max as isize + 1;
let mut v = vec![0isize; 2 * max + 3];
let mut trace: Vec<Vec<isize>> = Vec::new();
for d in 0..=max as isize {
trace.push(v.clone());
let mut k = -d;
while k <= d {
let at = (k + off) as usize;
let mut x = if k == -d || (k != d && v[at - 1] < v[at + 1]) {
v[at + 1]
} else {
v[at - 1] + 1
};
let mut y = x - k;
while (x as usize) < n && y >= 0 && (y as usize) < m && same(x as usize, y as usize) {
x += 1;
y += 1;
}
v[at] = x;
if x >= n as isize && y >= m as isize {
return backtrack(&trace, d, n, m, off);
}
k += 2;
}
}
Vec::new()
}
fn backtrack(
trace: &[Vec<isize>],
d: isize,
n: usize,
m: usize,
off: isize,
) -> Vec<(usize, usize)> {
let mut out = Vec::new();
let (mut x, mut y) = (n as isize, m as isize);
for d in (1..=d).rev() {
let v = &trace[d as usize];
let k = x - y;
let at = |k: isize| v[(k + off) as usize];
let prev_k = if k == -d || (k != d && at(k - 1) < at(k + 1)) {
k + 1
} else {
k - 1
};
let prev_x = at(prev_k);
let prev_y = prev_x - prev_k;
let (sx, sy) = if prev_k == k + 1 {
(prev_x, prev_y + 1)
} else {
(prev_x + 1, prev_y)
};
while x > sx && y > sy {
x -= 1;
y -= 1;
out.push((x as usize, y as usize));
}
x = prev_x;
y = prev_y;
}
while x > 0 && y > 0 {
x -= 1;
y -= 1;
out.push((x as usize, y as usize));
}
out.reverse();
out
}
fn shape(el: &Element) -> u64 {
use std::hash::{Hash, Hasher};
fn value(v: &Value, h: &mut std::collections::hash_map::DefaultHasher) {
match v {
Value::Str(s, _) => (0u8, s).hash(h),
Value::Theme(s, _) => (1u8, s).hash(h),
Value::Num(n) => (2u8, n.to_bits()).hash(h),
Value::Bool(b) => (3u8, b).hash(h),
Value::Prop(p) => (4u8, p).hash(h),
Value::Array(items) => {
(5u8, items.len()).hash(h);
for i in items {
value(i, h);
}
}
Value::Object(fields) => {
(6u8, fields.len()).hash(h);
for (k, v) in fields {
k.hash(h);
value(v, h);
}
}
other => format!("{other:?}").hash(h),
}
}
fn walk(el: &Element, h: &mut std::collections::hash_map::DefaultHasher) {
el.name.hash(h);
el.attrs.len().hash(h);
for a in &el.attrs {
a.name.hash(h);
value(&a.value, h);
}
el.children.len().hash(h);
for c in &el.children {
match c {
Child::Element(e) => walk(e, h),
Child::Text(parts, _) => {
for p in parts {
match p {
crate::parse::TextPart::Literal(s) => (0u8, s).hash(h),
crate::parse::TextPart::Prop(s) => (1u8, s).hash(h),
crate::parse::TextPart::Theme(s, _) => (2u8, s).hash(h),
}
}
}
}
}
}
let mut h = std::collections::hash_map::DefaultHasher::new();
walk(el, &mut h);
h.finish()
}
pub(crate) fn same_own(a: &Element, b: &Element) -> bool {
a.name == b.name
&& a.attrs.len() == b.attrs.len()
&& a.attrs.iter().zip(&b.attrs).all(|(x, y)| same_attr(x, y))
}
fn same_attr(a: &Attr, b: &Attr) -> bool {
a.name == b.name && same_value(&a.value, &b.value)
}
pub(crate) fn same_value(a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::Str(x, _), Value::Str(y, _)) => x == y,
(Value::Theme(x, _), Value::Theme(y, _)) => x == y,
(Value::Array(x), Value::Array(y)) => {
x.len() == y.len() && x.iter().zip(y).all(|(a, b)| same_value(a, b))
}
(Value::Object(x), Value::Object(y)) => {
x.len() == y.len()
&& x.iter()
.zip(y)
.all(|((ka, a), (kb, b))| ka == kb && same_value(a, b))
}
_ => a == b,
}
}
#[cfg(test)]
mod tests;
#[cfg(test)]
pub(crate) mod testing;