Skip to main content

trex/
engine.rs

1//! The matching engine.
2//!
3//! The engine folds the pattern over the token stream as a set of
4//! reachable states rather than a backtracking search. Each state
5//! is a token position plus a register environment, deduplicated
6//! through a HashSet. Because it explores positions as a set
7//! instead of trying paths one at a time, it never backtracks: an
8//! input that drives a backtracking matcher to exponential time
9//! runs in polynomial time here, with no catastrophic-backtracking
10//! cliff.
11//!
12//! This set-reachability form recomputes a nested quantifier's
13//! closure once per starting position, so its worst case is
14//! polynomial rather than linear. The single-pass NFA simulation in
15//! [`crate::nfa`] visits each state once per position for a
16//! worst-case-linear bound, and [`scan`] tries it first; this engine
17//! handles what that one declines - balanced groups, field anchors,
18//! and the axis predicates - and is the oracle the single-pass engine
19//! is differential-tested against.
20//!
21//! The active set is the operational form of the partial-derivative
22//! set of the pattern with respect to the consumed prefix: each
23//! reachable position is one residual. Registers ride alongside,
24//! making the machine a register-set automaton, which is what gives
25//! `:name` / `=name` long-distance binding without the exponential
26//! blowup of a backtracking backreference.
27//!
28//! The scan over a large token stream is data-parallel: the match
29//! attempt anchored at each position is an independent, pure call, so
30//! the attempts fan out across the available cores and only the cheap
31//! leftmost, non-overlapping selection runs on one thread. Small
32//! streams stay inline, where a thread spawn would cost more than the
33//! scan.
34
35use std::collections::{BTreeMap, HashSet};
36use std::rc::Rc;
37
38use crate::ast::{Atom, ByteClass, EmptyLoop, Greed, Pattern};
39use crate::lexer::text;
40use crate::spectral::SpectralField;
41use crate::token::{Token, TokenKind};
42
43/// One anchor's grain and cut with the byte offsets they select, ascending.
44pub(crate) type GrainCuts = (crate::ast::Grain, Option<crate::ast::AnchorCut>, Vec<usize>);
45
46/// The offsets kept for `grain` and `cut`, or `None` where the pattern names
47/// no anchor reading them.
48fn cuts_for(lists: &[GrainCuts], grain: crate::ast::Grain, cut: Option<crate::ast::AnchorCut>) -> Option<&[usize]> {
49    lists.iter().find(|(g, c, _)| *g == grain && *c == cut).map(|(_, _, v)| v.as_slice())
50}
51
52/// The precomputed axis fields threaded read-only through the set engine.
53/// Each is built once per scan, and only when the pattern queries that axis
54/// (`\F{...}` builds the spectral field, `@seam` the seam field); a pattern
55/// that queries no axis carries all-`None` and costs nothing. `Copy`, so
56/// threading it through the recursion costs no more than the single option it
57/// replaced, and a new axis is one more field here rather than a new param at
58/// every call site.
59#[derive(Clone, Copy, Default)]
60struct Fields<'a> {
61    /// The pooled spectral signature field (`\F{...}`).
62    spectral: Option<&'a SpectralField>,
63    /// The seam cuts each `@seam` anchor reads, one list per grain and cut the
64    /// pattern names, as ascending byte offsets, so the anchor is an O(log n)
65    /// lookup.
66    seams: &'a [GrainCuts],
67    /// The nesting depth of every token (`@nested>k`), in the order of the slice
68    /// the engine walks, so the anchor reads a token's depth by its own index.
69    depths: Option<&'a [u16]>,
70    /// The contested (vantage-dependent) points each `@ambiguous` anchor
71    /// reads, one list per grain and cut, as ascending byte offsets.
72    contested: &'a [GrainCuts],
73    /// The recurrence fields (`@novel` / `@echoed` / `@echo...`), one per
74    /// orbit rung the pattern counts at. Frames are index-aligned with the
75    /// token stream, so an anchor is a rung lookup and an O(1) index.
76    echo: &'a [(crate::orbit::OrbitGroup, crate::echo::EchoField)],
77    /// The upper-grain window (`@super`): which construct each token is in.
78    supers: Option<&'a crate::supertoken::SuperContext>,
79    /// How each timestamp compares with the timestamp before it
80    /// (`@order`): `Some(true)` at or after, `Some(false)` before, `None`
81    /// where the token is no timestamp or is the input's first.
82    order: Option<&'a [Option<bool>]>,
83    /// The input's line templates (`@shape:rare`): which template each
84    /// token's line has, and how many lines each template covers.
85    templates: Option<&'a crate::templates::Mining>,
86    /// The second inputs the join anchors read (`@echoed:@other`), each
87    /// keyed once at its rung against every token of this input.
88    joins: &'a [Join],
89    /// The pair field's readings at each grain an anchor names (`@strain`,
90    /// `@bound`, `@kin`), in the slot [`grain_slot`] gives the grain.
91    gravity: [Option<&'a crate::gravity::Readings>; 3],
92    /// Each `@kin` anchor's grain and example, with the type the example names
93    /// in this input, `None` where the input holds no such type.
94    kin: &'a [(crate::ast::Grain, String, Option<u32>)],
95    /// The stream's live periods and each token's index among the significant
96    /// tokens (`@phase:k/p`, `@phase:k#n`).
97    bands: Option<&'a Bands>,
98    /// Per token index, the 1-based comma-delimited field it starts, or `0`
99    /// when it does not start one (`@k`). Index-aligned with the token stream,
100    /// so the anchor is an O(1) lookup.
101    field_starts: Option<&'a [u32]>,
102    /// The rolling window's fold at each token (`\N{>+1}`); index-aligned.
103    context: Option<&'a crate::context::ContextField>,
104    /// The relation-admitted folds and the phase at each token
105    /// (`\N{>+1:phase}`, `\N{>+1:k}`, `@phase:2`); index-aligned.
106    relation: Option<&'a crate::context::RelationContext>,
107    /// The clock a typed predicate's timestamp clause reads (`\T{age<24h}`),
108    /// one reading per scan.
109    clock: crate::typed::Clock,
110    /// The ids of the registers bound under a repetition, whose every
111    /// binding a state keeps rather than the last.
112    lists: &'a [u16],
113}
114
115/// The context fields a pattern reads, built once per scan like the axis
116/// fields and only when the pattern asks for them.
117struct ContextBuild {
118    window: Option<crate::context::ContextField>,
119    relation: Option<crate::context::RelationContext>,
120}
121
122/// Build the context fields `pattern` reads over `toks`. The spectral and
123/// echo fields already built for other atoms are reused; missing ones are
124/// built here.
125fn build_context(
126    pattern: &Pattern,
127    input: &[u8],
128    toks: &[Token],
129    spectral: Option<&SpectralField>,
130    echo: Option<&crate::echo::EchoField>,
131) -> ContextBuild {
132    use crate::context::{ContextConfig, fold_windows_parallel, record_period, relate};
133    use crate::profile::AxisCtx;
134    let uses = pattern.context_uses();
135    // The predicates read magnitude, which is a function of the token's
136    // bytes, so the readings need no axis field behind them.
137    let ctx = AxisCtx::new(input);
138    let window = uses.window.then(|| {
139        let supers = crate::supertoken::SuperContext::build(toks, input);
140        fold_windows_parallel(toks, &ctx, &supers, &ContextConfig::default())
141    });
142    // Each input of the related context is built only for the scope that
143    // reads it: the regime cuts come from a spectral pass, the echo and key
144    // folds from the recurrence field, the phase from the record period.
145    let relation = uses.any_related().then(|| {
146        let own_spectral;
147        let cuts: &[usize] = if uses.regime {
148            match spectral {
149                Some(s) => &s.boundaries,
150                None => {
151                    // Only the change-point boundaries are read here, so the
152                    // field is built for those alone: the period scan and the
153                    // k-gram novelty would each be work at every byte of the
154                    // input for a reading that nothing uses.
155                    let mut needs = crate::spectral::Needs::none();
156                    needs.onset = true;
157                    needs.entropy = true;
158                    needs.bands = true;
159                    own_spectral =
160                        crate::spectral::analyze_needing(input, &Default::default(), needs);
161                    &own_spectral.boundaries
162                }
163            }
164        } else {
165            &[]
166        };
167        let own_echo;
168        let echo = if uses.echo || uses.key {
169            match echo {
170                Some(e) => e,
171                None => {
172                    own_echo = crate::echo::analyze(toks, input);
173                    &own_echo
174                }
175            }
176        } else {
177            own_echo =
178                crate::echo::EchoField { frames: Vec::new(), keyed: 0, distinct: 0, novel: 0, echoed: 0 };
179            &own_echo
180        };
181        let period = if uses.phase { record_period(toks, input) } else { None };
182        relate(toks, &ctx, cuts, echo, period)
183    });
184    ContextBuild { window, relation }
185}
186
187/// For each token index, the 1-based comma-delimited field that token begins,
188/// or `0` when it begins none. A token begins field `k` when exactly `k - 1`
189/// significant commas precede it and it is at the input start or directly
190/// after a comma.
191pub(crate) fn field_start_index(input: &[u8], toks: &[Token]) -> Vec<u32> {
192    let mut out = vec![0u32; toks.len()];
193    let mut commas = 0u32;
194    let mut any_significant = false;
195    let mut prev_was_comma = false;
196    for (p, t) in toks.iter().enumerate() {
197        if t.kind == TokenKind::Whitespace {
198            // Insignificant: it neither opens a field nor closes one.
199            if !any_significant || prev_was_comma {
200                out[p] = commas + 1;
201            }
202            continue;
203        }
204        if !any_significant || prev_was_comma {
205            out[p] = commas + 1;
206        }
207        any_significant = true;
208        if t.kind == TokenKind::Punct && text(input, t) == b"," {
209            commas += 1;
210            prev_was_comma = true;
211        } else {
212            prev_was_comma = false;
213        }
214    }
215    out
216}
217
218/// A register environment: register *id* to the bound value's byte span
219/// `(start, end)` in the input, resolved to text only when a register-
220/// equality atom compares it or a completed match reports it.
221///
222/// Three layers keep this off the per-state hot path, each removing a cost
223/// the measurement found scaled per register:
224///
225/// - **Interned key.** Register names are interned to a dense `u16` id once
226///   per scan (see [`collect_registers`]); the map is keyed by id, not by
227///   `String`. The active-set dedup hashes the whole state per reachable
228///   position, so a `String` key meant hashing register names on the hot
229///   path -- a cost that grew linearly with register count (+72% for one
230///   register, +120% for two). A `u16` key hashes in constant time and
231///   keeps the hash well-distributed, so the dedup never degrades.
232/// - **Span value.** The value is a byte span, not a `String`, so a bind
233///   stores two integers instead of allocating and copying the matched
234///   text; the text is resolved lazily at a register-equality check or at
235///   final capture output.
236/// - **Copy-on-write map.** The map is wrapped in `Rc`: a reachable state
237///   is *carried* far more often than its registers are *bound* (every
238///   atom match clones the state to advance its position, but a `:name`
239///   bind happens only at the few positions that capture), so sharing the
240///   map through `Rc` makes each carry-clone a refcount bump, and only a
241///   bind costs a real clone, via `Rc::make_mut`.
242///
243/// `Rc<T>` derives `Hash`/`Eq`/`Ord` from the pointee, so the dedup
244/// compares register contents, not pointers, and stays correct. Measured:
245/// carrying one register entry cost +112% with a cloned
246/// `BTreeMap<String, String>`; the three layers cut that to single digits.
247type Env = Rc<BTreeMap<u16, (usize, usize)>>;
248
249/// The distinct register names referenced by `pat`, in first-encounter
250/// order. A register's id is its index here; the set engine keys its
251/// environment by that id rather than by name, so names are compared once,
252/// at scan setup, instead of on every dedup of a reachable state.
253fn collect_registers(pat: &Pattern) -> Vec<String> {
254    fn walk(pat: &Pattern, out: &mut Vec<String>) {
255        let push = |name: &str, out: &mut Vec<String>| {
256            if !out.iter().any(|r| r == name) {
257                out.push(name.to_string());
258            }
259        };
260        match pat {
261            Pattern::Empty | Pattern::Guard(..) | Pattern::Anchor(_) => {}
262            Pattern::Assert(p, _, _) => walk(p, out),
263            Pattern::Atom(
264                Atom::RegisterEq(name, _)
265                | Atom::RegisterRelated(name, _)
266                | Atom::RegisterWithin(name, _, _)
267                | Atom::RegisterKin(name, _),
268            ) => {
269                push(name, out);
270            }
271            // A relative magnitude predicate keyed on a register reads that
272            // register, so it is referenced like an equality would be.
273            Pattern::Atom(a) => {
274                if let Some(name) = a.key_scope() {
275                    push(name, out);
276                }
277            }
278            Pattern::Concat(ps) | Pattern::Alt(ps, _) => ps.iter().for_each(|p| walk(p, out)),
279            Pattern::Opt(p, _)
280            | Pattern::Star(p, _)
281            | Pattern::Plus(p, _)
282            | Pattern::Repeat(p, _, _, _)
283            | Pattern::Atomic(p)
284            | Pattern::Balanced(_, p)
285            | Pattern::Field(_, p) => walk(p, out),
286            Pattern::Bind(name, _, p) => {
287                push(name, out);
288                walk(p, out);
289            }
290            Pattern::Within(v, _) => {
291                for e in v {
292                    if let Some((name, _)) = &e.bind {
293                        push(name, out);
294                    }
295                    walk(&Pattern::Atom(e.atom.clone()), out);
296                }
297            }
298        }
299    }
300    let mut out = Vec::new();
301    walk(pat, &mut out);
302    out
303}
304
305/// The interned id of register `name`, or `None` when the pattern never
306/// references it (a possibility only for a malformed register-equality with
307/// no matching bind, which then simply never matches).
308fn reg_id(regs: &[String], name: &str) -> Option<u16> {
309    regs.iter().position(|r| r == name).map(|i| i as u16)
310}
311
312/// The branch taken at each enclosing [`crate::ast::AltMode::First`],
313/// outermost first.
314///
315/// Compared lexicographically: the smallest path is the preferred derivation,
316/// which is what makes leftmost-first expressible in an engine that explores a
317/// set, where there is otherwise no preference order. `None` is the empty path
318/// and beats every non-empty one, so a pattern with no such alternation costs
319/// nothing. Shared through `Rc` for the same reason [`Env`] is: a state is
320/// carried far more often than a branch is taken.
321///
322/// Held in the state where it is short enough, and on the heap past that.
323///
324/// Measured over the crate's own source and over prose, every extension a scan
325/// makes produces a path of exactly one byte and none exceeds four, so the heap
326/// form was taking a block with a sixteen byte refcount header to carry a
327/// single byte three to four million times a scan. The inline form carries
328/// seven bytes and a length, which is what fits beside it in one word.
329///
330/// The heap arm is what keeps this a choice about speed rather than a bound on
331/// the language: a pattern whose first-match alternations nest deeply enough
332/// still gets a path as long as it needs, and neither corpus measured has one.
333///
334/// It costs width. `Option<Rc<[u8]>>` is sixteen bytes because the null
335/// pointer niche carries the `None`; two variants sharing no niche need a
336/// discriminant, so this is twenty-four and a `State` is that much wider. The
337/// sweep carries millions of states in vectors a fan-out grows, so the extra
338/// width is offset by the allocations it avoids.
339#[derive(Clone, Debug)]
340enum Rank {
341    /// A path that fits beside its length in a word.
342    Inline { len: u8, bytes: [u8; 7] },
343    /// A path past the inline room.
344    Heap(Rc<[u8]>),
345}
346
347/// How many bytes a path holds before it goes to the heap.
348const RANK_INLINE: usize = 7;
349
350impl Default for Rank {
351    fn default() -> Self {
352        Rank::Inline { len: 0, bytes: [0; RANK_INLINE] }
353    }
354}
355
356impl Rank {
357    /// The path as bytes, whichever form holds it.
358    fn as_slice(&self) -> &[u8] {
359        match self {
360            Rank::Inline { len, bytes } => &bytes[..*len as usize],
361            Rank::Heap(path) => path,
362        }
363    }
364
365    /// This path with `branch` appended.
366    fn extended(&self, branch: u8) -> Rank {
367        let path = self.as_slice();
368        let n = path.len();
369        if n < RANK_INLINE {
370            let mut bytes = [0u8; RANK_INLINE];
371            bytes[..n].copy_from_slice(path);
372            bytes[n] = branch;
373            return Rank::Inline { len: (n + 1) as u8, bytes };
374        }
375        // One allocation, and the iterator's shape is what makes it one: a
376        // slice collects in a single sized allocation only where the length can
377        // be trusted, which a map over a range reports and a chain does not.
378        Rank::Heap((0..n + 1).map(|i| if i < n { path[i] } else { branch }).collect())
379    }
380
381    /// The path these bytes spell, inline where they fit.
382    fn of(path: &[u8]) -> Rank {
383        if path.len() <= RANK_INLINE {
384            let mut bytes = [0u8; RANK_INLINE];
385            bytes[..path.len()].copy_from_slice(path);
386            return Rank::Inline { len: path.len() as u8, bytes };
387        }
388        Rank::Heap(Rc::from(path))
389    }
390}
391
392/// One binding of a register bound under a repetition, and the bindings
393/// before it: a list shared by every state that forked after the binding, so
394/// a fork copies one pointer and a binding adds one node.
395#[derive(Debug)]
396struct HistNode {
397    id: u16,
398    span: (usize, usize),
399    prev: Hist,
400}
401
402/// The bindings a state's registers under a repetition have made, newest
403/// first; nothing for a pattern that binds none under one.
404type Hist = Option<Rc<HistNode>>;
405
406/// One reachable matcher state.
407///
408/// `Eq` and `Hash` cover `pos` and `env` only, not `rank` or `hist`. Two
409/// derivations reaching the same position with the same registers have
410/// identical futures, so they are one state and the dedup collapses them; the
411/// branch path that got there decides only which of the two is preferred, and
412/// the bindings it made under a repetition ride with the one kept. Keeping
413/// rank out of the identity is what stops a preferred and a non-preferred
414/// derivation coexisting and doubling the set at every alternation.
415///
416/// Deduplicating with a linear scan would make each star fixpoint quadratic
417/// and the whole scan cubic, which is the difference between a matcher that
418/// shrugs off adversarial input and one that stalls on it.
419#[derive(Clone, Debug)]
420struct State {
421    /// Token index reached.
422    pos: usize,
423    /// Registers bound so far.
424    env: Env,
425    /// Preference path; see [`Rank`].
426    rank: Rank,
427    /// Every binding made so far under a repetition; see [`Hist`].
428    hist: Hist,
429}
430
431impl PartialEq for State {
432    fn eq(&self, other: &Self) -> bool {
433        self.pos == other.pos && self.env == other.env
434    }
435}
436
437impl Eq for State {}
438
439impl std::hash::Hash for State {
440    fn hash<H: std::hash::Hasher>(&self, h: &mut H) {
441        self.pos.hash(h);
442        self.env.hash(h);
443    }
444}
445
446/// The empty environment, shared rather than built.
447///
448/// [`State::start`] runs once at every anchor the sweep offers, and building a
449/// map there allocated an empty `BTreeMap` behind an `Rc` for every significant
450/// token in the input whether the pattern bound a register or not. Sampling the
451/// callers of a scan's allocations put `State::start` among the largest.
452///
453/// Sharing one is safe because the map is already copy-on-write: every write
454/// goes through `Rc::make_mut`, which clones when the count is above one, so a
455/// state that binds a register gets a map of its own and the shared empty one
456/// is never written through.
457///
458/// Per thread, because an `Rc` is not shared across threads - which is also
459/// where the parallel sweep wants it, each worker bumping a count no other
460/// worker's cache line holds.
461fn empty_env() -> Env {
462    thread_local! {
463        static EMPTY: Env = Rc::new(BTreeMap::new());
464    }
465    EMPTY.with(Clone::clone)
466}
467
468impl State {
469    /// A state at `pos` with no registers and no branch choices.
470    fn start(pos: usize) -> Self {
471        State { pos, env: empty_env(), rank: Rank::default(), hist: None }
472    }
473
474    /// The bindings under a repetition, oldest first, as register id and
475    /// byte span.
476    fn history(&self) -> Vec<(u16, (usize, usize))> {
477        let mut out = Vec::new();
478        let mut cur = self.hist.as_ref();
479        while let Some(node) = cur {
480            out.push((node.id, node.span));
481            cur = node.prev.as_ref();
482        }
483        out.reverse();
484        out
485    }
486
487    /// The preference path as a slice.
488    fn rank_slice(&self) -> &[u8] {
489        self.rank.as_slice()
490    }
491
492    /// This state's path extended by taking branch `branch`.
493    ///
494    /// This runs for every state on every branch of a first-match alternation
495    /// and for every state on every iteration of a quantifier's fixpoint, which
496    /// is the hottest place in this engine that allocates at all: sampling the
497    /// callers of a scan's allocations put this function in two of the three
498    /// largest rows, once for the `Rc` and once for the buffer beneath it.
499    ///
500    /// One allocation, and the shape of the iterator is what makes it one.
501    /// `Rc<[u8]>` collects in a single sized allocation only where the iterator
502    /// reports an exact length it can be trusted on; a chain of the path and
503    /// one more byte does not, and would collect into a vector first and copy
504    /// again. A map over a range does, so the extension is written as an index
505    /// walk that yields the path and then the branch.
506    fn with_branch(&self, branch: u8) -> Self {
507        // The length the extension produces, which is what the inline room has
508        // to hold before this reaches the heap at all.
509        note_extended(self.rank_slice().len() + 1);
510        State {
511            pos: self.pos,
512            env: self.env.clone(),
513            rank: self.rank.extended(branch),
514            hist: self.hist.clone(),
515        }
516    }
517}
518
519/// Order two preference paths, lower being preferred.
520///
521/// Every choice a derivation makes writes an entry, and each entry is already
522/// oriented so that the preferred option sorts lower: an earlier alternation
523/// branch, another iteration of a greedy quantifier, an earlier stop for a
524/// lazy one. Two derivations therefore agree entry by entry until the first
525/// point where they chose differently, and that entry decides, which is a
526/// plain lexicographic compare.
527fn cmp_rank(a: &[u8], b: &[u8]) -> std::cmp::Ordering {
528    note_compared();
529    a.cmp(b)
530}
531
532/// Where the preferred derivation is in `states`, by the rank order, or
533/// `None` for an empty set.
534///
535/// The index rather than the state, so a caller holding the vector can keep
536/// the one it names and drop the rest in place. Ties go to the earliest, which
537/// is the order `min_by` takes over the states themselves.
538fn best_index(states: &[State]) -> Option<usize> {
539    states
540        .iter()
541        .enumerate()
542        .min_by(|(_, a), (_, b)| cmp_rank(a.rank_slice(), b.rank_slice()))
543        .map(|(at, _)| at)
544}
545
546/// The single entry a choiceless quantifier writes for having run `iters`
547/// times, oriented so the preferred count sorts lower.
548fn count_key(g: Greed, iters: usize) -> u8 {
549    let k = u8::try_from(iters.min(u8::MAX as usize)).expect("clamped to a u8");
550    match g {
551        // More iterations is preferred, so a higher count must sort lower.
552        Greed::Greedy => u8::MAX - k,
553        Greed::Lazy => k,
554    }
555}
556
557/// How many register spans a match holds without allocating.
558///
559/// Two spans are sixteen bytes, which is what the fat pointer of the shared
560/// form already costs, so carrying them inline widens nothing: [`Regs`] is the
561/// size of the `Vec` it stands in for, and a match that binds nothing is no
562/// larger for it.
563pub const INLINE_REGS: usize = 2;
564
565/// The register spans of one match.
566///
567/// A match's register count is a property of its pattern, not of the match, so
568/// every match of one pattern carries the same number. What changes with that
569/// number is which way of holding it is cheapest, and the two costs are both
570/// measured here: a heap allocation and its free is 60.5 nanoseconds a match,
571/// and a reference count's clone and drop is 10.
572///
573/// So neither form wins everywhere. Up to [`INLINE_REGS`] the spans ride in the
574/// match and cost neither. Beyond it they are shared by reference count, which
575/// is six times cheaper than allocating per match and lets a match cross a
576/// thread without copying them.
577#[derive(Clone)]
578pub enum Regs {
579    /// No registers, which is what every match of a pattern that binds none
580    /// carries.
581    ///
582    /// A variant of its own rather than an empty `Inline`, because this is the
583    /// commonest match in the crate and it is built by a path that does nothing
584    /// else: `captures` over a non-binding pattern is `Match::from` per span and
585    /// no work besides. Filling a zeroed inline array there measured 6.8
586    /// nanoseconds a match against writing a discriminant, which over eight rows
587    /// of the comparison was 3.41 ms - more than holding the registers inline
588    /// won back on the two rows that bind.
589    None,
590    /// The spans a match holds itself, and how many of them are live.
591    Inline(u8, [Span; INLINE_REGS]),
592    /// More spans than ride inline, counted rather than copied.
593    Shared(std::sync::Arc<[Span]>),
594}
595
596impl Regs {
597    /// No registers.
598    #[must_use]
599    #[inline]
600    pub const fn none() -> Self {
601        Regs::None
602    }
603
604    /// The spans of `from`, inline where they fit and shared where they do not.
605    #[must_use]
606    #[inline]
607    pub fn from_slice(from: &[Span]) -> Self {
608        if from.is_empty() {
609            return Regs::None;
610        }
611        if from.len() <= INLINE_REGS {
612            let mut held = [Span { start: 0, end: 0 }; INLINE_REGS];
613            held[..from.len()].copy_from_slice(from);
614            let n = u8::try_from(from.len()).expect("at most INLINE_REGS");
615            Regs::Inline(n, held)
616        } else {
617            Regs::Shared(from.into())
618        }
619    }
620
621    /// The spans, whichever way they are held.
622    #[must_use]
623    #[inline]
624    pub fn as_slice(&self) -> &[Span] {
625        match self {
626            Regs::None => &[],
627            Regs::Inline(n, spans) => &spans[..*n as usize],
628            Regs::Shared(spans) => spans,
629        }
630    }
631
632    /// The spans to write through, for a caller rebasing them onto another
633    /// region of the input.
634    ///
635    /// Spans another match still holds are copied before the first such write,
636    /// so rebasing one match never rewrites another's registers.
637    #[inline]
638    pub fn as_mut_slice(&mut self) -> &mut [Span] {
639        match self {
640            Regs::None => &mut [],
641            Regs::Inline(n, spans) => &mut spans[..*n as usize],
642            Regs::Shared(spans) => {
643                if std::sync::Arc::get_mut(spans).is_none() {
644                    *spans = spans.to_vec().into();
645                }
646                std::sync::Arc::get_mut(spans).expect("no other holder remains")
647            }
648        }
649    }
650}
651
652impl std::ops::Deref for Regs {
653    type Target = [Span];
654    #[inline]
655    fn deref(&self) -> &[Span] {
656        self.as_slice()
657    }
658}
659
660impl From<Vec<Span>> for Regs {
661    #[inline]
662    fn from(v: Vec<Span>) -> Self {
663        Regs::from_slice(&v)
664    }
665}
666
667// Filled straight into the form the match will hold, so a caller that has an
668// iterator and not a slice also allocates nothing for the counts that ride
669// inline. The first spans past the inline width are what says a vector is
670// needed, and it is built only then.
671impl FromIterator<Span> for Regs {
672    fn from_iter<I: IntoIterator<Item = Span>>(iter: I) -> Self {
673        let mut it = iter.into_iter();
674        let mut held = [Span { start: 0, end: 0 }; INLINE_REGS];
675        let mut n = 0usize;
676        for slot in &mut held {
677            let Some(span) = it.next() else { break };
678            *slot = span;
679            n += 1;
680        }
681        match it.next() {
682            None if n == 0 => Regs::None,
683            None => Regs::Inline(u8::try_from(n).expect("at most INLINE_REGS"), held),
684            Some(over) => {
685                let mut all: Vec<Span> = Vec::with_capacity(n + 2);
686                all.extend_from_slice(&held[..n]);
687                all.push(over);
688                all.extend(it);
689                Regs::Shared(all.into())
690            }
691        }
692    }
693}
694
695// By the spans and not by how they are held: the same registers held inline and
696// held shared are the same registers, and a caller comparing two matches is
697// asking about the registers.
698impl PartialEq for Regs {
699    fn eq(&self, other: &Self) -> bool {
700        self.as_slice() == other.as_slice()
701    }
702}
703
704impl Eq for Regs {}
705
706impl std::fmt::Debug for Regs {
707    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
708        self.as_slice().fmt(f)
709    }
710}
711
712/// A reported match: a half-open byte span plus the registers bound
713/// when the match completed.
714#[derive(Clone, Debug, PartialEq, Eq)]
715pub struct Match {
716    /// Inclusive start byte offset of the match in the input.
717    pub start: usize,
718    /// Exclusive end byte offset of the match in the input.
719    pub end: usize,
720    /// Captured registers as spans, in the order [`Self::names`] holds their
721    /// names, which is sorted by name.
722    ///
723    /// Spans rather than text, because the engine binds positions and the
724    /// text is a slice of the input the caller already holds. A register
725    /// that bound nothing carries an empty span.
726    ///
727    /// Positions rather than pairs, because the names belong to the pattern and
728    /// not to any one of its matches: carrying an owned name in each cost 58.4
729    /// nanoseconds a match, half of what resolving a match cost at all.
730    ///
731    /// Read through [`Self::captures`], as the names are read through
732    /// [`Self::names`], so that how the spans are held stays this type's to
733    /// choose. Holding them in a `Vec` cost a heap allocation and its free per
734    /// match, 60.5 nanoseconds, which on a pattern binding one register was
735    /// more than the whole rest of resolving it.
736    pub(crate) captures: Regs,
737    /// The register names, in the order `captures` holds their spans, and
738    /// nothing where the pattern names none.
739    ///
740    /// Shared with every other match of the same pattern, so a match carries a
741    /// reference count rather than a copy of them - and a match of a pattern
742    /// that names no register carries neither. A shared empty list still costs
743    /// the two atomics of a clone and a drop a match, which on a scan reporting
744    /// fifty thousand is half a millisecond to hand back nothing.
745    names: Option<std::sync::Arc<[String]>>,
746    /// Every binding of each register bound under a repetition, oldest first,
747    /// keyed by the register's index in `names`; nothing for a pattern that
748    /// binds none under one, which is one word a match.
749    lists: Option<Box<Bindings>>,
750}
751
752/// The bindings the registers under a repetition made in one match: each
753/// register's index in the match's names with its spans, oldest first.
754type Bindings = Vec<(usize, Vec<Span>)>;
755
756impl Match {
757    /// A match with no registers bound.
758    ///
759    /// Inlined because a cursor builds one a match: it is five field writes,
760    /// and a call to it across the module boundary costs more than the writes.
761    #[must_use]
762    #[inline]
763    pub fn plain(start: usize, end: usize) -> Self {
764        Match { start, end, captures: Regs::none(), names: None, lists: None }
765    }
766
767    /// This match carrying `lists`: every binding of each register bound
768    /// under a repetition, keyed by its index in [`Self::names`].
769    #[must_use]
770    pub(crate) fn with_lists(mut self, lists: Bindings) -> Self {
771        self.lists = (!lists.is_empty()).then(|| Box::new(lists));
772        self
773    }
774
775    /// Every binding the register called `name` made in this match, oldest
776    /// first, where the register is bound under a repetition; `None` for a
777    /// register that is not, whose one binding [`Self::group_span`] holds.
778    #[must_use]
779    pub fn list(&self, name: &str) -> Option<&[Span]> {
780        let i = self.names().iter().position(|n| n == name)?;
781        self.lists.as_ref()?.iter().find(|(k, _)| *k == i).map(|(_, spans)| spans.as_slice())
782    }
783
784    /// Every register bound under a repetition, with its bindings.
785    pub fn lists(&self) -> impl Iterator<Item = (&str, &[Span])> {
786        self.lists
787            .as_deref()
788            .map_or(&[][..], Vec::as_slice)
789            .iter()
790            .map(|(k, spans)| (self.names()[*k].as_str(), spans.as_slice()))
791    }
792
793    /// A match with `captures`, named by `names` in the same order.
794    ///
795    /// An empty `names` is held as none, so a match of a pattern naming no
796    /// register is the same match however it was built.
797    #[must_use]
798    #[inline]
799    pub fn bound(
800        start: usize,
801        end: usize,
802        captures: Regs,
803        names: std::sync::Arc<[String]>,
804    ) -> Self {
805        Match { start, end, captures, names: (!names.is_empty()).then_some(names), lists: None }
806    }
807
808    /// The register names, in the order [`Self::captures`] holds their spans.
809    #[must_use]
810    pub fn names(&self) -> &[String] {
811        self.names.as_deref().unwrap_or(&[])
812    }
813
814    /// The spans this match's registers bound, in the order [`Self::names`]
815    /// holds their names.
816    #[must_use]
817    #[inline]
818    pub fn captures(&self) -> &[Span] {
819        self.captures.as_slice()
820    }
821
822    /// The register spans to write through, for a caller rebasing this match
823    /// onto another region of the input.
824    #[inline]
825    pub fn captures_mut(&mut self) -> &mut [Span] {
826        self.captures.as_mut_slice()
827    }
828
829    /// This match found over a window of a longer input, as a match of that
830    /// input: its span, its registers and every binding made under a
831    /// repetition moved by `by`, where the window begins in it.
832    ///
833    /// # Panics
834    ///
835    /// A register moved past the widest offset a span holds.
836    #[must_use]
837    pub fn shifted(mut self, by: usize) -> Match {
838        let by32 = u32::try_from(by).expect("a window begins within the widest offset a span holds");
839        let shift = |s: &mut Span| {
840            let moved = |at: u32| at.checked_add(by32).expect("a register ends within the widest offset a span holds");
841            s.start = moved(s.start);
842            s.end = moved(s.end);
843        };
844        self.start += by;
845        self.end += by;
846        self.captures.as_mut_slice().iter_mut().for_each(shift);
847        if let Some(lists) = self.lists.as_mut() {
848            lists.iter_mut().flat_map(|(_, spans)| spans.iter_mut()).for_each(shift);
849        }
850        self
851    }
852    /// The bytes the register called `name` bound, or `None` where the
853    /// pattern has no such register.
854    ///
855    /// A register the pattern has but this match did not bind returns an
856    /// empty slice, which is the same answer as binding nothing and is not
857    /// the same as the pattern never naming it.
858    #[must_use]
859    pub fn group<'h>(&self, name: &str, input: &'h [u8]) -> Option<&'h [u8]> {
860        self.group_span(name).map(|s| &input[s.range()])
861    }
862
863    /// The span the register called `name` bound.
864    #[must_use]
865    pub fn group_span(&self, name: &str) -> Option<Span> {
866        let i = self.names().iter().position(|n| n == name)?;
867        self.captures.get(i).copied()
868    }
869
870    /// The bytes of the `i`th register, in the order this match carries them.
871    ///
872    /// Positional access exists because a caller that knows the pattern knows
873    /// the positions; a caller that does not should ask by name, which is the
874    /// handle the pattern actually gave the register.
875    #[must_use]
876    pub fn group_at<'h>(&self, i: usize, input: &'h [u8]) -> Option<&'h [u8]> {
877        self.captures.get(i).map(|s| &input[s.range()])
878    }
879
880    /// The whole match's own span.
881    #[must_use]
882    #[inline]
883    pub fn span(&self) -> Span {
884        Span { start: self.start as u32, end: self.end as u32 }
885    }
886
887    /// The whole match and every register it bound, as a fixed-size array.
888    ///
889    /// The counterpart of the regex crate's `Captures::extract`, which exists
890    /// to destructure a match in one binding rather than unwrapping a group at
891    /// a time. Registers arrive in the order [`Self::captures`] holds them,
892    /// sorted by name: every trex binding is named and there are no numbered
893    /// groups, so there is no group number to order them by.
894    ///
895    /// [`crate::static_captures_len`] is what says `N` is right for a pattern
896    /// before any input is seen.
897    ///
898    /// # Panics
899    ///
900    /// When `N` is not the number of registers this match bound. That is the
901    /// same contract regex's has, and the reason both are checked rather than
902    /// truncated: a silently short array would bind the wrong register to the
903    /// wrong name.
904    #[must_use]
905    pub fn extract<'h, const N: usize>(&self, input: &'h [u8]) -> (&'h [u8], [&'h [u8]; N]) {
906        assert_eq!(
907            self.captures.len(),
908            N,
909            "extract::<{N}> on a match that bound {} registers",
910            self.captures.len()
911        );
912        let mut out = [&input[0..0]; N];
913        for (slot, span) in out.iter_mut().zip(self.captures()) {
914            *slot = &input[span.range()];
915        }
916        (&input[self.start..self.end], out)
917    }
918}
919
920/// A match's byte span alone, for the paths that carry no captures: eight
921/// bytes where a [`Match`] is fifty-six, twenty-four of them the registers a
922/// plain scan never fills.
923#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
924pub struct Span {
925    /// Inclusive start byte offset of the match in the input.
926    pub start: u32,
927    /// Exclusive end byte offset of the match in the input.
928    pub end: u32,
929}
930
931impl Span {
932    /// The start offset as an index.
933    #[must_use]
934    pub fn start(&self) -> usize {
935        self.start as usize
936    }
937
938    /// The end offset as an index.
939    #[must_use]
940    pub fn end(&self) -> usize {
941        self.end as usize
942    }
943
944    /// How many bytes the span covers.
945    #[must_use]
946    pub fn len(&self) -> usize {
947        self.end().saturating_sub(self.start())
948    }
949
950    /// Whether the span covers no bytes.
951    #[must_use]
952    pub fn is_empty(&self) -> bool {
953        self.end() <= self.start()
954    }
955
956    /// The matched bytes' range in the input.
957    #[must_use]
958    pub fn range(&self) -> std::ops::Range<usize> {
959        self.start as usize..self.end as usize
960    }
961}
962
963impl From<Span> for Match {
964    /// The span with no captures.
965    #[inline]
966    fn from(s: Span) -> Self {
967        Match::plain(s.start as usize, s.end as usize)
968    }
969}
970
971/// Whether `pattern` matches anywhere in `input`.
972///
973/// Equal to `!scan(pattern, input).is_empty()`, and cheaper where the answer
974/// can be reached before every match is found. The absent-literal refusal
975/// answers without lexing at all; the window route stops at the first window
976/// holding a match and never lexes the rest. Everywhere else the scan runs as
977/// it would have, since its routes read the input once and produce their
978/// matches together.
979#[must_use]
980pub fn is_match(pattern: &Pattern, input: &[u8]) -> bool {
981    if let Some(shapes) = crate::library::shapes_for(pattern) {
982        return !scan_with_shapes(pattern, input, &shapes).is_empty();
983    }
984    if let Some(found) = routed_is_match_early(pattern, input) {
985        return found;
986    }
987    // The same routes over the pattern with its bindings off, for the reason
988    // [`routed_spans`] takes them that way: the routes are written against the
989    // shapes the language spells without bindings, and a binding constrains
990    // nothing, so `\W:name "="` reaches the route `\W "="` takes only once its
991    // binding is off. Whether a match exists is the same question of both.
992    if let Some(bare) = pattern.without_bindings()
993        && let Some(found) = routed_is_match_early(&bare, input)
994    {
995        crate::trace::rung("is_match", "a route, with the bindings off", input.len());
996        return found;
997    }
998    if let Some(found) = crate::prefilter::any_required_window(pattern, input) {
999        crate::trace::rung("is_match", "the windows a literal opens", input.len());
1000        return found;
1001    }
1002    // Nothing above answered, so there is no literal to search for and the
1003    // only way to know whether a match exists is to lex until one does. A
1004    // prefix settles it where the pattern matches early, which is the common
1005    // case; where it does not, the full scan runs and the prefix has cost a
1006    // sixty-fourth of a megabyte.
1007    if crate::prefilter::any_in_prefix(pattern, input) == Some(true) {
1008        crate::trace::rung("is_match", "a prefix that found one", input.len());
1009        return true;
1010    }
1011    // The held walk, which stops at the first match rather than resuming past
1012    // it. Nothing above answered and the prefix found nothing, so a match here
1013    // is late or absent - and both are what the windowed walk is for, since it
1014    // puts the anchors it has not reached across the cores.
1015    if let Some(found) = crate::nfa::SerialWalk::any_match(pattern, input) {
1016        crate::trace::rung("is_match", "the held walk, windowed from the first ask", input.len());
1017        return found;
1018    }
1019    crate::trace::rung("is_match", "every match of the whole scan", input.len());
1020    !scan(pattern, input).is_empty()
1021}
1022
1023/// Whether `pattern` matches anywhere in `input`, from a route that reads to
1024/// the first match and stops, or `None` where every such route declined.
1025///
1026/// The half of [`is_match`]'s ladder whose answer is a byte route's, held as
1027/// its own function so the bindings-off form asks it and not the rungs below,
1028/// which lex. The counterpart of [`routed_first_early`], rung for rung.
1029fn routed_is_match_early(pattern: &Pattern, input: &[u8]) -> Option<bool> {
1030    if crate::prefilter::requires_absent(pattern, input) {
1031        crate::trace::rung("is_match", "an absent literal, no lex", input.len());
1032        return Some(false);
1033    }
1034    // One literal word token, answered from the first occurrence the bytes
1035    // confirm rather than from all of them. The full scan has to walk every
1036    // occurrence to report them; this reads to the first and stops.
1037    if let Some(lits) = crate::prefilter::byte_routable_literals(pattern)
1038        && let Some(found) = crate::prefilter::byte_route_any_word_literal(&lits, input)
1039    {
1040        crate::trace::rung("is_match", "a word literal route, no lex", input.len());
1041        return Some(found);
1042    }
1043    // The same literal behind `^`, read to the first occurrence that leads its
1044    // line. Without this rung the pattern falls to a lexed prefix, which is a
1045    // lex to answer what the bytes answer on every other ladder.
1046    if let Some(lit) = crate::prefilter::byte_routable_line_anchored_literal(pattern)
1047        && let Some(first) = crate::prefilter::byte_route_first_line_anchored_literal(lit, input, 0)
1048    {
1049        crate::trace::rung("is_match", "a line-anchored literal route, no lex", input.len());
1050        return Some(first.is_some());
1051    }
1052    // A word token then one byte of plain punctuation, answered the same way
1053    // from the first occurrence of that byte that is a match.
1054    if let Some(punct) = crate::prefilter::byte_routable_word_then_punct(pattern)
1055        && let Some(found) = crate::prefilter::byte_route_any_word_then_punct(punct, input)
1056    {
1057        crate::trace::rung("is_match", "a word-then-punct route, no lex", input.len());
1058        return Some(found);
1059    }
1060    // A byte pattern, answered from the first occurrence of its literal
1061    // prefix that opens a token the pattern matches whole.
1062    if let Some((bp, prefix)) = crate::prefilter::byte_routable_byte_pattern(pattern)
1063        && let Some(found) = crate::prefilter::byte_route_any_byte_pattern(bp, &prefix, input)
1064    {
1065        crate::trace::rung("is_match", "a byte-pattern route, no lex", input.len());
1066        return Some(found);
1067    }
1068    // A bare kind atom, answered from the first run the bytes settle as that
1069    // token. The route reports the leftmost match or that there is none, which
1070    // is this question with the span thrown away.
1071    if let Some(first) = crate::prefilter::byte_route_kind_from(pattern, input, 0) {
1072        crate::trace::rung("is_match", "a kind route, no lex", input.len());
1073        return Some(first.is_some());
1074    }
1075    // The windows, stopping at the first occurrence of the opening literal that
1076    // matches, which reads the input only as far as its answer. Above the two
1077    // rungs below because both read more of it than that: the required-window
1078    // route scans for every window a literal opens, and a prefix lexes a
1079    // sixty-fourth of a megabyte to settle a match that may be twenty bytes in.
1080    // The trace put is_match at 0.291 ms here where find, which reaches this
1081    // rung sooner, read 0.00172 for the same question.
1082    if let Some(found) = crate::prefilter::first_by_literal_windows(pattern, input) {
1083        crate::trace::rung("is_match", "windows to the first match", input.len());
1084        return Some(found.is_some());
1085    }
1086    None
1087}
1088
1089/// Tokenize `input` and scan it for `pattern`, returning the
1090/// leftmost, non-overlapping matches.
1091#[must_use]
1092pub fn scan(pattern: &Pattern, input: &[u8]) -> Vec<Span> {
1093    // A library kind exists only in a lex told to produce it, so a pattern
1094    // naming one takes the shaped lex before any route reads the bytes.
1095    if let Some(shapes) = crate::library::shapes_for(pattern) {
1096        return scan_with_shapes(pattern, input, &shapes);
1097    }
1098    if let Some(spans) = routed_spans(pattern, input) {
1099        // Named for what this rung knows, which is that the engine did not run.
1100        // Which route answered is the inner rung's to report, and only some of
1101        // them skip the lex: the window route lexes a token's worth of bytes at
1102        // every occurrence of the opening literal and reaches here too.
1103        crate::trace::rung("scan", "a route, not the engine", input.len());
1104        return spans;
1105    }
1106    // The single-pass engine handles the regular core plus binding,
1107    // register-equality, and the content guard in linear time, over the
1108    // significant stream in the lexer's parts. It returns `None` for
1109    // patterns with a balanced group or field node, whose variable-length
1110    // advance the set-reachability engine below handles instead.
1111    //
1112    // The two are named apart in the trace because they are different engines
1113    // with different costs, and a reader attributing a pattern to "the engine"
1114    // cannot tell which one answered it. Measured over one corpus: a balanced
1115    // group takes the set engine at 44.6203 ms and a star the single-pass one
1116    // at 37.3523, so a mechanism for either would be sized wrongly by a rung
1117    // that named only "an engine".
1118    if let Some(matches) = crate::nfa::scan_nfa(pattern, input) {
1119        crate::trace::rung("scan", "the single-pass engine over a whole lex", input.len());
1120        return matches;
1121    }
1122    crate::trace::rung("scan", "the set engine over a whole lex", input.len());
1123    scan_set_reachability(pattern, input)
1124}
1125
1126/// The matches of `pattern` from a route that answers without the engine, or
1127/// `None` where every route declined and the engine must run.
1128///
1129/// This is the front of [`scan`] held as its own function because a cursor
1130/// takes the same routes and must reach the same verdict: a route that
1131/// answers here answers for both, and only what falls through is walked one
1132/// match at a time. A second copy of the ladder would be a second set of
1133/// answers to the same question.
1134pub(crate) fn routed_spans(pattern: &Pattern, input: &[u8]) -> Option<Vec<Span>> {
1135    // A binding records what a match consumed and constrains nothing, so where
1136    // no atom reads one back the same spans match without it - and the routes
1137    // are written against the shapes the language spells without bindings, so
1138    // `\W:name "="` reaches the route `\W "="` takes only once its binding is
1139    // off. A caller wanting the registers resolves them against the pattern it
1140    // was given, over these spans.
1141    if let Some(bare) = pattern.without_bindings()
1142        && let Some(spans) = routed_spans(&bare, input)
1143    {
1144        crate::trace::rung("scan", "a route, with the bindings off", input.len());
1145        return Some(spans);
1146    }
1147    routed_spans_positional(pattern, input)
1148        .or_else(|| routed_spans_selective(pattern, input))
1149        .or_else(|| {
1150            // The literal windows, last because they do lex - a token's worth
1151            // of bytes an atom, at the occurrences of the literal the pattern
1152            // opens with, rather than the whole input. The routes above lex
1153            // nothing at all and answer their own shapes more cheaply.
1154            let spans = crate::prefilter::scan_by_literal_windows(pattern, input)?;
1155            crate::trace::rung("scan", "windows around the opening literal", input.len());
1156            Some(spans)
1157        })
1158}
1159
1160/// The leftmost match a route can answer, read to that match and no further,
1161/// or `None` where every route declined.
1162///
1163/// [`routed_spans`] reports every match because a scan must. A caller wanting
1164/// only the first had to take that and discard the rest, which reads the whole
1165/// input to answer about a match that may be in its first hundred bytes. The
1166/// routes that can stop early do so here; the ones that cannot yet fall through
1167/// to the full set, so this is never wrong and only sometimes fast.
1168///
1169/// The absent-literal refusal comes first for the same reason it does in
1170/// [`routed_spans_positional`]: it is a verdict of no match that costs no lex
1171/// and no positional scan at all.
1172///
1173/// The rungs are taken in the order [`routed_spans`] takes them, so a pattern
1174/// answers here from the same route that answers a scan. Two of them read no
1175/// further than the match they report - the byte routes, which stop at the
1176/// first occurrence the bytes confirm, and the windows, which stop at the first
1177/// window holding a match and lex none of the rest.
1178///
1179/// The kind route is deliberately not among them. It reads the whole
1180/// significant stream and returns every match, so taking its first is a scan
1181/// spent on one answer: `\W{2}` read 4.782 ms that way against 0.551 from the
1182/// widening prefix [`crate::find`] falls to when this declines. A caller
1183/// wanting one match is better served by a path that stops at it, and the scan
1184/// still takes the kind route through [`routed_spans_selective`].
1185pub(crate) fn routed_first(pattern: &Pattern, input: &[u8]) -> Option<Option<Span>> {
1186    routed_first_early(pattern, input)
1187        .or_else(|| routed_spans_positional(pattern, input).map(|v| v.into_iter().next()))
1188        .or_else(|| {
1189            // The windows, stopping at the first occurrence of the opening
1190            // literal that matches. The scan's form of this builds every match
1191            // because a scan reports every match; a caller wanting one reads
1192            // the bytes up to the first and no further.
1193            let first = crate::prefilter::first_by_literal_windows(pattern, input)?;
1194            crate::trace::rung("routed_first", "windows to the first match", input.len());
1195            Some(first)
1196        })
1197        .or_else(|| crate::prefilter::first_required_window(pattern, input))
1198}
1199
1200/// [`routed_first`] over the positional half of the ladder only.
1201///
1202/// For a caller whose answer depends on the matches not overlapping - the
1203/// soonest-ending match is the first one only when no later match can end
1204/// earlier, which whole tokens of a fixed count guarantee and the selective
1205/// routes do not.
1206pub(crate) fn routed_first_positional(pattern: &Pattern, input: &[u8]) -> Option<Option<Span>> {
1207    routed_first_early(pattern, input)
1208        .or_else(|| routed_spans_positional(pattern, input).map(|v| v.into_iter().next()))
1209        .or_else(|| {
1210            // The windows, which this half of the ladder may take for the same
1211            // reason the routes above it may: their shape is a flat sequence of
1212            // atoms, each consuming one token, so every match spans the same
1213            // number of whole tokens and a later match closes later. The
1214            // leftmost is therefore also the soonest-ending, which is what a
1215            // caller here is asking for.
1216            let first = crate::prefilter::first_by_literal_windows(pattern, input)?;
1217            crate::trace::rung("routed_first_positional", "windows to the first match", input.len());
1218            Some(first)
1219        })
1220}
1221
1222/// [`routed_first`] for a caller anchored at byte `at`.
1223///
1224/// The literal route begins its scan there rather than at the start, because
1225/// the bytes before `at` hold no match the caller wants by definition. The
1226/// other routes have no anchored form yet and fall through to the positional
1227/// set, filtered - which is what every anchored entry point did for all of
1228/// them before.
1229pub(crate) fn routed_first_at(pattern: &Pattern, input: &[u8], at: usize) -> Option<Option<Span>> {
1230    if crate::prefilter::requires_absent(pattern, input) {
1231        return Some(None);
1232    }
1233    if let Some(first) = routed_first_at_reading(pattern, input, at, &mut None) {
1234        crate::trace::rung("routed_first_at", "an anchored byte route, no lex", input.len());
1235        return Some(first);
1236    }
1237    // The same routes over the pattern with its bindings off, for the reason
1238    // [`routed_spans`] takes them that way. Stripped here and not inside
1239    // [`routed_first_at_reading`], whose caller asks once a piece: a pattern
1240    // rebuilt per ask would cost a copy of itself tens of thousands of times.
1241    if let Some(bare) = pattern.without_bindings()
1242        && let Some(first) = routed_first_at_reading(&bare, input, at, &mut None)
1243    {
1244        crate::trace::rung("routed_first_at", "an anchored route, with the bindings off", input.len());
1245        return Some(first);
1246    }
1247    // The windows, searched from `at` and stopping at the first occurrence that
1248    // matches. Above the whole-set fallback because that finds every match in
1249    // the input and throws away the ones before the offset.
1250    if let Some(first) = crate::prefilter::first_by_literal_windows_at(pattern, input, at) {
1251        crate::trace::rung("routed_first_at", "windows from the offset", input.len());
1252        return Some(first);
1253    }
1254    let whole = routed_spans_positional(pattern, input)?;
1255    // Not a byte route from the offset: every match found, then filtered. The
1256    // rung is named apart from the one above because they cost differently and
1257    // a reader attributing a row needs to know which answered.
1258    crate::trace::rung("routed_first_at", "the whole positional set, filtered", input.len());
1259    Some(whole.into_iter().find(|s| s.start() >= at))
1260}
1261
1262/// Every match of `pattern` in `input` at or after byte `at`, from a route that
1263/// answers without walking the whole token stream, or `None` where every route
1264/// declined and the walk must run.
1265///
1266/// The anchored counterpart of [`routed_spans`], for a caller resuming a scan.
1267/// Both routes below begin at `at` themselves rather than answering for the
1268/// whole input and being filtered afterward, which is the distinction
1269/// [`routed_first_at`] draws when it restricts its own filter to the positional
1270/// set: a selected match reaching back across `at` suppresses one starting at
1271/// `at`, so filtering a whole-input selection would lose that second match.
1272///
1273/// An empty vector is a scan that found nothing; `None` is a route that
1274/// declined to answer. They are not the same and a caller must not read one as
1275/// the other.
1276#[must_use]
1277pub(crate) fn routed_spans_at(pattern: &Pattern, input: &[u8], at: usize) -> Option<Vec<Span>> {
1278    if crate::prefilter::requires_absent(pattern, input) {
1279        crate::trace::rung("routed_spans_at", "a literal the input does not hold", input.len());
1280        return Some(Vec::new());
1281    }
1282    let spans = crate::prefilter::scan_by_literal_windows_at(pattern, input, at)?;
1283    crate::trace::rung("routed_spans_at", "windows from the offset", input.len());
1284    Some(spans)
1285}
1286
1287/// The byte routes of [`routed_first_at`] over a reader the caller holds, and
1288/// without its whole-set fallback.
1289///
1290/// For a caller asking at one ascending position after another - a split taking
1291/// its pieces one at a time. Those two rungs are the reader's, and repeating
1292/// them over a reader built per ask costs a fresh quote scan from byte zero
1293/// every time, which makes such a loop quadratic.
1294///
1295/// `None` means no byte route answered. It is the same refusal for every ask,
1296/// so a caller meeting it falls back once rather than once a piece; the absent
1297/// literal is left out here for the same reason, being a verdict on the whole
1298/// input that a caller asks for once.
1299///
1300/// The reader is made on the first ask that reaches a route needing one. A
1301/// reader opens with a search for each quote byte over the whole input, which a
1302/// pattern no route takes must not be charged for.
1303pub(crate) fn routed_first_at_reading<'i>(
1304    pattern: &Pattern,
1305    input: &'i [u8],
1306    at: usize,
1307    reader: &mut Option<crate::prefilter::ByteReader<'i>>,
1308) -> Option<Option<Span>> {
1309    if let Some(lits) = crate::prefilter::byte_routable_literals(pattern) {
1310        let held = reader.get_or_insert_with(|| crate::prefilter::ByteReader::new(input));
1311        if let Some(first) =
1312            crate::prefilter::byte_route_first_word_literal_reading(&lits, held, at)
1313        {
1314            return Some(first);
1315        }
1316    }
1317    // The same literal behind `^`, read to the first occurrence at or after
1318    // `at` that leads its line.
1319    if let Some(lit) = crate::prefilter::byte_routable_line_anchored_literal(pattern) {
1320        let held = reader.get_or_insert_with(|| crate::prefilter::ByteReader::new(input));
1321        if let Some(first) =
1322            crate::prefilter::byte_route_first_line_anchored_literal_reading(lit, held, at)
1323        {
1324            return Some(first);
1325        }
1326    }
1327    // A word token then one byte of plain punctuation, which the unanchored
1328    // ladder also takes, so the same pattern is answered from the bytes at any
1329    // offset.
1330    if let Some(punct) = crate::prefilter::byte_routable_word_then_punct(pattern) {
1331        let held = reader.get_or_insert_with(|| crate::prefilter::ByteReader::new(input));
1332        if let Some(first) =
1333            crate::prefilter::byte_route_first_word_then_punct_reading(punct, held, at)
1334        {
1335            return Some(first);
1336        }
1337    }
1338    // A byte pattern, whose match opens with a literal prefix, so the search
1339    // starts at `at` and every match it reaches begins there or later.
1340    if let Some((bp, prefix)) = crate::prefilter::byte_routable_byte_pattern(pattern) {
1341        let held = reader.get_or_insert_with(|| crate::prefilter::ByteReader::new(input));
1342        if let Some(first) =
1343            crate::prefilter::byte_route_first_byte_pattern_reading(bp, &prefix, held, at)
1344        {
1345            return Some(first);
1346        }
1347    }
1348    // The kind route takes an offset for the same reason the literal one does,
1349    // so an anchored ask is answered from `at` rather than from a whole set
1350    // filtered. These are the rows the ladder was losing worst: every "from an
1351    // offset" operation reached no route at all.
1352    crate::prefilter::byte_route_kind_reading(pattern, input, reader, at)
1353}
1354
1355/// The leftmost match from a route that can stop at it, or `None` where no
1356/// such route applies and a caller must fall back to a whole set.
1357///
1358/// Every route here is positional, so both callers above may take it.
1359fn routed_first_early(pattern: &Pattern, input: &[u8]) -> Option<Option<Span>> {
1360    if crate::prefilter::requires_absent(pattern, input) {
1361        return Some(None);
1362    }
1363    // The same routes over the pattern with its bindings off, for the reason
1364    // [`routed_spans`] takes them that way. Where the first match is does not
1365    // depend on what it records, so the span these report is the span the
1366    // pattern's own first match has; a caller wanting the registers resolves
1367    // them against the pattern it was given, over this span.
1368    if let Some(bare) = pattern.without_bindings()
1369        && let Some(first) = routed_first_early(&bare, input)
1370    {
1371        crate::trace::rung("routed_first", "a route, with the bindings off", input.len());
1372        return Some(first);
1373    }
1374    if let Some(lits) = crate::prefilter::byte_routable_literals(pattern)
1375        && let Some(first) = crate::prefilter::byte_route_first_word_literal(&lits, input)
1376    {
1377        return Some(first);
1378    }
1379    // `^ "let"` is the literal route with one more test on each occurrence, and
1380    // it stops at the first that passes. The spans route filters a whole set
1381    // instead, because a scan must report them all.
1382    if let Some(lit) = crate::prefilter::byte_routable_line_anchored_literal(pattern)
1383        && let Some(first) = crate::prefilter::byte_route_first_line_anchored_literal(lit, input, 0)
1384    {
1385        return Some(first);
1386    }
1387    if let Some(punct) = crate::prefilter::byte_routable_word_then_punct(pattern)
1388        && let Some(first) = crate::prefilter::byte_route_first_word_then_punct(punct, input)
1389    {
1390        return Some(first);
1391    }
1392    if let Some((bp, prefix)) = crate::prefilter::byte_routable_byte_pattern(pattern)
1393        && let Some(first) = crate::prefilter::byte_route_first_byte_pattern(bp, &prefix, input)
1394    {
1395        return Some(first);
1396    }
1397    // A bare kind atom, from the first run the bytes settle as that token. One
1398    // token of a fixed count, so it belongs on this half of the ladder: its
1399    // matches cannot overlap and the leftmost also ends first.
1400    if let Some(first) = crate::prefilter::byte_route_kind_from(pattern, input, 0) {
1401        return Some(first);
1402    }
1403    None
1404}
1405
1406/// Whether any route answers `pattern` over `input`, and with what.
1407///
1408/// For a measurement that means to time the engine: a route answering the
1409/// pattern makes every reading a reading of that route instead, and this is
1410/// how a bench says so rather than assuming.
1411#[doc(hidden)]
1412#[must_use]
1413pub fn routed_spans_public(pattern: &Pattern, input: &[u8]) -> Option<Vec<Span>> {
1414    routed_spans(pattern, input)
1415}
1416
1417/// The part of the route ladder whose answer does not depend on where the
1418/// search began.
1419///
1420/// Each of these routes matches whole tokens of a fixed count - one for a
1421/// literal or a byte pattern, two for a word then punctuation - and two of its
1422/// matches can never share a token: the second token of a `\W "="` match is
1423/// punctuation, so no match can begin there. With no overlap possible, the
1424/// leftmost non-overlapping selection from a later position is exactly the
1425/// tail of the selection from the start, and a caller anchored at `at` may
1426/// take these matches and keep the ones that begin at or after it.
1427///
1428/// That is what [`routed_spans_selective`] cannot promise, which is why the
1429/// ladder is cut here rather than kept whole.
1430pub(crate) fn routed_spans_positional(pattern: &Pattern, input: &[u8]) -> Option<Vec<Span>> {
1431    // Something every match must hold and the input does not: a byte string,
1432    // or - for the kinds that name no byte string - a run of one byte class
1433    // long enough to be a token. There is nothing to find, and answering here
1434    // skips the lex, which a scan that cannot match otherwise runs in full and
1435    // which costs 217 to 513 MB/s over the corpora it has been read on.
1436    if crate::prefilter::requires_absent(pattern, input) {
1437        return Some(Vec::new());
1438    }
1439    // One literal word token is answerable without lexing the input: a SIMD
1440    // search for it, the lexer's own quote reader, a neighbor test, and the
1441    // lexer over an occurrence's own run where the neighbors do not settle
1442    // it. The route hands back `None` where even that cannot, and then the
1443    // lex runs over the whole input.
1444    if let Some(lits) = crate::prefilter::byte_routable_literals(pattern)
1445        && let Some(spans) = crate::prefilter::byte_route_word_literals(&lits, input)
1446    {
1447        crate::trace::rung("scan", "a word literal route, no lex", input.len());
1448        return Some(spans);
1449    }
1450    // `^ "let"` is the literal route with one more test on each occurrence:
1451    // every byte back to the previous newline must be whitespace, which is what
1452    // makes the token the first significant one on its line. The occurrences it
1453    // rejects cost the search that found them and no lex.
1454    if let Some(lit) = crate::prefilter::byte_routable_line_anchored_literal(pattern)
1455        && let Some(spans) = crate::prefilter::byte_route_line_anchored_literal(lit, input)
1456    {
1457        crate::trace::rung("scan", "a line-anchored literal route, no lex", input.len());
1458        return Some(spans);
1459    }
1460    // A word token then one byte of plain punctuation, `\W "="`, is answered
1461    // the same way from every occurrence of that byte.
1462    if let Some(punct) = crate::prefilter::byte_routable_word_then_punct(pattern)
1463        && let Some(spans) = crate::prefilter::byte_route_word_then_punct(punct, input)
1464    {
1465        crate::trace::rung("scan", "a word-then-punctuation route, no lex", input.len());
1466        return Some(spans);
1467    }
1468    // A byte pattern is a whole-token match, and every match opens with the
1469    // pattern's literal prefix, so each occurrence of that prefix that opens
1470    // a token is the only place to try it.
1471    if let Some((bp, prefix)) = crate::prefilter::byte_routable_byte_pattern(pattern)
1472        && let Some(spans) = crate::prefilter::byte_route_byte_pattern(bp, &prefix, input)
1473    {
1474        crate::trace::rung("scan", "a byte-pattern route, no lex", input.len());
1475        return Some(spans);
1476    }
1477    None
1478}
1479
1480/// The part of the route ladder that selects leftmost non-overlapping matches
1481/// whose length is not fixed, so the selection from a later start can differ
1482/// from the tail of the selection from the start.
1483///
1484/// `\W{2}` over three words matches the first two; from the second word it
1485/// matches the second and third, which the first selection rejected as
1486/// overlapping. A caller anchored partway through must therefore run the
1487/// engine rather than filter these.
1488pub(crate) fn routed_spans_selective(pattern: &Pattern, input: &[u8]) -> Option<Vec<Span>> {
1489    // A plain literal, a word token and one byte of plain punctuation,
1490    // `"let" \W "="`, answered from the bytes: the literal by the word
1491    // literal route, the word and the punctuation by the reader's probes
1492    // at each anchor. First because it lexes nothing where the windows lex a
1493    // token's worth of bytes at every occurrence of the literal, and
1494    // selective rather than positional because a match's word may itself
1495    // be the literal, so the selection from a later start can differ.
1496    let literal_word_punct = || {
1497        let (lit, punct) = crate::prefilter::byte_routable_literal_word_punct(pattern)?;
1498        let spans = crate::prefilter::byte_route_literal_word_punct(lit, punct, input)?;
1499        crate::trace::rung("scan", "a literal, word, punctuation route, no lex", input.len());
1500        Some(spans)
1501    };
1502    literal_word_punct()
1503        // Every match holds the literals the pattern requires, and a match is
1504        // at most so many tokens long, so the spans around those literals'
1505        // occurrences are the only places a match can be. Lexing those rather
1506        // than the input is the saving, so the windows come before the routes
1507        // that lex the whole input; a pattern holding no literal to anchor on
1508        // is handed back before a byte is read, and where the spans would cost
1509        // what the whole lex costs the route declines and the scan runs on.
1510        .or_else(|| {
1511            let spans = crate::prefilter::scan_required_windows(pattern, input)?;
1512            crate::trace::rung("scan", "the windows a literal opens", input.len());
1513            Some(spans)
1514        })
1515        .or_else(|| routed_spans_kind(pattern, input))
1516        .or_else(|| routed_spans_balanced(pattern, input))
1517}
1518
1519/// A balanced group with an unconstrained interior, read off the bracket
1520/// pairing the lexer records.
1521///
1522/// The lexer pairs each bracket as it reads, so a group that asks only where
1523/// the brackets are has already been answered when the lex ends, and the scan
1524/// is one pass over the tokens. This shape reaches the set-reachability engine
1525/// otherwise, which read 44.6203 ms over 7.34 MB against a lex of 2.9.
1526///
1527/// The significant parts rather than the full token vector: the parts carry
1528/// mates of their own, so the route reads them where the chunks were lexed and
1529/// no chunk is copied into one array first.
1530fn routed_spans_balanced(pattern: &Pattern, input: &[u8]) -> Option<Vec<Span>> {
1531    let kind = crate::kind_route::bare_balanced(pattern)?;
1532    Some(crate::parallel_lex::lex_paired_parts_held(input, |parts| {
1533        crate::kind_route::scan_balanced_parts(kind, parts)
1534    }))
1535}
1536
1537/// A fixed sequence of token kinds - `\W`, `\N`, `\W \N` - read as a window
1538/// over the significant stream from the lexer's chunk parts in place.
1539///
1540/// Held apart from the rest of [`routed_spans_selective`] because the ladder
1541/// has two orders to keep in step: a scan takes the rungs in this order, and
1542/// so must a caller wanting one match, which reaches the windows after the
1543/// positional set as a scan does and leaves this rung out, as
1544/// [`routed_first`] says.
1545fn routed_spans_kind(pattern: &Pattern, input: &[u8]) -> Option<Vec<Span>> {
1546    if let Some(codes) = crate::kind_route::kind_sequence(pattern) {
1547        return Some(crate::parallel_lex::lex_significant_parts_held(input, |parts| {
1548            crate::kind_route::scan_kind_sequence(&codes, parts)
1549        }));
1550    }
1551    // A repeat whose bounds differ is a run rather than a fixed window, and the
1552    // same stream answers it: how many of one kind follow, capped at the upper
1553    // bound. `\W{2,4}` reached the per-anchor engine for that and read 11.3008
1554    // ms over 7.34 MB.
1555    let (code, lo, hi) = crate::kind_route::kind_run(pattern)?;
1556    Some(crate::parallel_lex::lex_significant_parts_held(input, |parts| {
1557        crate::kind_route::scan_kind_run(code, lo, hi, parts)
1558    }))
1559}
1560
1561/// Tokenize `input` and scan it for `pattern` under a named empty-loop
1562/// reading.
1563///
1564/// [`scan`] is this with [`EmptyLoop::Thompson`], which is the crate's reading
1565/// and what a pattern gets when it names none.
1566///
1567/// The reading is honored by choosing the engine that holds it, not by
1568/// teaching either engine the other's. The single-pass engine expresses
1569/// preference by the order it queues threads and cannot withdraw one it has
1570/// already queued, which is what the backtracking reading needs when a body
1571/// turns out to match empty; the set engine carries each derivation's path and
1572/// can compare them, so it holds that reading natively. `\|>` and an atomic
1573/// group route by the same rule and for the same reason.
1574///
1575/// Only a repetition whose body can match without consuming a token is
1576/// affected. Everywhere else the two readings agree, and the pattern stays on
1577/// the single-pass engine whichever it names.
1578#[must_use]
1579pub fn scan_with_empty_loop(pattern: &Pattern, input: &[u8], empty: EmptyLoop) -> Vec<Span> {
1580    if empty == EmptyLoop::Perl && crate::nfa::empty_loop_needs_set_engine(pattern) {
1581        return scan_set_reachability(pattern, input);
1582    }
1583    scan(pattern, input)
1584}
1585
1586/// Tokenize `input` with `shapes` in force and scan it for `pattern`.
1587///
1588/// The shapes decide token boundaries, so they belong to the lex rather than
1589/// the match: a `\{name}` atom in the pattern is matching a token the shape
1590/// created. Pass the same set to [`crate::parser::parse_with_shapes`], or the
1591/// atom naming a shape has no id to resolve.
1592#[must_use]
1593pub fn scan_with_shapes(
1594    pattern: &Pattern,
1595    input: &[u8],
1596    shapes: &crate::custom::ShapeSet,
1597) -> Vec<Span> {
1598    // The library kinds the pattern names join the caller's shapes, so a
1599    // `\{iban}` beside a declared shape is lexed as both.
1600    let shapes = shapes.with_library_shapes(&pattern.library_kinds());
1601    if shapes.is_empty() {
1602        return scan(pattern, input);
1603    }
1604    crate::trace::rung("scan", "the engines over a lex under the declared shapes", input.len());
1605    let blobs = crate::lexer::blob_runs(input);
1606    let toks = crate::lexer::lex_with_shapes(input, &blobs, &shapes, 0);
1607    // Same routing as `scan`: the single-pass engine first, the
1608    // set-reachability fold for what it declines. Handing the tokens over
1609    // rather than re-lexing is what keeps the shapes' boundaries.
1610    if let Some(matches) = crate::nfa::scan_nfa_over(pattern, input, &toks) {
1611        return matches;
1612    }
1613    scan_tokens_from(pattern, input, &toks, 0)
1614}
1615
1616/// [`scan_with_shapes`] from the first token starting at or after byte
1617/// `at`, the leftmost non-overlapping selection re-run from there.
1618#[must_use]
1619pub(crate) fn scan_with_shapes_from(
1620    pattern: &Pattern,
1621    input: &[u8],
1622    shapes: &crate::custom::ShapeSet,
1623    at: usize,
1624) -> Vec<Span> {
1625    // This entry point lexes `input` whole however small `at` leaves the walk,
1626    // so the three phases divide a cost that reads as one. A caller holding a
1627    // lex of the same bytes already - the streaming commit is one - runs the
1628    // first two over again, and the division is what says how much that is.
1629    let preparing = crate::trace::phase("the resumed scan: its shapes and blob runs");
1630    let shapes = shapes.with_library_shapes(&pattern.library_kinds());
1631    drop(preparing);
1632    // The routes read the input's own bytes and know nothing of a declared
1633    // shape, which is the line `scan` draws above [`routed_spans`] and
1634    // `pattern_set` draws before it routes a member. Asked above the lex
1635    // because a route that answers needs no tokens at all.
1636    if shapes.is_empty()
1637        && let Some(spans) = routed_spans_at(pattern, input, at)
1638    {
1639        return spans;
1640    }
1641    let tabling = crate::trace::phase("the resumed scan: its blob runs");
1642    let blobs = crate::lexer::blob_runs(input);
1643    drop(tabling);
1644    let lexing = crate::trace::phase("the resumed scan: its own lex");
1645    let toks = crate::lexer::lex_with_shapes(input, &blobs, &shapes, 0);
1646    drop(lexing);
1647    crate::trace::counted(
1648        "the resumed scan: bytes it lexes",
1649        u64::try_from(input.len()).expect("an input within the counter's width"),
1650    );
1651    scan_over_tokens_from(pattern, input, &toks, at)
1652}
1653
1654/// [`scan_with_shapes_from`] over a lex of `input` the caller already holds.
1655///
1656/// `toks` must be the lex this call would have taken itself. For a pattern
1657/// drawing no library kinds that is what [`crate::lexer::lex`] returns:
1658/// `lex` is [`crate::lexer::lex_with_shapes`] over an empty shape set, and the
1659/// set this builds is empty exactly then, so the two agree token for token.
1660#[must_use]
1661pub(crate) fn scan_over_tokens_from(
1662    pattern: &Pattern,
1663    input: &[u8],
1664    toks: &[Token],
1665    at: usize,
1666) -> Vec<Span> {
1667    let walking = crate::trace::phase("the resumed scan: its walk from the anchor");
1668    let start = toks.partition_point(|t| t.start() < at);
1669    let found = match crate::nfa::scan_nfa_over_serial_from(pattern, input, toks, start) {
1670        Some(matches) => matches,
1671        None => scan_tokens_from(pattern, input, toks, start),
1672    };
1673    drop(walking);
1674    found
1675}
1676
1677/// The registers each of `spans` bound, one [`Match`] per span in order.
1678///
1679/// `spans` are the matches a scan of `pattern` over `input` returned. Each is
1680/// resolved by re-running the attempt anchored where it starts, which is how
1681/// the engines resolve a selected match; a pattern that binds nothing gets
1682/// its spans back with empty captures and no lex.
1683///
1684/// # Panics
1685///
1686/// When a span is not a match of `pattern` over `input`.
1687#[must_use]
1688pub fn captures(pattern: &Pattern, input: &[u8], spans: &[Span]) -> Vec<Match> {
1689    if !pattern.binds() {
1690        crate::trace::rung("captures", "nothing bound, the spans back", input.len());
1691        return spans.iter().copied().map(Match::from).collect();
1692    }
1693    if let Some(shapes) = crate::library::shapes_for(pattern) {
1694        return captures_with_shapes(pattern, input, &shapes, spans);
1695    }
1696    // One span is what every first-match caller asks for, and a route found it
1697    // without lexing. Lexing the whole input to resolve it costs back exactly
1698    // what the route saved, so the tokens around the match answer it instead.
1699    if let [span] = spans
1700        && let Some(m) = captures_in_region(pattern, input, *span)
1701    {
1702        crate::trace::rung("captures", "one span, over its own region", input.len());
1703        return vec![m];
1704    }
1705    // A window at each span, for the reason the single-span case above gives: a
1706    // route found these without lexing, so reading a whole lex back to say what
1707    // they bound costs exactly what the route saved. A window is bounded - a
1708    // token's worth of bytes an atom - which is what a region settled per span
1709    // is not, and that difference is why this generalizes where the region does
1710    // not.
1711    // The bytes of a match say where its atoms' tokens are, for the shape whose
1712    // atoms are literals and word tokens, so this resolves without lexing at
1713    // all - which is what the route that found these spans also did.
1714    if let Some((ms, names)) = crate::prefilter::flat_captures_by_byte_bounds(pattern, input, spans) {
1715        crate::trace::rung("captures", "the registers off each match's own bytes", input.len());
1716        return ms
1717            .into_iter()
1718            .map(|m| {
1719                Match::bound(
1720                    m.span.start(),
1721                    m.span.end(),
1722                    Regs::from_slice(&m.regs[..names.len()]),
1723                    names.clone(),
1724                )
1725            })
1726            .collect();
1727    }
1728    if let Some(ms) = crate::prefilter::captures_by_windows(pattern, input, spans) {
1729        crate::trace::rung("captures", "a window at each span", input.len());
1730        return ms;
1731    }
1732    // The single-pass engine over the significant parts, which is the engine
1733    // the scan that produced these spans ran on. It declines exactly what that
1734    // scan routes to the set engine, and only those reach the stitched lex.
1735    if let Some(ms) = crate::nfa::captures_over_parts(pattern, input, spans) {
1736        crate::trace::rung("captures", "an attempt a span, over the lexer's parts", input.len());
1737        return ms;
1738    }
1739    crate::trace::rung("captures", "the set engine over a whole stitched lex", input.len());
1740    crate::parallel_lex::lex_parallel_held(input, |toks| {
1741        captures_routed(pattern, input, toks, spans, EmptyLoop::Thompson, false)
1742    })
1743}
1744
1745/// [`captures`], with every binding a register bound under a repetition
1746/// made kept beside its last, read back through [`Match::list`].
1747///
1748/// The lists live in the set engine's states, so the spans are resolved
1749/// there whatever engine found them; a caller that reads only the last
1750/// binding takes [`captures`] and its cheaper rungs. A pattern that binds
1751/// nothing under a repetition resolves as [`captures`] does.
1752#[must_use]
1753pub fn captures_with_lists(pattern: &Pattern, input: &[u8], spans: &[Span]) -> Vec<Match> {
1754    if !pattern.has_list_registers() {
1755        return captures(pattern, input, spans);
1756    }
1757    if let Some(shapes) = crate::library::shapes_for(pattern) {
1758        return captures_with_shapes_and_lists(pattern, input, &shapes, spans);
1759    }
1760    crate::trace::rung("captures", "the set engine, keeping every binding under a repetition", input.len());
1761    crate::parallel_lex::lex_parallel_held(input, |toks| {
1762        captures_routed(pattern, input, toks, spans, EmptyLoop::Thompson, true)
1763    })
1764}
1765
1766/// [`captures_with_shapes`], keeping every binding under a repetition as
1767/// [`captures_with_lists`] does.
1768#[must_use]
1769pub fn captures_with_shapes_and_lists(
1770    pattern: &Pattern,
1771    input: &[u8],
1772    shapes: &crate::custom::ShapeSet,
1773    spans: &[Span],
1774) -> Vec<Match> {
1775    if !pattern.has_list_registers() {
1776        return captures_with_shapes(pattern, input, shapes, spans);
1777    }
1778    let shapes = shapes.with_library_shapes(&pattern.library_kinds());
1779    if shapes.is_empty() {
1780        return captures_with_lists(pattern, input, spans);
1781    }
1782    let blobs = crate::lexer::blob_runs(input);
1783    let toks = crate::lexer::lex_with_shapes(input, &blobs, &shapes, 0);
1784    captures_routed(pattern, input, &toks, spans, EmptyLoop::Thompson, true)
1785}
1786
1787/// What `span` bound, resolved over the tokens around it, or `None` where no
1788/// region answers for the pattern and the whole input must be lexed.
1789///
1790/// The attempt anchored at the span's start is how both engines resolve a
1791/// selected match, and it reads no token outside the match except through the
1792/// assertions [`crate::prefilter::tokens_around`] refuses to settle a region
1793/// for.
1794fn captures_in_region(pattern: &Pattern, input: &[u8], span: Span) -> Option<Match> {
1795    match crate::prefilter::tokens_around(pattern, input, span) {
1796        Ok(toks) => crate::nfa::captures_over(pattern, input, &toks, &[span])
1797            .and_then(|v| v.into_iter().next()),
1798        Err(why) => {
1799            debug_assert!(
1800                !matches!(why, crate::prefilter::RegionRefusal::NoRegionForm)
1801                    || crate::nfa::bounded_max_len(pattern).is_none_or(|n| n == 0)
1802                    || pattern.starts_with_resume()
1803                    || pattern.mentions_reset_start()
1804                    || pattern.mentions_stream_end_anchor(),
1805                "a region was refused as `{why}` for a pattern that has one"
1806            );
1807            None
1808        }
1809    }
1810}
1811
1812/// [`captures`] for spans a [`scan_with_empty_loop`] under `empty` returned.
1813///
1814/// # Panics
1815///
1816/// When a span is not a match of `pattern` over `input` under that reading.
1817#[must_use]
1818pub fn captures_with_empty_loop(
1819    pattern: &Pattern,
1820    input: &[u8],
1821    empty: EmptyLoop,
1822    spans: &[Span],
1823) -> Vec<Match> {
1824    if !pattern.binds() {
1825        return spans.iter().copied().map(Match::from).collect();
1826    }
1827    crate::parallel_lex::lex_parallel_held(input, |toks| {
1828        captures_routed(pattern, input, toks, spans, empty, false)
1829    })
1830}
1831
1832/// [`captures`] for spans a [`scan_with_shapes`] under `shapes` returned: the
1833/// input is lexed with the same shapes, so the attempts see the boundaries
1834/// the scan saw.
1835///
1836/// # Panics
1837///
1838/// When a span is not a match of `pattern` over `input` under those shapes.
1839#[must_use]
1840pub fn captures_with_shapes(
1841    pattern: &Pattern,
1842    input: &[u8],
1843    shapes: &crate::custom::ShapeSet,
1844    spans: &[Span],
1845) -> Vec<Match> {
1846    let shapes = shapes.with_library_shapes(&pattern.library_kinds());
1847    if shapes.is_empty() {
1848        return captures(pattern, input, spans);
1849    }
1850    if !pattern.binds() {
1851        return spans.iter().copied().map(Match::from).collect();
1852    }
1853    let blobs = crate::lexer::blob_runs(input);
1854    let toks = crate::lexer::lex_with_shapes(input, &blobs, &shapes, 0);
1855    captures_routed(pattern, input, &toks, spans, EmptyLoop::Thompson, false)
1856}
1857
1858/// [`captures`] over the token stream the spans were scanned on.
1859///
1860/// # Panics
1861///
1862/// When a span is not a match of `pattern` over `toks`.
1863#[must_use]
1864pub fn captures_over(pattern: &Pattern, input: &[u8], toks: &[Token], spans: &[Span]) -> Vec<Match> {
1865    if !pattern.binds() {
1866        return spans.iter().copied().map(Match::from).collect();
1867    }
1868    captures_routed(pattern, input, toks, spans, EmptyLoop::Thompson, false)
1869}
1870
1871/// [`captures_over`], keeping every binding under a repetition as
1872/// [`captures_with_lists`] does; a pattern binding nothing under one
1873/// resolves as [`captures_over`] does.
1874///
1875/// # Panics
1876///
1877/// When a span is not a match of `pattern` over `toks`.
1878#[must_use]
1879pub fn captures_over_with_lists(
1880    pattern: &Pattern,
1881    input: &[u8],
1882    toks: &[Token],
1883    spans: &[Span],
1884) -> Vec<Match> {
1885    if !pattern.has_list_registers() {
1886        return captures_over(pattern, input, toks, spans);
1887    }
1888    captures_routed(pattern, input, toks, spans, EmptyLoop::Thompson, true)
1889}
1890
1891/// Resolve `spans` on the engine a scan under `empty` ran them on: the
1892/// single-pass engine where it takes the pattern, else the set engine, whose
1893/// attempt at a span's start token reproduces the match with its registers.
1894/// Under `keep_lists` the set engine resolves every span, since only its
1895/// states keep every binding a register makes under a repetition.
1896fn captures_routed(
1897    pattern: &Pattern,
1898    input: &[u8],
1899    toks: &[Token],
1900    spans: &[Span],
1901    empty: EmptyLoop,
1902    keep_lists: bool,
1903) -> Vec<Match> {
1904    let set_engine = (empty == EmptyLoop::Perl && crate::nfa::empty_loop_needs_set_engine(pattern))
1905        || (keep_lists && pattern.has_list_registers());
1906    if !set_engine && let Some(matches) = crate::nfa::captures_over(pattern, input, toks, spans) {
1907        return matches;
1908    }
1909    let n = toks.len();
1910    let analyses = Analyses::build(pattern, input, toks);
1911    let fields = analyses.fields();
1912    // The registers in name order, which is the order the single-pass engine
1913    // reports them in: a match from either engine carries the same registers at
1914    // the same positions, and the names are built once for the whole scan.
1915    let mut order: Vec<usize> = (0..analyses.regs.len()).collect();
1916    order.sort_by(|&a, &b| analyses.regs[a].cmp(&analyses.regs[b]));
1917    let names: std::sync::Arc<[String]> =
1918        order.iter().map(|&i| analyses.regs[i].clone()).collect();
1919    spans
1920        .iter()
1921        .map(|&span| {
1922            let i = toks.partition_point(|t| t.start < span.start);
1923            let (env, history) = (i < n)
1924                .then(|| {
1925                    best_at(pattern, input, toks, n, i, &analyses.absent, &analyses.regs, fields, &mut 0, |best| {
1926                        (best.pos, best.env.clone(), best.history())
1927                    })
1928                })
1929                .flatten()
1930                .filter(|&(end, _, _)| toks[end - 1].end == span.end)
1931                .map(|(_, env, history)| (env, history))
1932                .expect("a span a scan of this pattern returned is reproduced by the attempt at its start");
1933            // Every register the pattern names, not only the ones this match
1934            // bound: one that bound nothing carries an empty span, which is
1935            // what tells it from a register the pattern never named and is how
1936            // the single-pass engine reports it too.
1937            let captures = order
1938                .iter()
1939                .map(|&id| {
1940                    env.iter()
1941                        .find(|entry| *entry.0 as usize == id)
1942                        .map_or(Span { start: 0, end: 0 }, |entry| Span {
1943                            start: entry.1.0 as u32,
1944                            end: entry.1.1 as u32,
1945                        })
1946                })
1947                .collect();
1948            if !keep_lists {
1949                return Match::bound(span.start(), span.end(), captures, names.clone());
1950            }
1951            // Every register bound under a repetition, with each binding it
1952            // made in this match, oldest first, at its place in name order.
1953            let mut lists: Bindings = analyses
1954                .lists
1955                .iter()
1956                .filter_map(|&id| order.iter().position(|&o| o == usize::from(id)).map(|at| (at, Vec::new())))
1957                .collect();
1958            for (id, (s, e)) in history {
1959                if let Some(at) = order.iter().position(|&o| o == usize::from(id))
1960                    && let Some(slot) = lists.iter_mut().find(|(k, _)| *k == at)
1961                {
1962                    slot.1.push(Span { start: s as u32, end: e as u32 });
1963                }
1964            }
1965            Match::bound(span.start(), span.end(), captures, names.clone()).with_lists(lists)
1966        })
1967        .collect()
1968}
1969
1970/// The per-scan analyses the set engine reads through [`Fields`]: the guard
1971/// literals a prefilter proved absent, the interned register names, and each
1972/// axis field the pattern queries. Owned here, so an anchored attempt can be
1973/// run at any position after the scan that built them.
1974struct Analyses {
1975    absent: HashSet<Vec<u8>>,
1976    clock: crate::typed::Clock,
1977    regs: Vec<String>,
1978    spectral: Option<SpectralField>,
1979    seams: Vec<GrainCuts>,
1980    depths: Option<Vec<u16>>,
1981    contested: Vec<GrainCuts>,
1982    echo: Vec<(crate::orbit::OrbitGroup, crate::echo::EchoField)>,
1983    supers: Option<crate::supertoken::SuperContext>,
1984    order: Option<Vec<Option<bool>>>,
1985    templates: Option<crate::templates::Mining>,
1986    joins: Vec<Join>,
1987    gravity: [Option<crate::gravity::Readings>; 3],
1988    kin: Vec<(crate::ast::Grain, String, Option<u32>)>,
1989    bands: Option<Bands>,
1990    field_starts: Option<Vec<u32>>,
1991    context: ContextBuild,
1992    /// The ids of the registers bound under a repetition.
1993    lists: Vec<u16>,
1994}
1995
1996/// The periods a named phase anchor counts against, read once per scan.
1997struct Bands {
1998    /// The stream's live periods, strongest first.
1999    live: Vec<u16>,
2000    /// Per token, its index among the significant tokens, `u32::MAX` for an
2001    /// insignificant one.
2002    sig_index: Vec<u32>,
2003}
2004
2005impl Bands {
2006    fn build(toks: &[Token], input: &[u8]) -> Bands {
2007        let mut next = 0u32;
2008        let sig_index = toks
2009            .iter()
2010            .map(|t| {
2011                if t.is_significant() {
2012                    next += 1;
2013                    next - 1
2014                } else {
2015                    u32::MAX
2016                }
2017            })
2018            .collect();
2019        Bands { live: crate::context::live_periods(toks, input), sig_index }
2020    }
2021
2022    /// Whether token `p` is at column `k` of the period `period` names,
2023    /// where that period is live.
2024    fn at(&self, p: usize, k: u16, period: crate::ast::PeriodRef) -> bool {
2025        let named = match period {
2026            crate::ast::PeriodRef::Length(len) => self.live.contains(&len).then_some(len),
2027            crate::ast::PeriodRef::Rank(n) => usize::from(n).checked_sub(1).and_then(|i| self.live.get(i).copied()),
2028        };
2029        match (named, self.sig_index.get(p)) {
2030            (Some(len), Some(&i)) if i != u32::MAX => i % u32::from(len) == u32::from(k),
2031            (Some(_) | None, Some(_) | None) => false,
2032        }
2033    }
2034}
2035
2036/// The seam cuts every `@seam` anchor of `pattern` reads, one list per grain
2037/// and cut it names. The byte field and the supertokens are each read once
2038/// however many cuts select from them.
2039pub(crate) fn seam_lists(pattern: &Pattern, input: &[u8], toks: &[Token]) -> Vec<GrainCuts> {
2040    use crate::ast::Grain;
2041    let cfg = crate::seam::SeamConfig::default();
2042    let mut bytes: Option<crate::seam::SeamField> = None;
2043    let mut units: Option<Vec<crate::supertoken::SuperToken>> = None;
2044    let mut lists = Vec::new();
2045    for (grain, cut) in pattern.seam_cuts() {
2046        let cuts = match grain {
2047            Grain::Byte => {
2048                let field = bytes.get_or_insert_with(|| {
2049                    let _building = crate::trace::phase("the seam cuts");
2050                    crate::seam::analyze(input)
2051                });
2052                match cut {
2053                    None => field.strong_cuts(),
2054                    Some(c) => field.cuts_past(c.value.get() as f32, c.strict()),
2055                }
2056            }
2057            Grain::Token => {
2058                let _building = crate::trace::phase("the seam cuts, over the tokens");
2059                crate::seam::token_cuts(toks, &cfg, cut.map(|c| (c.value.get() as f32, c.strict())))
2060            }
2061            Grain::Super => {
2062                let units = units.get_or_insert_with(|| crate::supertoken::supertokens_from(toks, input));
2063                let _building = crate::trace::phase("the seam cuts, over the supertokens");
2064                crate::seam::supertoken_cuts(units, &cfg, cut.map(|c| (c.value.get() as f32, c.strict())))
2065            }
2066        };
2067        lists.push((grain, cut, cuts));
2068    }
2069    lists
2070}
2071
2072/// The contested points every `@ambiguous` anchor of `pattern` reads, one
2073/// list per grain and cut it names. A written cut replaces the reader's
2074/// threshold and keeps the rest of its configuration.
2075pub(crate) fn contested_lists(pattern: &Pattern, input: &[u8], toks: &[Token]) -> Vec<GrainCuts> {
2076    use crate::ast::Grain;
2077    use crate::observation::ObservationConfig;
2078    let mut units: Option<Vec<crate::supertoken::SuperToken>> = None;
2079    let mut lists = Vec::new();
2080    for (grain, cut) in pattern.contested_cuts() {
2081        let cfg = match cut {
2082            None => ObservationConfig::default(),
2083            Some(c) => ObservationConfig { contested_threshold: c.least_passing(), ..ObservationConfig::default() },
2084        };
2085        let points = match grain {
2086            Grain::Byte => {
2087                let _building = crate::trace::phase("the contested points");
2088                crate::observation::analyze_with(input, &cfg).contested
2089            }
2090            Grain::Token => {
2091                let _building = crate::trace::phase("the contested tokens");
2092                crate::observation::contested_tokens(toks, &cfg)
2093            }
2094            Grain::Super => {
2095                let units = units.get_or_insert_with(|| crate::supertoken::supertokens_from(toks, input));
2096                let _building = crate::trace::phase("the contested supertokens");
2097                crate::observation::contested_supertokens(units, &cfg)
2098            }
2099        };
2100        lists.push((grain, cut, points));
2101    }
2102    lists
2103}
2104
2105/// The slot a grain's readings take in [`Fields::gravity`].
2106fn grain_slot(grain: crate::ast::Grain) -> usize {
2107    match grain {
2108        crate::ast::Grain::Byte => 0,
2109        crate::ast::Grain::Token => 1,
2110        crate::ast::Grain::Super => 2,
2111    }
2112}
2113
2114impl Analyses {
2115    /// Build what `pattern` reads over `toks`. Each axis field is built only
2116    /// when the pattern queries it (`\F{...}` the spectral field, `@seam` the
2117    /// seam field); a pattern that queries no axis costs nothing.
2118    fn build(pattern: &Pattern, input: &[u8], toks: &[Token]) -> Self {
2119        let absent = crate::prefilter::absent_guard_literals(pattern, input);
2120        let regs = collect_registers(pattern);
2121        let lists: Vec<u16> =
2122            pattern.list_registers().iter().filter_map(|name| reg_id(&regs, name)).collect();
2123        // Built for the readings this pattern's atoms name and no others: the
2124        // field is a step a byte, so one it never reads would cost a step at
2125        // every byte of the input.
2126        let spectral = pattern.has_spectral().then(|| {
2127            let _building = crate::trace::phase("the spectral field");
2128            crate::spectral::analyze_needing(input, &Default::default(), pattern.spectral_needs())
2129        });
2130        let seams = seam_lists(pattern, input, toks);
2131        let depths = pattern.has_stress().then(|| {
2132            let _building = crate::trace::phase("the nesting depths");
2133            crate::stress::depths(toks)
2134        });
2135        let contested = contested_lists(pattern, input, toks);
2136        // One recurrence field per rung the pattern counts at, so a pattern
2137        // naming no orbit builds the one `@novel` and `@echoed` read.
2138        let echo: Vec<(crate::orbit::OrbitGroup, crate::echo::EchoField)> = {
2139            let _building = crate::trace::phase("the recurrence fields");
2140            pattern
2141                .echo_orbits()
2142                .into_iter()
2143                .map(|g| {
2144                    let cfg = crate::echo::EchoConfig { orbit: g, ..Default::default() };
2145                    (g, crate::echo::analyze_with(toks, input, &cfg))
2146                })
2147                .collect()
2148        };
2149        let supers = pattern.has_super().then(|| {
2150            let _building = crate::trace::phase("the supertoken tower");
2151            crate::supertoken::SuperContext::build(toks, input)
2152        });
2153        let clock = crate::typed::Clock::current();
2154        let order = pattern.has_order().then(|| {
2155            let _building = crate::trace::phase("the timestamp order");
2156            timestamp_order(toks, input, clock)
2157        });
2158        let templates = pattern.has_rare().then(|| {
2159            let _building = crate::trace::phase("the mined templates");
2160            crate::templates::Mining::mine_tokens(toks, input)
2161        });
2162        // Timed whether or not the pattern names an input to join against: a
2163        // shape with none reads this as the cost of asking, which is the figure
2164        // that says whether the question is worth skipping.
2165        let joins: Vec<Join> = {
2166            let _building = crate::trace::phase("the joins, one a named input");
2167            pattern
2168                .joins()
2169                .into_iter()
2170                .map(|(other, group)| Join::build(other, group, toks, input))
2171                .collect()
2172        };
2173        let gravity = [crate::ast::Grain::Byte, crate::ast::Grain::Token, crate::ast::Grain::Super].map(|g| {
2174            pattern.has_gravity_at(g).then(|| {
2175                let _building = crate::trace::phase(match g {
2176                    crate::ast::Grain::Byte => "the pair field, over the bytes",
2177                    crate::ast::Grain::Token => "the pair field, over the tokens",
2178                    crate::ast::Grain::Super => "the pair field, over the supertokens",
2179                });
2180                crate::gravity::Readings::read(g, input, toks)
2181            })
2182        });
2183        let kin = pattern
2184            .kin_examples()
2185            .into_iter()
2186            .map(|(g, x)| {
2187                let named = gravity[grain_slot(g)]
2188                    .as_ref()
2189                    .and_then(|r| crate::gravity::example_key(g, x.as_bytes()).and_then(|k| r.type_named(&k)));
2190                (g, x, named)
2191            })
2192            .collect();
2193        let bands = pattern.has_phase_in().then(|| {
2194            let _building = crate::trace::phase("the live periods");
2195            Bands::build(toks, input)
2196        });
2197        let field_starts = pattern.has_field_anchor().then(|| {
2198            let _building = crate::trace::phase("the field starts");
2199            field_start_index(input, toks)
2200        });
2201        let context = {
2202            let _building = crate::trace::phase("the context field");
2203            build_context(pattern, input, toks, spectral.as_ref(), echo_at(&echo, crate::orbit::OrbitGroup::Identity))
2204        };
2205        Analyses {
2206            absent,
2207            clock,
2208            regs,
2209            order,
2210            templates,
2211            joins,
2212            spectral,
2213            seams,
2214            depths,
2215            contested,
2216            echo,
2217            supers,
2218            gravity,
2219            kin,
2220            bands,
2221            field_starts,
2222            context,
2223            lists,
2224        }
2225    }
2226
2227    /// The fields as the matcher reads them.
2228    fn fields(&self) -> Fields<'_> {
2229        Fields {
2230            spectral: self.spectral.as_ref(),
2231            seams: &self.seams,
2232            depths: self.depths.as_deref(),
2233            contested: &self.contested,
2234            echo: &self.echo,
2235            supers: self.supers.as_ref(),
2236            order: self.order.as_deref(),
2237            templates: self.templates.as_ref(),
2238            joins: &self.joins,
2239            gravity: self.gravity.each_ref().map(Option::as_ref),
2240            kin: &self.kin,
2241            bands: self.bands.as_ref(),
2242            field_starts: self.field_starts.as_deref(),
2243            context: self.context.window.as_ref(),
2244            relation: self.context.relation.as_ref(),
2245            clock: self.clock,
2246            lists: &self.lists,
2247        }
2248    }
2249}
2250
2251/// Scan using only the set-reachability engine, bypassing the
2252/// single-pass engine. This is the fallback path for balanced and
2253/// field patterns, and the differential oracle the single-pass engine
2254/// is checked against.
2255pub(crate) fn scan_set_reachability(pattern: &Pattern, input: &[u8]) -> Vec<Span> {
2256    // The three phases a scan here spends its time in, named so a reading says
2257    // which one a change reached. The lex is the whole of what happens before
2258    // the closure runs, so its phase ends as the closure begins.
2259    let _whole = crate::trace::phase("the set engine");
2260    let lexing = crate::trace::phase("its lex, stitched");
2261    // Parallel lexer (serial under its own threshold) into this thread's
2262    // held buffers, so large balanced and field scans incur neither a serial
2263    // tokenization prefix nor fresh pages.
2264    crate::parallel_lex::lex_parallel_held(input, |toks| {
2265        drop(lexing);
2266        let building = crate::trace::phase("its axis fields");
2267        let analyses = Analyses::build(pattern, input, toks);
2268        drop(building);
2269        let _sweeping = crate::trace::phase("its sweep over the anchors");
2270        scan_tokens(pattern, input, toks, &analyses)
2271    })
2272}
2273
2274/// Scan a pre-lexed token stream, beginning the leftmost,
2275/// non-overlapping selection at token index `start` and computing match
2276/// attempts only at or after it. Token byte offsets are absolute, so a
2277/// caller streaming a growing token vector can resume here without
2278/// rescanning the committed prefix. Used by the dual-grain pipeline,
2279/// where the byte grain lexes ahead and the token grain matches behind.
2280#[must_use]
2281pub fn scan_tokens_from(
2282    pattern: &Pattern,
2283    input: &[u8],
2284    toks: &[Token],
2285    start: usize,
2286) -> Vec<Span> {
2287    let n = toks.len();
2288    let analyses = Analyses::build(pattern, input, toks);
2289    let fields = analyses.fields();
2290    let results: Vec<StartMatch> = (0..n)
2291        .map(|i| {
2292            if i < start {
2293                None
2294            } else {
2295                attempt_at(
2296                    pattern,
2297                    input,
2298                    toks,
2299                    n,
2300                    i,
2301                    &analyses.absent,
2302                    &analyses.regs,
2303                    fields,
2304                    &mut 0,
2305                )
2306            }
2307        })
2308        .collect();
2309    let mut matches = Vec::new();
2310    let mut s = start;
2311    while s < n {
2312        let s0 = skip_ws(toks, s, n);
2313        if s0 >= n {
2314            break;
2315        }
2316        if let Some(best_pos) = results[s0] {
2317            matches.push(Span { start: toks[s0].start, end: toks[best_pos - 1].end });
2318            s = best_pos;
2319        } else {
2320            s = s0 + 1;
2321        }
2322    }
2323    matches
2324}
2325
2326/// Token count below which the per-start scan runs on one thread while this
2327/// process has not yet dispatched across cores.
2328///
2329/// The pool spawns on its first dispatch, and a scan that only happens once
2330/// includes that spawn in its own time - about six and a half milliseconds of
2331/// it, which is more than a narrow scan of anything under twenty thousand
2332/// tokens costs in total. Over prefixes of real source, each reading the median
2333/// of nine processes that scan once and exit:
2334///
2335///    5890 tokens    1.0362 ms on one thread    7.0672 ms across cores
2336///   12425           2.2382                     7.3657
2337///   15748           2.6766                     8.0069
2338///   19025           3.3565                     7.3222
2339///   22496           3.9576                     7.5519
2340///   25473          11.7819                     9.6438
2341///   28484          12.2419                    10.1085
2342///   50005          18.3677                    13.1723
2343///  184329          39.4007                    17.3067
2344///
2345/// One thread wins by three times at 15748 and is still ahead at 22496; the
2346/// cores are ahead from 25473 on. The narrow reading climbs from 3.96 to 11.78
2347/// ms between those two, three times the cost for an eighth more work, and that
2348/// step is where the two curves cross. This bound is inside it.
2349///
2350/// Across cores the figure barely moves over the whole range - 7.07 to 10.11 ms
2351/// from 5890 tokens to 28484 - because what a cold wide scan costs is mostly
2352/// the spawn and hardly at all the scanning.
2353const PARALLEL_SCAN_THRESHOLD_COLD: usize = 24_576;
2354
2355/// The same bound once the pool is up, when the spawn has already happened.
2356///
2357/// Warm, on the same file and sizes, the median of five rounds with the
2358/// spreads disjoint except where this says they meet:
2359///
2360///    192 tokens   0.0100 ms on one thread   0.0173 ms across cores
2361///    400          0.0227                    0.0296
2362///    809          0.0448                    0.0438   (the spreads meet)
2363///   1259          0.0888                    0.0874   (the spreads overlap)
2364///   1672          0.1278                    0.0970
2365///   3337          0.4020                    0.2591
2366///
2367/// One thread wins below about four hundred tokens, the two are worth the same
2368/// from about eight hundred to about thirteen hundred, and the cores win by a
2369/// quarter at 1672 and better than a third at 3337. This bound is inside the
2370/// band where they are worth the same, so crossing it can cost nothing and
2371/// everything above it is a reading where the cores are ahead.
2372///
2373/// The figure this replaced said the parallel path wins "from a few hundred
2374/// tokens up". At four hundred tokens it loses by thirty percent.
2375const PARALLEL_SCAN_THRESHOLD_WARM: usize = 1024;
2376
2377/// Whether this process has already dispatched the sweep across cores.
2378///
2379/// The bound above depends on it, and nothing else can answer it: flynnel's
2380/// `global_local_arena` builds the arena when it is asked for, so there is no
2381/// way to read whether the pool is up without starting one. This records what
2382/// trex itself has done, which is the fact that decides whether the spawn is
2383/// still to come.
2384///
2385/// A process where something else warmed the pool reads `false` here and stays
2386/// on one thread, which forgoes the warm saving and never risks the cold cost.
2387static POOL_WARM: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
2388
2389/// Tokens this process has swept, until the pool is up.
2390///
2391/// A scan under the cold bound never spawns the pool, so a process that only
2392/// ever scans small inputs never reaches the warm bound however many it does -
2393/// and the warm bound is where the cores are ahead by a quarter. Counting what
2394/// has been swept lets the spawn be earned by the work in aggregate rather than
2395/// by one input being large enough on its own.
2396static TOKENS_SWEPT: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(0);
2397
2398/// Tokens a process must have swept before it spawns the pool for work that no
2399/// single scan would justify.
2400///
2401/// The spawn is about six and a half milliseconds. What warming gains is the
2402/// gap between the two warm paths, which is 0.0306 ms over 1672 tokens and
2403/// 0.1346 over 3337 - 18.3 and 40.3 nanoseconds a token. So the spawn is
2404/// recovered somewhere between 160,000 and 355,000 tokens of sweeping, and this
2405/// is at the far end of that: a process that stops short of it has lost
2406/// nothing, and one that carries on past it recovers the spawn several times
2407/// over.
2408const TOKENS_BEFORE_WARMING: usize = 350_000;
2409
2410/// The end position of the longest match anchored at one token index, or
2411/// `None` when nothing matches there.
2412type StartMatch = Option<usize>;
2413
2414fn scan_tokens(pattern: &Pattern, input: &[u8], toks: &[Token], analyses: &Analyses) -> Vec<Span> {
2415    let n = toks.len();
2416    // The two halves of the sweep, named apart. The attempts fan out across
2417    // cores and the selection is one serial walk, so a share that is the whole
2418    // sweep cannot say whether a change belongs in the anchor's work or in the
2419    // walk that reads it back. The phase wraps the fan-out rather than being
2420    // inside its leaf: a leaf takes a lock on the way out, and a lock per
2421    // anchor over millions of anchors would price the watch above the work.
2422    let attempting = crate::trace::phase("its sweep: an attempt a start");
2423    // `results[i]`: the longest match anchored at token `i`. Each attempt is
2424    // an independent, pure `advance` call, so they fan out across cores with
2425    // no shared state.
2426    let results = per_start_matches(
2427        pattern,
2428        input,
2429        toks,
2430        n,
2431        &analyses.absent,
2432        &analyses.regs,
2433        analyses.fields(),
2434    );
2435    drop(attempting);
2436
2437    let _selecting = crate::trace::phase("its sweep: the leftmost selection");
2438
2439    // The leftmost, non-overlapping selection over the precomputed per-start
2440    // matches: a serial pass identical to the one-pass scan, so only the
2441    // expensive per-start attempts were parallelized.
2442    // `\G` requires each match to begin where the previous ended. Every
2443    // continuation already does, since the loop resumes at the next
2444    // significant token after a match; what it forbids is the other branch,
2445    // where a token that cannot match is stepped over. So the run covers a
2446    // contiguous stretch and ends at the first token the pattern cannot take.
2447    let contiguous = pattern.starts_with_resume();
2448    // Unreserved on purpose. Half of what a match costs here is the growth this
2449    // would remove - 1300721 matches read 4.912 ms above the walk unreserved
2450    // and 2.322 reserved - but the only capacity that removes it is one
2451    // proportional to the token count, and a `Span` is sixteen bytes: reserving
2452    // `n` of them is about six bytes for every input byte, so a half-gigabyte
2453    // input would reserve gigabytes to hold what it actually matches. A fixed
2454    // cap removes the early doublings, which are the cheap ones. Counting the
2455    // matches first costs a pass over `results` worth more than the growth.
2456    let mut matches = Vec::new();
2457    let mut start = 0;
2458    while start < n {
2459        let s0 = skip_ws(toks, start, n);
2460        if s0 >= n {
2461            break;
2462        }
2463        if let Some(best_pos) = results[s0] {
2464            matches.push(Span { start: toks[s0].start, end: toks[best_pos - 1].end });
2465            start = best_pos;
2466        } else if contiguous {
2467            break;
2468        } else {
2469            start = s0 + 1;
2470        }
2471    }
2472    matches
2473}
2474
2475/// For each token index, the longest match anchored there. Large
2476/// streams split the work across the available cores; small streams
2477/// stay on one thread.
2478#[allow(clippy::too_many_arguments)]
2479fn per_start_matches(
2480    pattern: &Pattern,
2481    input: &[u8],
2482    toks: &[Token],
2483    n: usize,
2484    absent: &HashSet<Vec<u8>>,
2485    regs: &[String],
2486    fields: Fields<'_>,
2487) -> Vec<StartMatch> {
2488    let cores = std::thread::available_parallelism().map_or(1, std::num::NonZero::get);
2489    // Read once rather than once a leaf: it walks the pattern, and the pattern
2490    // does not change under the sweep.
2491    let opens = crate::kind_route::first_kinds(pattern);
2492    // Which bound applies is a fact about this process rather than this scan:
2493    // the spawn happens once and every scan after it is warm. Until it has
2494    // happened, what this process has already swept stands in for it - enough small scans
2495    // earn the spawn between them that no one of them would earn alone.
2496    let warm = POOL_WARM.load(std::sync::atomic::Ordering::Relaxed)
2497        || TOKENS_SWEPT.fetch_add(n, std::sync::atomic::Ordering::Relaxed) + n
2498            >= TOKENS_BEFORE_WARMING;
2499    let threshold =
2500        if warm { PARALLEL_SCAN_THRESHOLD_WARM } else { PARALLEL_SCAN_THRESHOLD_COLD };
2501    if n < threshold || cores <= 1 {
2502        let mut advanced = 0u64;
2503        let got: Vec<StartMatch> = (0..n)
2504            .map(|i| {
2505                if worth_attempting(opens, toks[i].kind) {
2506                    attempt_at(pattern, input, toks, n, i, absent, regs, fields, &mut advanced)
2507                } else {
2508                    None
2509                }
2510            })
2511            .collect();
2512        count_starts(opens, toks, 0, &got, advanced);
2513        return got;
2514    }
2515    // Each position's attempt is independent, so they run across cores
2516    // through the work-stealing pool. The unanchored attempt has a
2517    // triangular cost profile -- a low index attempts a longer match than a
2518    // high index -- so the leaf is kept fine (several per core) and the
2519    // pool steals the expensive low-index leaves off the workers that drew
2520    // them, instead of one contiguous chunk piling them onto one worker.
2521    // Set before the dispatch rather than after it: the pool is up from the
2522    // moment this call asks for it, and a scan on another thread reading the
2523    // flag while this one waits should see what it will find.
2524    POOL_WARM.store(true, std::sync::atomic::Ordering::Relaxed);
2525
2526    use flynnel::JobPlan;
2527    use flynnel::sched::par_iter::for_each_chunk_indexed_min_leaf;
2528
2529    let mut results: Vec<StartMatch> = (0..n).map(|_| None).collect();
2530    let min_leaf = n.div_ceil(cores * 8).max(64);
2531    let plan = JobPlan::new(0, n as u32)
2532        .with_leaf_shape(flynnel::LeafShape::PortCompute);
2533    for_each_chunk_indexed_min_leaf(&plan, &mut results, min_leaf, |start, slots| {
2534        let mut advanced = 0u64;
2535        for (j, slot) in slots.iter_mut().enumerate() {
2536            // Every slot arrives holding `None`, which is what an anchor no
2537            // attempt is made at answers, so one left alone is the same vector.
2538            let i = start + j;
2539            if worth_attempting(opens, toks[i].kind) {
2540                *slot = attempt_at(pattern, input, toks, n, i, absent, regs, fields, &mut advanced);
2541            }
2542        }
2543        count_starts(opens, toks, start, slots, advanced);
2544    });
2545    results
2546}
2547
2548/// Whether an attempt anchored at a token of `kind` could match at all, given
2549/// the kind `opens` says the pattern must begin with.
2550///
2551/// One place, because the serial path, the parallel path and the counter all
2552/// ask it, and three spellings of one predicate come apart. `best_at` answers
2553/// `None` at a whitespace anchor and the selection pass only queries
2554/// significant positions, so an attempt there computes a value defined to be
2555/// absent that nothing reads; `opens` refuses a significant anchor whose token
2556/// the pattern's first atom cannot be.
2557///
2558/// `None` from `opens` is a pattern that does not force its first token, and it
2559/// admits every significant anchor - which is the sweep exactly as it was.
2560fn worth_attempting(opens: Option<crate::kind_route::OpeningKinds>, kind: TokenKind) -> bool {
2561    kind != TokenKind::Whitespace && opens.is_none_or(|set| set.admits(kind))
2562}
2563
2564/// What a run of attempts reached, for the trace.
2565///
2566/// Handed over once a run rather than once an attempt, and read in a pass of
2567/// its own after the attempts rather than inside them: `counted` takes a lock,
2568/// and a lock per anchor over millions of anchors would price the watch above
2569/// the work it watches. Where rungs are not being kept this is one atomic load
2570/// a leaf and nothing else, so the attempts themselves carry no counter.
2571fn count_starts(
2572    opens: Option<crate::kind_route::OpeningKinds>,
2573    toks: &[Token],
2574    start: usize,
2575    got: &[StartMatch],
2576    advanced: u64,
2577) {
2578    if !crate::trace::keeping() {
2579        return;
2580    }
2581    let (mut skipped, mut refused, mut matched) = (0u64, 0u64, 0u64);
2582    for (j, slot) in got.iter().enumerate() {
2583        // Both counts read `worth_attempting`, which is the predicate the
2584        // attempts themselves are made under, so a count is of what happened
2585        // rather than of what some other rule would have done.
2586        let kind = toks[start + j].kind;
2587        if kind == TokenKind::Whitespace {
2588            skipped += 1;
2589        } else if !worth_attempting(opens, kind) {
2590            refused += 1;
2591        }
2592        matched += u64::from(slot.is_some());
2593    }
2594    let walked = u64::try_from(got.len()).expect("a leaf shorter than the counter's width");
2595    crate::trace::counted("the sweep: starts the fan-out walks", walked);
2596    crate::trace::counted("the sweep: starts skipped as whitespace", skipped);
2597    // A significant anchor whose token the pattern's first atom cannot be. The
2598    // sweep costs the same to enter an attempt that dies as one that matches,
2599    // so these are attempts whose whole cost is gone rather than shortened.
2600    crate::trace::counted("the sweep: starts the opening kind refuses", refused);
2601    crate::trace::counted("the sweep: attempts made", walked - skipped - refused);
2602    // The two ways an attempt fails, which a duration reads as one. An attempt
2603    // whose state set came back empty died on the way in and a cheaper
2604    // rejection would have caught it; one that came back with states and still
2605    // answered nothing did the work and was refused by the length filter, and
2606    // no prefilter reaches that.
2607    crate::trace::counted("the sweep: attempts whose state set survived", advanced);
2608    crate::trace::counted("the sweep: attempts that match", matched);
2609    report_lent();
2610}
2611
2612/// The end position of the longest match anchored at token index `i`, or
2613/// `None`.
2614#[allow(clippy::too_many_arguments)]
2615fn attempt_at(
2616    pattern: &Pattern,
2617    input: &[u8],
2618    toks: &[Token],
2619    n: usize,
2620    i: usize,
2621    absent: &HashSet<Vec<u8>>,
2622    regs: &[String],
2623    fields: Fields<'_>,
2624    advanced: &mut u64,
2625) -> StartMatch {
2626    best_at(pattern, input, toks, n, i, absent, regs, fields, advanced, |best| best.pos)
2627}
2628
2629/// The longest match anchored at token index `i`, read through `read` from
2630/// the state it ends in, or `None` when nothing matches there. Whitespace
2631/// anchors return `None`: the selection pass only queries significant
2632/// positions (the output of `skip_ws`).
2633#[allow(clippy::too_many_arguments)]
2634fn best_at<R>(
2635    pattern: &Pattern,
2636    input: &[u8],
2637    toks: &[Token],
2638    n: usize,
2639    i: usize,
2640    absent: &HashSet<Vec<u8>>,
2641    regs: &[String],
2642    fields: Fields<'_>,
2643    advanced: &mut u64,
2644    read: impl FnOnce(&State) -> R,
2645) -> Option<R> {
2646    if toks[i].kind == TokenKind::Whitespace {
2647        return None;
2648    }
2649    // The state vector, carried from the last attempt rather than built again.
2650    //
2651    // `advance` takes a vector by value and gives one back, and it is the same
2652    // allocation: the atom arm collects `State` into `State`, which reuses the
2653    // buffer it consumed. So one vector already serves a whole attempt - it was
2654    // just being built and dropped at each, which sampling put among the
2655    // largest allocating sites in a scan.
2656    //
2657    // Keeping it also keeps its capacity, so the doublings a fan-out needs
2658    // happen in the first few attempts instead of in every one.
2659    //
2660    // Taking it out leaves an empty vector behind, so an attempt that somehow
2661    // began inside another gets a fresh one rather than the outer attempt's
2662    // states, and whichever finishes last leaves a cleared vector for the next.
2663    let mut init = SCRATCH.with(|s| std::mem::take(&mut *s.borrow_mut()));
2664    init.clear();
2665    let lent = init.capacity();
2666    init.push(State::start(i));
2667    let mut results = advance(pattern, input, toks, n, init, absent, regs, fields);
2668    note_lent(lent, results.capacity());
2669    // Whether the state set survived at all, carried in a register the caller
2670    // owns and handed to the trace once a leaf. An attempt that comes back with
2671    // nothing died on the way in; one that comes back with states and still
2672    // answers `None` did the work and was refused by the `pos > i` filter
2673    // below. The two are the same row in a duration and different questions.
2674    *advanced += u64::from(!results.is_empty());
2675    // Best rank first, then longest. Rank orders the leftmost-first branches
2676    // that survived the whole pattern, so the preference is applied after the
2677    // continuation has pruned rather than before it has been consulted; length
2678    // breaks ties, which is the greedy reading every quantifier here takes.
2679    let answer = results
2680        .iter()
2681        .min_by(|a, b| cmp_rank(a.rank_slice(), b.rank_slice()).then(b.pos.cmp(&a.pos)))
2682        .filter(|best| best.pos > i)
2683        .map(read);
2684    // Handed back emptied, so the next attempt on this thread pushes into a
2685    // buffer that is already there. Nothing read from it outlives this: `read`
2686    // takes a state by reference and returns a value of its own.
2687    results.clear();
2688    SCRATCH.with(|s| *s.borrow_mut() = results);
2689    answer
2690}
2691
2692thread_local! {
2693    /// The state vector [`best_at`] lends to each attempt; see the note there.
2694    static SCRATCH: std::cell::RefCell<Vec<State>> =
2695        const { std::cell::RefCell::new(Vec::new()) };
2696
2697    /// What the lent vector was worth, summed over the attempts this thread has
2698    /// made and reported once a leaf by [`count_starts`].
2699    ///
2700    /// Sampling the callers of a scan's allocations says the state vector
2701    /// growing is what is left to remove, and that its count ROSE as the
2702    /// attempts around it were made cheaper. Two capacities say why: the room
2703    /// the attempt was handed, and the room it gave back. A buffer that is
2704    /// carrying the work comes back holding what the fan-out reached, so the
2705    /// second runs far ahead of the first and settles after a few attempts; one
2706    /// that is only riding along comes back the size of a result set while the
2707    /// vectors doing the work were grown and dropped inside.
2708    ///
2709    /// Summed rather than counted per borrow, because a lock per anchor over
2710    /// millions of anchors would price the watch above the thing it watches -
2711    /// the same reason the sweep's own counters are folded a leaf at a time.
2712    ///
2713    /// A borrow is not an attempt and the count must not be read as one.
2714    /// [`best_at`] is reached from the sweep and again from the capture pass
2715    /// that resolves each span's registers, the thread keeps its tally between
2716    /// leaves, and the capture pass runs outside any leaf - so a borrow made
2717    /// there lands in whichever leaf reports next, possibly of a later scan.
2718    /// What survives that is the RATIO of the two capacities, which is a
2719    /// property of each borrow and not of how they were grouped.
2720    static LENT: std::cell::Cell<(u64, u64, u64)> = const { std::cell::Cell::new((0, 0, 0)) };
2721
2722    /// What the quantifier fixpoint spends on vectors, summed the same way.
2723    ///
2724    /// [`unroll`] builds a frontier from empty at every iteration, and a second
2725    /// one where the body makes choices of its own. Sampling the callers of a
2726    /// scan's allocations puts this growth at 40% of what is left, so what
2727    /// decides whether carrying those buffers is worth anything is how many
2728    /// times the loop goes round: twice and a swap saves one vector, hundreds
2729    /// of times and it saves hundreds.
2730    static UNROLLED: std::cell::Cell<(u64, u64, u64)> = const { std::cell::Cell::new((0, 0, 0)) };
2731
2732    /// How often a preference path is extended against how often one is read.
2733    ///
2734    /// Extending is an allocation - `with_branch` is the second largest site a
2735    /// scan allocates from - and a cons-list would make it a fixed node shared
2736    /// with the path it grew from, costing nothing to extend and keeping
2737    /// `State` a thin pointer wide. What it would cost is the reading:
2738    /// `cmp_rank` compares two slices today and would walk two lists instead.
2739    ///
2740    /// So the trade is one count against the other, and it is not obvious which
2741    /// way round it falls - a path is extended once a branch and read once a
2742    /// dedup, and nothing says which a scan does more of.
2743    ///
2744    /// The lengths ride along because they decide a different question. Holding
2745    /// a short path inside the state rather than on the heap would remove this
2746    /// site's allocations outright, which a cons-list would not, and what makes
2747    /// that possible or not is how long a path actually gets.
2748    ///
2749    /// The tail is counted in buckets rather than carried as a maximum, because
2750    /// the trace SUMS what it is handed: a maximum reported through it becomes
2751    /// the sum of each leaf's maximum, which is a number about how the work was
2752    /// divided and not about any path. Counting the extensions that exceed a
2753    /// length is a sum and survives that, and three bounds give the shape of
2754    /// the tail without one of them having to be chosen in advance.
2755    static RANKED: std::cell::Cell<(u64, u64, u64, u64, u64, u64)> =
2756        const { std::cell::Cell::new((0, 0, 0, 0, 0, 0)) };
2757}
2758
2759thread_local! {
2760    /// How many entries a quantifier fixpoint's seen-map held when it
2761    /// finished, and how often it held more than a few.
2762    ///
2763    /// The map is built once per call to [`unroll`] and a call is an anchor,
2764    /// so its size is what decides whether a map is the right structure there
2765    /// at all: a handful of entries is a linear scan's territory, and a scan
2766    /// over a slice already in hand allocates nothing.
2767    ///
2768    /// Counted in buckets rather than as a maximum, for the reason the
2769    /// preference paths are: the trace SUMS what it is handed, so a maximum
2770    /// through it becomes the sum of every leaf's maximum, which describes how
2771    /// the work was divided and not any one call.
2772    static FIXPOINT_MAP: std::cell::Cell<(u64, u64, u64, u64, u64, u64, u64)> =
2773        const { std::cell::Cell::new((0, 0, 0, 0, 0, 0, 0)) };
2774}
2775
2776/// Record that a preference path was extended to `len` bytes.
2777fn note_extended(len: usize) {
2778    RANKED.with(|r| {
2779        let (extended, compared, sum, over4, over16, over64) = r.get();
2780        r.set((
2781            extended + 1,
2782            compared,
2783            sum + len as u64,
2784            over4 + u64::from(len > 4),
2785            over16 + u64::from(len > 16),
2786            over64 + u64::from(len > 64),
2787        ));
2788    });
2789}
2790
2791/// Record that two preference paths were compared.
2792fn note_compared() {
2793    RANKED.with(|r| {
2794        let (extended, compared, sum, over4, over16, over64) = r.get();
2795        r.set((extended, compared + 1, sum, over4, over16, over64));
2796    });
2797}
2798
2799/// Record that an attempt was handed `given` slots and gave `back` slots.
2800fn note_lent(given: usize, back: usize) {
2801    LENT.with(|l| {
2802        let (attempts, sum_given, sum_back) = l.get();
2803        l.set((attempts + 1, sum_given + given as u64, sum_back + back as u64));
2804    });
2805}
2806
2807/// Record that a quantifier fixpoint finished with `entries` in its seen-map.
2808fn note_fixpoint_map(entries: usize) {
2809    FIXPOINT_MAP.with(|m| {
2810        let (calls, sum, over4, over16, over64, over256, over1024) = m.get();
2811        m.set((
2812            calls + 1,
2813            sum + entries as u64,
2814            over4 + u64::from(entries > 4),
2815            over16 + u64::from(entries > 16),
2816            over64 + u64::from(entries > 64),
2817            over256 + u64::from(entries > 256),
2818            over1024 + u64::from(entries > 1024),
2819        ));
2820    });
2821}
2822
2823/// Record one iteration of a quantifier fixpoint, which built a frontier of
2824/// `grown` slots and handed `marked` slots to the body.
2825fn note_unrolled(grown: usize, marked: usize) {
2826    UNROLLED.with(|u| {
2827        let (iterations, sum_grown, sum_marked) = u.get();
2828        u.set((iterations + 1, sum_grown + grown as u64, sum_marked + marked as u64));
2829    });
2830}
2831
2832/// Hand the lending figures to the trace and zero them for the next leaf.
2833fn report_lent() {
2834    let (attempts, given, back) = LENT.with(|l| l.replace((0, 0, 0)));
2835    if attempts == 0 {
2836        return;
2837    }
2838    crate::trace::counted("the state vector: times it was borrowed", attempts);
2839    crate::trace::counted("the state vector: slots lent out", given);
2840    crate::trace::counted("the state vector: slots handed back", back);
2841
2842    let (iterations, grown, marked) = UNROLLED.with(|u| u.replace((0, 0, 0)));
2843    if iterations == 0 {
2844        return;
2845    }
2846    crate::trace::counted("the quantifier fixpoint: iterations", iterations);
2847    crate::trace::counted("the quantifier fixpoint: slots its frontier grew to", grown);
2848    crate::trace::counted("the quantifier fixpoint: slots handed to the body", marked);
2849
2850    let (calls, entries, over4, over16, over64, over256, over1024) =
2851        FIXPOINT_MAP.with(|m| m.replace((0, 0, 0, 0, 0, 0, 0)));
2852    if calls > 0 {
2853        crate::trace::counted("the fixpoint's seen-map: times one was built", calls);
2854        crate::trace::counted("the fixpoint's seen-map: entries they held", entries);
2855        crate::trace::counted("the fixpoint's seen-map: those past four entries", over4);
2856        crate::trace::counted("the fixpoint's seen-map: those past sixteen entries", over16);
2857        crate::trace::counted("the fixpoint's seen-map: those past sixty-four entries", over64);
2858        crate::trace::counted("the fixpoint's seen-map: those past two hundred and fifty-six", over256);
2859        crate::trace::counted("the fixpoint's seen-map: those past one thousand and twenty-four", over1024);
2860    }
2861
2862    let (extended, compared, sum, over4, over16, over64) =
2863        RANKED.with(|r| r.replace((0, 0, 0, 0, 0, 0)));
2864    if extended == 0 && compared == 0 {
2865        return;
2866    }
2867    crate::trace::counted("the preference path: times one was extended", extended);
2868    crate::trace::counted("the preference path: times two were compared", compared);
2869    crate::trace::counted("the preference path: bytes those extensions produced", sum);
2870    crate::trace::counted("the preference path: extensions past four bytes", over4);
2871    crate::trace::counted("the preference path: extensions past sixteen bytes", over16);
2872    crate::trace::counted("the preference path: extensions past sixty-four bytes", over64);
2873}
2874
2875/// Skip insignificant whitespace tokens, bounded by `end`.
2876fn skip_ws(toks: &[Token], mut pos: usize, end: usize) -> usize {
2877    while pos < end && toks[pos].kind == TokenKind::Whitespace {
2878        pos += 1;
2879    }
2880    pos
2881}
2882
2883/// Advance the active state set by matching `pat`. `absent` carries the
2884/// guard literals a prefilter has proven are nowhere in the input, so a
2885/// guard requiring one fails with no positional scan.
2886#[allow(clippy::too_many_arguments)]
2887fn advance(
2888    pat: &Pattern,
2889    input: &[u8],
2890    toks: &[Token],
2891    end: usize,
2892    states: Vec<State>,
2893    absent: &HashSet<Vec<u8>>,
2894    regs: &[String],
2895    fields: Fields<'_>,
2896) -> Vec<State> {
2897    match pat {
2898        Pattern::Empty => states,
2899        Pattern::Atom(a) => states
2900            .into_iter()
2901            .filter_map(|s| match_atom(a, input, toks, end, &s, regs, fields))
2902            .collect(),
2903        Pattern::Concat(parts) => {
2904            // Every arm of this function maps the set it is given forward -
2905            // filtering it, walking it, or handing it on - so an empty set in
2906            // is an empty set out, whatever the pattern. A concatenation whose
2907            // set has emptied therefore has its answer already, and the parts
2908            // after it would each be dispatched to do nothing.
2909            //
2910            // It is worth the check because most anchors fail: 86% of the
2911            // attempts a scan makes die on the way in, and a pattern of several
2912            // parts dies at one of them rather than at the last.
2913            let mut acc = states;
2914            for p in parts {
2915                if acc.is_empty() {
2916                    break;
2917                }
2918                acc = advance(p, input, toks, end, acc, absent, regs, fields);
2919            }
2920            acc
2921        }
2922        Pattern::Alt(parts, mode) => {
2923            alt(parts, *mode, input, toks, end, states, absent, regs, fields)
2924        }
2925        // An optional is a repetition bounded at one, so it ranks the same way
2926        // rather than through a second rule.
2927        Pattern::Opt(p, g) => {
2928            repeat(p, (0, Some(1)), *g, input, toks, end, states, absent, regs, fields)
2929        }
2930        Pattern::Star(p, g) => star(p, *g, input, toks, end, states, absent, regs, fields),
2931        Pattern::Plus(p, g) => {
2932            let once = advance(p, input, toks, end, states, absent, regs, fields);
2933            star(p, *g, input, toks, end, once, absent, regs, fields)
2934        }
2935        Pattern::Repeat(p, m, n, g) => {
2936            repeat(p, (*m, *n), *g, input, toks, end, states, absent, regs, fields)
2937        }
2938        // Atomic keeps only the length the body itself preferred. Every
2939        // derivation is already ranked, so the cut is taking the best one and
2940        // dropping the rest - the alternatives never reach what follows, and
2941        // an atom that needed one of them finds nothing to take.
2942        //
2943        // Each start is cut on its own. Collapsing the whole set to one state
2944        // would instead pick a single winner across unrelated starting
2945        // positions, which is a different and wrong thing: a scan explores
2946        // several starts at once here.
2947        Pattern::Atomic(p) => {
2948            // A single start is its own cut: with no second start to keep
2949            // apart, cutting the set and cutting each start are one operation,
2950            // so the set goes through unchanged. A scan anchors one state at
2951            // a time, which is what makes this the usual arrival.
2952            if states.len() == 1 {
2953                let mut out = advance(p, input, toks, end, states, absent, regs, fields);
2954                // The cut keeps one derivation, and that derivation is already
2955                // in the vector the body returned: moving it to the front and
2956                // dropping the rest answers with the allocation the body made
2957                // rather than with a second one holding a copy of it.
2958                match best_index(&out) {
2959                    Some(at) => {
2960                        out.swap(0, at);
2961                        out.truncate(1);
2962                    }
2963                    None => out.clear(),
2964                }
2965                return out;
2966            }
2967            let mut kept = Vec::with_capacity(states.len());
2968            for s in states {
2969                let out = advance(p, input, toks, end, vec![s], absent, regs, fields);
2970                if let Some(best) =
2971                    out.into_iter().min_by(|a, b| cmp_rank(a.rank_slice(), b.rank_slice()))
2972                {
2973                    kept.push(best);
2974                }
2975            }
2976            dedup(kept)
2977        }
2978        Pattern::Bind(name, _, p) => bind(name, p, input, toks, end, states, absent, regs, fields),
2979        Pattern::Balanced(kind, p) => balanced(*kind, p, input, toks, end, states, absent, regs, fields),
2980        Pattern::Guard(lit, neg) => guard(lit, *neg, input, toks, end, states, absent),
2981        Pattern::Assert(inner, neg, look) => {
2982            assert_zero_width(inner, *neg, *look, input, toks, end, states, absent, regs, fields)
2983        }
2984        Pattern::Field(k, inner) => field_match(*k, inner, input, toks, end, states, absent, regs, fields),
2985        Pattern::Anchor(kind) => anchor(kind, input, toks, end, states, fields),
2986        Pattern::Within(atoms, k) => {
2987            // `out` is the step's answer and leaves with it, so it is built
2988            // here where the scratch is carried: `dedup` hands the caller
2989            // either this vector or one it builds from it.
2990            let mut out = Vec::new();
2991            let mut scratch = EDITS.with(|e| std::mem::take(&mut *e.borrow_mut()));
2992            for s in states {
2993                within_edits_of(
2994                    atoms,
2995                    *k,
2996                    input,
2997                    toks,
2998                    end,
2999                    &s,
3000                    regs,
3001                    fields,
3002                    &mut scratch,
3003                    &mut out,
3004                );
3005            }
3006            EDITS.with(|e| *e.borrow_mut() = scratch);
3007            dedup(out)
3008        }
3009    }
3010}
3011
3012/// Append every run of tokens from `s` within `k` token edits of `atoms` to
3013/// `out`, as one state per run length, preferring fewer edits and then the
3014/// longer run.
3015///
3016/// The walk is the Levenshtein table with the atoms down one side and the
3017/// tokens along the other: a cell is one atom tested against one token, a
3018/// step down is an atom the run lacks, a step right a token it has extra, and
3019/// a step diagonal a match or a substitution. Only the band `k` wide either
3020/// side of the diagonal can hold a cost at or under `k`, so the work is the
3021/// atom count times `2k + 1` however long the input is.
3022///
3023/// Every alignment the last row admits is offered rather than one, because a
3024/// run that costs more can be the one the rest of the pattern needs - the
3025/// same reason an alternation offers every branch. The rank is the edit cost
3026/// then the run's shortfall, so a state that spent fewer edits, and among
3027/// those the one reaching furthest, is preferred.
3028///
3029/// Where two alignments cost the same the diagonal wins, so an atom that
3030/// could match the token beside it is recorded as having matched it. Several
3031/// minimum-cost alignments can exist and a register binds what the one taken
3032/// gave it.
3033#[allow(clippy::too_many_arguments)]
3034fn within_edits_of(
3035    atoms: &[crate::ast::EditAtom],
3036    k: u8,
3037    input: &[u8],
3038    toks: &[Token],
3039    end: usize,
3040    s: &State,
3041    regs: &[String],
3042    fields: Fields<'_>,
3043    scratch: &mut EditScratch,
3044    out: &mut Vec<State>,
3045) {
3046    let k = usize::from(k);
3047    let m = atoms.len();
3048    if m == 0 {
3049        return;
3050    }
3051    // The significant tokens the run could reach, at most `m + k` of them.
3052    let run = &mut scratch.run;
3053    run.clear();
3054    let mut p = skip_ws(toks, s.pos, end);
3055    while run.len() < m + k && p < end {
3056        run.push(p);
3057        p = skip_ws(toks, p + 1, end);
3058    }
3059    let n = run.len();
3060    // Cost, and the step that reached it, for every cell of the table. A cell
3061    // records where it came from rather than the alignment that reached it, so
3062    // the walk writes two words a cell where carrying the alignment forward
3063    // copied a vector of every atom's token into each one.
3064    let past = k + 1;
3065    let w = n + 1;
3066    let table = &mut scratch.table;
3067    table.clear();
3068    table.resize((m + 1) * w, (past, Step::Start));
3069    // The first row, where no atom has been consumed yet: reaching token `j`
3070    // costs the `j` tokens skipped to get there, and past `k` it is out of
3071    // budget.
3072    for (j, cell) in table.iter_mut().take(w).enumerate() {
3073        cell.0 = if j <= k { j } else { past };
3074    }
3075    for i in 1..=m {
3076        let lo = i.saturating_sub(k);
3077        let hi = (i + k).min(n);
3078        if lo == 0 {
3079            table[i * w] = (i, Step::AtomDeleted);
3080        }
3081        for j in lo.max(1)..=hi {
3082            let matched = atom_test(&atoms[i - 1].atom, input, toks, run[j - 1], s, regs, fields);
3083            let diagonal = table[(i - 1) * w + j - 1].0 + usize::from(!matched);
3084            let atom_deleted = table[(i - 1) * w + j].0 + 1;
3085            let token_extra = table[i * w + j - 1].0 + 1;
3086            let cost = diagonal.min(atom_deleted).min(token_extra);
3087            if cost > k {
3088                continue;
3089            }
3090            // The diagonal wins a tie, so an atom that matched its token is
3091            // recorded as having matched it rather than deleted beside an
3092            // equally cheap insertion.
3093            let step = if cost == diagonal {
3094                if matched { Step::Matched } else { Step::Substituted }
3095            } else if cost == atom_deleted {
3096                Step::AtomDeleted
3097            } else {
3098                Step::TokenExtra
3099            };
3100            table[i * w + j] = (cost, step);
3101        }
3102    }
3103    // The run must hold at least one token, so the empty alignment is never
3104    // offered even where every atom could be deleted.
3105    let taken = &mut scratch.taken;
3106    taken.clear();
3107    taken.resize(m, None);
3108    for j in 1..=n {
3109        let cost = table[m * w + j].0;
3110        if cost > k {
3111            continue;
3112        }
3113        // The path back to row zero is the alignment: each step says which cell
3114        // it came from, and a step that matched names the token its atom took.
3115        taken.iter_mut().for_each(|t| *t = None);
3116        let (mut i, mut col) = (m, j);
3117        while i > 0 {
3118            match table[i * w + col].1 {
3119                Step::Matched => {
3120                    taken[i - 1] = Some(run[col - 1]);
3121                    i -= 1;
3122                    col -= 1;
3123                }
3124                Step::Substituted => {
3125                    i -= 1;
3126                    col -= 1;
3127                }
3128                Step::AtomDeleted => i -= 1,
3129                Step::TokenExtra => col -= 1,
3130                // Row zero alone starts an alignment, and every cell a path
3131                // reaches was written with the step that reached it.
3132                Step::Start => {
3133                    debug_assert!(false, "an alignment stepped through an unreached cell");
3134                    break;
3135                }
3136            }
3137        }
3138        let mut env = s.env.clone();
3139        for (e, at) in atoms.iter().zip(taken.iter()) {
3140            if let (Some((name, _)), Some(at)) = (&e.bind, at)
3141                && let Some(id) = reg_id(regs, name)
3142            {
3143                let span = (toks[*at].start(), toks[*at].end());
3144                Rc::make_mut(&mut env).insert(id, span);
3145            }
3146        }
3147        // Fewer edits first, then the longer run: both oriented so the
3148        // preferred option sorts lower, as every other rank entry is.
3149        let mut rank = s.rank_slice().to_vec();
3150        rank.push(u8::try_from(cost).unwrap_or(u8::MAX));
3151        rank.push(u8::try_from(n - j).unwrap_or(u8::MAX));
3152        out.push(State {
3153            pos: run[j - 1] + 1,
3154            env,
3155            rank: Rank::of(&rank),
3156            hist: s.hist.clone(),
3157        });
3158    }
3159}
3160
3161/// Which cell an alignment stepped from, so the tokens an endpoint's atoms took
3162/// are walked back out of the cost table.
3163#[derive(Clone, Copy)]
3164enum Step {
3165    /// Row zero, where an alignment begins, and the state of a cell no
3166    /// alignment reached.
3167    Start,
3168    /// From the cell up and left, the atom matching its token.
3169    Matched,
3170    /// From the cell up and left, the atom standing in for its token.
3171    Substituted,
3172    /// From the cell above: an atom the run lacks.
3173    AtomDeleted,
3174    /// From the cell to the left: a token the run has spare.
3175    TokenExtra,
3176}
3177
3178/// The buffers the edit-distance walk runs in: the run of tokens it reads, its
3179/// cost table, and the tokens one alignment took. The walk runs once per state,
3180/// so each of these is an allocation a state otherwise.
3181///
3182/// Each field is cleared where the walk reaches it, so a scratch carries no
3183/// meaning between uses and one arriving full is the same as one arriving
3184/// empty.
3185#[derive(Default)]
3186struct EditScratch {
3187    run: Vec<usize>,
3188    table: Vec<(usize, Step)>,
3189    taken: Vec<Option<usize>>,
3190}
3191
3192impl EditScratch {
3193    /// An empty scratch, holding no capacity until a walk asks for some.
3194    const fn new() -> Self {
3195        Self { run: Vec::new(), table: Vec::new(), taken: Vec::new() }
3196    }
3197}
3198
3199thread_local! {
3200    /// The scratch an edit-distance walk runs in, held past the call that
3201    /// filled it; see [`EditScratch`].
3202    ///
3203    /// A `Within` pattern builds one scratch per step, and a scan steps once
3204    /// per anchor, so its three vectors reached the heap once an anchor each -
3205    /// the largest single source of allocation a scan has. Held here they
3206    /// reach a capacity the corpus needs and stop asking for more.
3207    ///
3208    /// Taking it out leaves an empty scratch behind, so a `Within` reached
3209    /// from inside another gets its own rather than the outer walk's table.
3210    static EDITS: std::cell::RefCell<EditScratch> =
3211        const { std::cell::RefCell::new(EditScratch::new()) };
3212}
3213
3214/// Filter the reachable set to states whose next significant token is at a
3215/// boundary in a precomputed axis field. Zero-width: a surviving state keeps
3216/// its position (the anchor consumes no token), so a following atom matches
3217/// from the same place. `@seam` keeps a state whose token starts at a seam
3218/// cut - the predictive-segmentation boundary the set engine built once.
3219/// Is token `p` the first significant token of its line, or of the input?
3220///
3221/// Significance skips whitespace, so an indented token still leads its
3222/// line. Scans back from the token start over whitespace only: reaching
3223/// the start of input, or a newline, means nothing precedes it on this
3224/// line.
3225fn leads_its_line(input: &[u8], start: usize) -> bool {
3226    let mut i = start;
3227    while i > 0 {
3228        let b = input[i - 1];
3229        if b == b'\n' {
3230            return true;
3231        }
3232        if !b.is_ascii_whitespace() {
3233            return false;
3234        }
3235        i -= 1;
3236    }
3237    true
3238}
3239
3240/// Is token `p` the last significant token of its line, or of the input?
3241/// The mirror of [`leads_its_line`], scanning forward from the token end.
3242fn ends_its_line(input: &[u8], end: usize) -> bool {
3243    let mut i = end;
3244    while i < input.len() {
3245        let b = input[i];
3246        if b == b'\n' {
3247            return true;
3248        }
3249        if !b.is_ascii_whitespace() {
3250            return false;
3251        }
3252        i += 1;
3253    }
3254    true
3255}
3256
3257/// Whether a positional anchor holds at token index `p`.
3258///
3259/// These four read the input either side of a token, so they are functions of
3260/// the tokens, the bytes and the position, with no precomputed field behind
3261/// them. Both engines call this, which is what keeps them from drifting: the
3262/// single-pass engine evaluates it as an epsilon instruction and the set
3263/// engine as a filter over the reachable set, and neither has its own copy of
3264/// the predicate.
3265///
3266/// The property anchors (`@seam` and the rest) are not here. Each reads a
3267/// field built once per scan, which only the set engine carries.
3268pub(crate) fn positional_anchor_holds(
3269    kind: &crate::ast::AnchorKind,
3270    input: &[u8],
3271    toks: &[Token],
3272    p: usize,
3273) -> bool {
3274    let t = &toks[p];
3275    anchor_holds_at(
3276        kind,
3277        input,
3278        t.start(),
3279        t.end(),
3280        !toks[..p].iter().any(Token::is_significant),
3281        !toks[p + 1..].iter().any(Token::is_significant),
3282    )
3283}
3284
3285/// [`positional_anchor_holds`] of a token given by its byte span alone, with
3286/// whether it is the first significant token of the input and whether it is
3287/// the last: what a stream that holds only the significant tokens can say.
3288pub(crate) fn anchor_holds_at(
3289    kind: &crate::ast::AnchorKind,
3290    input: &[u8],
3291    start: usize,
3292    end: usize,
3293    first: bool,
3294    last: bool,
3295) -> bool {
3296    use crate::ast::AnchorKind;
3297    match kind {
3298        AnchorKind::LineStart => leads_its_line(input, start),
3299        AnchorKind::LineEnd => ends_its_line(input, end),
3300        AnchorKind::InputStart => first,
3301        AnchorKind::InputEnd => last,
3302        // `\G` constrains which match a scan may keep, not which tokens a
3303        // match may take, so it is an epsilon here and the non-overlapping
3304        // selection applies it. A match attempt in isolation has no previous
3305        // match to abut.
3306        AnchorKind::Resume => true,
3307        // `\K` moves the reported start rather than testing the position, and
3308        // the parser refuses it on any pattern that reaches the set engine,
3309        // so it consumes nothing and asserts nothing wherever it is seen.
3310        AnchorKind::ResetStart => true,
3311        _ => false,
3312    }
3313}
3314
3315/// Whether the pair field's `reading` at the token starting at byte `start`
3316/// satisfies `cmp` against `level`. Strain is read at the unit holding that byte;
3317/// the binding of the cut before a unit is read only where a unit starts
3318/// there. The first unit reads neither, so no reading holds there. A
3319/// percentile is of this input's readings at the grain.
3320fn gravity_holds(
3321    r: &crate::gravity::Readings,
3322    reading: crate::ast::GravityReading,
3323    cmp: crate::ast::Cmp,
3324    level: crate::ast::Level,
3325    start: usize,
3326) -> bool {
3327    use crate::ast::{GravityReading, Level};
3328    let unit = match reading {
3329        GravityReading::Strain => r.unit_at(start),
3330        GravityReading::Bound => r.unit_starting_at(start),
3331    };
3332    let Some(u) = unit else { return false };
3333    let value = match reading {
3334        GravityReading::Strain => r.strain[u],
3335        GravityReading::Bound => r.binding[u],
3336    };
3337    let Some(value) = value else { return false };
3338    let bar = match (level, reading) {
3339        (Level::Bits(v), _) => Some(v.get()),
3340        (Level::Percentile(q), GravityReading::Strain) => r.strain_percentile(q.get()).map(f64::from),
3341        (Level::Percentile(q), GravityReading::Bound) => r.binding_percentile(q.get()).map(f64::from),
3342    };
3343    bar.is_some_and(|b| cmp.holds(f64::from(value).total_cmp(&b)))
3344}
3345
3346fn anchor(
3347    kind: &crate::ast::AnchorKind,
3348    input: &[u8],
3349    toks: &[Token],
3350    end: usize,
3351    states: Vec<State>,
3352    fields: Fields<'_>,
3353) -> Vec<State> {
3354    use crate::ast::AnchorKind;
3355    states
3356        .into_iter()
3357        .filter(|s| {
3358            let p = skip_ws(toks, s.pos, end);
3359            if p >= end {
3360                return false;
3361            }
3362            match kind {
3363                // The positional four share one predicate with the
3364                // single-pass engine, so the two cannot drift.
3365                AnchorKind::LineStart
3366                | AnchorKind::LineEnd
3367                | AnchorKind::InputStart
3368                | AnchorKind::InputEnd
3369                | AnchorKind::Resume => positional_anchor_holds(kind, input, toks, p),
3370                // The cut offsets are ascending, so a binary search resolves
3371                // membership in O(log n).
3372                // Each grain and cut reads its own cut set, and only the grains
3373                // a pattern names are segmented at all. The cuts are ascending
3374                // byte offsets whatever stream produced them, so one lookup
3375                // shape serves all three.
3376                AnchorKind::Seam(grain, cut) => cuts_for(fields.seams, *grain, *cut)
3377                    .is_some_and(|c| c.binary_search(&toks[p].start()).is_ok()),
3378                // A reading of the pair field at the unit containing this
3379                // token's start, held against the input's own percentile or
3380                // bits.
3381                AnchorKind::Gravity(reading, grain, cmp, level) => fields.gravity[grain_slot(*grain)]
3382                    .is_some_and(|r| gravity_holds(r, *reading, *cmp, *level, toks[p].start())),
3383                // The unit's type is the example's, or shares its placed class.
3384                AnchorKind::Kin(grain, example) => fields.gravity[grain_slot(*grain)].is_some_and(|r| {
3385                    let named = fields
3386                        .kin
3387                        .iter()
3388                        .find(|(g, x, _)| g == grain && x == example)
3389                        .and_then(|(_, _, t)| *t);
3390                    match (named, r.unit_at(toks[p].start())) {
3391                        (Some(want), Some(unit)) => r.kin(r.type_of(unit), want),
3392                        (None, _) | (_, None) => false,
3393                    }
3394                }),
3395                // The token is at least `min` brackets deep.
3396                AnchorKind::Nested(min) => fields.depths.is_some_and(|d| d[p] >= *min),
3397                // The token's span contains a contested (vantage-dependent)
3398                // point: the first contested offset at or after the token start
3399                // is still before the token end.
3400                AnchorKind::Ambiguous(grain, cut) => cuts_for(fields.contested, *grain, *cut).is_some_and(|c| {
3401                    let i = c.partition_point(|&x| x < toks[p].start());
3402                    c.get(i).is_some_and(|&x| x < toks[p].end())
3403                }),
3404                // Echo-axis anchors: the frames are index-aligned with the
3405                // token stream, so each is a rung lookup and an O(1) index.
3406                AnchorKind::Novel => echo_at(fields.echo, crate::orbit::OrbitGroup::Identity)
3407                    .is_some_and(|f| f.frames.get(p).is_some_and(crate::echo::EchoFrame::novel)),
3408                AnchorKind::Echoed => echo_at(fields.echo, crate::orbit::OrbitGroup::Identity)
3409                    .is_some_and(|f| f.frames.get(p).is_some_and(crate::echo::EchoFrame::echoed)),
3410                AnchorKind::Echo(pred, group) => echo_at(fields.echo, *group)
3411                    .and_then(|f| f.frames.get(p))
3412                    .is_some_and(|fr| echo_pred_holds(pred, fr)),
3413                // How this timestamp compares with the one before it,
3414                // read once per scan into a table the anchor indexes.
3415                AnchorKind::Order(want) => fields
3416                    .order
3417                    .and_then(|o| o.get(p).copied())
3418                    .flatten()
3419                    .is_some_and(|forward| forward == (*want == crate::ast::TimeOrder::Asc)),
3420                // Whether this token's line has a template rarer
3421                // than the cut, over templates mined once per scan.
3422                AnchorKind::Rare(cut) => {
3423                    fields.templates.is_some_and(|m| m.token_is_rare(p, *cut))
3424                }
3425                // Whether this token's content occurs in the second input,
3426                // from a table keyed once per scan at the anchor's rung; a
3427                // token the echo axis does not key holds for neither reading.
3428                AnchorKind::Joined { other, recurs, group } => fields
3429                    .joins
3430                    .iter()
3431                    .find(|j| std::sync::Arc::ptr_eq(&j.other, other) && j.group == *group)
3432                    .and_then(|j| j.recurs.get(p).copied().flatten())
3433                    .is_some_and(|in_other| in_other == *recurs),
3434                // The parser refuses `\K` on any pattern that reaches this
3435                // engine, since there is no match-start slot here to move.
3436                AnchorKind::ResetStart => true,
3437                // The upper-grain window: is this token where a construct
3438                // begins, and what is the construct it is in. Both are O(1)
3439                // lookups into a table built once for the scan.
3440                AnchorKind::SuperStart => fields.supers.is_some_and(|s| s.starts_unit(p)),
3441                AnchorKind::SuperRole(want) => {
3442                    fields.supers.is_some_and(|s| s.unit_of(p).is_some_and(|u| u.role == *want))
3443                }
3444                // The token's phase of the dominant token-kind period, read
3445                // from the related context; a stream with no period holds
3446                // no phase.
3447                AnchorKind::Phase(k) => {
3448                    fields.relation.is_some_and(|r| r.phase_of.get(p) == Some(k))
3449                }
3450                // The token's column of a named period, where it is live.
3451                AnchorKind::PhaseIn(k, period) => fields.bands.is_some_and(|b| b.at(p, *k, *period)),
3452            }
3453        })
3454        .collect()
3455}
3456
3457fn match_atom(
3458    a: &Atom,
3459    input: &[u8],
3460    toks: &[Token],
3461    end: usize,
3462    s: &State,
3463    regs: &[String],
3464    fields: Fields<'_>,
3465) -> Option<State> {
3466    // Whitespace is insignificant between atoms, so it is skipped before
3467    // matching - EXCEPT when the atom itself asks for whitespace (`\S`, or
3468    // the `\s` byte class), which must see the token the skip would jump
3469    // over.
3470    let wants_ws = matches!(
3471        a,
3472        Atom::Kind(TokenKind::Whitespace) | Atom::Byte(crate::ast::ByteClass::Space)
3473    );
3474    let p = if wants_ws { s.pos } else { skip_ws(toks, s.pos, end) };
3475    if p >= end {
3476        return None;
3477    }
3478    let ok = atom_test(a, input, toks, p, s, regs, fields);
3479    if ok {
3480        Some(State { pos: p + 1, env: s.env.clone(), rank: s.rank.clone(), hist: s.hist.clone() })
3481    } else {
3482        None
3483    }
3484}
3485
3486/// Whether the token at index `p` satisfies an atom, with no position
3487/// advance.
3488///
3489/// Separated from [`match_atom`] so a class can test its members against the
3490/// same token: membership recurses here, and every atom the language has is a
3491/// possible member for free.
3492fn atom_test(
3493    a: &Atom,
3494    input: &[u8],
3495    toks: &[Token],
3496    p: usize,
3497    s: &State,
3498    regs: &[String],
3499    fields: Fields<'_>,
3500) -> bool {
3501    let t = &toks[p];
3502    let txt = text(input, t);
3503    match a {
3504        Atom::Kind(k) => t.kind == *k,
3505        Atom::Any => true,
3506        Atom::Literal(lit, group) => register_eq_matches(lit.as_bytes(), txt, *group),
3507        Atom::RegisterEq(name, group) => reg_id(regs, name)
3508            .and_then(|id| s.env.get(&id))
3509            .is_some_and(|&(a, b)| register_eq_matches(&input[a..b], txt, *group)),
3510        // The unit the bound value starts in and this token's unit, at the
3511        // atom's grain, share a type or a placed gravity class.
3512        Atom::RegisterKin(name, grain) => reg_id(regs, name).and_then(|id| s.env.get(&id)).is_some_and(|&(a, _)| {
3513            fields.gravity[grain_slot(*grain)].is_some_and(|r| match (r.unit_at(a), r.unit_at(t.start())) {
3514                (Some(bound), Some(here)) => r.kin(r.type_of(bound), r.type_of(here)),
3515                (None, _) | (_, None) => false,
3516            })
3517        }),
3518        Atom::RegisterRelated(name, relation) => reg_id(regs, name)
3519            .and_then(|id| s.env.get(&id))
3520            .is_some_and(|&(a, b)| relation.related(&input[a..b], txt)),
3521        Atom::LiteralWithin(lit, k, group) => within_edits(lit.as_bytes(), txt, *k, *group),
3522        Atom::RegisterWithin(name, k, group) => reg_id(regs, name)
3523            .and_then(|id| s.env.get(&id))
3524            .is_some_and(|&(a, b)| within_edits(&input[a..b], txt, *k, *group)),
3525        Atom::Byte(bc) => byte_class_matches(*bc, txt),
3526        Atom::BytePattern(bp) => bp.matches_whole(txt),
3527        Atom::Spectral(pred) => spectral_pred_matches(pred, fields.spectral, t.start(), t.end()),
3528        Atom::Magnitude(pred) => magnitude_matches(pred, toks, p, txt, s, regs, fields),
3529        Atom::KindMag(k, pred) => {
3530            t.kind == *k && magnitude_matches(pred, toks, p, txt, s, regs, fields)
3531        }
3532        Atom::KindPred(k, pred) => t.kind == *k && pred.matches_as(t.kind, txt, || fields.clock),
3533        Atom::Since(op, signed, name) => {
3534            t.kind == crate::token::TokenKind::Timestamp
3535                && reg_id(regs, name).and_then(|id| s.env.get(&id)).is_some_and(|&(a, b)| {
3536                    crate::typed::since_holds(*op, signed, &input[a..b], txt, fields.clock)
3537                })
3538        }
3539        Atom::Class(c) => {
3540            let hit = c.any.iter().any(|m| atom_test(m, input, toks, p, s, regs, fields))
3541                && (c.all.is_empty()
3542                    || c.all.iter().any(|m| atom_test(m, input, toks, p, s, regs, fields)))
3543                && !c.none.iter().any(|m| atom_test(m, input, toks, p, s, regs, fields));
3544            hit != c.negated
3545        }
3546    }
3547}
3548
3549/// Whether the token at `p` satisfies a magnitude predicate, absolute or
3550/// relative to the context the predicate names.
3551fn magnitude_matches(
3552    pred: &crate::ast::MagPred,
3553    toks: &[Token],
3554    p: usize,
3555    txt: &[u8],
3556    s: &State,
3557    regs: &[String],
3558    fields: Fields<'_>,
3559) -> bool {
3560    let mag = crate::magnitude::token_magnitude(toks[p].kind, txt);
3561    let context = pred.scope().and_then(|scope| magnitude_context(scope, toks, p, s, regs, fields));
3562    pred.matches(mag, context)
3563}
3564
3565/// The magnitude fold a relative predicate at token `p` compares against:
3566/// the rolling window before the token, one of its relation-admitted
3567/// contexts, or the value history of the key a register holds. `None` when
3568/// the field was not built or the token has no such context.
3569fn magnitude_context<'a>(
3570    scope: &crate::ast::Scope,
3571    toks: &[Token],
3572    p: usize,
3573    s: &State,
3574    regs: &[String],
3575    fields: Fields<'a>,
3576) -> Option<&'a crate::profile::MagnitudeProfile> {
3577    use crate::ast::Scope;
3578    match scope {
3579        // The window before the token is the window's fold at the
3580        // significant token before it.
3581        Scope::Window => {
3582            let field = fields.context?;
3583            let prev = (0..p).rev().find(|&j| toks[j].is_significant())?;
3584            Some(&field.at_token.get(prev)?.magnitude)
3585        }
3586        Scope::Phase => Some(&fields.relation?.at_token.get(p)?.phase.magnitude),
3587        Scope::Regime => Some(&fields.relation?.at_token.get(p)?.regime.magnitude),
3588        Scope::Echo => Some(&fields.relation?.at_token.get(p)?.echoing.magnitude),
3589        Scope::Enclosing => Some(&fields.relation?.at_token.get(p)?.enclosing.magnitude),
3590        // The register holds a span; the token starting there is the key
3591        // whose value history the predicate reads.
3592        Scope::Key(name) => {
3593            let field = fields.relation?;
3594            let id = reg_id(regs, name)?;
3595            let &(start, _) = s.env.get(&id)?;
3596            let key = toks.partition_point(|t| t.start() < start);
3597            Some(&field.value_history.get(key)?.magnitude)
3598        }
3599    }
3600}
3601
3602/// Evaluate a spectral predicate against the pooled field signature of the
3603/// token span `[start, end)`. A pattern carrying a spectral atom always
3604/// builds the field (see `scan_set_reachability`); the `None` guard simply
3605/// declines to match when no field was built.
3606/// A second input a join anchor reads, keyed at one rung: per token of the
3607/// scanned input, whether its key occurs anywhere in the other input, and
3608/// `None` for a token the echo axis does not key.
3609pub(crate) struct Join {
3610    other: std::sync::Arc<crate::ast::OtherInput>,
3611    group: crate::orbit::OrbitGroup,
3612    recurs: Vec<Option<bool>>,
3613}
3614
3615impl Join {
3616    /// Key the other input's tokens at `group` once, then read every token
3617    /// of this input against them.
3618    fn build(
3619        other: std::sync::Arc<crate::ast::OtherInput>,
3620        group: crate::orbit::OrbitGroup,
3621        toks: &[Token],
3622        input: &[u8],
3623    ) -> Join {
3624        let keys: std::collections::HashSet<String> = other
3625            .tokens
3626            .iter()
3627            .filter(|t| crate::echo::keyed_kind(t.kind))
3628            .map(|t| crate::orbit::canonical(&other.bytes[t.span()], group))
3629            .collect();
3630        let recurs = toks
3631            .iter()
3632            .map(|t| {
3633                crate::echo::keyed_kind(t.kind)
3634                    .then(|| keys.contains(&crate::orbit::canonical(&input[t.span()], group)))
3635            })
3636            .collect();
3637        Join { other, group, recurs }
3638    }
3639}
3640
3641/// How each timestamp token compares with the timestamp before it:
3642/// `Some(true)` at or after it, `Some(false)` before it, and `None` for a
3643/// token that is no timestamp, one whose text does not read as an instant,
3644/// one the clock cannot place, and the first timestamp of an input, which
3645/// has nothing to compare with.
3646pub(crate) fn timestamp_order(
3647    toks: &[Token],
3648    input: &[u8],
3649    clock: crate::typed::Clock,
3650) -> Vec<Option<bool>> {
3651    let mut out = vec![None; toks.len()];
3652    let mut prev: Option<(i64, u32)> = None;
3653    for (i, t) in toks.iter().enumerate() {
3654        if t.kind != crate::token::TokenKind::Timestamp {
3655            continue;
3656        }
3657        let text = String::from_utf8_lossy(&input[t.span()]);
3658        let Some(here) = crate::typed::parse_civil(&text).and_then(|c| c.epoch(clock)) else {
3659            continue;
3660        };
3661        if let Some(before) = prev {
3662            out[i] = Some(here >= before);
3663        }
3664        prev = Some(here);
3665    }
3666    out
3667}
3668
3669/// The recurrence field counted at `group`, or `None` where the pattern
3670/// names no anchor at that rung.
3671fn echo_at(
3672    fields: &[(crate::orbit::OrbitGroup, crate::echo::EchoField)],
3673    group: crate::orbit::OrbitGroup,
3674) -> Option<&crate::echo::EchoField> {
3675    fields.iter().find(|(g, _)| *g == group).map(|(_, f)| f)
3676}
3677
3678/// Whether one token's echo frame satisfies a reading of the axis. An
3679/// unkeyed token carries no recurrence and satisfies none of them.
3680fn echo_pred_holds(pred: &crate::ast::EchoPred, fr: &crate::echo::EchoFrame) -> bool {
3681    use crate::ast::EchoPred;
3682    if !fr.keyed {
3683        return false;
3684    }
3685    match pred {
3686        EchoPred::Count(op, k) => op.holds(fr.count.cmp(k)),
3687        EchoPred::Nth(op, k) => {
3688            // A negative index counts back from the last occurrence, so -1 is
3689            // the last and -2 the one before it.
3690            let want = if *k < 0 {
3691                let from_end = k.unsigned_abs();
3692                if from_end > fr.count {
3693                    return false;
3694                }
3695                fr.count - from_end + 1
3696            } else {
3697                k.unsigned_abs()
3698            };
3699            op.holds(fr.nth.cmp(&want))
3700        }
3701        // A frame with no period carries zero, which no regular recurrence can
3702        // read as its own, so the test for having one is the test for it being
3703        // above zero.
3704        EchoPred::Period => fr.period > 0.0,
3705        EchoPred::PeriodAt(op, k) => {
3706            fr.period > 0.0 && op.holds((fr.period.round() as i64).cmp(&i64::from(*k)))
3707        }
3708    }
3709}
3710
3711fn spectral_pred_matches(
3712    pred: &crate::ast::SpectralPred,
3713    field: Option<&SpectralField>,
3714    start: usize,
3715    end: usize,
3716) -> bool {
3717    use crate::ast::{SpecTexture, SpectralPred};
3718    use crate::spectral::Texture;
3719    let Some(f) = field else { return false };
3720    f.assert_carries(crate::spectral::Needs::of(pred), "a spectral atom");
3721    // Pooled inside the arms that read a frame rather than before the match.
3722    // Pooling sums a band a frame across the token's span, and the onset reads
3723    // the change-points instead, so taking it first hands one predicate a
3724    // frame it discards at every anchor it is asked about.
3725    match pred {
3726        SpectralPred::Onset => f.boundary_in(start, end),
3727        SpectralPred::EntropyGe(p) => f.signature(start, end).entropy * 100.0 >= f32::from(*p),
3728        SpectralPred::EntropyLe(p) => f.signature(start, end).entropy * 100.0 <= f32::from(*p),
3729        SpectralPred::PeriodEq(n) => f.signature(start, end).period == *n,
3730        SpectralPred::PeriodAny => f.signature(start, end).period != 0,
3731        SpectralPred::Texture(want) => matches!(
3732            (want, crate::spectral::texture_of(&f.signature(start, end))),
3733            (SpecTexture::Prose, Texture::Prose)
3734                | (SpecTexture::Code, Texture::Code)
3735                | (SpecTexture::Math, Texture::Math)
3736                | (SpecTexture::Data, Texture::Data)
3737        ),
3738    }
3739}
3740
3741/// Whether a token `txt` matches a register's bound span `bound` under an
3742/// orbit `group`. The Identity orbit is a byte-for-byte compare (the exact-
3743/// repeat backreference, kept bit-identical to the original behavior); any
3744/// other group compares the two spans' canonical orbit representatives, so
3745/// the reference matches every token in the bound token's orbit (a fuzzy
3746/// backreference). Shared by both engines so the compare lives in one place.
3747pub(crate) fn register_eq_matches(bound: &[u8], txt: &[u8], group: crate::orbit::OrbitGroup) -> bool {
3748    use crate::orbit::{OrbitGroup, canonical};
3749    match group {
3750        OrbitGroup::Identity => bound == txt,
3751        g => canonical(bound, g) == canonical(txt, g),
3752    }
3753}
3754
3755/// Whether `bound` and `txt` are within `k` edits of each other, over the
3756/// two as `group` canonicalizes them.
3757pub(crate) fn within_edits(bound: &[u8], txt: &[u8], k: u8, group: crate::orbit::OrbitGroup) -> bool {
3758    use crate::orbit::{OrbitGroup, canonical};
3759    match group {
3760        OrbitGroup::Identity => crate::edit::within(bound, txt, k),
3761        g => crate::edit::within(canonical(bound, g).as_bytes(), canonical(txt, g).as_bytes(), k),
3762    }
3763}
3764
3765pub(crate) fn byte_class_matches(bc: ByteClass, txt: &[u8]) -> bool {
3766    if txt.is_empty() {
3767        return false;
3768    }
3769    match bc {
3770        ByteClass::Digit => txt.iter().all(u8::is_ascii_digit),
3771        ByteClass::Word => txt.iter().all(|b| *b == b'_' || b.is_ascii_alphanumeric()),
3772        ByteClass::Space => txt.iter().all(u8::is_ascii_whitespace),
3773        ByteClass::Hex => txt.iter().all(u8::is_ascii_hexdigit),
3774        ByteClass::Alpha => txt.iter().all(u8::is_ascii_alphabetic),
3775        ByteClass::Upper => txt.iter().all(u8::is_ascii_uppercase),
3776        ByteClass::Lower => txt.iter().all(u8::is_ascii_lowercase),
3777    }
3778}
3779
3780/// Alternation, per [`crate::ast::AltMode`].
3781///
3782/// `Committed` takes the first branch that yields any state and never consults
3783/// what follows, so a branch the continuation later contradicts kills the
3784/// pattern. `Longest` unions every branch, leaving the choice to the final
3785/// longest-match selection. `First` also unions, but tags each branch's states
3786/// with their index, so the selection prefers the earliest branch that
3787/// survives the continuation - the fallback regex gives and commitment does
3788/// not.
3789#[allow(clippy::too_many_arguments)]
3790fn alt(
3791    parts: &[Pattern],
3792    mode: crate::ast::AltMode,
3793    input: &[u8],
3794    toks: &[Token],
3795    end: usize,
3796    mut states: Vec<State>,
3797    absent: &HashSet<Vec<u8>>,
3798    regs: &[String],
3799    fields: Fields<'_>,
3800) -> Vec<State> {
3801    use crate::ast::AltMode;
3802    // The last branch to be tried takes the set rather than a copy of it: no
3803    // branch after it needs one, and a copy kept beside it holds a second
3804    // reference to every register map its results carry, which makes the next
3805    // write to one copy a map it could have written in place.
3806    let last = parts.len().saturating_sub(1);
3807    match mode {
3808        AltMode::Committed => {
3809            for (i, p) in parts.iter().enumerate() {
3810                let taken =
3811                    if i == last { std::mem::take(&mut states) } else { states.clone() };
3812                let out = advance(p, input, toks, end, taken, absent, regs, fields);
3813                if !out.is_empty() {
3814                    return dedup(out);
3815                }
3816            }
3817            Vec::new()
3818        }
3819        AltMode::Longest => {
3820            let mut out = Vec::new();
3821            for (i, p) in parts.iter().enumerate() {
3822                let taken =
3823                    if i == last { std::mem::take(&mut states) } else { states.clone() };
3824                out.extend(advance(p, input, toks, end, taken, absent, regs, fields));
3825            }
3826            dedup(out)
3827        }
3828        AltMode::First => {
3829            let mut out = Vec::new();
3830            for (i, p) in parts.iter().enumerate() {
3831                // Saturating at 255 only collapses the preference between
3832                // branch 255 and beyond, which no readable pattern reaches.
3833                let branch = u8::try_from(i).unwrap_or(u8::MAX);
3834                let tagged: Vec<State> = states.iter().map(|s| s.with_branch(branch)).collect();
3835                out.extend(advance(p, input, toks, end, tagged, absent, regs, fields));
3836            }
3837            // Branches were walked in order, so the first arrival at a given
3838            // (pos, env) came from the earliest branch and carries the best
3839            // rank; `dedup` keeps the first, which is that one.
3840            dedup(out)
3841        }
3842    }
3843}
3844
3845#[allow(clippy::too_many_arguments)]
3846fn star(
3847    p: &Pattern,
3848    g: Greed,
3849    input: &[u8],
3850    toks: &[Token],
3851    end: usize,
3852    states: Vec<State>,
3853    absent: &HashSet<Vec<u8>>,
3854    regs: &[String],
3855    fields: Fields<'_>,
3856) -> Vec<State> {
3857    unroll(p, g, None, input, toks, end, states, absent, regs, fields)
3858}
3859
3860/// Closure under repetition, recording which way the quantifier leaned.
3861///
3862/// Adds states reachable by one more application of `p` until an iteration
3863/// reaches nothing it has not already reached as well or better, or until
3864/// `bound` iterations. Each state is held once, at its preferred derivation,
3865/// in a `HashMap` keyed on the state's identity, so the fixpoint is linear in
3866/// the number of distinct states rather than quadratic.
3867///
3868/// Two judgments, made separately. What a state records is decided on the
3869/// entry, so a derivation arriving at a state it cannot improve still offers
3870/// the entry it would write. Whether that state iterates again is decided on
3871/// the arrival, and only a strictly preferred arrival goes round.
3872///
3873/// The second is what terminates the fixpoint: a preference path is only ever
3874/// extended, and an extension compares larger than the path it grew from, so a
3875/// body that matched empty hands its state back ranked below the one it
3876/// arrived with and is refused.
3877///
3878/// Every state that stops here records the choice, in one of two forms. A body
3879/// that makes no choice of its own is fully described by its iteration count,
3880/// so one entry carries it. A body that does make choices needs its per
3881/// iteration entries to line up against another derivation's, so each
3882/// iteration writes a continue marker and stopping writes a stop marker; the
3883/// two markers are ordered by `g`, which is the whole of what lazy means here.
3884/// How many states a fixpoint's seen-set holds before it stops being a slice
3885/// and becomes a map.
3886///
3887/// Measured over the crate's own source: of the five shapes that reach a
3888/// fixpoint, four hold at most sixty-four states in every call they make - zero
3889/// calls past it - and the fifth exceeds sixty-four in 5,352 of its 864,634
3890/// calls, two hundred and fifty-six in 1,467 and a thousand in 333. So a slice
3891/// answers almost every call and the map is there for the one shape that
3892/// reaches hundreds.
3893const SEEN_LINEAR: usize = 16;
3894
3895/// The states a fixpoint has already reached, each with where its entry is in
3896/// the result and the rank it arrived by.
3897///
3898/// A slice beats a map at these sizes twice over. The allocation is the first:
3899/// a map asks the heap on its first insert and a fixpoint runs once per
3900/// anchor, where the slice is held per thread and cleared. The comparison is
3901/// the second: [`State`] rejects on `pos` before it reads the register map,
3902/// and hashing must read both, so a short scan reads less of a state than one
3903/// hash does.
3904///
3905/// It is a slice only while it is short. Past [`SEEN_LINEAR`] the states move
3906/// into a map, which bounds what a scan costs on a shape that reaches hundreds
3907/// of them.
3908struct Seen {
3909    few: Vec<(State, usize, Rank)>,
3910    many: Option<std::collections::HashMap<State, (usize, Rank), crate::fxhash::FxBuild>>,
3911}
3912
3913impl Seen {
3914    /// Where `s` is and the rank it arrived by, or `None` for a state this
3915    /// fixpoint has not reached.
3916    fn get(&self, s: &State) -> Option<(usize, Rank)> {
3917        match &self.many {
3918            Some(m) => m.get(s).map(|(at, rank)| (*at, rank.clone())),
3919            None => self
3920                .few
3921                .iter()
3922                .find(|(held, _, _)| held == s)
3923                .map(|(_, at, rank)| (*at, rank.clone())),
3924        }
3925    }
3926
3927    /// Record that `s` is at `at` and arrived by `rank`, replacing whatever
3928    /// the set held for it.
3929    fn insert(&mut self, s: State, at: usize, rank: Rank) {
3930        if let Some(m) = &mut self.many {
3931            m.insert(s, (at, rank));
3932            return;
3933        }
3934        if let Some(slot) = self.few.iter_mut().find(|(held, _, _)| *held == s) {
3935            slot.1 = at;
3936            slot.2 = rank;
3937            return;
3938        }
3939        self.few.push((s, at, rank));
3940        if self.few.len() > SEEN_LINEAR {
3941            let mut m = std::collections::HashMap::with_capacity_and_hasher(
3942                self.few.len() * 2,
3943                crate::fxhash::FxBuild::process(),
3944            );
3945            for (state, at, rank) in self.few.drain(..) {
3946                m.insert(state, (at, rank));
3947            }
3948            self.many = Some(m);
3949        }
3950    }
3951
3952    /// How many states the set holds.
3953    fn held(&self) -> usize {
3954        match &self.many {
3955            Some(m) => m.len(),
3956            None => self.few.len(),
3957        }
3958    }
3959}
3960
3961thread_local! {
3962    /// The slice a fixpoint's seen-set scans, held past the call that filled
3963    /// it; see [`Seen`].
3964    ///
3965    /// A vector clears in its length where a map clears in its capacity, which
3966    /// is what makes this one safe to hold where the map at [`dedup`] was
3967    /// measured worse for being held.
3968    ///
3969    /// Taking it out leaves an empty vector behind, so a fixpoint reached from
3970    /// inside another gets its own rather than the outer one's states.
3971    static SEEN: std::cell::RefCell<Vec<(State, usize, Rank)>> =
3972        const { std::cell::RefCell::new(Vec::new()) };
3973}
3974
3975#[allow(clippy::too_many_arguments)]
3976fn unroll(
3977    p: &Pattern,
3978    g: Greed,
3979    bound: Option<usize>,
3980    input: &[u8],
3981    toks: &[Token],
3982    end: usize,
3983    states: Vec<State>,
3984    absent: &HashSet<Vec<u8>>,
3985    regs: &[String],
3986    fields: Fields<'_>,
3987) -> Vec<State> {
3988    let choiceful = p.has_choice();
3989    let (cont, stop) = match g {
3990        Greed::Greedy => (0u8, 1u8),
3991        Greed::Lazy => (1u8, 0u8),
3992    };
3993    // The rank each state was last reached at, and where its entry is in
3994    // `result`, so a preferred arrival replaces the entry rather than adding a
3995    // second one for the same state. Looked up once per state per iteration,
3996    // which is what makes the shape of [`Seen`] worth its lines: a slice while
3997    // it is short and a map past that. `result` holds the entries in first-seen
3998    // order, so neither how the set is held nor where a state is inside it
3999    // can reach the answer.
4000    let mut best =
4001        Seen { few: SEEN.with(|s| std::mem::take(&mut *s.borrow_mut())), many: None };
4002    let mut result: Vec<State> = Vec::new();
4003    let mut frontier: Vec<State> = states;
4004    let mut iters: usize = 0;
4005    // The next round's frontier, built once and recycled rather than at each
4006    // iteration.
4007    //
4008    // Measured over the crate's own source, this loop goes round 3.1 million
4009    // times for `(?>\W*) "="` and 4.5 million for `\W \B`, and its frontier
4010    // reaches four states each time - a vector of 160 bytes, which is the size
4011    // bucket the sampled allocation sites kept naming. Building one an
4012    // iteration was the largest remaining allocation in a scan.
4013    //
4014    // The recycling is the swap below: draining the frontier leaves a vector
4015    // that is empty and still holds its capacity, and that is what next round
4016    // pushes into. A body that makes choices of its own still collects a marked
4017    // copy, which is a second vector this does not remove.
4018    let mut fresh: Vec<State> = Vec::new();
4019    loop {
4020        fresh.clear();
4021        for s in frontier.drain(..) {
4022            let entry = if choiceful {
4023                s.with_branch(stop)
4024            } else {
4025                s.with_branch(count_key(g, iters))
4026            };
4027            let Some((i, held)) = best.get(&s) else {
4028                best.insert(s.clone(), result.len(), s.rank.clone());
4029                result.push(entry);
4030                fresh.push(s);
4031                continue;
4032            };
4033            // What a derivation records is judged on the entry itself, so a
4034            // pass that ends where it began still offers the loop the single
4035            // empty iteration leftmost-first allows it before stopping.
4036            if cmp_rank(entry.rank_slice(), result[i].rank_slice()) == std::cmp::Ordering::Less {
4037                result[i] = entry;
4038            }
4039            // Whether to go round again is judged on the arrival instead, and
4040            // an extended path never beats the one it grew from. That is what
4041            // separates the one empty iteration from a second.
4042            let held = held.as_slice();
4043            if cmp_rank(s.rank_slice(), held) == std::cmp::Ordering::Less {
4044                best.insert(s.clone(), i, s.rank.clone());
4045                fresh.push(s);
4046            }
4047        }
4048        if fresh.is_empty() || bound.is_some_and(|nn| iters >= nn) {
4049            note_fixpoint_map(best.held());
4050            // The slice goes back whether or not the states moved into a map:
4051            // promotion drains it, so what returns is empty either way and
4052            // still holds the capacity the next call pushes into.
4053            let mut few = best.few;
4054            few.clear();
4055            SEEN.with(|s| *s.borrow_mut() = few);
4056            return result;
4057        }
4058        let grown = fresh.capacity();
4059        let marked: Vec<State> = if choiceful {
4060            fresh.iter().map(|s| s.with_branch(cont)).collect()
4061        } else {
4062            std::mem::take(&mut fresh)
4063        };
4064        note_unrolled(grown, marked.capacity());
4065        // The drained frontier is empty and still holds its capacity, so it
4066        // becomes next round's buffer before `advance` replaces it. Where the
4067        // body made no choices `fresh` was just handed away, and this is what
4068        // it gets back in place of a fresh allocation.
4069        std::mem::swap(&mut fresh, &mut frontier);
4070        frontier = advance(p, input, toks, end, marked, absent, regs, fields);
4071        iters += 1;
4072    }
4073}
4074
4075// Threads the scan context (input, tokens, end, state set, prefilter,
4076// register table) of a recursive descent; bundling it would not change the
4077// generated code.
4078#[allow(clippy::too_many_arguments)]
4079fn repeat(
4080    p: &Pattern,
4081    bounds: (usize, Option<usize>),
4082    g: Greed,
4083    input: &[u8],
4084    toks: &[Token],
4085    end: usize,
4086    states: Vec<State>,
4087    absent: &HashSet<Vec<u8>>,
4088    regs: &[String],
4089    fields: Fields<'_>,
4090) -> Vec<State> {
4091    let (m, n) = bounds;
4092    // The first `m` copies are mandatory, so they carry no choice and write
4093    // no rank entry. Only the optional tail leans.
4094    let mut cur = states;
4095    for _ in 0..m {
4096        cur = advance(p, input, toks, end, cur, absent, regs, fields);
4097        if cur.is_empty() {
4098            return cur;
4099        }
4100    }
4101    let optional = n.map(|nn| nn.saturating_sub(m));
4102    unroll(p, g, optional, input, toks, end, cur, absent, regs, fields)
4103}
4104
4105#[allow(clippy::too_many_arguments)]
4106fn bind(
4107    name: &str,
4108    p: &Pattern,
4109    input: &[u8],
4110    toks: &[Token],
4111    end: usize,
4112    states: Vec<State>,
4113    absent: &HashSet<Vec<u8>>,
4114    regs: &[String],
4115    fields: Fields<'_>,
4116) -> Vec<State> {
4117    // The name is interned once here, not per produced state: every bound
4118    // register was collected into `regs` at scan setup.
4119    let id = reg_id(regs, name).expect("bind register is collected");
4120    // A register bound under a repetition keeps every binding: each is one
4121    // node on the state's list, shared with every fork after it.
4122    let listed = fields.lists.contains(&id);
4123    let record = |r: &mut State, span: (usize, usize)| {
4124        if listed {
4125            r.hist = Some(Rc::new(HistNode { id, span, prev: r.hist.take() }));
4126        }
4127    };
4128
4129    // Fast path: binding a single atom. An atom consumes exactly the one
4130    // significant token at `r.pos - 1`, so its bound span is that token and
4131    // each input state maps to at most one result. This skips the general
4132    // path's per-state one-element vector and `advance` dispatch, which the
4133    // flame graph showed dominate a register-binding scan.
4134    if let Pattern::Atom(atom) = p {
4135        let out: Vec<State> = states
4136            .into_iter()
4137            .filter_map(|s| {
4138                let mut r = match_atom(atom, input, toks, end, &s, regs, fields)?;
4139                let tok = r.pos - 1;
4140                let span = (toks[tok].start(), toks[tok].end());
4141                Rc::make_mut(&mut r.env).insert(id, span);
4142                record(&mut r, span);
4143                Some(r)
4144            })
4145            .collect();
4146        return dedup(out);
4147    }
4148
4149    let mut out = Vec::new();
4150    for s in states {
4151        let start_pos = skip_ws(toks, s.pos, end);
4152        // The state moves into the call rather than being copied beside it. A
4153        // copy kept here holds a second reference to the register map
4154        // the results share, which is what makes the `make_mut` below copy that
4155        // map where it could have written it in place.
4156        for mut r in advance(p, input, toks, end, vec![s], absent, regs, fields) {
4157            let span = if r.pos > start_pos {
4158                (toks[start_pos].start(), toks[r.pos - 1].end())
4159            } else {
4160                (0, 0)
4161            };
4162            // Copy-on-write: clones the shared map only here, at the bind,
4163            // not on every carry-clone of a state. The value is the span,
4164            // resolved to text lazily, so no text is allocated here.
4165            Rc::make_mut(&mut r.env).insert(id, span);
4166            record(&mut r, span);
4167            out.push(r);
4168        }
4169    }
4170    dedup(out)
4171}
4172
4173#[allow(clippy::too_many_arguments)]
4174fn balanced(
4175    kind: Option<crate::token::BracketKind>,
4176    p: &Pattern,
4177    input: &[u8],
4178    toks: &[Token],
4179    end: usize,
4180    states: Vec<State>,
4181    absent: &HashSet<Vec<u8>>,
4182    regs: &[String],
4183    fields: Fields<'_>,
4184) -> Vec<State> {
4185    // Registers bound with `::name` inside this group are scoped to it: their
4186    // values are restored to the pre-group state when the group closes, so a
4187    // reference cannot leak out. Computed once from the interior pattern.
4188    let mut scoped_ids: Vec<u16> = Vec::new();
4189    collect_scoped_ids(p, regs, &mut scoped_ids);
4190
4191    let mut out = Vec::new();
4192    for s in states {
4193        let p0 = skip_ws(toks, s.pos, end);
4194        if p0 >= end {
4195            continue;
4196        }
4197        let TokenKind::Open(bk) = toks[p0].kind else {
4198            continue;
4199        };
4200        if let Some(want) = kind
4201            && want != bk
4202        {
4203            continue;
4204        }
4205        let Some(m) = toks[p0].mate() else {
4206            continue;
4207        };
4208        if m >= end {
4209            continue;
4210        }
4211        // The interior must consume exactly the enclosed span.
4212        let pre_env = s.env.clone();
4213        let inner_start =
4214            vec![State { pos: p0 + 1, env: s.env.clone(), rank: s.rank.clone(), hist: s.hist.clone() }];
4215        for ist in advance(p, input, toks, m, inner_start, absent, regs, fields) {
4216            if skip_ws(toks, ist.pos, m) == m {
4217                let rank = ist.rank.clone();
4218                let hist = ist.hist.clone();
4219                let env = restore_scoped(ist.env, &pre_env, &scoped_ids);
4220                out.push(State { pos: m + 1, env, rank, hist });
4221            }
4222        }
4223    }
4224    dedup(out)
4225}
4226
4227/// The register ids bound with a scoped `::name` anywhere in `pat`, deduplicated.
4228/// A balanced group uses this to know which bindings to drop when it closes.
4229fn collect_scoped_ids(pat: &Pattern, regs: &[String], out: &mut Vec<u16>) {
4230    match pat {
4231        Pattern::Bind(name, scoped, p) => {
4232            if *scoped
4233                && let Some(id) = reg_id(regs, name)
4234                && !out.contains(&id)
4235            {
4236                out.push(id);
4237            }
4238            collect_scoped_ids(p, regs, out);
4239        }
4240        Pattern::Within(v, _) => {
4241            for (name, scoped) in v.iter().filter_map(|e| e.bind.as_ref()) {
4242                if *scoped
4243                    && let Some(id) = reg_id(regs, name)
4244                    && !out.contains(&id)
4245                {
4246                    out.push(id);
4247                }
4248            }
4249        }
4250        Pattern::Empty | Pattern::Atom(_) | Pattern::Guard(..) | Pattern::Anchor(_) => {}
4251        Pattern::Assert(p, _, _) | Pattern::Atomic(p) => collect_scoped_ids(p, regs, out),
4252        Pattern::Star(p, _) | Pattern::Plus(p, _) | Pattern::Opt(p, _) => {
4253            collect_scoped_ids(p, regs, out);
4254        }
4255        Pattern::Repeat(p, _, _, _) | Pattern::Balanced(_, p) | Pattern::Field(_, p) => {
4256            collect_scoped_ids(p, regs, out);
4257        }
4258        Pattern::Concat(v) | Pattern::Alt(v, _) => {
4259            for c in v {
4260                collect_scoped_ids(c, regs, out);
4261            }
4262        }
4263    }
4264}
4265
4266/// Restore each scoped register id in `env` to its value in `pre` (the state
4267/// before the group was entered), removing it when it was unbound there. This
4268/// drops any binding a `::name` made inside the group so it cannot leak out.
4269/// When nothing is scoped, `env` is returned untouched (no clone).
4270fn restore_scoped(env: Env, pre: &Env, scoped_ids: &[u16]) -> Env {
4271    if scoped_ids.is_empty() {
4272        return env;
4273    }
4274    let mut e = env;
4275    let map = Rc::make_mut(&mut e);
4276    for &id in scoped_ids {
4277        match pre.get(&id) {
4278            Some(&v) => {
4279                map.insert(id, v);
4280            }
4281            None => {
4282                map.remove(&id);
4283            }
4284        }
4285    }
4286    e
4287}
4288
4289thread_local! {
4290    /// The vector an assertion's probe runs from; see [`probe_matches`].
4291    static PROBE: std::cell::RefCell<Vec<State>> =
4292        const { std::cell::RefCell::new(Vec::new()) };
4293}
4294
4295/// Whether `inner` matches starting at token `at`, under `env`.
4296///
4297/// An assertion asks this once per state it filters, and at each position in a
4298/// window for the counting forms, so a scan runs it far more often than it runs
4299/// the assertion itself. Each run needs a vector holding the one state it
4300/// starts from, and building one at each was among the sites a sampled scan
4301/// allocates from.
4302///
4303/// The vector is carried instead, on the same argument that lets `best_at`
4304/// carry its own: `advance` takes one by value and gives one back, and what
4305/// comes back is read for emptiness and dropped here rather than escaping.
4306/// Taking it out leaves an empty vector behind, so an assertion nested inside
4307/// another gets a fresh one rather than the outer probe's states.
4308#[allow(clippy::too_many_arguments)]
4309fn probe_matches(
4310    inner: &Pattern,
4311    at: usize,
4312    env: &Env,
4313    input: &[u8],
4314    toks: &[Token],
4315    end: usize,
4316    absent: &HashSet<Vec<u8>>,
4317    regs: &[String],
4318    fields: Fields<'_>,
4319) -> bool {
4320    let mut probe = PROBE.with(|p| std::mem::take(&mut *p.borrow_mut()));
4321    probe.clear();
4322    probe.push(State { pos: at, env: env.clone(), rank: Rank::default(), hist: None });
4323    let mut out = advance(inner, input, toks, end, probe, absent, regs, fields);
4324    let hit = !out.is_empty();
4325    out.clear();
4326    PROBE.with(|p| *p.borrow_mut() = out);
4327    hit
4328}
4329
4330/// A zero-width sub-pattern assertion: keep a state when `inner` matches at
4331/// the position, without advancing it.
4332///
4333/// Looking ahead runs `inner` from the position and asks only whether any
4334/// state survives. Looking behind asks a different question - whether `inner`
4335/// matches with its end at the position, not its start - so it tries each
4336/// start in a window and requires the run to land exactly there. The window is
4337/// bounded by the sub-pattern's longest match, which is why an unbounded one
4338/// is rejected at parse time rather than scanning the whole prefix.
4339///
4340/// The probe's own bindings are discarded: the surviving state keeps the
4341/// registers and position it arrived with, so an assertion is a filter and
4342/// never a capture.
4343#[allow(clippy::too_many_arguments)]
4344fn assert_zero_width(
4345    inner: &Pattern,
4346    neg: bool,
4347    look: crate::ast::Look,
4348    input: &[u8],
4349    toks: &[Token],
4350    end: usize,
4351    states: Vec<State>,
4352    absent: &HashSet<Vec<u8>>,
4353    regs: &[String],
4354    fields: Fields<'_>,
4355) -> Vec<State> {
4356    use crate::ast::Look;
4357    let window = crate::nfa::bounded_max_len(inner).unwrap_or(0);
4358    states
4359        .into_iter()
4360        .filter(|s| {
4361            let p = skip_ws(toks, s.pos, end);
4362            let hit = match look {
4363                Look::Ahead => probe_matches(inner, p, &s.env, input, toks, end, absent, regs, fields),
4364                Look::Within { window, at_least, at_most } => {
4365                    // The next `window` significant tokens, this one first.
4366                    // Each is a start position the sub-pattern is tried at, so
4367                    // the question is how many of them it matches at rather
4368                    // than whether it matches exactly here.
4369                    let mut tried = 0usize;
4370                    let mut found = 0usize;
4371                    let mut j = p;
4372                    // One past the top is already a failure, so counting stops
4373                    // there; with no top, the first hit that meets the bottom
4374                    // settles it.
4375                    let enough = at_most.map_or(at_least, |hi| hi + 1);
4376                    while j < end && tried < window && found < enough {
4377                        if toks[j].kind != TokenKind::Whitespace {
4378                            tried += 1;
4379                            if probe_matches(
4380                                inner, j, &s.env, input, toks, end, absent, regs, fields,
4381                            ) {
4382                                found += 1;
4383                            }
4384                        }
4385                        j += 1;
4386                    }
4387                    found >= at_least && at_most.is_none_or(|hi| found <= hi)
4388                }
4389                Look::InGroup { at_least, at_most } => {
4390                    // The region is the group opening here, so the lexer's
4391                    // bracket pairing gives its extent and nothing in the
4392                    // pattern does. A position opening no group holds an empty
4393                    // region, whose count is zero under either polarity.
4394                    let mut interior = p..p;
4395                    if p < end
4396                        && matches!(toks[p].kind, TokenKind::Open(_))
4397                        && let Some(m) = toks[p].mate()
4398                        && m < end
4399                    {
4400                        interior = p + 1..m;
4401                    }
4402                    let close = interior.end;
4403                    let mut found = 0usize;
4404                    // One past the top is already a failure, so counting stops
4405                    // there; with no top, the first hit that meets the bottom
4406                    // settles it.
4407                    let enough = at_most.map_or(at_least, |hi| hi + 1);
4408                    for j in interior {
4409                        if found >= enough {
4410                            break;
4411                        }
4412                        if toks[j].kind == TokenKind::Whitespace {
4413                            continue;
4414                        }
4415                        // The region's close is the sub-pattern's end, so a
4416                        // counted match cannot run past the bracket bounding
4417                        // the region it is counted in.
4418                        if probe_matches(
4419                            inner, j, &s.env, input, toks, close, absent, regs, fields,
4420                        ) {
4421                            found += 1;
4422                        }
4423                    }
4424                    found >= at_least && at_most.is_none_or(|hi| found <= hi)
4425                }
4426                Look::Behind => {
4427                    // Every token index that could start a match ending at p.
4428                    // `window` counts significant tokens, and whitespace only
4429                    // widens the span, so scanning back twice that many token
4430                    // slots covers it.
4431                    let lo = p.saturating_sub(window.saturating_mul(2));
4432                    (lo..p).any(|j| {
4433                        if toks[j].kind == TokenKind::Whitespace {
4434                            return false;
4435                        }
4436                        // Carried like the other probes, but read rather than
4437                        // only tested: looking behind asks where the run ended
4438                        // and not merely whether it ran, so the states are
4439                        // needed and `probe_matches` cannot answer it.
4440                        let mut probe = PROBE.with(|b| std::mem::take(&mut *b.borrow_mut()));
4441                        probe.clear();
4442                        probe.push(State {
4443                            pos: j,
4444                            env: s.env.clone(),
4445                            rank: Rank::default(),
4446                            hist: None,
4447                        });
4448                        let mut out = advance(inner, input, toks, p, probe, absent, regs, fields);
4449                        let hit = out.iter().any(|r| skip_ws(toks, r.pos, p) == p);
4450                        out.clear();
4451                        PROBE.with(|b| *b.borrow_mut() = out);
4452                        hit
4453                    })
4454                }
4455            };
4456            hit != neg
4457        })
4458        .collect()
4459}
4460
4461fn guard(
4462    lit: &str,
4463    neg: bool,
4464    input: &[u8],
4465    toks: &[Token],
4466    end: usize,
4467    states: Vec<State>,
4468    absent: &HashSet<Vec<u8>>,
4469) -> Vec<State> {
4470    // A literal the prefilter proved is nowhere in the input resolves every
4471    // state at once with no positional scan: a positive guard fails (the
4472    // literal it requires cannot appear), a negative guard passes (the
4473    // literal it forbids is confirmed absent).
4474    if absent.contains(lit.as_bytes()) {
4475        return if neg { states } else { Vec::new() };
4476    }
4477    states
4478        .into_iter()
4479        .filter(|s| {
4480            let p = skip_ws(toks, s.pos, end);
4481            let from = if p < toks.len() { toks[p].start() } else { input.len() };
4482            // Positive guard keeps a state where the literal is present ahead;
4483            // negative guard keeps it where the literal is absent.
4484            crate::byte_simd::contains(&input[from..], lit.as_bytes()) != neg
4485        })
4486        .collect()
4487}
4488
4489/// Collapse states sharing a position and register environment, keeping the
4490/// preferred one.
4491///
4492/// Identity is `(pos, env)`, so two derivations that arrived the same way are
4493/// one state with one future; which of them is kept decides only the
4494/// preference carried forward, and [`cmp_rank`] picks it. Keeping whichever
4495/// arrived first instead would be right only while branches are walked in
4496/// order, and is wrong as soon as a greedy repetition reaches a position by
4497/// two paths of different length.
4498fn dedup(states: Vec<State>) -> Vec<State> {
4499    // A set of zero or one state has no duplicate to remove, so skip the
4500    // allocation. Bind and the atom-bind path produce one-state sets
4501    // constantly, so this removes a per-call allocation from the hot path of
4502    // every register-binding scan.
4503    if states.len() <= 1 {
4504        return states;
4505    }
4506    // The crate's own hash rather than SipHash, for the reason the single-pass
4507    // engine's register-dedup set uses it: a state's key carries its whole
4508    // register map, and this runs on every arm of every step. The map is an
4509    // index only - `out` holds the states in first-seen order - so which
4510    // bucket a state lands in cannot reach the answer.
4511    //
4512    // The map is built here and sized to this call. Holding one per thread and
4513    // clearing it instead was measurably worse: a hash map clears in the size
4514    // of its capacity rather than its length, so one pattern that briefly
4515    // reaches a wide set leaves every later two-state dedup clearing the whole
4516    // retained table.
4517    let mut at: std::collections::HashMap<State, usize, crate::fxhash::FxBuild> =
4518        std::collections::HashMap::with_capacity_and_hasher(
4519            states.len(),
4520            crate::fxhash::FxBuild::process(),
4521        );
4522    let mut out: Vec<State> = Vec::with_capacity(states.len());
4523    for s in states {
4524        match at.get(&s) {
4525            Some(&i) => {
4526                if cmp_rank(s.rank_slice(), out[i].rank_slice()) == std::cmp::Ordering::Less {
4527                    out[i] = s;
4528                }
4529            }
4530            None => {
4531                at.insert(s.clone(), out.len());
4532                out.push(s);
4533            }
4534        }
4535    }
4536    out
4537}
4538
4539/// Match the inner pattern only at the start of the k-th comma-
4540/// delimited field. `@k` anchors to the input's field structure, so a
4541/// state advances only when its position is that field's first token.
4542#[allow(clippy::too_many_arguments)]
4543fn field_match(
4544    k: usize,
4545    inner: &Pattern,
4546    input: &[u8],
4547    toks: &[Token],
4548    end: usize,
4549    states: Vec<State>,
4550    absent: &HashSet<Vec<u8>>,
4551    regs: &[String],
4552    fields: Fields<'_>,
4553) -> Vec<State> {
4554    let mut out = Vec::new();
4555    for s in states {
4556        let p = skip_ws(toks, s.pos, end);
4557        if is_field_start(end, p, k, fields) {
4558            let init = vec![State { pos: p, env: s.env, rank: s.rank, hist: s.hist }];
4559            out.extend(advance(inner, input, toks, end, init, absent, regs, fields));
4560        }
4561    }
4562    dedup(out)
4563}
4564
4565/// Whether token position `p` begins the k-th comma-delimited field. `@0` and
4566/// `@1` both name the first field.
4567fn is_field_start(end: usize, p: usize, k: usize, fields: Fields<'_>) -> bool {
4568    if p >= end {
4569        return false;
4570    }
4571    fields.field_starts.is_some_and(|f| f.get(p).copied() == Some(k.max(1) as u32))
4572}
4573
4574#[cfg(test)]
4575mod tests {
4576    use super::*;
4577    use crate::parser::parse;
4578
4579    /// Holding the registers inline must not widen a match, because every match
4580    /// of every pattern carries them and most patterns bind none. Two spans are
4581    /// sixteen bytes and the shared form's fat pointer is sixteen, so the two
4582    /// variants are the same width and the enum is the width of the `Vec` it
4583    /// replaced.
4584    #[test]
4585    fn holding_registers_inline_does_not_widen_a_match() {
4586        assert_eq!(
4587            size_of::<Regs>(),
4588            size_of::<Vec<Span>>(),
4589            "Regs is {} bytes against a Vec's {}",
4590            size_of::<Regs>(),
4591            size_of::<Vec<Span>>()
4592        );
4593        // The bindings under a repetition are one word, a null pointer for
4594        // every match of a pattern that binds none under one.
4595        assert_eq!(size_of::<Match>(), 64, "a match is {} bytes", size_of::<Match>());
4596    }
4597
4598    /// The two ways of holding registers answer alike, at every count either
4599    /// can hold, and a match compares by its registers and not by how it holds
4600    /// them.
4601    #[test]
4602    fn registers_read_the_same_inline_and_shared() {
4603        let spans: Vec<Span> =
4604            (0..8u32).map(|i| Span { start: i * 10, end: i * 10 + 4 }).collect();
4605        for n in 0..=spans.len() {
4606            let regs = Regs::from_slice(&spans[..n]);
4607            assert_eq!(regs.as_slice(), &spans[..n], "{n} registers read back");
4608            assert_eq!(regs.len(), n, "{n} registers counted");
4609            let shared = Regs::Shared(spans[..n].into());
4610            assert_eq!(regs, shared, "{n} registers compare alike however held");
4611            let held = match n {
4612                0 => matches!(regs, Regs::None),
4613                1..=INLINE_REGS => matches!(regs, Regs::Inline(..)),
4614                _ => matches!(regs, Regs::Shared(_)),
4615            };
4616            assert!(held, "{n} registers held the way its count calls for");
4617            // No registers is the commonest match in the crate and is built by
4618            // a path that does nothing else, so it must cost a discriminant
4619            // and not a zeroed array.
4620            assert!(matches!(Regs::none(), Regs::None), "no registers holds nothing");
4621            // Writing through the shared form must not reach another holder of
4622            // the same spans.
4623            let mut mine = shared.clone();
4624            let theirs = Regs::Shared(spans[..n].into());
4625            for sp in mine.as_mut_slice() {
4626                sp.start += 1;
4627            }
4628            assert_eq!(theirs.as_slice(), &spans[..n], "{n} registers left alone");
4629            assert!(
4630                mine.iter().zip(&spans[..n]).all(|(a, b)| a.start == b.start + 1),
4631                "{n} registers written through"
4632            );
4633        }
4634    }
4635
4636    /// A scan with its captures resolved, so a test reads a match the way a
4637    /// caller wanting the registers does.
4638    fn run(pattern: &str, input: &str) -> Vec<Match> {
4639        let p = parse(pattern).unwrap();
4640        let spans = scan(&p, input.as_bytes());
4641        captures(&p, input.as_bytes(), &spans)
4642    }
4643
4644    /// A binding constrains nothing, so the anchored ladders answer a pattern
4645    /// that binds exactly as they answer its unbound twin, and both agree with
4646    /// the scan. The rung that makes them fast must not make them differ.
4647    #[test]
4648    fn the_anchored_ladders_answer_a_binding_pattern_as_they_answer_its_twin() {
4649        let mut text = String::new();
4650        for i in 0..300u32 {
4651            text.push_str(&format!("let value_{i} = {} ; call_{i}(alpha, beta) ;\n", i * 37));
4652        }
4653        let input = text.as_bytes();
4654        // The last pair matches nowhere - the corpus holds no `!` - so the
4655        // ladders are held to a verdict of no match as well as to a span.
4656        for (bound, bare) in [
4657            ("\\W:name \"=\"", "\\W \"=\""),
4658            ("\"let\" \\W:v \"=\"", "\"let\" \\W \"=\""),
4659            ("\\W:w", "\\W"),
4660            ("\\N:n", "\\N"),
4661            ("\\W:w \"!\"", "\\W \"!\""),
4662        ] {
4663            let (b, u) = (parse(bound).expect(bound), parse(bare).expect(bare));
4664            let spans = scan(&u, input);
4665            assert_eq!(scan(&b, input), spans, "scan {bound}");
4666            assert_eq!(is_match(&b, input), !spans.is_empty(), "is_match {bound}");
4667            assert_eq!(is_match(&u, input), !spans.is_empty(), "is_match {bare}");
4668            let first = spans.first().copied();
4669            assert_eq!(routed_first(&b, input).flatten(), first, "first {bound}");
4670            assert_eq!(routed_first(&u, input).flatten(), first, "first {bare}");
4671            // From past the first match, so the ladder is answering about the
4672            // second rather than repeating the first.
4673            let at = first.map_or(0, |s| s.end());
4674            let next = spans.iter().find(|s| s.start() >= at).copied();
4675            assert_eq!(routed_first_at(&b, input, at).flatten(), next, "at {bound}");
4676            assert_eq!(routed_first_at(&u, input, at).flatten(), next, "at {bare}");
4677        }
4678    }
4679
4680    /// The soonest-ending match of a flat shape is its leftmost, so the
4681    /// positional half of the ladder may take the windows - and must report
4682    /// what the whole scan's first match is when it does.
4683    #[test]
4684    fn the_positional_ladder_takes_the_windows_and_answers_what_the_scan_does() {
4685        let mut text = String::new();
4686        for i in 0..300u32 {
4687            text.push_str(&format!("call_{i}(alpha) ; let value_{i} = {i} ;\n"));
4688        }
4689        let input = text.as_bytes();
4690        // Each opens with a literal, so the windows answer them, and none has a
4691        // byte route of its own: this is the rung under test.
4692        for src in ["\"let\" \\W \"=\"", "\"let\" \\W:v \"=\"", "\"let\" \\W"] {
4693            let p = parse(src).expect(src);
4694            let first = scan(&p, input).first().copied();
4695            assert!(first.is_some(), "{src} matches the corpus");
4696            assert_eq!(routed_first_positional(&p, input).flatten(), first, "{src}");
4697            assert_eq!(crate::shortest_match(&p, input), first.map(|s| s.end()), "{src}");
4698        }
4699    }
4700
4701    #[test]
4702    fn a_pattern_names_the_reading_its_empty_loops_take() {
4703        use crate::parser::parse_with_empty_loop;
4704        let spans = |ms: Vec<Span>| -> Vec<(usize, usize)> {
4705            ms.iter().map(|m| (m.start(), m.end())).collect()
4706        };
4707        let input = b"42 baz bar ";
4708
4709        // The body's highest-priority branch is nullable and its sibling
4710        // consumes, which is the whole of where the two families differ. The
4711        // crate's reading drops the thread that matched empty, so the branch
4712        // that consumed wins and the loop runs on. The backtracking reading
4713        // takes that iteration and leaves the loop, so the match ends earlier
4714        // and a second one follows it.
4715        for src in [r"(\N? | \W)+ .", r"(\N? | \W)* ."] {
4716            let p = parse(src).expect("parses");
4717            assert_eq!(
4718                spans(scan_with_empty_loop(&p, input, EmptyLoop::Thompson)),
4719                vec![(0, 10)],
4720                "{src} under the crate's reading"
4721            );
4722            assert_eq!(
4723                spans(scan_with_empty_loop(&p, input, EmptyLoop::Perl)),
4724                vec![(0, 6), (7, 10)],
4725                "{src} under the backtracking reading"
4726            );
4727            // Naming no reading is naming the crate's.
4728            assert_eq!(spans(scan(&p, input)), vec![(0, 10)], "{src} unnamed");
4729        }
4730
4731        // Two controls. Putting the consuming branch first leaves no choice
4732        // for a reading to make, and a body with no alternation offers no
4733        // branch to prefer; both read the same either way.
4734        for src in [r"(\W | \N?)+ .", r"(\N?)+ \W"] {
4735            let p = parse(src).expect("parses");
4736            assert_eq!(
4737                spans(scan_with_empty_loop(&p, input, EmptyLoop::Thompson)),
4738                spans(scan_with_empty_loop(&p, input, EmptyLoop::Perl)),
4739                "{src} reads the same either way"
4740            );
4741        }
4742
4743        // A pattern with no nullable loop names a reading and keeps the
4744        // single-pass engine, because there is nothing for the readings to
4745        // disagree about.
4746        let flat = parse(r"\W+ \N").expect("parses");
4747        assert!(!crate::nfa::empty_loop_needs_set_engine(&flat));
4748
4749        // The directive is read off the front and does not enter the tree.
4750        let (p, mode) = parse_with_empty_loop(r"(?empty:perl)(\N? | \W)+ .").expect("parses");
4751        assert_eq!(mode, EmptyLoop::Perl);
4752        assert_eq!(p, parse(r"(\N? | \W)+ .").expect("parses"));
4753        assert_eq!(spans(scan_with_empty_loop(&p, input, mode)), vec![(0, 6), (7, 10)]);
4754
4755        let (_, mode) = parse_with_empty_loop(r"\W+").expect("parses");
4756        assert_eq!(mode, EmptyLoop::Thompson, "the crate's reading is the default");
4757
4758        let e = parse_with_empty_loop(r"(?empty:pcre)\W+").expect_err("names no reading");
4759        assert!(e.msg.contains("thompson"), "the error names the readings: {}", e.msg);
4760    }
4761
4762    /// Lines binding values to a few recurring keys: latencies near a
4763    /// hundred, sizes near a million, and one latency planted four orders
4764    /// above its kind.
4765    fn log_with_an_outlier() -> (String, usize) {
4766        let mut s = String::new();
4767        let mut planted = 0;
4768        for i in 0..240 {
4769            match i % 3 {
4770                0 => {
4771                    let v = if i == 150 { 5_000_000 } else { 90 + (i * 37) % 21 };
4772                    if i == 150 {
4773                        planted = s.len() + format!("svc_{} latency = ", i % 7).len();
4774                    }
4775                    // The value has no unit: a unit symbol one space
4776                    // after a number is that quantity's, and the number then
4777                    // belongs to a quantity token rather than to `\N`.
4778                    s.push_str(&format!("svc_{} latency = {v} ;\n", i % 7));
4779                }
4780                1 => s.push_str(&format!("svc_{} size = {} ;\n", i % 7, 1_000_000 + i)),
4781                _ => s.push_str(&format!("state: {} ;\n", if i % 2 == 0 { "ready" } else { "busy" })),
4782            }
4783        }
4784        (s, planted)
4785    }
4786
4787    #[test]
4788    fn a_relative_magnitude_predicate_reads_its_threshold_from_the_window() {
4789        let (log, planted) = log_with_an_outlier();
4790        // Every size is six orders; every latency two; words one or two. A
4791        // window mean is between, so the planted latency at six and a
4792        // half orders clears it by two and the sizes do too - the window
4793        // knows neighborhoods, not keys.
4794        let m = run("\\N{>+2}", &log);
4795        assert!(m.iter().any(|m| m.start == planted), "the planted latency is found: {m:?}");
4796        assert!(m.iter().all(|m| log[m.start..m.end].len() >= 7), "only the large numbers: {m:?}");
4797        // Two standard deviations above the window, the same reading in the
4798        // window's own units.
4799        let m = run("\\N{>+2s}", &log);
4800        assert!(m.iter().any(|m| m.start == planted), "the planted latency clears two sigmas: {m:?}");
4801        // An absolute threshold still takes its route and compares the
4802        // magnitude itself.
4803        let m = run("\\N{mag>6}", &log);
4804        assert!(m.iter().all(|m| log[m.start..m.end].len() >= 7) && m.len() > 70, "{}", m.len());
4805    }
4806
4807    #[test]
4808    fn a_keyed_relative_predicate_reads_the_history_of_that_keys_values() {
4809        let (log, planted) = log_with_an_outlier();
4810        // The baseline is the values bound to earlier `latency` keys, so the
4811        // sizes, which are large but bound to another key, are not outliers
4812        // and the planted latency is the one match.
4813        let m = run("\"latency\":k \"=\" \\N{>+1:k}", &log);
4814        assert_eq!(m.len(), 1, "{m:?}");
4815        assert!(log[m[0].start..m[0].end].starts_with("latency = 5000000"), "{m:?}");
4816        assert_eq!(m[0].end, planted + "5000000".len(), "the match ends at the planted value");
4817        // The first occurrence of a key has no history, so nothing at all is
4818        // an outlier against it.
4819        let m = run("\"size\":k \"=\" \\N{>+0:k}", &log);
4820        assert!(m.iter().all(|m| !log[..m.start].is_empty()), "the first size never matches: {m:?}");
4821        assert!(m.len() > 30, "later sizes are at the mean, so `>+0` holds where a value repeats: {}", m.len());
4822    }
4823
4824    #[test]
4825    fn a_phase_anchor_selects_a_column_of_a_periodic_record() {
4826        // Six significant tokens a row, no header: word, comma, number,
4827        // comma, word, semicolon.
4828        let mut rows = String::new();
4829        for i in 0..200 {
4830            rows.push_str(&format!("r{i} , {} , x{} ;\n", i * 3, i % 4));
4831        }
4832        let toks = crate::lexer::lex(rows.as_bytes());
4833        assert_eq!(crate::context::record_period(&toks, rows.as_bytes()), Some(6), "the row is the period");
4834        let m = run("@phase:2 \\N", &rows);
4835        assert_eq!(m.len(), 200, "every row's number is at phase two: {}", m.len());
4836        assert!(run("@phase:0 \\N", &rows).is_empty(), "no number is at phase zero");
4837        assert_eq!(run("@phase:4 \\W", &rows).len(), 200, "the second word is at phase four");
4838    }
4839
4840    #[test]
4841    fn a_declared_shape_becomes_a_token_the_default_lexer_would_split() {
4842        use crate::custom::{Precedence, ShapeSet};
4843        let input = b"ticket ABC-1234 done";
4844        // The default lexer splits this into a word, a hyphen and a number.
4845        let split = crate::lexer::lex(input);
4846        assert!(
4847            split.iter().filter(|t| t.is_significant()).count() > 3,
4848            "the default lexer splits ABC-1234"
4849        );
4850
4851        let mut shapes = ShapeSet::new();
4852        shapes.declare("order = `[A-Z]{3}-[0-9]{4}`", Precedence::Before).expect("declares");
4853        let pat = crate::parser::parse_with_shapes("\\{order}", &shapes).expect("parses");
4854        let m = crate::engine::scan_with_shapes(&pat, input, &shapes);
4855        assert_eq!(m.len(), 1, "the shape matches once");
4856        assert_eq!(&input[m[0].range()], b"ABC-1234");
4857    }
4858
4859    #[test]
4860    fn both_engines_agree_on_custom_kinds() {
4861        use crate::custom::{Precedence, ShapeSet};
4862        let mut shapes = ShapeSet::new();
4863        shapes.declare("order = `[A-Z]{3}-[0-9]{4}`", Precedence::Before).expect("declares");
4864        shapes.declare("level = `(DEBUG|INFO|WARN|ERROR)`", Precedence::Before).expect("declares");
4865
4866        let inputs: &[&str] = &[
4867            "",
4868            "ABC-1234",
4869            "ticket ABC-1234 done",
4870            "ERROR ABC-1234 and WARN XYZ-9999 here",
4871            "no shapes at all in this line",
4872            "ABC-1234 ABC-1234 ABC-1234",
4873            "INFO 12 ABC-1234 (nested DEF-5678) tail",
4874        ];
4875        let patterns: &[&str] = &[
4876            "\\{order}",
4877            "\\{level}",
4878            "\\{level} \\{order}",
4879            "\\{order}+",
4880            "\\{order} | \\{level}",
4881            "\\{level} .* \\{order}",
4882            "\\W \\{order}",
4883            "\\{order}:x =x",
4884        ];
4885        for pat_src in patterns {
4886            let pat = crate::parser::parse_with_shapes(pat_src, &shapes).expect("parses");
4887            for inp in inputs {
4888                let bytes = inp.as_bytes();
4889                // One lex, both engines, so the comparison is of matching and
4890                // not of two different token streams.
4891                let toks = crate::lexer::lex_with_shapes(bytes, &[], &shapes, 0);
4892                let set = scan_tokens_from(&pat, bytes, &toks, 0);
4893                if let Some(nfa) = crate::nfa::scan_nfa_over(&pat, bytes, &toks) {
4894                    assert_eq!(nfa, set, "engines differ on {pat_src:?} over {inp:?}");
4895                }
4896                // The public entry must agree with the set engine too.
4897                assert_eq!(
4898                    crate::engine::scan_with_shapes(&pat, bytes, &shapes),
4899                    set,
4900                    "scan_with_shapes differs on {pat_src:?} over {inp:?}"
4901                );
4902            }
4903        }
4904    }
4905
4906    #[test]
4907    fn the_device_gate_declines_a_custom_kind() {
4908        use crate::custom::{Precedence, ShapeSet};
4909        let mut shapes = ShapeSet::new();
4910        shapes.declare("order = `[A-Z]{3}-[0-9]{4}`", Precedence::Before).expect("declares");
4911        let pat = crate::parser::parse_with_shapes("\\{order}", &shapes).expect("parses");
4912        // The device lexes for itself with no shape set, so its stream holds
4913        // no such token; the gate must refuse rather than return empty.
4914        assert!(!crate::gpu::gpu_eligible(&pat));
4915        // A pattern of only built-in kinds is still eligible.
4916        assert!(crate::gpu::gpu_eligible(&parse("\\W \\N").expect("parses")));
4917    }
4918
4919    #[test]
4920    fn a_shape_atom_is_unknown_without_its_set() {
4921        // The same pattern text with no shapes declared names nothing.
4922        assert!(parse("\\{order}").is_err());
4923    }
4924
4925    /// A state's width, pinned, because the sweep carries millions of them.
4926    ///
4927    /// A scan holds states in vectors that a fan-out grows, so every byte here
4928    /// is multiplied by the state set and copied again at each growth. It is not
4929    /// a number to change without knowing: widening the rank from a thin
4930    /// pointer to a slice's fat one cost eight bytes and was worth it for
4931    /// halving an allocation, but that was weighed rather than discovered
4932    /// afterward, and a later change should get to weigh it too.
4933    ///
4934    /// The parts are asserted beside the whole so a failure says which field
4935    /// moved rather than only that something did.
4936    #[test]
4937    fn a_state_is_the_width_the_sweep_was_tuned_for() {
4938        use std::mem::size_of;
4939        // Sixteen, not twenty-four: the heap arm's pointer cannot be null, and
4940        // that niche carries the discriminant, so the inline arm's seven bytes
4941        // and length fit in the room the fat pointer already took. Holding the
4942        // path inline therefore costs no width at all, which two readings of
4943        // this layout in prose got wrong in both directions before the compiler
4944        // was asked.
4945        assert_eq!(size_of::<Rank>(), 16, "the preference path: seven bytes inline, or a slice");
4946        assert_eq!(size_of::<Env>(), 8, "the register map, a thin pointer behind an Rc");
4947        assert_eq!(size_of::<Hist>(), 8, "the binding list, a thin pointer behind an Rc");
4948        assert_eq!(size_of::<State>(), 40, "a position and those three");
4949    }
4950
4951    #[test]
4952    fn shape_precedence_decides_an_overlap_with_a_builtin() {
4953        use crate::custom::{Precedence, ShapeSet};
4954        let input = b"10.0.0.1";
4955        let mut before = ShapeSet::new();
4956        before.declare("quad = `[0-9.]{8}`", Precedence::Before).expect("declares");
4957        let toks = crate::lexer::lex_with_shapes(input, &[], &before, 0);
4958        assert_eq!(toks[0].kind, crate::token::TokenKind::Custom(0), "Before wins the overlap");
4959
4960        let mut after = ShapeSet::new();
4961        after.declare("quad = `[0-9.]{8}`", Precedence::After).expect("declares");
4962        let toks = crate::lexer::lex_with_shapes(input, &[], &after, 0);
4963        assert_eq!(toks[0].kind, crate::token::TokenKind::Ip, "After leaves the IP reading");
4964    }
4965
4966    #[test]
4967    fn an_orbit_scope_makes_literals_compare_under_a_symmetry() {
4968        // Identity is the default: byte equality.
4969        assert_eq!(run("\"Cat\"", "Cat cat CAT").len(), 1);
4970        // Case: the whole orbit of the literal matches.
4971        assert_eq!(run("(?orbit:case \"Cat\")", "Cat cat CAT").len(), 3);
4972        // The scope reaches every literal inside it, at any depth.
4973        assert_eq!(run("(?orbit:case (\"cat\" | \"dog\"))", "CAT Dog bird").len(), 2);
4974        // And leaves atoms that are not literals alone.
4975        assert_eq!(run("(?orbit:case \\N)", "12 ab").len(), 1);
4976    }
4977
4978    #[test]
4979    fn an_orbit_scope_reaches_into_a_token_class() {
4980        assert_eq!(run("(?orbit:case [\"cat\" \"dog\"])", "CAT Dog bird").len(), 2);
4981    }
4982
4983    #[test]
4984    fn a_bad_orbit_modifier_is_a_parse_error() {
4985        assert!(parse("(?bogus:case \"x\")").is_err());
4986        assert!(parse("(?orbit:nosuch \"x\")").is_err());
4987        assert!(parse("(?orbit \"x\")").is_err());
4988        // A plain group is untouched by the modifier syntax.
4989        assert!(parse("(\"x\" | \"y\")").is_ok());
4990    }
4991
4992    #[test]
4993    fn lookahead_is_zero_width_and_both_polarities_work() {
4994        // `~(P)` consumes nothing: the following atom matches from the same
4995        // place, so the reported span is the word alone.
4996        let m = run("~(\\W) \\W", "cat 12 dog");
4997        assert_eq!(m.len(), 2);
4998        assert_eq!(&"cat 12 dog"[m[0].start..m[0].end], "cat");
4999        // A word that is followed by a number.
5000        let followed: Vec<_> = run("\\W ~(\\N)", "cat 12 dog 34 end")
5001            .iter()
5002            .map(|m| "cat 12 dog 34 end"[m.start..m.end].to_string())
5003            .collect();
5004        assert_eq!(followed, vec!["cat", "dog"]);
5005        // Negative: a word not followed by a number.
5006        let unfollowed: Vec<_> = run("\\W !~(\\N)", "cat 12 dog 34 end")
5007            .iter()
5008            .map(|m| "cat 12 dog 34 end"[m.start..m.end].to_string())
5009            .collect();
5010        assert_eq!(unfollowed, vec!["end"]);
5011    }
5012
5013    #[test]
5014    fn lookbehind_reads_the_direction_the_matcher_never_exposed() {
5015        // A number preceded by a word.
5016        let after_word: Vec<_> = run("~<(\\W) \\N", "cat 12 34 dog 56")
5017            .iter()
5018            .map(|m| "cat 12 34 dog 56"[m.start..m.end].to_string())
5019            .collect();
5020        assert_eq!(after_word, vec!["12", "56"]);
5021        // A number not preceded by a word.
5022        let not_after_word: Vec<_> = run("!~<(\\W) \\N", "cat 12 34 dog 56")
5023            .iter()
5024            .map(|m| "cat 12 34 dog 56"[m.start..m.end].to_string())
5025            .collect();
5026        assert_eq!(not_after_word, vec!["34"]);
5027    }
5028
5029    #[test]
5030    fn an_unbounded_backward_assertion_is_a_parse_error() {
5031        assert!(parse("~<(\\W*) \\N").is_err());
5032        assert!(parse("~<(\\W+) \\N").is_err());
5033        assert!(parse("~<(\\W{2,}) \\N").is_err());
5034        assert!(parse("~<(\\W{2,3}) \\N").is_ok());
5035    }
5036
5037    #[test]
5038    fn the_literal_guard_still_works_and_stays_forward() {
5039        // The guard reads the window after the atom it follows, so `END`
5040        // itself does not match: nothing beyond it contains `END`.
5041        let present: Vec<_> = run(". ~\"END\"", "a b END c")
5042            .iter()
5043            .map(|m| "a b END c"[m.start..m.end].to_string())
5044            .collect();
5045        assert_eq!(present, vec!["a", "b"]);
5046        let absent: Vec<_> = run(". !~\"END\"", "a b END c")
5047            .iter()
5048            .map(|m| "a b END c"[m.start..m.end].to_string())
5049            .collect();
5050        assert_eq!(absent, vec!["END", "c"]);
5051        // The literal form has no backward reading.
5052        assert!(parse("~<\"END\"").is_err());
5053    }
5054
5055    #[test]
5056    fn a_proximity_window_asks_within_how_many_tokens() {
5057        // Distance in tokens, which is the unit a token stream has. The
5058        // window counts significant tokens from the position outward, the
5059        // one under test counted first.
5060        // The window opens at the position the assertion holds at, which is
5061        // the token after the word the pattern consumed. So a word matches
5062        // when END is among the next k tokens, not counting itself.
5063        let hay = "alpha one two three END beta";
5064        let near: Vec<_> = run("\\W ~>2(\"END\")", hay)
5065            .iter()
5066            .map(|m| hay[m.start..m.end].to_string())
5067            .collect();
5068        assert_eq!(near, vec!["two", "three"], "END is within two tokens after each");
5069
5070        let wider: Vec<_> = run("\\W ~>3(\"END\")", hay)
5071            .iter()
5072            .map(|m| hay[m.start..m.end].to_string())
5073            .collect();
5074        assert_eq!(wider, vec!["one", "two", "three"], "one more token of reach");
5075    }
5076
5077    #[test]
5078    fn a_bounded_gap_between_two_tokens_spans_both_of_them() {
5079        // The subsequence question - these two in this order, anything
5080        // between, within so many tokens - asked by a bounded repeat of any
5081        // token. The match covers both ends and the gap, where a proximity
5082        // assertion consumes nothing and covers only the token it is on.
5083        let hay = "alpha p q beta gamma alpha r s t u beta";
5084        let near: Vec<_> = run("\"alpha\" .{0,2} \"beta\"", hay)
5085            .iter()
5086            .map(|m| hay[m.start..m.end].to_string())
5087            .collect();
5088        assert_eq!(near, vec!["alpha p q beta"], "the far pair has four tokens between");
5089
5090        let wider: Vec<_> = run("\"alpha\" .{0,4} \"beta\"", hay)
5091            .iter()
5092            .map(|m| hay[m.start..m.end].to_string())
5093            .collect();
5094        assert_eq!(wider.len(), 2, "both pairs reach at four: {wider:?}");
5095    }
5096
5097    #[test]
5098    fn a_proximity_window_and_its_negation_partition_the_matches() {
5099        // Every token either has the target in its window or does not, so the
5100        // two readings together are what the bare pattern matches, with no
5101        // token in both.
5102        let hay = "a b c END d e f";
5103        let all = run("\\W", hay).len();
5104        let inside = run("\\W ~>2(\"END\")", hay).len();
5105        let outside = run("\\W !~>2(\"END\")", hay).len();
5106        assert_eq!(inside + outside, all, "{inside} within and {outside} not, of {all}");
5107        assert!(inside > 0 && outside > 0, "the corpus must exercise both sides");
5108    }
5109
5110    #[test]
5111    fn a_window_can_be_asked_how_many_and_not_only_whether() {
5112        // Counting occurrences nearby is not a regular property, so no
5113        // regular expression asks it. Six words, then three numbers.
5114        // Each window opens on the token after the word, so e sees f 1 2 3.
5115        let hay = "a b c d e f 1 2 3";
5116        let three: Vec<_> = run("\\W ~>4{3,}(\\N)", hay)
5117            .iter()
5118            .map(|m| hay[m.start..m.end].to_string())
5119            .collect();
5120        assert_eq!(three, vec!["e", "f"], "all three numbers are within four of each");
5121
5122        let two: Vec<_> = run("\\W ~>4{2,}(\\N)", hay)
5123            .iter()
5124            .map(|m| hay[m.start..m.end].to_string())
5125            .collect();
5126        assert_eq!(two, vec!["d", "e", "f"], "d reaches two of them");
5127
5128        // A top as well as a bottom: at most one number in the window.
5129        let sparse: Vec<_> = run("\\W ~>4{0,1}(\\N)", hay)
5130            .iter()
5131            .map(|m| hay[m.start..m.end].to_string())
5132            .collect();
5133        assert_eq!(sparse, vec!["a", "b", "c"], "d is the first to see two");
5134    }
5135
5136    #[test]
5137    fn a_count_over_a_region_is_bounded_by_the_bracket_not_by_a_distance() {
5138        // "at least three numbers inside this group". The region is the group
5139        // opening at the position, so its extent is the input's and not a
5140        // number the pattern carries.
5141        let hay = "f(1, 2, 3) g(4, 5) h(6, 7, 8, 9)";
5142        let full: Vec<_> = run("\\W ~#{3,}(\\N) \\B", hay)
5143            .iter()
5144            .map(|m| hay[m.start..m.end].to_string())
5145            .collect();
5146        assert_eq!(full, vec!["f(1, 2, 3)", "h(6, 7, 8, 9)"], "g holds two");
5147
5148        // The same count over a window instead of a region, on an input where
5149        // the two disagree: the group holds two numbers and three more follow
5150        // it. A window reaches past the bracket and a region does not, which is
5151        // the whole of the difference between the two forms.
5152        let spill = "f(1, 2) 3 4 5";
5153        let by_region: Vec<_> = run("\\W ~#{3,}(\\N) \\B", spill)
5154            .iter()
5155            .map(|m| spill[m.start..m.end].to_string())
5156            .collect();
5157        assert!(by_region.is_empty(), "the region stops at `)`: {by_region:?}");
5158
5159        let by_window: Vec<_> = run("\\W ~>9{3,}(\\N) \\B", spill)
5160            .iter()
5161            .map(|m| spill[m.start..m.end].to_string())
5162            .collect();
5163        assert_eq!(by_window, vec!["f(1, 2)"], "nine tokens of reach cross the bracket");
5164    }
5165
5166    #[test]
5167    fn a_region_reaches_its_own_close_through_nesting() {
5168        // What puts this past a regular language: finding the region's end
5169        // means counting brackets. Stopping at the first `)` would read f's
5170        // region as `a(1, 2` and count two, so this input separates a matcher
5171        // that counts brackets from one that cannot.
5172        let hay = "f(a(1, 2), 3) g(b(1), 2)";
5173        let three: Vec<_> = run("\\W ~#{3,}(\\N) \\B", hay)
5174            .iter()
5175            .map(|m| hay[m.start..m.end].to_string())
5176            .collect();
5177        assert_eq!(three, vec!["f(a(1, 2), 3)"], "only f's region holds three");
5178
5179        // Nested tokens are inside the region, so the inner group's numbers
5180        // count toward the outer group's total.
5181        let two: Vec<_> = run("\\W ~#{2}(\\N) \\B", hay)
5182            .iter()
5183            .map(|m| hay[m.start..m.end].to_string())
5184            .collect();
5185        assert_eq!(two, vec!["a(1, 2)", "g(b(1), 2)"], "exactly two, counting through nesting");
5186    }
5187
5188    #[test]
5189    fn a_region_count_and_its_negation_partition_every_position() {
5190        // A position opening no group has an empty region, so its count is
5191        // zero rather than undefined. That is what keeps the two polarities
5192        // complements everywhere instead of only where a bracket is.
5193        let hay = "f(1, 2) x y g(3)";
5194        let all = run("\\W", hay).len();
5195        let inside = run("\\W ~#{1,}(\\N)", hay).len();
5196        let outside = run("\\W !~#{1,}(\\N)", hay).len();
5197        assert_eq!(inside + outside, all, "{inside} over a region and {outside} not, of {all}");
5198        assert_eq!(inside, 2, "f and g open a group holding a number");
5199        assert_eq!(outside, 2, "x and y open no group at all");
5200    }
5201
5202    #[test]
5203    fn a_region_count_satisfied_by_absence_does_not_make_its_literal_required() {
5204        // The same shortcut the window form can be wrong about. A region
5205        // asking for at most one of something is satisfied by none of it, so
5206        // its literal is not required, and treating it as required would
5207        // refuse an input, unscanned, that matches.
5208        let hay = "f(alpha) g(beta)";
5209        let found: Vec<_> = run("\\W ~#{0,1}(\"zzzqqq\") \\B", hay)
5210            .iter()
5211            .map(|m| hay[m.start..m.end].to_string())
5212            .collect();
5213        assert_eq!(found, vec!["f(alpha)", "g(beta)"], "every region holds at most one: none");
5214        assert!(run("\\W ~#{1,}(\"zzzqqq\") \\B", hay).is_empty(), "a floor above zero needs it");
5215
5216        let none = parse("\\W ~#{0,1}(\"zzzqqq\")").expect("parses");
5217        let some = parse("\\W ~#{1,}(\"zzzqqq\")").expect("parses");
5218        assert!(!crate::prefilter::requires_absent(&none, hay.as_bytes()));
5219        assert!(crate::prefilter::requires_absent(&some, hay.as_bytes()));
5220    }
5221
5222    #[test]
5223    fn a_region_count_keeps_the_pattern_off_the_prefix_path() {
5224        // A cut inside the group leaves the opening token with no mate, which
5225        // reads as an empty region and a count of zero - no match reported on
5226        // an input that has one. No token count describes the reach, so there
5227        // is no reserve to hold and the pattern declines the prefix instead.
5228        let region = parse("\\W ~#{1,}(\\N)").expect("parses");
5229        assert!(region.has_assert(), "the reach is the input's, not the pattern's");
5230        assert_eq!(region.widest_forward_window(), 0, "no token count describes it");
5231        assert!(!crate::prefilter::settles_from_a_prefix(&region));
5232
5233        // Whatever route it takes, the answer is the scan's answer.
5234        let mut hay = String::new();
5235        for i in 0..40_000 {
5236            hay.push_str(&format!("key_{i} : {i} ;\n"));
5237        }
5238        hay.push_str("alpha ( 7 ) ;\n");
5239        assert_eq!(
5240            crate::find(&region, hay.as_bytes()),
5241            crate::scan(&region, hay.as_bytes()).first().copied()
5242        );
5243    }
5244
5245    #[test]
5246    fn a_counting_selector_refuses_what_it_cannot_mean() {
5247        assert!(parse("\\W ~#{3,2}(\\N)").is_err(), "a top below its bottom");
5248        assert!(parse("\\W ~#{,2}(\\N)").is_err(), "the bottom is not optional");
5249
5250        // A bare literal is the content guard, which asks whether the literal
5251        // is anywhere ahead - a wider question than either selector narrowed
5252        // to, so it is refused rather than silently answered.
5253        assert!(parse("\\W ~#{2,}\"END\"").is_err(), "the region form needs parentheses");
5254        assert!(parse("\\W ~>3\"END\"").is_err(), "the window form needs parentheses");
5255        assert!(parse("\\W ~\"END\"").is_ok(), "the plain guard is still the literal form");
5256    }
5257
5258    #[test]
5259    fn a_prefix_does_not_settle_a_pattern_that_reads_past_its_match() {
5260        // A prefix truncates the input, so anything deciding a match from
5261        // outside the match's own span can be decided against a stream that
5262        // is not there. The reserve covers a bounded assertion's reach; an
5263        // unbounded one keeps the pattern off the prefix path entirely.
5264        let bounded = parse("\\W ~>3(\\N)").expect("parses");
5265        let unbounded = parse("\\W ~(\\N \\N)").expect("parses");
5266        let guarded = parse("\\W ~\"END\"").expect("parses");
5267        assert!(crate::prefilter::settles_from_a_prefix(&bounded));
5268        assert!(!crate::prefilter::settles_from_a_prefix(&unbounded));
5269        assert!(!crate::prefilter::settles_from_a_prefix(&guarded));
5270
5271        // The reserve is the match's own length plus the assertion's reach,
5272        // so a cut cannot be inside what the assertion is reading.
5273        assert_eq!(bounded.widest_forward_window(), 4, "three positions and a one-token inner");
5274        assert_eq!(unbounded.widest_forward_window(), 0, "no bounded window to reserve for");
5275
5276        // Whatever route it takes, the answer is the scan's answer.
5277        let mut hay = String::new();
5278        for i in 0..40_000 {
5279            hay.push_str(&format!("key_{i} : {i} ;\n"));
5280        }
5281        hay.push_str("alpha 7 ;\n");
5282        for p in [&bounded, &unbounded, &guarded] {
5283            assert_eq!(crate::find(p, hay.as_bytes()), crate::scan(p, hay.as_bytes()).first().copied());
5284        }
5285    }
5286
5287    #[test]
5288    fn a_count_satisfied_by_absence_does_not_make_its_literal_required() {
5289        // The prefilter refuses an input that cannot hold a literal every
5290        // match needs. A window asking for at most one of something is
5291        // satisfied by none of it, so its literal is not one of those - and
5292        // treating it as one would refuse an input that matches, which is the
5293        // false negative the prefilter exists never to produce.
5294        let hay = "alpha beta gamma delta";
5295        let found = run("\\W ~>4{0,1}(\"zzzqqq\")", hay);
5296        assert_eq!(found.len(), 4, "every word has at most one zzzqqq nearby: none");
5297
5298        // A floor above zero does require it, and the input does not hold it.
5299        assert!(run("\\W ~>4{1,}(\"zzzqqq\")", hay).is_empty());
5300
5301        // The same question put to the prefilter directly, since that is
5302        // where the refusal would happen.
5303        let none = parse("\\W ~>4{0,1}(\"zzzqqq\")").expect("parses");
5304        let some = parse("\\W ~>4{1,}(\"zzzqqq\")").expect("parses");
5305        assert!(!crate::prefilter::requires_absent(&none, hay.as_bytes()));
5306        assert!(crate::prefilter::requires_absent(&some, hay.as_bytes()));
5307    }
5308
5309    #[test]
5310    fn a_count_that_nothing_can_satisfy_is_refused() {
5311        assert!(parse("\\W ~>4{3,2}(\\N)").is_err(), "a top below its bottom");
5312        assert!(parse("\\W ~>2{5,}(\\N)").is_err(), "more occurrences than the window holds");
5313        assert!(parse("\\W ~>4{,2}(\\N)").is_err(), "the bottom is not optional");
5314    }
5315
5316    #[test]
5317    fn a_window_of_zero_tokens_is_refused_rather_than_matched() {
5318        // A window that can hold nothing is a pattern that can never be
5319        // satisfied, which is worth a parse error rather than silence.
5320        assert!(parse("\\W ~>0(\"END\")").is_err());
5321        assert!(parse("\\W ~>(\"END\")").is_err(), "the count is not optional");
5322    }
5323
5324    #[test]
5325    fn a_bounded_assertion_does_not_depend_on_the_whole_input() {
5326        // The reach is part of the pattern, so a chunked scanner holding that
5327        // many tokens past a match can finalize it. An unbounded one cannot.
5328        let bounded = parse("\\W ~>3(\"END\")").expect("parses");
5329        let unbounded = parse("\\W ~(\"END\")").expect("parses");
5330        assert!(!bounded.has_assert(), "a bounded assertion is not an unbounded one");
5331        assert!(unbounded.has_assert());
5332        assert!(!bounded.depends_on_whole_input(), "so it can be finalized from a chunk");
5333        assert!(unbounded.depends_on_whole_input());
5334    }
5335
5336    #[test]
5337    fn an_assertion_binds_nothing_that_escapes_it() {
5338        // The probe's registers are discarded, so the template surface sees no
5339        // capture from inside an assertion.
5340        let p = parse("~(\\W:inner) \\W:outer").expect("parses");
5341        assert_eq!(p.capture_names(), vec!["outer".to_string()]);
5342    }
5343
5344    #[test]
5345    fn token_classes_union_complement_intersect_and_subtract() {
5346        // Union: a number or a word, not the punctuation between them.
5347        assert_eq!(run("[\\N \\W]", "ab 12 , cd").len(), 3);
5348        // Complement: everything that is not a number.
5349        let not_num: Vec<_> = run("[^\\N]", "ab 12 cd")
5350            .iter()
5351            .map(|m| "ab 12 cd"[m.start..m.end].to_string())
5352            .collect();
5353        assert_eq!(not_num, vec!["ab", "cd"]);
5354        // Intersection: a word whose bytes are all hex.
5355        let hex: Vec<_> = run("[\\W && \\h]", "deadbeef zzz cafe")
5356            .iter()
5357            .map(|m| "deadbeef zzz cafe"[m.start..m.end].to_string())
5358            .collect();
5359        assert_eq!(hex, vec!["deadbeef", "cafe"]);
5360        // Difference: a word that is not all uppercase.
5361        let lower: Vec<_> = run("[\\W -- \\u]", "ABC def GHI jkl")
5362            .iter()
5363            .map(|m| "ABC def GHI jkl"[m.start..m.end].to_string())
5364            .collect();
5365        assert_eq!(lower, vec!["def", "jkl"]);
5366    }
5367
5368    #[test]
5369    fn a_class_composes_with_literals_and_byte_patterns() {
5370        assert_eq!(run("[\"cat\" \"dog\"]", "cat bird dog").len(), 2);
5371        assert_eq!(run("[`[a-z]+` \\N]", "abc DEF 12").len(), 2);
5372    }
5373
5374    #[test]
5375    fn malformed_classes_are_parse_errors() {
5376        for bad in ["[", "[\\N", "[]", "[&& \\N]"] {
5377            assert!(parse(bad).is_err(), "should reject: {bad}");
5378        }
5379    }
5380
5381    #[test]
5382    fn a_balanced_square_group_still_parses_after_classes() {
5383        // `\B[...]` consumes its own bracket, so a bare `[` becoming a class
5384        // opener must not disturb it.
5385        assert_eq!(run("\\B[.*]", "x [a b] y").len(), 1);
5386    }
5387
5388    #[test]
5389    fn input_anchors_are_distinct_from_line_anchors() {
5390        // Every anchor here is a prefix: it constrains the token the following
5391        // atom consumes, so `\z \W` reads "a word that ends the input".
5392        let two_lines = "alpha beta\ngamma delta\n";
5393        // `^` heads every line; `\A` heads only the stream.
5394        assert_eq!(run("^ \\W", two_lines).len(), 2);
5395        let at_start = run("\\A \\W", two_lines);
5396        assert_eq!(at_start.len(), 1);
5397        assert_eq!(&two_lines[at_start[0].start..at_start[0].end], "alpha");
5398        // `$` ends every line; `\z` ends only the stream.
5399        assert_eq!(run("$ \\W", two_lines).len(), 2);
5400        let at_end = run("\\z \\W", two_lines);
5401        assert_eq!(at_end.len(), 1);
5402        assert_eq!(&two_lines[at_end[0].start..at_end[0].end], "delta");
5403        // On a single line the two readings coincide.
5404        assert_eq!(run("^ \\W", "only line\n").len(), run("\\A \\W", "only line\n").len());
5405        assert_eq!(run("$ \\W", "only line\n").len(), run("\\z \\W", "only line\n").len());
5406    }
5407
5408    #[test]
5409    fn resume_anchor_takes_a_contiguous_run_instead_of_finding_occurrences() {
5410        // The difference between tokenizing and searching. Plain `\N` finds
5411        // every number anywhere; `\G \N` takes numbers only while
5412        // they keep coming and stops at the word.
5413        let input = "1 2 3 stop 4 5";
5414        assert_eq!(run("\\N", input).len(), 5, "a search finds all five");
5415        let contiguous = run("\\G \\N", input);
5416        assert_eq!(contiguous.len(), 3, "the run ends at the word");
5417        assert_eq!(&input[contiguous[2].start..contiguous[2].end], "3");
5418
5419        // It has to start at the beginning, so a stream that opens with a
5420        // non-match yields nothing at all rather than skipping ahead.
5421        assert!(run("\\G \\N", "stop 1 2 3").is_empty(), "no run to take");
5422        assert_eq!(run("\\N", "stop 1 2 3").len(), 3, "and searching still finds them");
5423    }
5424
5425    #[test]
5426    fn resume_anchor_is_refused_where_it_could_only_fail() {
5427        // Anywhere but the head it asks whether an interior token is where
5428        // the previous match ended, which a non-overlapping run never makes
5429        // true, so it is a parse error rather than a silent never-match.
5430        assert!(crate::parser::parse("\\G \\N").is_ok(), "the head is where it belongs");
5431        for bad in ["\\N \\G", "\\N (\\G \\W)", "(\\W | \\G \\N)", "\\G \\N \\G"] {
5432            let err = crate::parser::parse(bad).expect_err("refused: {bad}");
5433            assert!(format!("{err:?}").contains("\\\\G"), "names the construct: {err:?}");
5434        }
5435    }
5436
5437    #[test]
5438    fn reset_start_reports_only_what_follows_it() {
5439        // A lookbehind with no width limit: the key and colon are required
5440        // and not reported.
5441        let input = "name: alice age: bob";
5442        let got = run("\\W \":\" \\K \\W", input);
5443        assert_eq!(got.len(), 2);
5444        assert_eq!(&input[got[0].start..got[0].end], "alice");
5445        assert_eq!(&input[got[1].start..got[1].end], "bob");
5446        // Without it the whole construct is reported.
5447        let whole = run("\\W \":\" \\W", input);
5448        assert_eq!(&input[whole[0].start..whole[0].end], "name: alice");
5449
5450        // The scan still resumes past what was matched, not past what was
5451        // reported, so a run does not re-read the hidden part.
5452        assert_eq!(got.len(), whole.len(), "same matches, different spans");
5453    }
5454
5455    #[test]
5456    fn reset_start_is_refused_where_the_engine_cannot_carry_it() {
5457        assert!(crate::parser::parse("\\W \":\" \\K \\W").is_ok());
5458        // Each of these routes to the set engine, whose match start is the
5459        // position the attempt was anchored at and cannot be moved.
5460        for bad in ["\\B( \\K \\W )", "@seam \\K \\W", "\\K \\S", "~(\\W) \\K \\W"] {
5461            assert!(crate::parser::parse(bad).is_err(), "refused: {bad}");
5462        }
5463    }
5464
5465    #[test]
5466    fn an_atomic_group_keeps_only_the_length_its_body_preferred() {
5467        // The whole of what atomic means: the star takes every word, the cut
5468        // discards the shorter lengths, and the atom after it is offered
5469        // nothing. Without the cut the star hands one back.
5470        let input = "a b c";
5471        assert_eq!(run("\\W* \\W", input).len(), 1, "greedy hands one back");
5472        assert!(run("(?>\\W*) \\W", input).is_empty(), "atomic does not");
5473        assert!(run("\\W*+ \\W", input).is_empty(), "and the possessive form is the same");
5474
5475        // With something the body cannot take, the cut leaves it there.
5476        assert_eq!(run("(?>\\N*) \\W", "1 2 end").len(), 1, "numbers stop at the word");
5477        // A cut over a body that had one length to give changes nothing.
5478        assert_eq!(run("(?>\\W) \\W", input).len(), 1);
5479    }
5480
5481    #[test]
5482    fn possessive_quantifiers_read_as_the_atomic_form() {
5483        let atomic = crate::parser::parse("(?>\\W*)").expect("parses");
5484        let possessive = crate::parser::parse("\\W*+").expect("parses");
5485        assert_eq!(atomic, possessive, "the spellings agree");
5486        for (poss, group) in
5487            [("\\N++", "(?>\\N+)"), ("\\N?+", "(?>\\N?)"), ("\\N{2,4}+", "(?>\\N{2,4})")]
5488        {
5489            assert_eq!(
5490                crate::parser::parse(poss).expect("parses"),
5491                crate::parser::parse(group).expect("parses"),
5492                "{poss} is {group}"
5493            );
5494        }
5495        // Nothing skips whitespace before the `+`, so a spaced one is still a
5496        // punctuation literal rather than a possessive marker.
5497        let spaced = crate::parser::parse("\\W+ \"+\"").expect("parses");
5498        assert_ne!(spaced, crate::parser::parse("\\W++").expect("parses"));
5499    }
5500
5501    #[test]
5502    fn an_atom_can_be_conditioned_on_the_construct_containing_it() {
5503        // The reading regex has no way to state, because it has no container.
5504        // The same token kind means different things by where it is: a
5505        // number in an assignment is a value, a number in a call is an
5506        // argument, and nothing about the token itself separates them.
5507        // x = 41  lexes to one assign unit holding the number; f(42) to a
5508        // call unit for the name and a separate numeric unit, one bracket
5509        // deeper, holding the argument.
5510        let input = "x = 41\nf(42)\ny = 43\n";
5511        assert_eq!(run("\\N", input).len(), 3, "three numbers, taken plainly");
5512
5513        let assigned = run("@super:assign \\N", input);
5514        assert_eq!(assigned.len(), 2, "two of them are values in a binding");
5515        assert_eq!(&input[assigned[0].start..assigned[0].end], "41");
5516        assert_eq!(&input[assigned[1].start..assigned[1].end], "43");
5517
5518        let argument = run("@super:numeric \\N", input);
5519        assert_eq!(argument.len(), 1, "and one is an argument on its own");
5520        assert_eq!(&input[argument[0].start..argument[0].end], "42");
5521
5522        // The name being called is reachable the same way.
5523        let callee = run("@super:call \\W", input);
5524        assert_eq!(callee.len(), 1);
5525        assert_eq!(&input[callee[0].start..callee[0].end], "f");
5526    }
5527
5528    #[test]
5529    fn the_construct_boundary_is_an_anchor_of_its_own() {
5530        // The upper-grain counterpart of @seam: where one construct ends and
5531        // the next begins, which is a structural fact rather than a
5532        // statistical one.
5533        // A key-value entry and a list, each several words wide, so heads are
5534        // a strict subset of words rather than coinciding with them.
5535        let input = "k: v\na, b, c\n";
5536        let heads = run("@super \\W", input);
5537        assert_eq!(heads.len(), 2, "one head per construct");
5538        for (got, want) in heads.iter().zip(["k", "a"]) {
5539            assert_eq!(&input[got.start..got.end], want);
5540        }
5541        // Without the anchor every word matches, not only the heads.
5542        assert_eq!(run("\\W", input).len(), 5, "five words, two of them heads");
5543    }
5544
5545    #[test]
5546    fn a_seam_names_which_stream_has_to_stop_predicting_itself() {
5547        // The same input, three sequences, three different questions. A
5548        // byte-grain cut is where the characters stop predicting each
5549        // other; a token-grain cut where the sequence of kinds does; a
5550        // supertoken-grain cut where the sequence of roles does.
5551        let input = "let x = 1 ; let y = 2 ; print x ; print y ;";
5552        let by_byte = run("@seam:byte \\W", input);
5553        let by_token = run("@seam:token \\W", input);
5554        let by_super = run("@seam:super \\W", input);
5555        // The unqualified spelling is one of the three rather than a fourth
5556        // reading, and it is the token grain.
5557        assert_eq!(
5558            run("@seam \\W", input).iter().map(|m| m.start).collect::<Vec<_>>(),
5559            by_token.iter().map(|m| m.start).collect::<Vec<_>>(),
5560            "an unqualified seam reads the token stream"
5561        );
5562
5563        // Each grain finds something, and they are not the same set - a
5564        // qualified reading is a different reading, not a filter on one.
5565        assert!(!by_token.is_empty(), "the token stream is segmented");
5566        assert_ne!(
5567            by_byte.iter().map(|m| m.start).collect::<Vec<_>>(),
5568            by_token.iter().map(|m| m.start).collect::<Vec<_>>(),
5569            "byte and token grain disagree about where the breaks are"
5570        );
5571        // A supertoken cut can only land where a construct begins.
5572        let units = crate::supertoken::supertokens_from(&crate::lexer::lex(input.as_bytes()), input.as_bytes());
5573        let heads: Vec<usize> = units.iter().map(|u| u.start).collect();
5574        assert!(
5575            by_super.iter().all(|m| heads.contains(&m.start)),
5576            "a construct-grain seam keys to construct starts"
5577        );
5578    }
5579
5580    #[test]
5581    fn only_the_grains_a_pattern_names_are_segmented() {
5582        use crate::ast::Grain;
5583        let p = crate::parser::parse("@seam:token \\W").expect("parses");
5584        assert!(p.has_seam_at(Grain::Token));
5585        assert!(!p.has_seam_at(Grain::Byte), "the byte stream is not segmented for this");
5586        assert!(!p.has_seam_at(Grain::Super));
5587        // The unqualified spelling is the token grain, and the byte stream is
5588        // segmented only where `@seam:byte` asks for it.
5589        let plain = crate::parser::parse("@seam \\W").expect("parses");
5590        assert!(plain.has_seam_at(Grain::Token));
5591        assert!(!plain.has_seam_at(Grain::Byte), "the bytes are segmented only when named");
5592        let bytes = crate::parser::parse("@seam:byte \\W").expect("parses");
5593        assert!(bytes.has_seam_at(Grain::Byte));
5594        assert!(!bytes.has_seam_at(Grain::Token));
5595    }
5596
5597    #[test]
5598    fn the_observation_axis_takes_a_grain_the_same_way() {
5599        use crate::ast::Grain;
5600        let p = crate::parser::parse("@ambiguous:token \\W").expect("parses");
5601        assert!(p.has_observation_at(Grain::Token));
5602        assert!(!p.has_observation_at(Grain::Byte), "only the named stream is read");
5603        let plain = crate::parser::parse("@ambiguous \\W").expect("parses");
5604        assert!(plain.has_observation_at(Grain::Byte));
5605        assert!(!plain.has_observation_at(Grain::Token));
5606
5607        // Every grain scans, and a contested point is a subset of the words,
5608        // so the anchor is selective rather than a no-op at any of them.
5609        let input = "let x = 1 ; print x ; let yy = 22 ; print yy ;";
5610        let all = run("\\W", input).len();
5611        for pat in ["@ambiguous \\W", "@ambiguous:token \\W", "@ambiguous:super \\W"] {
5612            assert!(run(pat, input).len() <= all, "{pat} selects from the words");
5613        }
5614        assert!(crate::parser::parse("@ambiguous:nonesuch \\W").is_err());
5615    }
5616
5617    #[test]
5618    fn an_unknown_grain_is_refused() {
5619        assert!(crate::parser::parse("@seam:token \\W").is_ok());
5620        assert!(crate::parser::parse("@seam:super \\W").is_ok());
5621        assert!(crate::parser::parse("@seam:byte \\W").is_ok());
5622        let err = crate::parser::parse("@seam:nonesuch \\W").expect_err("refused");
5623        assert!(format!("{err:?}").contains("nonesuch"), "names it: {err:?}");
5624    }
5625
5626    #[test]
5627    fn an_unknown_supertoken_role_is_refused() {
5628        assert!(crate::parser::parse("@super:call \\N").is_ok());
5629        assert!(crate::parser::parse("@super").is_ok());
5630        let err = crate::parser::parse("@super:nonesuch \\N").expect_err("refused");
5631        assert!(format!("{err:?}").contains("nonesuch"), "names it: {err:?}");
5632    }
5633
5634    #[test]
5635    fn uuid_is_spelled_out_and_g_is_the_anchor() {
5636        let id = "550e8400-e29b-41d4-a716-446655440000";
5637        assert_eq!(run("\\{uuid}", id).len(), 1, "the long spelling reads a uuid");
5638        // `\G` is the anchor now, so against a uuid it takes the run from the
5639        // start rather than naming the token kind.
5640        assert!(crate::parser::parse("\\G").is_ok());
5641    }
5642
5643    #[test]
5644    fn lazy_quantifiers_prefer_the_shorter_match() {
5645        // Laziness shows only where several lengths match from one start. In
5646        // `\W*? \N` the start already fixes the length, so both leans agree;
5647        // a repeated terminator is what gives the quantifier a real choice.
5648        let input = "a q b q";
5649        let greedy = run(".* \"q\"", input);
5650        let lazy = run(".*? \"q\"", input);
5651        assert_eq!(greedy.len(), 1, "greedy runs to the last terminator");
5652        assert_eq!(&input[greedy[0].start..greedy[0].end], "a q b q");
5653        assert_eq!(lazy.len(), 2, "lazy stops at the first, then resumes");
5654        assert_eq!(&input[lazy[0].start..lazy[0].end], "a q");
5655        assert_eq!(&input[lazy[1].start..lazy[1].end], "b q");
5656
5657        // A lazy optional prefers to match nothing, which needs both lengths
5658        // to succeed from the same start: leftmost outranks either lean, so
5659        // `\W?? \N` over `x 1` still takes the word rather than failing.
5660        let opt_in = "a b";
5661        let g_opt = run("\\W? \\W", opt_in);
5662        let l_opt = run("\\W?? \\W", opt_in);
5663        assert_eq!(&opt_in[g_opt[0].start..g_opt[0].end], "a b");
5664        assert_eq!(&opt_in[l_opt[0].start..l_opt[0].end], "a");
5665    }
5666
5667    #[test]
5668    fn a_lazy_quantifier_over_a_branching_body_still_prefers_shorter() {
5669        // The body makes a choice per iteration, so this quantifier takes the
5670        // per-iteration rank encoding rather than the single-count one.
5671        let input = "a 1 q b 2 q";
5672        let greedy = run("(\\W | \\N)* \"q\"", input);
5673        let lazy = run("(\\W | \\N)*? \"q\"", input);
5674        assert_eq!(greedy.len(), 1);
5675        assert_eq!(&input[greedy[0].start..greedy[0].end], "a 1 q b 2 q");
5676        assert_eq!(lazy.len(), 2);
5677        assert_eq!(&input[lazy[0].start..lazy[0].end], "a 1 q");
5678        assert_eq!(&input[lazy[1].start..lazy[1].end], "b 2 q");
5679    }
5680
5681    #[test]
5682    fn lazy_and_greedy_accept_the_same_inputs() {
5683        // Laziness changes the span reported, never whether there is one.
5684        for input in ["a b c 1", "1", "a 1 b 2", "no number here", ""] {
5685            assert_eq!(
5686                run("\\W* \\N", input).is_empty(),
5687                run("\\W*? \\N", input).is_empty(),
5688                "greedy and lazy must agree on acceptance for {input:?}"
5689            );
5690        }
5691    }
5692
5693    #[test]
5694    fn anchors_route_by_what_they_read() {
5695        // A positional anchor is a function of the tokens, the bytes and the
5696        // position, so the single-pass engine evaluates it as an epsilon.
5697        for src in ["\\A \\W", "\\z \\W", "^ \\W", "$ \\W", "^ $ \\W"] {
5698            let p = parse(src).expect("parses");
5699            assert!(
5700                crate::nfa::scan_nfa(&p, b"a b\nc d\n").is_some(),
5701                "the single-pass engine must handle {src:?}"
5702            );
5703        }
5704        // A property anchor reads a field built once per scan, which only the
5705        // set engine carries, so it still routes away.
5706        for src in ["@seam \\W", "@nested>1 \\W", "@novel \\W", "@echoed \\W"] {
5707            let p = parse(src).expect("parses");
5708            assert!(
5709                crate::nfa::scan_nfa(&p, b"a b\nc d\n").is_none(),
5710                "the single-pass engine must decline {src:?}"
5711            );
5712        }
5713    }
5714
5715    #[test]
5716    fn both_engines_agree_on_positional_anchors() {
5717        // Now that both run them, they can disagree, so this checks they do
5718        // not. One lex, both engines, over inputs where line and input
5719        // readings come apart.
5720        for src in ["\\A \\W", "\\z \\W", "^ \\W", "$ \\W", "^ $ \\W", "^ \\W | \\z \\N"] {
5721            let p = parse(src).expect("parses");
5722            for inp in [
5723                "",
5724                "a",
5725                "alpha beta\ngamma delta\n",
5726                "  indented\nplain\n",
5727                "solo\n",
5728                "a b c\n\n d e\n",
5729                "1\nx 2\n",
5730            ] {
5731                let bytes = inp.as_bytes();
5732                let nfa = crate::nfa::scan_nfa(&p, bytes).expect("the linear engine handles it");
5733                let set = scan_set_reachability(&p, bytes);
5734                assert_eq!(nfa, set, "engines differ on {src:?} over {inp:?}");
5735            }
5736        }
5737    }
5738
5739    #[test]
5740    fn an_input_anchor_ignores_an_enclosing_group() {
5741        // `\A` asks about the stream, so it holds at a group that opens the
5742        // input and not at one further in.
5743        assert_eq!(run("\\A \\B(\\W)", "(a) x").len(), 1);
5744        assert!(run("\\A \\B(\\W)", "x (a)").is_empty());
5745        // Inside the group it still asks about the input, where the open
5746        // bracket is a significant token preceding the interior, so it cannot
5747        // hold there at all. A group-scoped reading would have matched.
5748        assert!(run("\\B(\\A \\W)", "(a) x").is_empty());
5749    }
5750
5751    #[test]
5752    fn mac_keeps_its_named_atom_after_a_lost_its_letter() {
5753        let m = run("\\{mac}", "nic 01:23:45:67:89:ab up");
5754        assert_eq!(m.len(), 1);
5755        assert_eq!(&"nic 01:23:45:67:89:ab up"[m[0].start..m[0].end], "01:23:45:67:89:ab");
5756    }
5757
5758    #[test]
5759    fn the_three_alternation_modes_differ_as_documented() {
5760        // `|` leftmost-first: the short branch is preferred, but yields when
5761        // the continuation contradicts it.
5762        assert_eq!(run("(\"a\" | \"a\" \"b\") \"c\"", "a b c").len(), 1);
5763        assert_eq!(run("\\W | \\W \\W", "a b").len(), 2);
5764        // `||` leftmost-longest: no preference, the longest overall wins.
5765        let longest = run("\\W || \\W \\W", "a b");
5766        assert_eq!(longest.len(), 1);
5767        assert_eq!(&"a b"[longest[0].start..longest[0].end], "a b");
5768        // `|>` committed: the first branch that matches wins outright, so the
5769        // contradicted continuation kills the match instead of falling back.
5770        assert!(run("(\"a\" |> \"a\" \"b\") \"c\"", "a b c").is_empty());
5771        assert_eq!(run("(\"a\" \"b\" |> \"a\") \"c\"", "a b c").len(), 1);
5772    }
5773
5774    #[test]
5775    fn alternation_agrees_across_the_router_inside_a_balanced_group() {
5776        // A balanced group forces the set engine and requires the interior to
5777        // consume exactly to the close, so a committed short branch leaves a
5778        // token over and fails having never tried the branch that fits.
5779        assert_eq!(run("\\B(\\W | \\W \\W)", "(a b)").len(), 1);
5780        assert_eq!(run("\\B((\"a\" | \"a\" \"b\") \"c\")", "(a b c)").len(), 1);
5781        assert!(run("\\B(\\W |> \\W \\W)", "(a b)").is_empty());
5782    }
5783
5784    #[test]
5785    fn mixing_alternation_kinds_at_one_level_is_a_parse_error() {
5786        let e = parse("\\N | \\W || \\Q").unwrap_err();
5787        assert!(e.msg.contains("mixed alternation kinds"), "got {:?}", e.msg);
5788        // Parenthesizing says which binds tighter, and parses.
5789        assert!(parse("\\N | (\\W || \\Q)").is_ok());
5790        assert!(parse("(\\N | \\W) || \\Q").is_ok());
5791    }
5792
5793    #[test]
5794    fn a_bare_slash_is_still_a_literal() {
5795        // `/` was considered for committed choice and rejected: the close-tag
5796        // pattern needs it as punctuation.
5797        let hay = "<div>hi</div>";
5798        let m = run("<\\W:t>.*</=t>", hay);
5799        assert_eq!(m.len(), 1);
5800        assert_eq!(m[0].group("t", hay.as_bytes()), Some(b"div" as &[u8]));
5801    }
5802
5803    #[test]
5804    fn explicit_whitespace_atoms_match() {
5805        // \S (the whitespace token kind) and \s (the space byte class) must
5806        // see the whitespace token the inter-atom skip would jump over. They
5807        // cannot start a pattern (matches anchor at significant tokens).
5808        assert_eq!(run(r"\W \S \W", "a b").len(), 1, "\\S between atoms");
5809        assert_eq!(run(r"\W \s \W", "a  b").len(), 1, "\\s between atoms");
5810        // Constraining: no whitespace between the atoms = no match.
5811        assert_eq!(run(r"\W \S \W \S \W", "a b").len(), 0);
5812    }
5813
5814    #[test]
5815    fn lowercase_byte_class_atoms() {
5816        // \h hex, \a alpha, \u upper, \l lower each match a whole token whose
5817        // bytes all satisfy the class, regardless of the token's kind.
5818        let hex: Vec<_> = run("\\h", "deadbeef 123 xyz").iter().map(|m| m.end - m.start).collect();
5819        assert_eq!(hex.len(), 2, "deadbeef and 123 are all-hex; xyz is not");
5820        assert_eq!(run("\\u", "ABC def GHI").len(), 2);
5821        assert_eq!(run("\\l", "ABC def GHI").len(), 1);
5822        assert_eq!(run("\\a", "abc d3f ghi").iter().map(|m| m.end - m.start).sum::<usize>(), 6);
5823    }
5824
5825    #[test]
5826    fn spectral_atom_parses_every_predicate_form() {
5827        for ok in ["\\F{entropy>0.8}", "\\F{entropy<0.3}", "\\F{period=4}", "\\F{period:line}", "\\F{texture:code}", "\\F{texture:prose}", "\\F{onset}"] {
5828            assert!(parse(ok).is_ok(), "should parse: {ok}");
5829        }
5830        for bad in ["\\F{bogus}", "\\F{entropy}", "\\F{texture:nope}", "\\F{period=x}"] {
5831            assert!(parse(bad).is_err(), "should reject: {bad}");
5832        }
5833    }
5834
5835    #[test]
5836    fn spectral_texture_atom_discriminates_code_from_prose() {
5837        // A spectral atom routes to the set engine, which builds the field
5838        // once and threads it to the matcher. Code carries many
5839        // code-textured tokens; prose carries essentially none.
5840        let code = "fn add(a,b){let c=a+b;return c*2;} impl P{fn n(&self){self.x*self.x+self.y*self.y}}";
5841        let prose = "the quick brown fox jumps over the lazy dog near the old stone bridge in the cool air";
5842        let code_hits = run("\\F{texture:code}", code).len();
5843        let prose_hits = run("\\F{texture:code}", prose).len();
5844        assert!(code_hits > prose_hits, "code {code_hits} should exceed prose {prose_hits}");
5845        assert!(code_hits > 0, "code texture should match in code");
5846    }
5847
5848    #[test]
5849    fn matches_number_then_word() {
5850        // A unit symbol one space after a number belongs to that quantity, so
5851        // the word this reads is in no unit table.
5852        let m = run("\\N \\W", "weight 12 items here");
5853        assert_eq!(m.len(), 1);
5854        assert_eq!(&"weight 12 items here"[m[0].start..m[0].end], "12 items");
5855        assert!(run("\\N \\W", "weight 12 kg here").is_empty(), "`12 kg` is one quantity token");
5856    }
5857
5858    #[test]
5859    fn matched_tag_binds_and_checks() {
5860        let hay = "<div>hi</div>";
5861        let ok = run("<\\W:t>.*</=t>", hay);
5862        assert_eq!(ok.len(), 1);
5863        assert_eq!(&hay[ok[0].start..ok[0].end], "<div>hi</div>");
5864        assert_eq!(ok[0].group("t", hay.as_bytes()), Some(b"div" as &[u8]));
5865
5866        let bad = run("<\\W:t>.*</=t>", "<div>hi</span>");
5867        assert!(bad.is_empty(), "mismatched tag must not match");
5868    }
5869
5870    #[test]
5871    fn atom_bind_through_set_engine_captures() {
5872        // A balanced group forces the set-reachability engine; the leading
5873        // `\W:t` is a single-atom bind, which takes the batched fast path
5874        // inside that engine. The capture must still be exact.
5875        let hay = "tag (5) rest";
5876        let m = run("\\W:t \\B(\\N)", hay);
5877        assert_eq!(m.len(), 1);
5878        assert_eq!(&hay[m[0].start..m[0].end], "tag (5)");
5879        assert_eq!(m[0].group("t", hay.as_bytes()), Some(b"tag" as &[u8]));
5880
5881        // Two atom binds: the second register interns to a distinct id, so
5882        // this also covers the multi-register environment in the set engine.
5883        let hay2 = "alpha beta (7)";
5884        let m2 = run("\\W:t \\W:u \\B(\\N)", hay2);
5885        assert_eq!(m2.len(), 1);
5886        assert_eq!(m2[0].group("t", hay2.as_bytes()), Some(b"alpha" as &[u8]));
5887        assert_eq!(m2[0].group("u", hay2.as_bytes()), Some(b"beta" as &[u8]));
5888    }
5889
5890    #[test]
5891    fn repeated_token_matches_only_a_repeat() {
5892        let ok = run("\\W:x =x", "the the cat");
5893        assert_eq!(ok.len(), 1);
5894        assert_eq!(&"the the cat"[ok[0].start..ok[0].end], "the the");
5895
5896        let none = run("\\W:x =x", "the cat sat");
5897        assert!(none.is_empty());
5898    }
5899
5900    #[test]
5901    fn scoped_binding_does_not_leak_out_of_its_group() {
5902        // A global `:x` bound inside a group leaks out, so a following `=x`
5903        // matches the repeat outside the group.
5904        let global = run("\\B(\\W:x) =x", "(cat) cat");
5905        assert_eq!(global.len(), 1);
5906        assert_eq!(&"(cat) cat"[global[0].start..global[0].end], "(cat) cat");
5907        // A scoped `::x` is dropped when the group closes, so the outside `=x`
5908        // has no binding and cannot match.
5909        assert!(
5910            run("\\B(\\W::x) =x", "(cat) cat").is_empty(),
5911            "a scoped bind must not leak out of its group"
5912        );
5913        // Inside the group a scoped reference still resolves normally.
5914        assert_eq!(run("\\B(\\W::x =x)", "(the the)").len(), 1);
5915        assert!(run("\\B(\\W::x =x)", "(the cat)").is_empty());
5916    }
5917
5918    #[test]
5919    fn balanced_group_handles_nesting() {
5920        let m = run("\\W\\B(.*)", "call f(g(x)) end");
5921        assert_eq!(m.len(), 1);
5922        assert_eq!(&"call f(g(x)) end"[m[0].start..m[0].end], "f(g(x))");
5923
5924        let none = run("\\W\\B(.*)", "bare word");
5925        assert!(none.is_empty());
5926    }
5927
5928    #[test]
5929    fn ordered_choice_matches_either() {
5930        let a = run("\\N | \\W", "12");
5931        assert_eq!(a.len(), 1);
5932        let b = run("\\N | \\W", "hi");
5933        assert_eq!(b.len(), 1);
5934    }
5935
5936    #[test]
5937    fn ordered_choice_is_committed() {
5938        // The first alternative (a single word) wins, so the match
5939        // is "a", not the longer "a b" the second alternative would
5940        // produce. Union semantics would take the longer span.
5941        let m = run("\\W | \\W \\W", "a b");
5942        assert_eq!(m.len(), 2);
5943        assert_eq!(&"a b"[m[0].start..m[0].end], "a");
5944    }
5945
5946    #[test]
5947    fn guard_requires_forward_literal() {
5948        let hit = run(". ~\"END\"", "begin END");
5949        assert!(!hit.is_empty());
5950        let miss = run(". ~\"END\"", "begin only");
5951        assert!(miss.is_empty());
5952    }
5953
5954    #[test]
5955    fn parallel_scan_over_large_input_is_correct() {
5956        // 2000 word tokens exceed PARALLEL_SCAN_THRESHOLD, so the
5957        // per-start attempts run across cores. The repeated-token
5958        // pattern pairs them up: 2000 words yield 1000 non-overlapping
5959        // matches, and the parallel result must equal that exactly.
5960        let input = vec!["x"; 2000].join(" ");
5961        let m = run("\\W:p =p", &input);
5962        assert_eq!(m.len(), 1000);
5963        assert_eq!(&input[m[0].start..m[0].end], "x x");
5964    }
5965
5966    #[test]
5967    fn ambiguous_anchor_fires_at_contested_points() {
5968        // `@ambiguous` fires on tokens overlapping a vantage-dependent point.
5969        // A garden-path sentence has such points, and the anchor is selective:
5970        // it matches some tokens but not every one, unlike a bare `.`.
5971        let src = "the old man the boats";
5972        let garden = run("@ambiguous .", src);
5973        assert!(!garden.is_empty(), "a garden-path sentence has contested points");
5974        let all = run(".", src).len();
5975        assert!(garden.len() < all, "@ambiguous is selective, not every token");
5976    }
5977
5978    #[test]
5979    fn nested_anchor_matches_by_depth() {
5980        // `@nested>1` fires only on tokens more than one bracket deep. In
5981        // `f(g(x))`, `x` is two parens deep; nothing else is.
5982        let deep = run("@nested>1 .", "a f(g(x)) b");
5983        let got: Vec<&str> = deep.iter().map(|h| &"a f(g(x)) b"[h.start..h.end]).collect();
5984        assert_eq!(got, vec!["x"]);
5985        // `@nested>=1` fires on every token at least one bracket deep.
5986        let one = run("@nested>=1 .", "a (b c) d");
5987        let g1: Vec<&str> = one.iter().map(|h| &"a (b c) d"[h.start..h.end]).collect();
5988        assert_eq!(g1, vec!["b", "c"]);
5989    }
5990
5991    #[test]
5992    fn seam_anchor_is_selective() {
5993        // `@seam` fires only at a cut of the token-kind reading, where the
5994        // sequence of kinds stops predicting itself, and never at the input's
5995        // first token, so it matches a strict, non-empty subset of the tokens
5996        // a bare `.` takes.
5997        let src = "let x = 1 ; let y = 2 ; print x ; print y ;";
5998        let all = run(".", src).len();
5999        let seams = run("@seam .", src);
6000        assert!(all >= 5, "the bare-dot baseline should match every token");
6001        assert!(
6002            !seams.is_empty() && seams.len() < all,
6003            "@seam should be selective: {} of {all} token(s), not none and not all",
6004            seams.len()
6005        );
6006        assert!(seams.iter().all(|m| m.start > 0), "no seam at the input's first token");
6007    }
6008
6009    #[test]
6010    fn a_strain_anchor_takes_the_units_above_its_percentile() {
6011        // A stream that repeats one word, with a word of unseen bytes once in
6012        // the middle: its first byte is the most strained in the input.
6013        let src = format!("{}zqxj {}", "abcd ".repeat(400), "abcd ".repeat(400));
6014        let all = run(".", &src).len();
6015        let high = run("@strain:byte>99.5 .", &src);
6016        let got: Vec<&str> = high.iter().map(|m| &src[m.start..m.end]).collect();
6017        assert!(got.contains(&"zqxj"), "the unseen word is among the most strained: {got:?}");
6018        assert!(high.len() < all / 20, "a high percentile is selective: {} of {all}", high.len());
6019        assert!(run("@strain:byte>1000b .", &src).is_empty(), "no byte is strained a thousand bits");
6020    }
6021
6022    #[test]
6023    fn a_bound_anchor_takes_the_weakest_cuts() {
6024        // Two runs of different bytes: the cut between them binds least.
6025        let src = format!("{}{}", "ab ab ".repeat(300), "xy xy ".repeat(300));
6026        let first_x = src.find('x').expect("the second run");
6027        let weakest = run("@bound:byte<=0.5 .", &src);
6028        assert!(
6029            weakest.iter().any(|m| m.start == first_x),
6030            "the seam between the runs is among the weakest cuts: {:?}",
6031            weakest.iter().map(|m| m.start).collect::<Vec<_>>()
6032        );
6033        let all = run(".", &src).len();
6034        assert!(weakest.len() < all / 10, "a low percentile is selective: {} of {all}", weakest.len());
6035    }
6036
6037    #[test]
6038    fn the_gravity_anchors_hold_nowhere_at_the_first_unit() {
6039        // The first unit has nothing before it and no cut before it, so it
6040        // reads neither strain nor binding at any grain.
6041        let src = "let x = f(a) ; let y = g(b) ; print x";
6042        for pat in ["@bound<10 .", "@strain>90 .", "@bound:byte<10 .", "@strain:super>=0 .", "@bound<=100b ."] {
6043            let found = run(pat, src);
6044            assert!(found.iter().all(|m| m.start > 0), "{pat} holds at the input's first unit: {found:?}");
6045        }
6046        assert!(!run("@bound<=100b .", src).is_empty(), "every later cut is read");
6047    }
6048
6049    #[test]
6050    fn a_kin_anchor_takes_its_example_and_what_the_field_places_with_it() {
6051        let src = "alpha beta; gamma(delta)\n".repeat(200);
6052        let a_words = run("@kin:byte(\"a\") .", &src);
6053        let got: Vec<&str> = a_words.iter().map(|m| &src[m.start..m.end]).collect();
6054        assert!(got.contains(&"alpha"), "a token opening with the example's byte is kin to it: {got:?}");
6055        assert!(run("@kin:byte(\"~\") .", &src).is_empty(), "an example the input never holds matches nothing");
6056    }
6057
6058    #[test]
6059    fn a_kin_reference_takes_a_token_the_field_places_with_the_bound_one() {
6060        use crate::ast::{Atom, Grain, Pattern};
6061        assert_eq!(crate::parse("=kin a").expect("parses"), Pattern::Atom(Atom::RegisterKin("a".into(), Grain::Token)));
6062        assert_eq!(
6063            crate::parse("=kin:super a").expect("parses"),
6064            Pattern::Atom(Atom::RegisterKin("a".into(), Grain::Super))
6065        );
6066        assert!(
6067            matches!(crate::parse("=kin").expect("parses"), Pattern::Atom(Atom::RegisterEq(name, _)) if name == "kin"),
6068            "with no register after it, kin is a register's name"
6069        );
6070        // Every word is one type at the token grain, so a word is kin to a word.
6071        assert_eq!(run("(\\W):a =kin a", "x y").len(), 1);
6072        // Too little input places no class, so only the same type is kin.
6073        assert!(run("(\\W):a =kin a", "x ;").is_empty(), "a word and a semicolon are not kin");
6074    }
6075
6076    #[test]
6077    fn a_phase_anchor_names_a_period_by_length_or_by_rank() {
6078        use crate::ast::{AnchorKind, Pattern, PeriodRef};
6079        assert_eq!(crate::parse("@phase:2/6").expect("parses"), Pattern::Anchor(AnchorKind::PhaseIn(2, PeriodRef::Length(6))));
6080        assert_eq!(crate::parse("@phase:1#2").expect("parses"), Pattern::Anchor(AnchorKind::PhaseIn(1, PeriodRef::Rank(2))));
6081        assert!(crate::parse("@phase:6/6").is_err(), "a column is below its period");
6082        assert!(crate::parse("@phase:0/40").is_err(), "a period is within the lags searched");
6083        assert!(crate::parse("@phase:0#0").is_err(), "the strongest period is #1");
6084        assert!(crate::parse("@phase:40").is_err(), "no column is past the longest period");
6085        // Rows of two shapes, four tokens and six: both periods are live.
6086        let src = format!("{}{}", "k = 1 ;\n".repeat(150), "k = 1 , 2 ;\n".repeat(150));
6087        assert!(!run("@phase:0/4 .", &src).is_empty(), "column 0 of the 4-token period holds");
6088        assert!(!run("@phase:0/6 .", &src).is_empty(), "column 0 of the 6-token period holds");
6089        assert!(run("@phase:0/5 .", &src).is_empty(), "no 5-token period lives here");
6090        assert_eq!(
6091            run("@phase:0#1 .", &src).len(),
6092            run("@phase:0 .", &src).len(),
6093            "#1 is the strongest period, the one plain @phase counts in"
6094        );
6095    }
6096
6097    #[test]
6098    fn the_gravity_anchors_parse_their_thresholds_and_refuse_what_names_nothing() {
6099        use crate::ast::{AnchorKind, Cmp, Grain, GravityReading, Level, Pattern, Real};
6100        let p = crate::parse("@strain>90").expect("a percentile");
6101        assert_eq!(
6102            p,
6103            Pattern::Anchor(AnchorKind::Gravity(
6104                GravityReading::Strain,
6105                Grain::Token,
6106                Cmp::Gt,
6107                Level::Percentile(Real::new(90.0))
6108            ))
6109        );
6110        let p = crate::parse("@bound:super<=-1.5b").expect("a negative value in bits");
6111        assert_eq!(
6112            p,
6113            Pattern::Anchor(AnchorKind::Gravity(GravityReading::Bound, Grain::Super, Cmp::Le, Level::Bits(Real::new(-1.5))))
6114        );
6115        assert!(crate::parse("@strain").is_err(), "a comparison is required");
6116        assert!(crate::parse("@strain>150").is_err(), "a percentile runs 0 to 100");
6117        assert!(crate::parse("@strain>150b").is_ok(), "bits are not a percentile");
6118        assert!(crate::parse("@strain:bytes>1").is_err(), "an unknown grain is refused");
6119        assert!(crate::parse("@kin:byte(\"ab\")").is_err(), "a byte-grain example is one byte");
6120        assert!(crate::parse("@kin(\"   \")").is_err(), "an example of whitespace names no token");
6121    }
6122
6123    #[test]
6124    fn silhouette_matches_by_structural_form() {
6125        // `#"W(W,W)"` matches a two-argument call whatever the identifiers
6126        // are, and rejects a one-arg or three-arg call: structure, not content.
6127        let src = "foo(a,b) and bar(x,y) but baz(1) and qux(p,q,r)";
6128        let m = run("#\"W(W,W)\"", src);
6129        let got: Vec<&str> = m.iter().map(|h| &src[h.start..h.end]).collect();
6130        assert_eq!(got, vec!["foo(a,b)", "bar(x,y)"]);
6131    }
6132
6133    #[test]
6134    fn negative_guard_requires_absence() {
6135        // `!~"lit"` keeps a match only when its forward window does not
6136        // contain the literal - negative lookahead that stays linear.
6137        let src = "run 7 fail 9 pass";
6138        let neg = run("\\N !~\"fail\"", src);
6139        let got: Vec<&str> = neg.iter().map(|h| &src[h.start..h.end]).collect();
6140        assert_eq!(got, vec!["9"], "only the number with no 'fail' after it");
6141        // The positive guard is unchanged: a number followed by 'fail'.
6142        let pos = run("\\N ~\"fail\"", src);
6143        let gotp: Vec<&str> = pos.iter().map(|h| &src[h.start..h.end]).collect();
6144        assert_eq!(gotp, vec!["7"]);
6145    }
6146
6147    #[test]
6148    fn shape_backreference_is_a_fuzzy_repeat() {
6149        // `=shape x` matches any later token in the bound token's shape orbit,
6150        // not just an exact repeat: `cat dog` and `pin bad` are CVC CVC pairs.
6151        let src = "cat dog and pin bad the sky";
6152        let m = run("\\W:x =shape x", src);
6153        let got: Vec<&str> = m.iter().map(|h| &src[h.start..h.end]).collect();
6154        assert_eq!(got, vec!["cat dog", "pin bad"]);
6155        // The plain reference stays an exact repeat (no orbit widening).
6156        let exact = run("\\W:x =x", src);
6157        assert!(exact.is_empty(), "no adjacent exact word repeat in {src:?}");
6158    }
6159
6160    #[test]
6161    fn case_backreference_matches_across_case() {
6162        let src = "Foo foo bar BAZ baz";
6163        let m = run("\\W:x =case x", src);
6164        let got: Vec<&str> = m.iter().map(|h| &src[h.start..h.end]).collect();
6165        assert_eq!(got, vec!["Foo foo", "BAZ baz"]);
6166    }
6167
6168    #[test]
6169    fn magnitude_atom_parses_every_predicate_form() {
6170        for ok in ["\\M{>6}", "\\M{>=6}", "\\M{<3}", "\\M{<=3}", "\\M{mag>6}", "\\M{ mag >= 9 }"] {
6171            assert!(parse(ok).is_ok(), "should parse: {ok}");
6172        }
6173        for bad in ["\\M{}", "\\M{6}", "\\M{>x}", "\\M{bogus}"] {
6174            assert!(parse(bad).is_err(), "should reject: {bad}");
6175        }
6176    }
6177
6178    #[test]
6179    fn magnitude_matches_numbers_by_scale() {
6180        // The scale substrate: `5` and `5000000000` are indistinguishable to
6181        // every other axis (both Number tokens), but the magnitude predicate
6182        // separates them - the capability regex structurally lacks.
6183        let src = "a 5 b 5000000000 c 42 d 999999999999";
6184        let m = run("\\M{>6}", src);
6185        let got: Vec<&str> = m.iter().map(|h| &src[h.start..h.end]).collect();
6186        assert_eq!(got, vec!["5000000000", "999999999999"]);
6187    }
6188
6189    #[test]
6190    fn kind_magnitude_intersects_kind_and_scale() {
6191        // `\N{mag>6}` is a Number and magnitude > 6. The big number matches; a
6192        // word long enough to have magnitude > 6 (len > 64) does not, because
6193        // the kind must be Number - where kind-blind `\M{>6}` would take it.
6194        let longword = "a".repeat(70);
6195        let src = format!("5000000000 42 {longword}");
6196        let n: Vec<String> =
6197            run("\\N{mag>6}", &src).iter().map(|h| src[h.start..h.end].to_string()).collect();
6198        assert_eq!(n, vec!["5000000000".to_string()]);
6199        // `\M{>6}` is kind-blind, so it also takes the long word.
6200        let m: Vec<String> =
6201            run("\\M{>6}", &src).iter().map(|h| src[h.start..h.end].to_string()).collect();
6202        assert!(m.contains(&longword), "\\M{{>6}} is kind-blind and takes the long word");
6203        // The repeat form on a kind atom is unchanged: `\N{2}` is two numbers.
6204        let rep = run("\\N{2}", "a 1 2 3 b");
6205        assert_eq!(&"a 1 2 3 b"[rep[0].start..rep[0].end], "1 2");
6206    }
6207
6208    #[test]
6209    fn magnitude_le_matches_small_tokens() {
6210        // A low-magnitude predicate keeps the small numbers and short words
6211        // and drops the huge value.
6212        let m = run("\\M{<3}", "tiny 5 huge 5000000000 mid 900");
6213        assert!(m.iter().all(|h| &"tiny 5 huge 5000000000 mid 900"[h.start..h.end] != "5000000000"));
6214        assert!(m.iter().any(|h| &"tiny 5 huge 5000000000 mid 900"[h.start..h.end] == "900"));
6215    }
6216
6217    #[test]
6218    fn kv_lens_matches_both_separators() {
6219        // `@kv` generalizes assignment to `:` and `=`; a word with no
6220        // separator after it is not a key/value pair.
6221        let m = run("@kv", "name: value  x=5");
6222        let got: Vec<&str> = m.iter().map(|h| &"name: value  x=5"[h.start..h.end]).collect();
6223        assert_eq!(got, vec!["name:", "x="]);
6224    }
6225
6226    #[test]
6227    fn flag_lens_matches_short_and_long() {
6228        // `-x` and `--verbose` are flags; `-5` is a dash then a number, not a
6229        // flag (the word requirement rejects it).
6230        let m = run("@flag", "run -x --verbose -5 end");
6231        let got: Vec<&str> = m.iter().map(|h| &"run -x --verbose -5 end"[h.start..h.end]).collect();
6232        assert_eq!(got, vec!["-x", "--verbose"]);
6233    }
6234
6235    #[test]
6236    fn list_lens_requires_a_comma() {
6237        // A comma-separated run is a list; a bare word is not.
6238        let m = run("@list", "items a, b, c but lone words");
6239        assert_eq!(m.len(), 1);
6240        assert_eq!(&"items a, b, c but lone words"[m[0].start..m[0].end], "a, b, c");
6241    }
6242
6243    #[test]
6244    fn range_lens_joins_numbers_not_clocks() {
6245        // `..`, `-`, and `:` all join two numbers; a `HH:MM` clock lexes as a
6246        // single timestamp token, so it is not a range.
6247        let src = "span 1..10 and 3-7 and 2:9 but 12:30 clock";
6248        let m = run("@range", src);
6249        let got: Vec<&str> = m.iter().map(|h| &src[h.start..h.end]).collect();
6250        assert_eq!(got, vec!["1..10", "3-7", "2:9"]);
6251    }
6252
6253    #[test]
6254    fn field_addressing_anchors_to_csv_field() {
6255        let m = run("@3 \"ERROR\"", "a, b, ERROR");
6256        assert_eq!(m.len(), 1);
6257        assert_eq!(&"a, b, ERROR"[m[0].start..m[0].end], "ERROR");
6258        // Field 3 holds OK, not ERROR, so nothing matches.
6259        assert!(run("@3 \"ERROR\"", "a, b, OK").is_empty());
6260        // @2 selects the second field's word.
6261        let m2 = run("@2 \\W", "a, hello, c");
6262        assert_eq!(m2.len(), 1);
6263        assert_eq!(&"a, hello, c"[m2[0].start..m2[0].end], "hello");
6264    }
6265
6266    /// `^` selects the token that leads its line, which separates a word
6267    /// being used from the same word mentioned later in the line.
6268    #[test]
6269    fn line_start_anchor_selects_only_line_leading_tokens() {
6270        let input = "cat file\nrun cat\ncat again";
6271        let m = run(r#"^ "cat""#, input);
6272        assert_eq!(m.len(), 2, "two lines LEAD with cat, one merely mentions it");
6273        assert_eq!(m[0].start, 0);
6274        assert_eq!(&input[m[1].start..m[1].end], "cat");
6275        assert!(m[1].start > input.find("run").unwrap(), "the third line's cat");
6276
6277        // Indentation does not stop a token leading its line.
6278        assert_eq!(run(r#"^ "cat""#, "   \n\t cat x").len(), 1);
6279        // Without the anchor every occurrence matches.
6280        assert_eq!(run(r#""cat""#, input).len(), 3);
6281        // A token that never leads a line is never selected.
6282        assert_eq!(run(r#"^ "cat""#, "run cat here").len(), 0);
6283    }
6284
6285    /// `$` is the mirror: the token that ends its line.
6286    #[test]
6287    fn line_end_anchor_selects_only_line_trailing_tokens() {
6288        let input = "run cat\ncat file\nx cat";
6289        let m = run(r#"$ "cat""#, input);
6290        assert_eq!(m.len(), 2, "two lines END with cat");
6291        assert_eq!(run(r#"$ "cat""#, "cat trailing spaces   ").len(), 0);
6292        assert_eq!(run(r#"$ "cat""#, "ends with cat   ").len(), 1, "trailing space still ends the line");
6293    }
6294
6295    /// Composed with alternation, the anchors express "at a position where
6296    /// a command may begin" for line-oriented input, without the pattern
6297    /// language needing to know any particular shell's syntax.
6298    #[test]
6299    fn anchors_compose_with_alternation_into_a_position_class() {
6300        let pat = r#"(^ | "|" | ";") "cat""#;
6301        assert_eq!(run(pat, "cat x").len(), 1, "start of input");
6302        assert_eq!(run(pat, "a | cat x").len(), 1, "after a pipe");
6303        assert_eq!(run(pat, "a ; cat x").len(), 1, "after a separator");
6304        assert_eq!(run(pat, "echo the cat sat").len(), 0, "mid-line mention");
6305    }
6306
6307    /// Both anchors hold at once for a token that is alone on its line.
6308    #[test]
6309    fn a_token_alone_on_its_line_both_leads_and_ends_it() {
6310        assert_eq!(run(r#"^ $ "cat""#, "x\ncat\ny").len(), 1);
6311        assert_eq!(run(r#"^ $ "cat""#, "x\ncat y\nz").len(), 0);
6312    }
6313
6314    /// Byte spans of a match list, for comparing one engine against another
6315    /// without asserting on the registers each happened to bind.
6316    fn byte_spans(ms: &[Span]) -> Vec<(usize, usize)> {
6317        ms.iter().map(|m| (m.start(), m.end())).collect()
6318    }
6319
6320    /// A greedy repetition over a lazily quantified body reaches a position
6321    /// twice: once inside a single long body match, and again as two short
6322    /// ones. The second is the derivation leftmost-first prefers, so the
6323    /// repetition must keep it rather than whichever iteration arrived first.
6324    #[test]
6325    fn a_greedy_loop_over_a_lazy_body_keeps_the_preferred_derivation() {
6326        let p = parse("(.+?)+").unwrap();
6327        let input = "956 116 bar";
6328        let set = scan(&p, input.as_bytes());
6329        let single = crate::nfa::scan_nfa(&p, input.as_bytes())
6330            .expect("the single-pass engine takes this pattern");
6331        assert_eq!(byte_spans(&set), byte_spans(&single), "the two engines rank this alike");
6332        assert_eq!(set.len(), 1, "one match spanning the input, not one per token");
6333    }
6334
6335    /// Taking a greedy option and letting a nullable body match empty
6336    /// outranks declining the option, because continuing sorts below stopping
6337    /// where the two differ. Each match is then the single token the leading
6338    /// atom consumes.
6339    #[test]
6340    fn a_greedy_option_prefers_a_nullable_body_matching_empty() {
6341        let p = parse(". (.{0,2}?)?").unwrap();
6342        let input = "488 foo 786 qux ";
6343        let set = scan(&p, input.as_bytes());
6344        let single = crate::nfa::scan_nfa(&p, input.as_bytes())
6345            .expect("the single-pass engine takes this pattern");
6346        assert_eq!(byte_spans(&set), byte_spans(&single), "the two engines rank this alike");
6347        assert_eq!(set.len(), 4, "one match per token, not one per two");
6348    }
6349}