Skip to main content

devpage_design/ids/
mod.rs

1//! Element ids kept across edits: a file's new parse matched against its
2//! last, sibling list by sibling list, so an element keeps its id however
3//! the lines around it change. Siblings with a `key` are matched by it; the
4//! others by a shortest edit script over the list (Myers' diff), comparing
5//! each element's name and own attributes, and then, among what is left
6//! between two matches, an element with one of the same name, in order, as
7//! an edit of it. A matched element's children are matched the same way; an
8//! element left over is new and takes the file's next id.
9//!
10//! The cost is a pass over the file: lists are trimmed of what they share at
11//! either end, and an edit script longer than [`MAX_EDITS`] gives way to
12//! matching the rest in order, so no list costs more than its length times
13//! that.
14
15use std::collections::HashMap;
16
17use crate::parse::{Attr, Child, Element, File, Value};
18
19/// The longest edit script searched for between two lists of siblings.
20pub const MAX_EDITS: usize = 256;
21
22/// `new`, its elements given the ids of the elements of `last` they match,
23/// and fresh ones from `last`'s next id where they match none.
24pub fn matched(mut new: File, last: &File) -> File {
25    let mut next = last.next_id;
26    if new.root.name == last.root.name {
27        pair(&mut new.root, &last.root, &mut next);
28    } else {
29        fresh(&mut new.root, &mut next);
30    }
31    new.next_id = next;
32    new
33}
34
35/// The key an element names itself by among its siblings, if it has one.
36pub fn key(el: &Element) -> Option<&str> {
37    el.attrs.iter().find_map(|a| match (&*a.name, &a.value) {
38        ("key", Value::Str(s, _)) => Some(s.as_str()),
39        _ => None,
40    })
41}
42
43fn fresh(el: &mut Element, next: &mut u32) {
44    el.id = *next;
45    *next += 1;
46    for child in &mut el.children {
47        if let Child::Element(e) = child {
48            fresh(e, next);
49        }
50    }
51}
52
53fn pair(new: &mut Element, old: &Element, next: &mut u32) {
54    new.id = old.id;
55    let olds: Vec<&Element> = elements(&old.children).collect();
56    let mut news: Vec<&mut Element> = new
57        .children
58        .iter_mut()
59        .filter_map(|c| match c {
60            Child::Element(e) => Some(e),
61            Child::Text(..) => None,
62        })
63        .collect();
64    let matches = siblings(&news.iter().map(|e| &**e).collect::<Vec<_>>(), &olds);
65    for (n, el) in news.iter_mut().enumerate() {
66        match matches[n] {
67            Some(o) => pair(el, olds[o], next),
68            None => fresh(el, next),
69        }
70    }
71}
72
73fn elements(children: &[Child]) -> impl Iterator<Item = &Element> {
74    children.iter().filter_map(|c| match c {
75        Child::Element(e) => Some(e),
76        Child::Text(..) => None,
77    })
78}
79
80/// For each of `news`, the one of `olds` it is, if any.
81fn siblings(news: &[&Element], olds: &[&Element]) -> Vec<Option<usize>> {
82    let mut out = vec![None; news.len()];
83    let mut taken = vec![false; olds.len()];
84    // By key, wherever each now is.
85    let mut keyed: HashMap<(&str, &str), usize> = HashMap::new();
86    for (o, el) in olds.iter().enumerate() {
87        if let Some(k) = key(el) {
88            keyed.entry((&el.name, k)).or_insert(o);
89        }
90    }
91    for (n, el) in news.iter().enumerate() {
92        if let Some(k) = key(el)
93            && let Some(&o) = keyed.get(&(el.name.as_str(), k))
94            && !taken[o]
95        {
96            out[n] = Some(o);
97            taken[o] = true;
98        }
99    }
100    // The rest, by an edit script over the elements without a key.
101    let free_new: Vec<usize> = (0..news.len())
102        .filter(|&n| out[n].is_none() && key(news[n]).is_none())
103        .collect();
104    let free_old: Vec<usize> = (0..olds.len())
105        .filter(|&o| !taken[o] && key(olds[o]).is_none())
106        .collect();
107    // The edit script keeps what is the same throughout — its own
108    // attributes and everything in it — so a row inserted before a run of
109    // rows alike in their own attributes is that row, not a shift of them all.
110    let shapes_new: Vec<u64> = free_new.iter().map(|&n| shape(news[n])).collect();
111    let shapes_old: Vec<u64> = free_old.iter().map(|&o| shape(olds[o])).collect();
112    let same = |n: usize, o: usize| {
113        shapes_new[n] == shapes_old[o] && same_own(news[free_new[n]], olds[free_old[o]])
114    };
115    let anchors = common(free_new.len(), free_old.len(), &same);
116    // Between two anchors, what is left pairs up by name, in order.
117    let mut last = (0, 0);
118    let ends = anchors
119        .iter()
120        .copied()
121        .chain([(free_new.len(), free_old.len())]);
122    for (an, ao) in ends {
123        let mut olds_left: Vec<usize> = (last.1..ao).collect();
124        for n in last.0..an {
125            let name = &news[free_new[n]].name;
126            if let Some(at) = olds_left
127                .iter()
128                .position(|&o| olds[free_old[o]].name == *name)
129            {
130                let o = olds_left.remove(at);
131                out[free_new[n]] = Some(free_old[o]);
132                // What was before it can no longer pair with what is after.
133                olds_left.drain(..at);
134            }
135        }
136        if an < free_new.len() {
137            out[free_new[an]] = Some(free_old[ao]);
138        }
139        last = (an + 1, ao + 1);
140    }
141    out
142}
143
144/// The pairs `(n, o)`, in order, of a longest common subsequence of two
145/// lists `same` compares — shared ends trimmed first, then Myers' O(ND)
146/// diff, up to [`MAX_EDITS`] edits; past that the middle has no pairs, and
147/// its elements pair by name in order.
148fn common(n: usize, m: usize, same: &dyn Fn(usize, usize) -> bool) -> Vec<(usize, usize)> {
149    let mut pre = 0;
150    while pre < n && pre < m && same(pre, pre) {
151        pre += 1;
152    }
153    let mut suf = 0;
154    while suf < n - pre && suf < m - pre && same(n - 1 - suf, m - 1 - suf) {
155        suf += 1;
156    }
157    let mut out: Vec<(usize, usize)> = (0..pre).map(|i| (i, i)).collect();
158    let (a, b) = (n - pre - suf, m - pre - suf);
159    let mid = myers(a, b, &|i, j| same(pre + i, pre + j));
160    out.extend(mid.into_iter().map(|(i, j)| (pre + i, pre + j)));
161    out.extend((0..suf).map(|i| (n - suf + i, m - suf + i)));
162    out
163}
164
165/// Myers' shortest edit script between two lists of lengths `n` and `m`,
166/// as the pairs it keeps; none when it would take more than [`MAX_EDITS`].
167fn myers(n: usize, m: usize, same: &dyn Fn(usize, usize) -> bool) -> Vec<(usize, usize)> {
168    if n == 0 || m == 0 {
169        return Vec::new();
170    }
171    let max = (n + m).min(MAX_EDITS);
172    let off = max as isize + 1;
173    let mut v = vec![0isize; 2 * max + 3];
174    let mut trace: Vec<Vec<isize>> = Vec::new();
175    for d in 0..=max as isize {
176        trace.push(v.clone());
177        let mut k = -d;
178        while k <= d {
179            let at = (k + off) as usize;
180            let mut x = if k == -d || (k != d && v[at - 1] < v[at + 1]) {
181                v[at + 1]
182            } else {
183                v[at - 1] + 1
184            };
185            let mut y = x - k;
186            while (x as usize) < n && y >= 0 && (y as usize) < m && same(x as usize, y as usize) {
187                x += 1;
188                y += 1;
189            }
190            v[at] = x;
191            if x >= n as isize && y >= m as isize {
192                return backtrack(&trace, d, n, m, off);
193            }
194            k += 2;
195        }
196    }
197    Vec::new()
198}
199
200/// The pairs the edit script found keeps, walked back from its end: each
201/// step `d` taken from the state `trace[d]` it was made from.
202fn backtrack(
203    trace: &[Vec<isize>],
204    d: isize,
205    n: usize,
206    m: usize,
207    off: isize,
208) -> Vec<(usize, usize)> {
209    let mut out = Vec::new();
210    let (mut x, mut y) = (n as isize, m as isize);
211    for d in (1..=d).rev() {
212        let v = &trace[d as usize];
213        let k = x - y;
214        let at = |k: isize| v[(k + off) as usize];
215        let prev_k = if k == -d || (k != d && at(k - 1) < at(k + 1)) {
216            k + 1
217        } else {
218            k - 1
219        };
220        let prev_x = at(prev_k);
221        let prev_y = prev_x - prev_k;
222        // Where the snake that ends at `(x, y)` starts: just past the move.
223        let (sx, sy) = if prev_k == k + 1 {
224            (prev_x, prev_y + 1)
225        } else {
226            (prev_x + 1, prev_y)
227        };
228        while x > sx && y > sy {
229            x -= 1;
230            y -= 1;
231            out.push((x as usize, y as usize));
232        }
233        x = prev_x;
234        y = prev_y;
235    }
236    while x > 0 && y > 0 {
237        x -= 1;
238        y -= 1;
239        out.push((x as usize, y as usize));
240    }
241    out.reverse();
242    out
243}
244
245/// A hash of an element and everything in it but for where it is written
246/// and its id: what two elements the same throughout share.
247fn shape(el: &Element) -> u64 {
248    use std::hash::{Hash, Hasher};
249    fn value(v: &Value, h: &mut std::collections::hash_map::DefaultHasher) {
250        match v {
251            Value::Str(s, _) => (0u8, s).hash(h),
252            Value::Theme(s, _) => (1u8, s).hash(h),
253            Value::Num(n) => (2u8, n.to_bits()).hash(h),
254            Value::Bool(b) => (3u8, b).hash(h),
255            Value::Prop(p) => (4u8, p).hash(h),
256            Value::Array(items) => {
257                (5u8, items.len()).hash(h);
258                for i in items {
259                    value(i, h);
260                }
261            }
262            Value::Object(fields) => {
263                (6u8, fields.len()).hash(h);
264                for (k, v) in fields {
265                    k.hash(h);
266                    value(v, h);
267                }
268            }
269            other => format!("{other:?}").hash(h),
270        }
271    }
272    fn walk(el: &Element, h: &mut std::collections::hash_map::DefaultHasher) {
273        el.name.hash(h);
274        el.attrs.len().hash(h);
275        for a in &el.attrs {
276            a.name.hash(h);
277            value(&a.value, h);
278        }
279        el.children.len().hash(h);
280        for c in &el.children {
281            match c {
282                Child::Element(e) => walk(e, h),
283                Child::Text(parts, _) => {
284                    for p in parts {
285                        match p {
286                            crate::parse::TextPart::Literal(s) => (0u8, s).hash(h),
287                            crate::parse::TextPart::Prop(s) => (1u8, s).hash(h),
288                            crate::parse::TextPart::Theme(s, _) => (2u8, s).hash(h),
289                        }
290                    }
291                }
292            }
293        }
294    }
295    let mut h = std::collections::hash_map::DefaultHasher::new();
296    walk(el, &mut h);
297    h.finish()
298}
299
300/// Whether two elements are the same but for where they are written: the
301/// same name and the same attributes, in the same order, with the same
302/// values.
303pub(crate) fn same_own(a: &Element, b: &Element) -> bool {
304    a.name == b.name
305        && a.attrs.len() == b.attrs.len()
306        && a.attrs.iter().zip(&b.attrs).all(|(x, y)| same_attr(x, y))
307}
308
309fn same_attr(a: &Attr, b: &Attr) -> bool {
310    a.name == b.name && same_value(&a.value, &b.value)
311}
312
313pub(crate) fn same_value(a: &Value, b: &Value) -> bool {
314    match (a, b) {
315        (Value::Str(x, _), Value::Str(y, _)) => x == y,
316        (Value::Theme(x, _), Value::Theme(y, _)) => x == y,
317        (Value::Array(x), Value::Array(y)) => {
318            x.len() == y.len() && x.iter().zip(y).all(|(a, b)| same_value(a, b))
319        }
320        (Value::Object(x), Value::Object(y)) => {
321            x.len() == y.len()
322                && x.iter()
323                    .zip(y)
324                    .all(|((ka, a), (kb, b))| ka == kb && same_value(a, b))
325        }
326        _ => a == b,
327    }
328}
329
330#[cfg(test)]
331mod tests;
332
333#[cfg(test)]
334pub(crate) mod testing;