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(®s, 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(®ion));
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(®ion, hay.as_bytes()),
5220 crate::scan(®ion, 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}