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