tree_pattern_match/
tree_match.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8//! Naive find and replace implementation on a tree-ish structure.
9//!
10//! Intended to be used as part of Rust proc-macro logic, but separate
11//! from the `proc_macro` crate for easier testing.
12
13use std::borrow::Cow;
14use std::collections::HashMap;
15use std::fmt;
16use std::hash::Hash;
17use std::sync::Arc;
18use std::sync::OnceLock;
19use std::sync::RwLock;
20
21use bitflags::bitflags;
22
23/// Minimal abstraction for tree-like.
24#[derive(Clone, PartialEq, Eq, Debug)]
25pub enum Item<T> {
26    Tree(T, Vec<Item<T>>),
27    Item(T),
28    Placeholder(Placeholder<T>),
29}
30
31/// Placeholder for capturing. Currently supports single item (`__`, like `?` in
32/// glob) and mult-item (`___`, like `*` in glob), with `g` to indicate matching
33/// trees (groups).
34/// Might be extended (like, adding fields of custom functions) to support more
35/// complex matches (ex. look ahead, balanced brackets, limited tokens, etc).
36#[derive(Clone)]
37pub struct Placeholder<T> {
38    name: String,
39    /// If set, specify whether to match an item.
40    /// Can be useful to express `[0-9a-f]` like in glob.
41    matches_item: Option<Arc<dyn (Fn(&Item<T>) -> bool) + 'static>>,
42    /// If true, matches empty or multiple items, like `*`.
43    /// Otherwise, matches one item.
44    matches_multiple: bool,
45}
46
47impl<T> PartialEq for Placeholder<T> {
48    fn eq(&self, other: &Self) -> bool {
49        self.name == other.name
50    }
51}
52
53impl<T> Eq for Placeholder<T> {}
54
55impl<T> fmt::Debug for Placeholder<T> {
56    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
57        self.name.fmt(f)
58    }
59}
60
61impl<T> Placeholder<T> {
62    pub fn new(name: String) -> Self {
63        let matches_multiple = name.starts_with("___");
64        Self {
65            name,
66            matches_item: None,
67            matches_multiple,
68        }
69    }
70
71    pub fn set_matches_item(
72        &mut self,
73        matches_item: impl (Fn(&Item<T>) -> bool) + 'static,
74    ) -> &mut Self {
75        self.matches_item = Some(Arc::new(matches_item));
76        self
77    }
78
79    pub fn set_matches_multiple(&mut self, matches_multiple: bool) -> &mut Self {
80        self.matches_multiple = matches_multiple;
81        self
82    }
83
84    pub fn name(&self) -> &str {
85        self.name.as_str()
86    }
87
88    // true: match 0 or many items; false: match 1 item
89    pub fn matches_multiple(&self) -> bool {
90        self.matches_multiple
91    }
92
93    /// Test matching against a single item.
94    pub fn matches_item(&self, item: &Item<T>) -> bool {
95        match self.matches_item.as_ref() {
96            None => true,
97            Some(f) => f(item),
98        }
99    }
100}
101
102pub trait PlaceholderExt<T> {
103    fn with_placeholder_matching_items<F: Fn(&Item<T>) -> bool + 'static>(
104        self,
105        name_matches_item_pairs: impl IntoIterator<Item = (&'static str, F)>,
106    ) -> Self;
107}
108
109impl<T> PlaceholderExt<T> for Vec<Item<T>> {
110    fn with_placeholder_matching_items<F: Fn(&Item<T>) -> bool + 'static>(
111        mut self,
112        name_matches_item_pairs: impl IntoIterator<Item = (&'static str, F)>,
113    ) -> Self {
114        fn rewrite_items<T, F: Fn(&Item<T>) -> bool + 'static>(
115            items: &mut [Item<T>],
116            mapping: &mut HashMap<&str, F>,
117        ) {
118            for item in items.iter_mut() {
119                match item {
120                    Item::Placeholder(p) => {
121                        if let Some(f) = mapping.remove(p.name()) {
122                            p.set_matches_item(f);
123                        }
124                    }
125                    Item::Tree(_, children) => {
126                        rewrite_items(children, mapping);
127                    }
128                    _ => {}
129                }
130            }
131        }
132
133        let mut mapping: HashMap<&str, F> = name_matches_item_pairs.into_iter().collect();
134        rewrite_items(&mut self, &mut mapping);
135        self
136    }
137}
138
139/// Similar to regex match. A match can have multiple captures.
140#[derive(Debug, Clone, Eq, PartialEq)]
141pub struct Match<T> {
142    /// End of the match (exclusive).
143    end: usize,
144    /// Start of the match. `items[start .. start + len]` matches `pat`.
145    start: usize,
146    /// Placeholder -> matched items.
147    pub captures: Captures<T>,
148}
149type Captures<T> = HashMap<String, Vec<Item<T>>>;
150
151// T does not ened to implement `Default`.
152impl<T> Default for Match<T> {
153    fn default() -> Self {
154        Self {
155            end: 0,
156            start: 0,
157            captures: Default::default(),
158        }
159    }
160}
161
162/// Replace matches. Similar to Python `re.sub` but is tree aware.
163pub fn replace_all<T: fmt::Debug + Clone + PartialEq + 'static>(
164    items: &[Item<T>],
165    pat: &[Item<T>],
166    replace: impl Replace<T>,
167) -> Vec<Item<T>> {
168    TreeMatchState::default()
169        .replace_all(items, pat, &replace)
170        .into_owned()
171}
172
173/// Find matches. Similar to Python `re.findall` but is tree aware.
174pub fn find_all<T: fmt::Debug + Clone + PartialEq>(
175    items: &[Item<T>],
176    pat: &[Item<T>],
177) -> Vec<Match<T>> {
178    TreeMatchState::default().find_all(items, pat)
179}
180
181/// Find a match align with the start of items. Similar to Python `re.match`.
182pub fn matches_start<T: fmt::Debug + Clone + PartialEq>(
183    items: &[Item<T>],
184    pat: &[Item<T>],
185) -> Option<Match<T>> {
186    TreeMatchState::default().find_one(items, pat, TreeMatchMode::MatchBegin)
187}
188
189/// Find a match align with items.
190pub fn matches_full<T: fmt::Debug + Clone + PartialEq>(
191    items: &[Item<T>],
192    pat: &[Item<T>],
193) -> Option<Match<T>> {
194    TreeMatchState::default().find_one(items, pat, TreeMatchMode::MatchFull)
195}
196
197/// Takes a single match and output its replacement.
198pub trait Replace<T> {
199    fn expand(&self, m: &Match<T>) -> Vec<Item<T>>;
200}
201
202impl<T: Clone> Replace<T> for &[Item<T>] {
203    fn expand(&self, m: &Match<T>) -> Vec<Item<T>> {
204        expand_replace(self, &m.captures)
205    }
206}
207
208impl<T: Clone> Replace<T> for &Vec<Item<T>> {
209    fn expand(&self, m: &Match<T>) -> Vec<Item<T>> {
210        expand_replace(self, &m.captures)
211    }
212}
213
214impl<T: Clone> Replace<T> for Vec<Item<T>> {
215    fn expand(&self, m: &Match<T>) -> Vec<Item<T>> {
216        expand_replace(self, &m.captures)
217    }
218}
219
220impl<T, F> Replace<T> for F
221where
222    F: Fn(&'_ Match<T>) -> Vec<Item<T>>,
223{
224    fn expand(&self, m: &Match<T>) -> Vec<Item<T>> {
225        (self)(m)
226    }
227}
228
229/// Expand `replace` with captured items.
230fn expand_replace<T: Clone>(replace: &[Item<T>], captures: &Captures<T>) -> Vec<Item<T>> {
231    let mut result = Vec::with_capacity(replace.len());
232    for item in replace {
233        match item {
234            Item::Tree(delimiter, sub_items) => {
235                let sub_expanded = expand_replace(sub_items, captures);
236                let new_tree = Item::Tree(delimiter.clone(), sub_expanded);
237                result.push(new_tree);
238            }
239            Item::Placeholder(p) => {
240                if let Some(items) = captures.get(p.name()) {
241                    result.extend_from_slice(items);
242                }
243            }
244            _ => result.push(item.clone()),
245        }
246    }
247    result
248}
249
250/// Match state for trees.
251#[derive(Clone)]
252struct TreeMatchState<'a, T> {
253    /// (pat, items) => SeqMatchState.
254    /// Only caches `allow_remaining = false` cases.
255    cache: Arc<RwLock<HashMap<TreeMatchCacheKey, Arc<SeqMatchState<'a, T>>>>>,
256}
257
258/// Turn `&[Item<T>]` Eq / Hash from O(N) to O(1) based on address.
259#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
260struct TreeMatchCacheKey {
261    pat: (usize, usize),
262    items: (usize, usize),
263    opts: TreeMatchMode,
264}
265
266/// Match state focused on one depth level.
267struct SeqMatchState<'a, T> {
268    parent: TreeMatchState<'a, T>,
269    cache: Vec<SeqMatched>,
270    pat: &'a [Item<T>],
271    items: &'a [Item<T>],
272    /// Matched length. None: not matched.
273    match_end: Option<usize>,
274    /// Only set for TreeMatchMode::Search. Non-overlapping matched ends.
275    match_ends: Vec<usize>,
276}
277
278/// Options for `TreeMatchState::match`.
279#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
280enum TreeMatchMode {
281    /// `pat` must match `items`, consuming the entire sequence.
282    MatchFull,
283    /// `pat` can match `items[..subset]`, not the entire `items`.
284    MatchBegin,
285    /// Perform a search to find all matches. Start / end / depth do not
286    /// have to match.
287    Search,
288}
289
290bitflags! {
291    /// Match state used by SeqMatchState.
292    /// How an item matches a pattern. Note: there could be multiple different ways to match.
293    #[derive(Debug, Clone, Copy, Eq, PartialEq)]
294    struct SeqMatched: u8 {
295        /// Match a single item, not a placeholder.
296        const MATCH_ITEM = 1;
297        /// Match a single tree, not recursive, not a placeholder.
298        const MATCH_TREE = 2;
299        /// Match a single item (`?` in glob) placeholder.
300        const MATCH_PLACEHOLDER_SINGLE = 4;
301        /// Match a multi-item (wildcard, `*` in glob) placeholder.
302        const MATCH_PLACEHOLDER_MULTI = 8;
303        /// Match a multi-item placeholder by extending its matched items.
304        const MATCH_PLACEHOLDER_MULTI_EXTEND = 16;
305        /// Hard-coded match at boundary.
306        const MATCH_INIT = 32;
307        /// Not yet calculated.
308        const UNKNOWN = 128;
309    }
310}
311
312// T does not have to implement "Default".
313impl<'a, T> Default for TreeMatchState<'a, T> {
314    fn default() -> Self {
315        Self {
316            cache: Default::default(),
317        }
318    }
319}
320
321impl TreeMatchCacheKey {
322    fn new<T>(pat: &[T], items: &[T], opts: TreeMatchMode) -> Self {
323        Self {
324            pat: (pat.as_ptr() as usize, pat.len()),
325            items: (items.as_ptr() as usize, items.len()),
326            opts,
327        }
328    }
329}
330
331impl SeqMatched {
332    fn has_match(self) -> bool {
333        !self.is_empty()
334    }
335}
336
337impl<'a, T: PartialEq + Clone + fmt::Debug> SeqMatchState<'a, T> {
338    /// Whether pat[..pat_end] matches items[..item_end].
339    /// Dynamic programming. O(len(pat) * len(items)) worst case for this single level.
340    /// Deeper-level matches require more time complexity.
341    /// For `TreeMatchMode::Search`, do not check deeper levels.
342    fn matched(&mut self, pat_end: usize, item_end: usize, opts: TreeMatchMode) -> SeqMatched {
343        let cached = *self.get_cache_mut(pat_end, item_end);
344        if cached != SeqMatched::UNKNOWN {
345            return cached;
346        }
347        let result = match (pat_end, item_end) {
348            (0, 0) => SeqMatched::MATCH_INIT,
349            (0, _) if matches!(opts, TreeMatchMode::Search) => {
350                // search mode: the start does not have to match.
351                SeqMatched::MATCH_INIT
352            }
353            (1, 0) if matches!(&self.pat[pat_end - 1], Item::Placeholder(p) if p.matches_multiple()) => {
354                SeqMatched::MATCH_PLACEHOLDER_MULTI
355            }
356            (_, 0) | (0, _) => SeqMatched::empty(),
357            _ => {
358                let mut result = SeqMatched::empty();
359                match &self.pat[pat_end - 1] {
360                    Item::Tree(t1, pat_children) => {
361                        if let Item::Tree(t2, item_children) = &self.items[item_end - 1] {
362                            // The order of the conditions start from the easiest to the (maybe) hardest.
363                            if t1 == t2 /* not recursive */ && self.matched(pat_end - 1, item_end - 1, opts).has_match() && self.parent.matched(pat_children, item_children, TreeMatchMode::MatchFull).has_match()
364                            {
365                                result |= SeqMatched::MATCH_TREE;
366                            }
367                        }
368                    }
369                    Item::Item(t1) => {
370                        if matches!(&self.items[item_end - 1], Item::Item(t2) if t1 == t2)
371                            && self.matched(pat_end - 1, item_end - 1, opts).has_match()
372                        {
373                            result |= SeqMatched::MATCH_ITEM;
374                        }
375                    }
376                    Item::Placeholder(p) => {
377                        if p.matches_multiple() {
378                            // item: . . . .
379                            //            /
380                            // pat:  . . . p (new match against empty slice)
381                            if self.matched(pat_end - 1, item_end, opts).has_match() {
382                                result |= SeqMatched::MATCH_PLACEHOLDER_MULTI;
383                            }
384                            // item: . . . .
385                            //            \|
386                            // pat:  . . . p (extend match)
387                            let m = self.matched(pat_end, item_end - 1, opts);
388                            if m.intersects(
389                                SeqMatched::MATCH_PLACEHOLDER_MULTI
390                                    | SeqMatched::MATCH_PLACEHOLDER_MULTI_EXTEND,
391                            ) {
392                                if p.matches_item(&self.items[item_end - 1]) {
393                                    result |= SeqMatched::MATCH_PLACEHOLDER_MULTI_EXTEND;
394                                }
395                            }
396                        } else if p.matches_item(&self.items[item_end - 1])
397                            && self.matched(pat_end - 1, item_end - 1, opts).has_match()
398                        {
399                            result |= SeqMatched::MATCH_PLACEHOLDER_SINGLE;
400                        }
401                    }
402                };
403                result
404            }
405        };
406        assert!(!result.contains(SeqMatched::UNKNOWN));
407        *self.get_cache_mut(pat_end, item_end) = result;
408        result
409    }
410
411    /// Backtrack the match and fill `captures`.
412    fn fill_match(&self, r#match: &mut Match<T>) {
413        self.fill_match_with_match_end(r#match, None, true);
414    }
415
416    /// Backtrack all matches. Used together with `TreeMatchMode::Search`.
417    ///
418    /// Matches are reported from start to end picking the longest.
419    /// Overlapping matches are skipped.
420    ///
421    /// DOES NOT fill matches in nested trees.
422    /// `SeqMatchState` only calculates matches at the current layer.
423    fn fill_matches(&self, matches: &mut Vec<Match<T>>) {
424        for &end in &self.match_ends {
425            let mut m = Match::default();
426            self.fill_match_with_match_end(&mut m, Some(end), true);
427            matches.push(m);
428        }
429    }
430
431    // Find the "start" of a match. This could be O(N).
432    fn backtrack_match_start(&self, end: usize) -> usize {
433        let mut m = Match::default();
434        self.fill_match_with_match_end(&mut m, Some(end), false);
435        assert_eq!(m.end, end, "find_match_start requires 'end' to be a match");
436        m.start
437    }
438
439    // If fill_captures is false, still report match start.
440    fn fill_match_with_match_end(
441        &self,
442        r#match: &mut Match<T>,
443        match_end: Option<usize>,
444        fill_captures: bool,
445    ) {
446        let mut pat_len = self.pat.len();
447        let mut multi_len = 0;
448        let match_end = match_end.unwrap_or_else(|| self.match_end.unwrap());
449        let mut item_len = match_end;
450        loop {
451            let mut item_dec = 1;
452            let matched = self.get_cache(pat_len, item_len);
453            if matched.contains(SeqMatched::MATCH_ITEM) {
454                pat_len -= 1;
455            } else if matched.contains(SeqMatched::MATCH_TREE) {
456                if let (Item::Tree(_, pat_children), Item::Tree(_, item_children)) =
457                    (&self.pat[pat_len - 1], &self.items[item_len - 1])
458                {
459                    if fill_captures {
460                        self.parent
461                            .matched(pat_children, item_children, TreeMatchMode::MatchFull)
462                            .fill_match_with_match_end(r#match, None, fill_captures);
463                    }
464                    pat_len -= 1;
465                } else {
466                    unreachable!("bug: MATCH_TREE does not actually match trees");
467                }
468            } else if matched.contains(SeqMatched::MATCH_PLACEHOLDER_MULTI_EXTEND) {
469                multi_len += 1;
470            } else if matched.intersects(
471                SeqMatched::MATCH_PLACEHOLDER_MULTI | SeqMatched::MATCH_PLACEHOLDER_SINGLE,
472            ) {
473                let (start, len) = if matched.intersects(SeqMatched::MATCH_PLACEHOLDER_SINGLE) {
474                    (item_len - 1, 1)
475                } else {
476                    item_dec = 0;
477                    (item_len, multi_len)
478                };
479                if let Item::Placeholder(p) = &self.pat[pat_len - 1] {
480                    if fill_captures {
481                        r#match.captures.insert(
482                            p.name().to_string(),
483                            self.items[start..start + len].to_vec(),
484                        );
485                    }
486                } else {
487                    unreachable!("bug: MATCH_PLACEHOLDER does not actually match a placeholder");
488                }
489                pat_len -= 1;
490                multi_len = 0;
491            }
492            if pat_len == 0 && item_len > 0 {
493                item_len -= item_dec;
494                break;
495            }
496            if item_len == 0 {
497                break;
498            } else {
499                item_len -= item_dec;
500            }
501        }
502        r#match.start = item_len;
503        r#match.end = match_end;
504    }
505
506    /// Cached match result for calculate(pat_end, item_end).
507    fn get_cache_mut(&mut self, pat_end: usize, item_end: usize) -> &mut SeqMatched {
508        debug_assert!(pat_end <= self.pat.len() && item_end <= self.items.len());
509        &mut self.cache[(item_end) * (self.pat.len() + 1) + pat_end]
510    }
511
512    fn get_cache(&self, pat_end: usize, item_end: usize) -> SeqMatched {
513        debug_assert!(pat_end <= self.pat.len() && item_end <= self.items.len());
514        self.cache[(item_end) * (self.pat.len() + 1) + pat_end]
515    }
516
517    /// Reset cache to UNKNOWN state for item_start..item_end.
518    fn clear_cache_range(&mut self, item_start: usize, item_end: usize) {
519        let pat_len = self.pat.len() + 1;
520        let start = item_start * pat_len;
521        let end = item_end * pat_len;
522        self.cache[start..end].fill(SeqMatched::UNKNOWN);
523    }
524
525    /// Modify states so new matches cannot overlap with the previous match ending at `end`.
526    /// i.e. new match.start must >= end.
527    fn cut_off_matches_at(&mut self, end: usize) {
528        // Move the "boundary" conditions from item_end=0 to item_end=end.
529        // Check `fn matched` for the boundary conditions.
530        for i in 0..self.pat.len() {
531            let matched = match i {
532                0 => SeqMatched::MATCH_INIT,
533                1 if matches!(self.pat.first(), Some(Item::Placeholder(p)) if p.matches_multiple()) => {
534                    SeqMatched::MATCH_PLACEHOLDER_MULTI
535                }
536                _ => SeqMatched::empty(),
537            };
538            *self.get_cache_mut(i, end) = matched;
539        }
540        // cache[pat=end, item=end] is intentionally unchanged
541        // so backtracking (to fill captures) still works.
542    }
543
544    fn has_match(&self) -> bool {
545        self.match_end.is_some()
546    }
547}
548
549impl<'a, T: PartialEq + Clone + fmt::Debug> TreeMatchState<'a, T> {
550    /// Match items. `pat` must match `items` from start to end.
551    fn matched(
552        &self,
553        pat: &'a [Item<T>],
554        items: &'a [Item<T>],
555        opts: TreeMatchMode,
556    ) -> Arc<SeqMatchState<'a, T>> {
557        let key = TreeMatchCacheKey::new(pat, items, opts);
558        if let Some(cached) = self.cache.read().unwrap().get(&key) {
559            return cached.clone();
560        }
561
562        let parent = self.clone();
563        let cache = vec![SeqMatched::UNKNOWN; (items.len() + 1) * (pat.len() + 1)];
564        let mut seq = SeqMatchState {
565            parent,
566            cache,
567            pat,
568            items,
569            match_end: None,
570            match_ends: Default::default(),
571        };
572        match opts {
573            TreeMatchMode::MatchFull => {
574                if !seq.matched(pat.len(), items.len(), opts).is_empty() {
575                    seq.match_end = Some(items.len());
576                }
577            }
578            TreeMatchMode::MatchBegin | TreeMatchMode::Search => {
579                // Figure out the longest match.
580                let is_search = opts == TreeMatchMode::Search;
581                let mut last_cutoff = 0;
582                'next: for end in 1..=items.len() {
583                    'retry: loop {
584                        if !seq.matched(pat.len(), end, opts).is_empty() {
585                            if is_search {
586                                // Deal with overlapping.
587                                // There are probably smarter ways to handle this...
588                                if let Some(&last_end) = seq.match_ends.last() {
589                                    let start = seq.backtrack_match_start(end);
590                                    let last_start = seq.backtrack_match_start(last_end);
591                                    if last_start >= start {
592                                        // Current match is better than last. Replace last.
593                                        seq.match_ends.pop();
594                                    } else if last_end > start {
595                                        // Current match overlaps with last.
596                                        if last_cutoff < last_end {
597                                            // Re-run the search with updated cut off state.
598                                            seq.clear_cache_range(last_end + 1, end + 1);
599                                            seq.cut_off_matches_at(last_end);
600                                            last_cutoff = last_end;
601                                            continue 'retry;
602                                        } else {
603                                            // Already cut off. No need to re-run the search.
604                                            continue 'next;
605                                        }
606                                    }
607                                }
608                                seq.match_ends.push(end);
609                            }
610                            seq.match_end = Some(end);
611                        }
612                        break;
613                    }
614                }
615            }
616        }
617        self.cache
618            .write()
619            .unwrap()
620            .entry(key)
621            .or_insert(Arc::new(seq))
622            .clone()
623    }
624
625    fn find_all(&self, items: &'a [Item<T>], pat: &'a [Item<T>]) -> Vec<Match<T>> {
626        let matched = self.matched(pat, items, TreeMatchMode::Search);
627        let mut result = Vec::new();
628        matched.fill_matches(&mut result);
629        let current_depth_matches_len = result.len();
630        // fill_matches() only reports matches in depth 0. Need to scan children recursively.
631        for (i, item) in items.iter().enumerate() {
632            if let Item::Tree(.., children) = item {
633                if is_covered(i, &result[..current_depth_matches_len]) {
634                    // Do not overlap.
635                    continue;
636                }
637                result.extend(self.find_all(children, pat));
638            }
639        }
640        result
641    }
642
643    fn find_one(
644        &self,
645        items: &'a [Item<T>],
646        pat: &'a [Item<T>],
647        mode: TreeMatchMode,
648    ) -> Option<Match<T>> {
649        let matched = self.matched(pat, items, mode);
650        matched.match_end.map(|_| {
651            let mut m = Match::default();
652            matched.fill_match(&mut m);
653            m
654        })
655    }
656}
657
658impl<'a, T: PartialEq + Clone + fmt::Debug + 'static> TreeMatchState<'a, T> {
659    fn replace_all(
660        &self,
661        items: &'a [Item<T>],
662        pat: &'a [Item<T>],
663        replace: &dyn Replace<T>,
664    ) -> Cow<'a, [Item<T>]> {
665        let matched = self.matched(pat, items, TreeMatchMode::Search);
666
667        // Step 1. Calculate matches on the current depth.
668        let mut matches = Vec::new();
669        let mut replaced = MaybeOwned::<T>(OnceLock::new());
670        matched.fill_matches(&mut matches);
671
672        // Step 2. For subtrees that are not covered by the matches, replace them first.
673        // This is because the replaces are 1-item to 1-item, not shifting indexes around.
674        for (i, item) in items.iter().enumerate() {
675            if let Item::Tree(t, children) = item {
676                if is_covered(i, &matches) {
677                    // Do not overlap.
678                    continue;
679                }
680                let new_children = self.replace_all(children, pat, replace);
681                if is_owned(&new_children) {
682                    replaced.maybe_init(items);
683                    replaced.as_mut()[i] = Item::Tree(t.clone(), new_children.into_owned());
684                }
685            }
686        }
687
688        // Step 3. Replace at the current depth.
689        if !matches.is_empty() {
690            let mut new_items = Vec::with_capacity(items.len());
691            let mut end = 0;
692            for m in matches {
693                new_items.extend_from_slice(replaced.slice(items, end, m.start));
694                let replaced = replace.expand(&m);
695                new_items.extend(replaced);
696                end = m.end;
697            }
698            new_items.extend_from_slice(replaced.slice(items, end, items.len()));
699            replaced.0 = new_items.into();
700        }
701
702        match replaced.0.into_inner() {
703            None => Cow::Borrowed(items),
704            Some(items) => Cow::Owned(items),
705        }
706    }
707}
708
709// Work with Cow. This is mainly to avoid the lifetime of a `Cow`.
710struct MaybeOwned<T>(OnceLock<Vec<Item<T>>>);
711
712impl<T: Clone + 'static> MaybeOwned<T> {
713    fn maybe_init(&self, init_value: &[Item<T>]) {
714        self.0.get_or_init(|| init_value.to_vec());
715    }
716
717    fn slice<'a>(&'a self, fallback: &'a [Item<T>], start: usize, end: usize) -> &'a [Item<T>] {
718        match self.0.get() {
719            None => &fallback[start..end],
720            Some(v) => &v[start..end],
721        }
722    }
723}
724
725impl<T: Clone + 'static> MaybeOwned<T> {
726    fn as_mut(&mut self) -> &mut Vec<Item<T>> {
727        self.0.get_mut().unwrap()
728    }
729}
730
731// clippy: intentionally want to test on Cow but not consume it.
732#[allow(clippy::ptr_arg)]
733fn is_owned<T: ToOwned + ?Sized>(value: &Cow<'_, T>) -> bool {
734    matches!(value, Cow::Owned(_))
735}
736
737fn is_covered<T>(index: usize, sorted_matches: &[Match<T>]) -> bool {
738    let m = match sorted_matches.binary_search_by_key(&index, |m| m.start) {
739        Ok(idx) => sorted_matches.get(idx),
740        Err(idx) => sorted_matches.get(idx.saturating_sub(1)),
741    };
742    if let Some(m) = m {
743        if m.start <= index && m.end > index {
744            return true;
745        }
746    }
747    false
748}