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