Skip to main content

trex/
cursor.rs

1//! Matches taken one at a time, and the operations that stop before the end.
2//!
3//! [`crate::scan`] returns every match. A caller that wants the first, or the
4//! first three, or the text between matches, pays for all of them and discards
5//! what it did not want. The regex crate divides the same surface into `find`,
6//! `find_iter`, `split`, `replacen` and the rest; each is this cursor with a
7//! different stopping rule, which is why they live together here rather than
8//! as separate walks over the input.
9//!
10//! # What stopping early buys, and what it does not
11//!
12//! A match over bytes costs one pass, so stopping the pass early is the whole
13//! saving. A match over tokens costs a lex and then a walk, the lex runs at a
14//! pattern-independent rate, and the walk is the cheaper half. So a cursor
15//! that stops the walk early and lexes the input in full saves the cheaper
16//! half only: correct, and not yet fast.
17//!
18//! That is deliberate and it is the first of two steps. This cursor is the
19//! surface and the correctness contract - every operation below agrees span
20//! for span with the equivalent over [`crate::scan`], which is what its tests
21//! assert. A source that lexes a block at a time goes underneath it next,
22//! against this one as the control arm.
23//!
24//! The routes are the exception already: a pattern a byte route answers never
25//! reaches the lexer, here or in [`crate::scan`], because both take the same
26//! ladder in [`crate::engine::routed_spans`].
27
28use crate::ast::Pattern;
29use crate::engine::Span;
30
31/// Where a cursor's matches come from.
32enum Source {
33    /// A route answered without the engine, and hands over every match at
34    /// once. Position is an index into them.
35    ///
36    /// These routes do not lex the input, so taking all of their matches is
37    /// not the cost that taking all of the engine's would be.
38    Routed { spans: Vec<Span>, at: usize },
39    /// The single-pass engine, held between matches so the next one resumes
40    /// rather than restarts.
41    ///
42    /// Boxed because a held walk owns its token stream, its compiled program
43    /// and two thread lists, and an enum is as large as its largest variant:
44    /// inline, every routed cursor would carry the walk's size without ever
45    /// holding one.
46    Walk(Box<crate::nfa::SerialWalk<crate::nfa::OwnedStream>>),
47}
48
49/// The matches of one pattern over one input, taken in order and on demand.
50///
51/// Leftmost and non-overlapping, the same selection [`crate::scan`] makes:
52/// collecting a cursor and scanning give the same spans in the same order.
53pub struct Cursor<'h> {
54    input: &'h [u8],
55    source: Source,
56    /// The start byte of every significant token, ascending, built only if a
57    /// caller asks for a token extent from a source that did not lex.
58    ///
59    /// A byte route answers without the lexer, which is the whole of what it
60    /// is for, so counting tokens for it costs the lex the route avoided.
61    /// That cost is paid on the first extent asked for and not before, and
62    /// never at all by a caller that only wants spans.
63    starts: Option<Vec<usize>>,
64}
65
66impl<'h> Cursor<'h> {
67    /// A cursor over every match of `pattern` in `input`.
68    #[must_use]
69    pub fn new(pattern: &Pattern, input: &'h [u8]) -> Self {
70        if let Some(shapes) = crate::library::shapes_for(pattern) {
71            return Cursor::over_library_kinds(pattern, input, &shapes);
72        }
73        if let Some(spans) = crate::engine::routed_spans(pattern, input) {
74            return Cursor { input, source: Source::Routed { spans, at: 0 }, starts: None };
75        }
76        Cursor::past_the_routes(pattern, input)
77    }
78
79    /// A cursor for a pattern naming a library kind, whose tokens only a lex
80    /// under the library's shapes produces: the matches arrive together from
81    /// that lex, with the token starts it saw.
82    fn over_library_kinds(pattern: &Pattern, input: &'h [u8], shapes: &crate::custom::ShapeSet) -> Self {
83        let blobs = crate::lexer::blob_runs(input);
84        let toks = crate::lexer::lex_with_shapes(input, &blobs, shapes, 0);
85        let spans = match crate::nfa::scan_nfa_over(pattern, input, &toks) {
86            Some(spans) => spans,
87            None => crate::engine::scan_tokens_from(pattern, input, &toks, 0),
88        };
89        let starts =
90            toks.iter().filter(|t| t.is_significant()).map(crate::token::Token::start).collect();
91        Cursor { input, source: Source::Routed { spans, at: 0 }, starts: Some(starts) }
92    }
93
94    /// A cursor over spans the scan found together, for a caller that will take
95    /// all of them.
96    ///
97    /// [`Self::new`] holds a resumable walk where no route answers, which stops
98    /// at each match and carries its state to the next. That is what a caller
99    /// taking one match wants and it costs the cores: the scan dispatches its
100    /// attempt per anchor across them and a walk yielding matches in order
101    /// cannot. On the comparison's corpus a scan finds fifty thousand matches in
102    /// 11.7 ms where the walk takes 44.1, and both lex once.
103    ///
104    /// So a caller that will exhaust the cursor takes the scan instead. The
105    /// spans are the same and in the same order, which is this type's own
106    /// contract.
107    fn over_every_match(pattern: &Pattern, input: &'h [u8]) -> Self {
108        let spans = crate::engine::scan(pattern, input);
109        Cursor { input, source: Source::Routed { spans, at: 0 }, starts: None }
110    }
111
112    /// A cursor for a pattern whose routes have already been tried and
113    /// declined, so the ladder is not walked twice.
114    fn past_the_routes(pattern: &Pattern, input: &'h [u8]) -> Self {
115        // The walk lexes only if it takes the pattern, so a refusal here costs
116        // the compile and not the input.
117        if let Some(walk) = crate::nfa::SerialWalk::over(pattern, input) {
118            return Cursor { input, source: Source::Walk(Box::new(walk)), starts: None };
119        }
120        // A balanced group or a field node, which only the set-reachability
121        // engine advances. It has no resumable form, so its matches arrive
122        // together and the cursor hands them out.
123        let spans = crate::engine::scan_set_reachability(pattern, input);
124        Cursor { input, source: Source::Routed { spans, at: 0 }, starts: None }
125    }
126
127    /// The next match and how many significant tokens it spans.
128    ///
129    /// A match's length in tokens is the unit this engine actually works in -
130    /// a match is bounded in tokens and a token is not bounded in bytes, so a
131    /// blob run of several kilobytes is one of them. [`Span`] reports bytes
132    /// and is deliberately eight of them, so the count rides here rather than
133    /// on the span.
134    ///
135    /// The engine walk names a match by the token indices it runs between, so
136    /// the count is free there. A byte route never lexed, so the first call
137    /// against one builds an index of significant-token starts and later
138    /// calls read it.
139    pub fn next_extent(&mut self) -> Option<(Span, usize)> {
140        if let Source::Walk(w) = &mut self.source {
141            return w.next_span_and_extent(self.input);
142        }
143        let s = self.next()?;
144        // Read out before the index is borrowed, so building it does not hold
145        // the cursor while reading the cursor's own input.
146        let input = self.input;
147        let starts = self.starts.get_or_insert_with(|| {
148            crate::parallel_lex::lex_parallel(input)
149                .iter()
150                .filter(|t| t.is_significant())
151                .map(crate::token::Token::start)
152                .collect()
153        });
154        let first = starts.partition_point(|&b| b < s.start());
155        let past = starts.partition_point(|&b| b < s.end());
156        Some((s, past - first))
157    }
158}
159
160impl Iterator for Cursor<'_> {
161    type Item = Span;
162
163    fn next(&mut self) -> Option<Span> {
164        match &mut self.source {
165            Source::Routed { spans, at } => {
166                let s = spans.get(*at).copied();
167                if s.is_some() {
168                    *at += 1;
169                }
170                s
171            }
172            Source::Walk(w) => w.next_span(self.input),
173        }
174    }
175}
176
177/// Where `pattern` first matches in `input`, or `None` where it does not.
178///
179/// The counterpart of the regex crate's `find`, and the answer
180/// `scan(pattern, input).first().copied()` gives without finding the rest.
181///
182/// Three sources, in the order of what they cost. A byte route answers from
183/// the input's bytes and never lexes. Failing that, a prefix is lexed and
184/// widened until it settles the answer, which is the saving on an input whose
185/// first match is not near its end. Failing that - an anchor that reads the
186/// whole stream, or a pattern the single-pass engine does not take - the whole
187/// input is lexed and the cursor takes its first match.
188#[must_use]
189pub fn find(pattern: &Pattern, input: &[u8]) -> Option<Span> {
190    if crate::library::shapes_for(pattern).is_some() {
191        crate::trace::rung("find", "a lex under the library's shapes", input.len());
192        return Cursor::new(pattern, input).next();
193    }
194    // The routed answer read to the first match and no further, where the route
195    // can do that. Taking every match and discarding all but one reads the
196    // whole input to answer about a match that is often in its first bytes.
197    if let Some(first) = crate::engine::routed_first(pattern, input) {
198        // Named for what this rung knows, which is that neither the prefix
199        // nor the cursor ran. Which route answered is the inner rung's to
200        // report: the windows route lexes at each opening literal and reaches
201        // here as well as the byte routes do.
202        crate::trace::rung("find", "a route, not the cursor", input.len());
203        return first;
204    }
205    if let Some(answer) = crate::prefilter::find_by_growing_prefix(pattern, input) {
206        crate::trace::rung("find", "a widening prefix", input.len());
207        return answer;
208    }
209    crate::trace::rung("find", "the cursor over a whole lex", input.len());
210    Cursor::past_the_routes(pattern, input).next()
211}
212
213/// [`find`] with the whole input lexed up front and no widening prefix.
214///
215/// The arm [`find`] is measured against: same routes, same engine, same
216/// selection, and the only difference is how much of the input reaches the
217/// lexer. Keeping it callable is what lets the two be timed side by side on
218/// one corpus instead of across two builds.
219#[doc(hidden)]
220#[must_use]
221pub fn find_by_full_lex(pattern: &Pattern, input: &[u8]) -> Option<Span> {
222    Cursor::new(pattern, input).next()
223}
224
225/// Every match of `pattern` in `input`, in order, taken as they are asked for.
226///
227/// The lazy form of [`crate::scan`]: the same spans in the same order, without
228/// a vector holding all of them.
229pub fn find_iter<'h>(pattern: &Pattern, input: &'h [u8]) -> Cursor<'h> {
230    Cursor::new(pattern, input)
231}
232
233/// Where `pattern` first matches at or after byte `at`.
234///
235/// Not the same question as the first match of the whole input that happens to
236/// start at or after `at`: the leftmost, non-overlapping selection from `at`
237/// can hold a match that overlaps one the selection from zero preferred, so
238/// this re-runs the selection from there rather than filtering.
239///
240/// Not for walking an input. Each call builds its own byte reader, whose quote
241/// scan runs from the input's start to `at`, so a loop that advances `at`
242/// through every match reads those bytes again for each one. A few calls cost
243/// little and a walk is quadratic. [`find_iter`] and [`crate::captures_iter`]
244/// hold their state between matches and are what a walk wants.
245#[must_use]
246pub fn find_at(pattern: &Pattern, input: &[u8], at: usize) -> Option<Span> {
247    if let Some(shapes) = crate::library::shapes_for(pattern) {
248        return crate::engine::scan_with_shapes_from(pattern, input, &shapes, at).into_iter().next();
249    }
250    // The routes whose matches are whole tokens of a fixed count cannot
251    // overlap, so the ones at or after `at` are the ones that begin there.
252    // Taking them costs no lex, and where the route has an anchored form the
253    // scan begins at `at` rather than reading the bytes before it to find
254    // matches the caller has already excluded.
255    // The rung is named by routed_first_at itself, which knows whether a byte
256    // route answered or the whole positional set was filtered.
257    if let Some(first) = crate::engine::routed_first_at(pattern, input, at) {
258        return first;
259    }
260    // A prefix lexed and widened until it settles the answer, as [`find`] takes
261    // for the same question from zero. A caller asks from where it last
262    // stopped, so the prefix that reaches past the offset is usually small.
263    if let Some(answer) = crate::prefilter::find_at_by_growing_prefix(pattern, input, at) {
264        crate::trace::rung("find_at", "a widening prefix from the offset", input.len());
265        return answer;
266    }
267    if let Some(mut w) = crate::nfa::SerialWalk::over(pattern, input) {
268        crate::trace::rung("find_at", "the held walk over a whole fused lex", input.len());
269        w.seek(at);
270        return w.next_span(input);
271    }
272    // A balanced or field pattern, which the walk declines and the set engine
273    // takes. The walk lexes nothing when it declines, so this is the only lex
274    // on this path.
275    let toks = crate::parallel_lex::lex_parallel(input);
276    let start = toks.partition_point(|t| t.start() < at);
277    crate::engine::scan_tokens_from(pattern, input, &toks, start).into_iter().next()
278}
279
280/// Whether `pattern` matches anywhere at or after byte `at`.
281#[must_use]
282pub fn is_match_at(pattern: &Pattern, input: &[u8], at: usize) -> bool {
283    find_at(pattern, input, at).is_some()
284}
285
286/// Where a match cursor's matches come from.
287enum MatchSource<'h> {
288    /// A route answered, so the spans arrived together and their registers
289    /// were resolved together.
290    ///
291    /// Held as the vector's own iterator rather than a vector and an index: the
292    /// cursor owns these matches and is the only thing that will ever read
293    /// them, so handing one out is a move. Indexing meant cloning a match a
294    /// caller was about to be given - a capture list and a name per register,
295    /// measured at 4.058 ms of the 14.436 that `captures_iter` cost over fifty
296    /// thousand matches.
297    Resolved(std::vec::IntoIter<crate::engine::Match>),
298    /// A route answered a pattern whose program is a flat run of atoms, so the
299    /// registers came back inline and a match is built only when it is asked
300    /// for.
301    ///
302    /// [`MatchSource::Resolved`] owns a vector a match before the caller has
303    /// asked for any of them, which on fifty thousand matches is fifty thousand
304    /// live allocations where one would do. These records allocate nothing, and
305    /// the one match this hands out is freed before the next is built, so the
306    /// allocator serves them all from the same block.
307    Flat {
308        ms: std::vec::IntoIter<crate::nfa::FlatMatch>,
309        names: std::sync::Arc<[String]>,
310    },
311    /// A pattern that binds nothing, so every match carries no registers and
312    /// there is nothing to resolve. Spans come from an ordinary cursor and
313    /// each is widened to a match with an empty capture list.
314    Plain(Box<Cursor<'h>>),
315    /// The single-pass engine, held between matches. Its save slots are
316    /// resolved as each match is taken. Boxed for the reason [`Source`] gives.
317    Walk(Box<crate::nfa::SerialWalk<crate::nfa::OwnedStream>>),
318}
319
320/// The matches of one pattern over one input with the registers each bound,
321/// taken in order and on demand.
322///
323/// [`Cursor`] over [`crate::engine::Match`] rather than [`Span`]: the same
324/// selection, and each match carries what it bound.
325pub struct MatchCursor<'h> {
326    input: &'h [u8],
327    source: MatchSource<'h>,
328    /// The match [`MatchCursor::next_ref`] last took, held so the view it hands
329    /// back has something to borrow. Untouched by the owning iterator.
330    held: Option<Held>,
331}
332
333/// One match kept inside the cursor for a borrowed view to point at.
334enum Held {
335    /// A flat record, whose registers are inline and whose names belong to the
336    /// source rather than to it.
337    Flat(crate::nfa::FlatMatch),
338    /// A match already built, whose names it owns a share of.
339    Owned(crate::engine::Match),
340    /// A span from a pattern that binds nothing.
341    Span(Span),
342}
343
344/// One match, borrowing the cursor that produced it.
345///
346/// [`crate::Match`] owns a share of the pattern's register names, which costs
347/// the two atomics of a clone and a drop and gives every match a destructor to
348/// run. Over fifty thousand matches that is 30.2 nanoseconds each, measured
349/// against the same loop building matches that bind nothing. A caller that
350/// reads each match in turn and keeps none of them pays neither here.
351///
352/// Borrowed from the cursor and not from the input, so a view is invalidated by
353/// asking for the next match. [`MatchRef::to_match`] takes an owned copy where
354/// one is wanted.
355pub struct MatchRef<'c> {
356    /// Inclusive start byte offset of the match in the input.
357    pub start: usize,
358    /// Exclusive end byte offset of the match in the input.
359    pub end: usize,
360    regs: &'c [Span],
361    names: &'c [String],
362}
363
364impl<'c> MatchRef<'c> {
365    /// The match's own span.
366    #[must_use]
367    pub fn span(&self) -> Span {
368        Span { start: self.start as u32, end: self.end as u32 }
369    }
370
371    /// The spans this match's registers bound, in the order [`Self::names`]
372    /// holds their names.
373    #[must_use]
374    pub fn captures(&self) -> &[Span] {
375        self.regs
376    }
377
378    /// The register names, in the order [`Self::captures`] holds their spans.
379    #[must_use]
380    pub fn names(&self) -> &[String] {
381        self.names
382    }
383
384    /// The bytes the register called `name` bound, or `None` where the pattern
385    /// has no such register.
386    #[must_use]
387    pub fn group<'h>(&self, name: &str, input: &'h [u8]) -> Option<&'h [u8]> {
388        let k = self.names.iter().position(|n| n == name)?;
389        self.regs.get(k).map(|s| &input[s.range()])
390    }
391
392    /// This match as an owned one, which is where the share of the names and
393    /// the destructor that come with it are paid for.
394    #[must_use]
395    pub fn to_match(&self) -> crate::engine::Match {
396        if self.names.is_empty() {
397            return crate::engine::Match::plain(self.start, self.end);
398        }
399        crate::engine::Match::bound(
400            self.start,
401            self.end,
402            crate::engine::Regs::from_slice(self.regs),
403            std::sync::Arc::from(self.names.to_vec()),
404        )
405    }
406}
407
408impl MatchCursor<'_> {
409    /// The next match, borrowing this cursor rather than owning a share of the
410    /// pattern's names.
411    ///
412    /// The lending counterpart of this cursor's [`Iterator`], for a caller that
413    /// reads each match in turn and keeps none. A view borrows the cursor, so
414    /// asking for the next match invalidates the one before it, which is why
415    /// this is a method and not an `Iterator`.
416    ///
417    /// Where a route answered the pattern the registers are already inline and
418    /// the names belong to the cursor, so no match is built and nothing is
419    /// cloned. Where the engine ran, a match was built to resolve its save
420    /// slots and this borrows that; the saving is the route's.
421    pub fn next_ref(&mut self) -> Option<MatchRef<'_>> {
422        let input = self.input;
423        self.held = match &mut self.source {
424            MatchSource::Flat { ms, .. } => ms.next().map(Held::Flat),
425            MatchSource::Resolved(ms) => ms.next().map(Held::Owned),
426            MatchSource::Plain(c) => c.next().map(Held::Span),
427            MatchSource::Walk(w) => w.next_match(input).map(Held::Owned),
428        };
429        let flat_names: &[String] = match &self.source {
430            MatchSource::Flat { names, .. } => names,
431            _ => &[],
432        };
433        match self.held.as_ref()? {
434            Held::Flat(m) => Some(MatchRef {
435                start: m.span.start(),
436                end: m.span.end(),
437                regs: &m.regs[..flat_names.len()],
438                names: flat_names,
439            }),
440            Held::Owned(m) => {
441                Some(MatchRef { start: m.start, end: m.end, regs: m.captures(), names: m.names() })
442            }
443            Held::Span(s) => {
444                Some(MatchRef { start: s.start(), end: s.end(), regs: &[], names: &[] })
445            }
446        }
447    }
448}
449
450impl Iterator for MatchCursor<'_> {
451    type Item = crate::engine::Match;
452
453    fn next(&mut self) -> Option<crate::engine::Match> {
454        match &mut self.source {
455            MatchSource::Resolved(ms) => ms.next(),
456            MatchSource::Flat { ms, names } => ms.next().map(|m| {
457                crate::engine::Match::bound(
458                    m.span.start(),
459                    m.span.end(),
460                    crate::engine::Regs::from_slice(&m.regs[..names.len()]),
461                    names.clone(),
462                )
463            }),
464            MatchSource::Plain(c) => c.next().map(crate::engine::Match::from),
465            MatchSource::Walk(w) => w.next_match(self.input),
466        }
467    }
468}
469
470/// Every match of `pattern` in `input` with its captures, taken as they are
471/// asked for.
472///
473/// The lazy form of [`crate::captures`], and the counterpart of the regex
474/// crate's `captures_iter`. Where the engine runs, a match's registers are
475/// resolved from the save slots the walk already carried, so taking the first
476/// match resolves one match's worth rather than every match's.
477pub fn captures_iter<'h>(pattern: &Pattern, input: &'h [u8]) -> MatchCursor<'h> {
478    // A pattern that binds nothing has no registers to resolve, so the engine
479    // has nothing to be run for. Spans come from an ordinary cursor and each
480    // widens to a match with an empty capture list.
481    if !pattern.binds() {
482        let c = Cursor::new(pattern, input);
483        return MatchCursor { input, source: MatchSource::Plain(Box::new(c)), held: None };
484    }
485    // A library kind lives only in a lex under the library's shapes, which
486    // the scan and the resolution both take; the matches arrive together.
487    if crate::library::shapes_for(pattern).is_some() {
488        let spans = crate::engine::scan(pattern, input);
489        let ms = crate::engine::captures(pattern, input, &spans);
490        return MatchCursor { input, source: MatchSource::Resolved(ms.into_iter()), held: None };
491    }
492    // One pass over the windows, keeping what each attempt bound, with the
493    // registers inline and the names held once here. Taking the spans from the
494    // ladder and resolving them afterwards lexes every window twice and runs
495    // every attempt twice, because the first pass throws the save slots away to
496    // report a span.
497    //
498    // Taken wherever `flat_shape` accepts the program, including shapes the
499    // bytes do walk: for `"let" \W:v "="` this answers at 7.9091 ms against
500    // 10.9338 for the byte route below, so a walk over a match's bytes being
501    // cheaper than a lex does not settle which rung is cheaper.
502    if let Some((ms, names)) = crate::prefilter::scan_flat_by_literal_windows(pattern, input) {
503        crate::trace::rung("captures_iter", "windows, registers inline", input.len());
504        return MatchCursor { input, source: MatchSource::Flat { ms: ms.into_iter(), names }, held: None };
505    }
506    // No such guard here, measured rather than assumed: this rung answers
507    // `"let" \W:v "="` at 8.7636 ms where the byte-route rung below answers it
508    // at 10.9338, so the vector of matches it builds is worth what it costs on
509    // a shape the bytes do walk.
510    if let Some(ms) = crate::prefilter::scan_captures_by_literal_windows(pattern, input) {
511        crate::trace::rung("captures_iter", "windows, matched and resolved at once", input.len());
512        return MatchCursor { input, source: MatchSource::Resolved(ms.into_iter()), held: None };
513    }
514    if let Some(spans) = crate::engine::routed_spans(pattern, input) {
515        // The route never lexed, and the bytes of a match say where its atoms'
516        // tokens are, so this resolves without lexing either.
517        if let Some((ms, names)) =
518            crate::prefilter::flat_captures_by_byte_bounds(pattern, input, &spans)
519        {
520            crate::trace::rung("captures_iter", "a byte route, registers off the bytes", input.len());
521            return MatchCursor { input, source: MatchSource::Flat { ms: ms.into_iter(), names }, held: None };
522        }
523        if let Some((ms, names)) = crate::prefilter::flat_captures_by_windows(pattern, input, &spans) {
524            crate::trace::rung("captures_iter", "a byte route, registers inline", input.len());
525            return MatchCursor { input, source: MatchSource::Flat { ms: ms.into_iter(), names }, held: None };
526        }
527        let ms = crate::engine::captures(pattern, input, &spans);
528        crate::trace::rung("captures_iter", "a byte route, resolved together", input.len());
529        return MatchCursor { input, source: MatchSource::Resolved(ms.into_iter()), held: None };
530    }
531    if let Some(walk) = crate::nfa::SerialWalk::over(pattern, input) {
532        crate::trace::rung("captures_iter", "the held walk over a whole fused lex", input.len());
533        return MatchCursor { input, source: MatchSource::Walk(Box::new(walk)), held: None };
534    }
535    let spans = crate::engine::scan_set_reachability(pattern, input);
536    let ms = crate::engine::captures(pattern, input, &spans);
537    MatchCursor { input, source: MatchSource::Resolved(ms.into_iter()), held: None }
538}
539
540/// The captures of the first match of `pattern` in `input`.
541///
542/// The counterpart of the regex crate's `captures`, which resolves one
543/// match's registers rather than every match's.
544///
545/// It takes the same widening prefix [`find`] does, and resolves the match's
546/// registers over the tokens that prefix holds - the same tokens the match
547/// was found over. Asking a full lex for them would cost the whole input to
548/// answer about a match a sixty-fourth of a megabyte already settled.
549#[must_use]
550pub fn captures_first(pattern: &Pattern, input: &[u8]) -> Option<crate::engine::Match> {
551    if crate::library::shapes_for(pattern).is_some() {
552        let first = find(pattern, input)?;
553        return crate::engine::captures(pattern, input, &[first]).into_iter().next();
554    }
555    if let Some(first) = crate::engine::routed_first(pattern, input) {
556        let first = first?;
557        return crate::engine::captures(pattern, input, &[first]).into_iter().next();
558    }
559    let settled = crate::prefilter::settle_first_from_a_prefix(pattern, input, |s, toks| {
560        crate::nfa::captures_over(pattern, input, toks, &[s]).and_then(|v| v.into_iter().next())
561    });
562    match settled {
563        // The prefix settled it and resolved what the match bound.
564        Some(Some(Some(m))) => Some(m),
565        // The prefix reached the whole input and found nothing.
566        Some(None) => None,
567        // Either no prefix can settle this pattern, or one found a match and
568        // this engine would not resolve its registers. Both are questions for
569        // the full lex rather than answers, and reporting no match for either
570        // would report absence where a match was actually found.
571        Some(Some(None)) | None => captures_iter(pattern, input).next(),
572    }
573}
574
575/// The captures of the first match of `pattern` at or after byte `at`.
576///
577/// The counterpart of the regex crate's `captures_at`, standing to
578/// [`captures_first`] as [`find_at`] stands to [`find`]. The bytes before `at`
579/// are still read, so a backward assertion and a start anchor see what precedes
580/// the position; slicing the input instead would hide it from them and report a
581/// different answer.
582///
583/// It takes the same ladder as [`find_at`] and resolves one match's registers
584/// at the end of it, so no route is walked twice and no lex is paid for twice.
585#[must_use]
586pub fn captures_at(pattern: &Pattern, input: &[u8], at: usize) -> Option<crate::engine::Match> {
587    // Nothing bound means nothing to resolve, so the span answer is the whole
588    // answer and the engine has no reason to run.
589    if !pattern.binds() {
590        return find_at(pattern, input, at).map(crate::engine::Match::from);
591    }
592    if crate::library::shapes_for(pattern).is_some() {
593        let first = find_at(pattern, input, at)?;
594        return crate::engine::captures(pattern, input, &[first]).into_iter().next();
595    }
596    // Only the routes whose matches cannot overlap may be filtered by the
597    // caller's position, which is why the ladder is split at that line. An
598    // anchored ask takes the anchored form, which begins its scan at `at`.
599    if let Some(first) = crate::engine::routed_first_at(pattern, input, at) {
600        let first = first?;
601        return crate::engine::captures(pattern, input, &[first]).into_iter().next();
602    }
603    // The prefix finds the span, and resolving one span costs the region around
604    // it rather than a second reading of the input.
605    if let Some(first) = crate::prefilter::find_at_by_growing_prefix(pattern, input, at) {
606        crate::trace::rung("captures_at", "a widening prefix from the offset", input.len());
607        let first = first?;
608        return crate::engine::captures(pattern, input, &[first]).into_iter().next();
609    }
610    if let Some(mut w) = crate::nfa::SerialWalk::over(pattern, input) {
611        crate::trace::rung("captures_at", "the held walk over a whole fused lex", input.len());
612        w.seek(at);
613        return w.next_match(input);
614    }
615    // A balanced or field pattern, which the walk declines and the set engine
616    // takes. The walk lexes nothing when it declines, so this is the only lex.
617    let toks = crate::parallel_lex::lex_parallel(input);
618    let start = toks.partition_point(|t| t.start() < at);
619    let span = crate::engine::scan_tokens_from(pattern, input, &toks, start).into_iter().next()?;
620    crate::engine::captures_over(pattern, input, &toks, &[span]).into_iter().next()
621}
622
623/// How far the soonest-ending match of `pattern` in `input` reaches, as a
624/// byte offset, or `None` where nothing matches.
625///
626/// The counterpart of the regex crate's `shortest_match`, and it stands in the
627/// same relation to [`find`] there as here: the end reported can be earlier
628/// than the match [`find`] returns, because the question is where a match is
629/// first known to have occurred rather than which match the pattern prefers.
630/// Where a pattern has one way to match, the two agree.
631///
632/// A pattern the single-pass engine does not take - a balanced group, a field
633/// node - has no earliest-accept to report, so this answers from the ordinary
634/// scan and the two ends are the same.
635#[must_use]
636pub fn shortest_match(pattern: &Pattern, input: &[u8]) -> Option<usize> {
637    // A library kind is one token with one way to match, so the first match
638    // under the library's lex ends where the soonest one does.
639    if crate::library::shapes_for(pattern).is_some() {
640        return find(pattern, input).map(|s| s.end());
641    }
642    // A byte route reports whole-token matches with one way to match each, so
643    // its first match is also the soonest-ending one, and it is read to that
644    // match and no further.
645    if let Some(first) = crate::engine::routed_first_positional(pattern, input) {
646        crate::trace::rung("shortest_match", "a positional byte route, no lex", input.len());
647        return first.map(|s| s.end());
648    }
649    // The windows a required literal opens, each asked where a match is first
650    // known to have occurred. The selective routes cannot answer this caller
651    // from their matches, since a later match can end sooner than a selected
652    // one, but a window answers the question directly over its own tokens.
653    match crate::prefilter::shortest_end_in_windows(pattern, input) {
654        Ok(end) => {
655            crate::trace::rung("shortest_match", "the windows a literal opens", input.len());
656            return end;
657        }
658        // Every refusal falls to the next rung, so the ladder itself needs no
659        // more than that one was made. The reason is counted rather than
660        // dropped: a pattern the windows can never take and one this input
661        // happens to refuse are the same fall from here and different facts,
662        // and the counter is what tells them apart afterwards.
663        Err(why) => {
664            crate::trace::rung("shortest_match", &format!("the windows refuse: {why}"), input.len());
665        }
666    }
667    // A prefix lexed and widened until it settles the answer, as [`find`] takes
668    // for its own question. A match beginning past the cut ends past it, so an
669    // end clear of the cut is already the soonest.
670    if let Some(end) = crate::prefilter::shortest_end_by_growing_prefix(pattern, input) {
671        crate::trace::rung("shortest_match", "a widening prefix", input.len());
672        return end;
673    }
674    // The engine over the significant stream in the lexer's parts. It lexes
675    // only once the pattern has compiled, so a pattern it declines costs the
676    // compile and not the input, and the stitched stream below is reached only
677    // by a pattern it does not take.
678    if let Some(end) = crate::nfa::shortest_end_from_byte(pattern, input, 0) {
679        crate::trace::rung("shortest_match", "the engine over a whole fused lex", input.len());
680        return Some(end);
681    }
682    if crate::nfa::compile_pattern(pattern).is_some() {
683        // The engine took the pattern and found nothing, which is an answer.
684        crate::trace::rung("shortest_match", "the engine, which found nothing", input.len());
685        return None;
686    }
687    crate::trace::rung("shortest_match", "the set engine over a stitched lex", input.len());
688    let toks = crate::parallel_lex::lex_parallel(input);
689    crate::engine::scan_tokens_from(pattern, input, &toks, 0).first().map(Span::end)
690}
691
692/// How far the soonest-ending match of `pattern` at or after byte `at` reaches.
693///
694/// The counterpart of the regex crate's `shortest_match_at`. The bytes before
695/// `at` are read as [`captures_at`] reads them, so the position selects where a
696/// match may begin and never what the input is.
697#[must_use]
698pub fn shortest_match_at(pattern: &Pattern, input: &[u8], at: usize) -> Option<usize> {
699    if crate::library::shapes_for(pattern).is_some() {
700        return find_at(pattern, input, at).map(|s| s.end());
701    }
702    if let Some(first) = crate::engine::routed_first_at(pattern, input, at) {
703        return first.map(|s| s.end());
704    }
705    // A prefix widened until it settles the answer, as [`shortest_match`] takes
706    // for the same question from zero.
707    if let Some(end) = crate::prefilter::shortest_end_by_growing_prefix_from(pattern, input, at) {
708        crate::trace::rung("shortest_match_at", "a widening prefix from the offset", input.len());
709        return end;
710    }
711    if let Some(end) = crate::nfa::shortest_end_from_byte(pattern, input, at) {
712        crate::trace::rung("shortest_match_at", "the engine over a whole fused lex", input.len());
713        return Some(end);
714    }
715    if crate::nfa::compile_pattern(pattern).is_some() {
716        // The engine took the pattern and found nothing, which is an answer.
717        crate::trace::rung("shortest_match_at", "the engine, which found nothing", input.len());
718        return None;
719    }
720    crate::trace::rung("shortest_match_at", "the set engine over a stitched lex", input.len());
721    let toks = crate::parallel_lex::lex_parallel(input);
722    let start = toks.partition_point(|t| t.start() < at);
723    crate::engine::scan_tokens_from(pattern, input, &toks, start).first().map(Span::end)
724}
725
726/// The pieces of `input` between the matches of `pattern`.
727///
728/// The gaps a [`Cursor`] leaves, which is why it is the same walk: a piece
729/// ends where the next match begins and the next piece starts where that match
730/// ended. A match at the very start or the very end yields an empty piece on
731/// that side, so the pieces always number one more than the matches and
732/// rejoining them with the matched text gives back the input.
733/// Where a split's separators come from.
734enum Separators<'h> {
735    /// A cursor, which either holds every match or resumes a walk.
736    Cursor(Box<Cursor<'h>>),
737    /// One separator at a time from an ascending offset, over a reader held
738    /// across the pieces so the quote scan runs once for all of them.
739    ///
740    /// For a limited split whose pattern an anchored byte route takes: a cursor
741    /// would find every match in the input to hand over three. The pattern is
742    /// cloned rather than borrowed so [`Split`] keeps the one lifetime its
743    /// callers already pass. The reader is boxed: it is most of this variant,
744    /// and held inline it would make every other variant as large, a split
745    /// allocating it once where a source moved by value copies it whole.
746    Anchored { pattern: Pattern, reader: Box<Option<crate::prefilter::ByteReader<'h>>>, at: usize },
747    /// A prefix of the input, lexed once and widened only when the matches it
748    /// settled run out.
749    ///
750    /// For a pattern with no literal to anchor a window at and no anchored byte
751    /// route: a split into four pieces of a pattern matching at the first token
752    /// otherwise finds every match in the input to report three. Widening by
753    /// doubling makes the whole walk a geometric series over the prefix sizes
754    /// it actually needed, where asking a fresh prefix per piece is quadratic.
755    Prefix {
756        pattern: Pattern,
757        settled: std::vec::IntoIter<Span>,
758        covered: usize,
759        /// The byte past which a match has not been handed out yet. A wider
760        /// prefix re-reports everything a narrower one did, and this is what
761        /// tells the new matches from the repeats - not where the narrow prefix
762        /// ended, because a match starting inside it may have reached into the
763        /// tokens that prefix held in reserve and so was never handed out.
764        handed: usize,
765    },
766    /// The held walk, which the anchored source becomes where a route refuses
767    /// part-way through.
768    Walk(Box<crate::nfa::SerialWalk<crate::nfa::OwnedStream>>),
769}
770
771impl<'h> Separators<'h> {
772    /// The next separator at or after wherever this source has reached.
773    ///
774    /// # Panics
775    ///
776    /// Never in practice: see the reasoning at the fallback below.
777    fn next(&mut self, input: &'h [u8]) -> Option<Span> {
778        // The refusal replaces the source it was read from, so the decision is
779        // made inside the borrow and acted on outside it.
780        let (pattern, from) = match self {
781            Separators::Cursor(c) => return c.next(),
782            Separators::Walk(w) => return w.next_span(input),
783            Separators::Prefix { pattern, settled, covered, handed } => loop {
784                if let Some(s) = settled.next() {
785                    // An empty match would leave the offset where it is, so
786                    // step past it as the walk does.
787                    *handed = s.end().max(s.start() + 1);
788                    return Some(s);
789                }
790                if *covered >= input.len() {
791                    // The prefix reached the whole input and its matches are
792                    // exhausted, so there are none left.
793                    return None;
794                }
795                *covered = (*covered * 2).min(input.len());
796                let Some((found, _)) =
797                    crate::prefilter::settled_prefix_matches(pattern, input, *covered)
798                else {
799                    // The refusal is the same at every width, so this is the
800                    // shape refusing rather than the widening: the walk below
801                    // takes it from where this stopped.
802                    break (pattern.clone(), *handed);
803                };
804                let fresh: Vec<Span> =
805                    found.into_iter().filter(|s| s.start() >= *handed).collect();
806                *settled = fresh.into_iter();
807            },
808            Separators::Anchored { pattern, reader, at } => {
809                // The byte routes first, over the reader they share; then the
810                // windows, which read the input only as far as the separator
811                // they report. Both answer one ask at a time, and a refusal
812                // from both is what the walk below is for.
813                let answered = crate::engine::routed_first_at_reading(pattern, input, *at, reader)
814                    .or_else(|| {
815                        crate::prefilter::first_by_literal_windows_at(pattern, input, *at)
816                    });
817                match answered {
818                    Some(found) => {
819                        let Some(s) = found else {
820                            // No match at or after the offset is a verdict over
821                            // the rest of the input, so the offset goes to the
822                            // end. Without that, a split of a pattern that is
823                            // absent asks the route again for every piece and
824                            // searches the whole input each time.
825                            *at = input.len();
826                            return None;
827                        };
828                        // An empty match would leave the offset where it is, so
829                        // step past it as the walk does.
830                        *at = s.end().max(s.start() + 1);
831                        return Some(s);
832                    }
833                    // The bytes settled every offset before this one and cannot
834                    // settle this one. Ending here would drop every separator
835                    // after it, and a cursor's selection from zero is not the
836                    // selection re-run from an offset, so neither will do.
837                    None => (pattern.clone(), *at),
838                }
839            }
840        };
841        // The walk seeked to the offset is what find_at would give from here:
842        // the same selection, re-run from the same place. It cannot decline,
843        // because a pattern an anchored byte route answered is a literal, an
844        // alternation of them, a line-anchored literal, a word then plain
845        // punctuation, a byte pattern or a bare kind atom - and none of those
846        // holds a balanced group, a field node or a named capture, which are
847        // the only things SerialWalk::over turns away.
848        let mut walk = crate::nfa::SerialWalk::over(&pattern, input)
849            .expect("a pattern an anchored byte route answered compiles for this engine");
850        walk.seek(from);
851        let next = walk.next_span(input);
852        *self = Separators::Walk(Box::new(walk));
853        next
854    }
855}
856
857pub struct Split<'h> {
858    separators: Separators<'h>,
859    input: &'h [u8],
860    last: usize,
861    /// Pieces this split may still yield. [`usize::MAX`] is no limit.
862    left: usize,
863    done: bool,
864    /// The bracket depth a match must sit at to separate, and the field that
865    /// answers what depth a byte is at. `None` splits on every match.
866    depth: Option<(crate::stress::StressField, u16)>,
867}
868
869impl<'h> Iterator for Split<'h> {
870    type Item = &'h [u8];
871
872    fn next(&mut self) -> Option<&'h [u8]> {
873        if self.done || self.left == 0 {
874            return None;
875        }
876        // The last piece a limit allows is the whole remainder, unsplit: that
877        // is what makes `splitn(k)` yield `k` pieces rather than `k` pieces
878        // and a silently discarded tail.
879        if self.left == 1 {
880            self.done = true;
881            return Some(&self.input[self.last..]);
882        }
883        loop {
884            match self.separators.next(self.input) {
885                Some(s) => {
886                    // A match at the wrong bracket depth is not a separator,
887                    // so it stays inside the piece being built rather than
888                    // ending it.
889                    if let Some((field, want)) = &self.depth
890                        && field.depth_at(s.start()) != *want
891                    {
892                        continue;
893                    }
894                    let piece = &self.input[self.last..s.start()];
895                    self.last = s.end();
896                    self.left = self.left.saturating_sub(1);
897                    return Some(piece);
898                }
899                None => {
900                    self.done = true;
901                    return Some(&self.input[self.last..]);
902                }
903            }
904        }
905    }
906}
907
908/// `input` split on every match of `pattern`.
909///
910/// Every match is a separator, so the cursor is exhausted by definition and
911/// takes [`Cursor::over_every_match`]: the resume a walk offers is worth nothing
912/// to a caller that will ask for all of them, and it is paid for in cores.
913/// [`splitn`] keeps the walk, since a caller naming a limit may stop early.
914pub fn split<'h>(pattern: &Pattern, input: &'h [u8]) -> Split<'h> {
915    Split {
916        separators: Separators::Cursor(Box::new(Cursor::over_every_match(pattern, input))),
917        input,
918        last: 0,
919        left: usize::MAX,
920        done: false,
921        depth: None,
922    }
923}
924
925/// [`split`] yielding at most `limit` pieces, the last being the unsplit
926/// remainder. A `limit` of zero yields nothing.
927/// The laziness is at construction and not only in the loop. A cursor finds
928/// every match in the input before the first piece is handed over, which for a
929/// limit of four is fifty thousand matches to report three. Where an anchored
930/// byte route takes the pattern the separators are asked for one at a time
931/// instead, over a reader held across them, so the quote scan runs once for the
932/// whole split rather than from byte zero per piece.
933/// The first prefix a split lexes when it has neither a route nor a window.
934///
935/// A sixty-fourth of a megabyte, which is what the widening prefix elsewhere in
936/// the crate starts at, so a pattern matching early is settled by one lex of it
937/// and a pattern matching late pays a doubling series rather than a fresh lex an
938/// ask.
939const PREFIX_FIRST_BYTES: usize = 64 * 1024;
940
941pub fn splitn<'h>(pattern: &Pattern, input: &'h [u8], limit: usize) -> Split<'h> {
942    // The ladder decides, not a second copy of its refusals: a route that
943    // answers the first ask is asked again for each piece, and one that
944    // declines at the outset declines for every offset. The answer here is
945    // discarded rather than kept, which costs one route call and keeps the
946    // source's state in one place.
947    // The absent literal first, because routed_first_at_reading leaves it out:
948    // it is a verdict over the whole input, and the n-gram filter settles it
949    // without a search where the literal rung below would sweep every byte.
950    let mut reader = None;
951    // A split reports where the separators are and never what they bound, and
952    // the routes are written against the shapes the language spells without
953    // bindings, so the bare twin is what the source asks. Stripped once here
954    // and carried: the source asks once a piece, and a pattern rebuilt per ask
955    // would copy itself tens of thousands of times.
956    let bare = pattern.without_bindings();
957    let sought = bare.as_ref().unwrap_or(pattern);
958    let separators = if crate::library::shapes_for(pattern).is_some() {
959        crate::trace::rung("splitn", "a cursor over a lex under the library's shapes", input.len());
960        Separators::Cursor(Box::new(Cursor::new(pattern, input)))
961    } else if crate::prefilter::requires_absent(pattern, input) {
962        crate::trace::rung("splitn", "a cursor over every match", input.len());
963        Separators::Cursor(Box::new(Cursor::new(pattern, input)))
964    } else if crate::engine::routed_first_at_reading(sought, input, 0, &mut reader).is_some()
965        || crate::prefilter::first_by_literal_windows_at(sought, input, 0).is_some()
966    {
967        // Either the anchored byte routes or the stopping windows answer one
968        // ask at a time, which is what a limited split wants; the source tries
969        // both at each offset and becomes a walk where neither settles one.
970        crate::trace::rung("splitn", "one separator at a time", input.len());
971        Separators::Anchored { pattern: sought.clone(), reader: Box::new(reader), at: 0 }
972    } else if let Some((settled, _)) =
973        crate::prefilter::settled_prefix_matches(sought, input, PREFIX_FIRST_BYTES)
974    {
975        // No literal to window at and no anchored route, but a prefix settles
976        // it: the separators come from one lexed prefix that widens only when
977        // they run out.
978        crate::trace::rung("splitn", "a prefix widened only when it runs out", input.len());
979        Separators::Prefix {
980            pattern: sought.clone(),
981            settled: settled.into_iter(),
982            covered: PREFIX_FIRST_BYTES.min(input.len()),
983            handed: 0,
984        }
985    } else {
986        crate::trace::rung("splitn", "a cursor over every match", input.len());
987        Separators::Cursor(Box::new(Cursor::new(pattern, input)))
988    };
989    Split { separators, input, last: 0, left: limit, done: false, depth: None }
990}
991
992/// [`split`] separating only on matches that sit `depth` brackets deep.
993///
994/// The operation a byte-level split cannot express. Splitting `f(a, g(b, c),
995/// d)` on a comma gives five pieces over bytes, because the commas inside
996/// `g(...)` look exactly like the ones outside it; a regular expression has no
997/// way to tell them apart, since telling them apart requires counting brackets
998/// and a regular language cannot count. At depth one it gives three, which is
999/// the argument list.
1000///
1001/// A match at any other depth is not a separator and stays inside the piece
1002/// being built, so the pieces still rejoin to the input.
1003pub fn split_at_depth<'h>(pattern: &Pattern, input: &'h [u8], depth: u16) -> Split<'h> {
1004    Split {
1005        separators: Separators::Cursor(Box::new(Cursor::over_every_match(pattern, input))),
1006        input,
1007        last: 0,
1008        left: usize::MAX,
1009        done: false,
1010        depth: Some((crate::stress::analyze_bytes(input), depth)),
1011    }
1012}
1013
1014/// `text` as a pattern matching exactly that text and nothing else.
1015///
1016/// The counterpart of the regex crate's `escape`, and not the same operation,
1017/// because the thing being escaped into is not the same. A regular expression
1018/// matches bytes, so making text inert there is a matter of putting a
1019/// backslash in front of the characters that would otherwise be syntax. A trex
1020/// literal matches one token, so text that is several tokens cannot be one
1021/// literal at all, however it is quoted: `a+b` is a word, a punctuation mark
1022/// and a word, and a single literal atom asking for the three of them together
1023/// would never match anything.
1024///
1025/// So the text is lexed, and each significant token becomes its own quoted
1026/// literal in sequence. Escaping inside each is then the small part: within
1027/// `"..."` a backslash takes the next byte literally and an unescaped `"` ends
1028/// the literal, so those two are the only characters that need one.
1029///
1030/// Text that is empty or all whitespace escapes to the empty pattern, which
1031/// matches the empty token sequence.
1032#[must_use]
1033pub fn escape(text: &str) -> String {
1034    let bytes = text.as_bytes();
1035    let mut out = String::with_capacity(text.len() + 2);
1036    for t in crate::lexer::lex(bytes).iter().filter(|t| t.is_significant()) {
1037        if !out.is_empty() {
1038            out.push(' ');
1039        }
1040        out.push('"');
1041        // Read through the bytes rather than slicing the string by the
1042        // lexer's offsets: the lexer works in bytes, and indexing a `str`
1043        // anywhere but a character boundary is a panic rather than an error.
1044        for c in String::from_utf8_lossy(&bytes[t.start()..t.end()]).chars() {
1045            if c == '\\' || c == '"' {
1046                out.push('\\');
1047            }
1048            out.push(c);
1049        }
1050        out.push('"');
1051    }
1052    out
1053}
1054
1055/// The names `pattern` binds, in the order it binds them, without repeats.
1056///
1057/// The regex crate's `capture_names` answers the same question over numbered
1058/// groups with optional names; every trex binding is named, so there is no
1059/// unnamed slot to report and no index to report it under.
1060///
1061/// A name written only inside an assertion is not among them. An assertion
1062/// runs its sub-pattern as a filter and the probe's bindings are discarded, so
1063/// no match ever carries one, and a caller sizing a buffer by this would get a
1064/// slot that never fills.
1065#[must_use]
1066pub fn capture_names(pattern: &Pattern) -> Vec<String> {
1067    pattern.capture_names()
1068}
1069
1070/// How many distinct names `pattern` binds.
1071#[must_use]
1072pub fn captures_len(pattern: &Pattern) -> usize {
1073    capture_names(pattern).len()
1074}
1075
1076
1077#[cfg(test)]
1078mod tests {
1079    use super::*;
1080
1081    /// The borrowed view reports the match the owning iterator reports, over
1082    /// every source the cursor has.
1083    ///
1084    /// The two run different code: the owning form builds a match from each
1085    /// record, and the borrowed one points at the record. A view agreeing on
1086    /// the span but not on the registers, or reporting the names of a pattern
1087    /// in the wrong order against its spans, is a defect no caller of the
1088    /// owning form would ever see.
1089    ///
1090    /// The patterns reach different sources on purpose: one that binds nothing
1091    /// takes the plain cursor, and the binding ones take whichever rung the
1092    /// ladder gives them.
1093    #[test]
1094    fn the_borrowed_view_reports_what_the_owning_iterator_reports() {
1095        let mut text = String::new();
1096        for i in 0..400u32 {
1097            text.push_str(&format!("let value_{i} = {i} ; call_{i}(alpha, beta) ;\n"));
1098        }
1099        let input = text.as_bytes();
1100        for src in ["\"let\" \\W:v \"=\"", "\\W:w", "\"alpha\"", "\\W \"=\"", "\\N:n \";\""] {
1101            let p = crate::parse(src).expect("the test's own patterns parse");
1102            let owned: Vec<crate::engine::Match> = captures_iter(&p, input).collect();
1103            let mut cur = captures_iter(&p, input);
1104            let mut seen = 0usize;
1105            while let Some(m) = cur.next_ref() {
1106                let want = &owned[seen];
1107                assert_eq!((m.start, m.end), (want.start, want.end), "{src} span at {seen}");
1108                assert_eq!(m.captures(), want.captures(), "{src} registers at {seen}");
1109                assert_eq!(m.names(), want.names(), "{src} names at {seen}");
1110                assert_eq!(&m.to_match(), want, "{src} owned copy at {seen}");
1111                seen += 1;
1112            }
1113            assert_eq!(seen, owned.len(), "{src} reported a different number of matches");
1114        }
1115    }
1116
1117    /// A split reports where the separators are, so a pattern that binds and
1118    /// its bare twin split identically - which is what lets the source ask the
1119    /// twin and reach the routes written against it.
1120    #[test]
1121    fn a_binding_separator_splits_where_its_bare_twin_splits() {
1122        let mut text = String::new();
1123        for i in 0..400u32 {
1124            text.push_str(&format!("let value_{i} = {i} ; call_{i}(alpha, beta) ;\n"));
1125        }
1126        let input = text.as_bytes();
1127        for (bound, bare) in [
1128            ("\\W:name \"=\"", "\\W \"=\""),
1129            ("\"let\" \\W:v \"=\"", "\"let\" \\W \"=\""),
1130            ("\\W:w", "\\W"),
1131        ] {
1132            let b = crate::parser::parse(bound).expect(bound);
1133            let u = crate::parser::parse(bare).expect(bare);
1134            for limit in [1usize, 2, 4, 50, usize::MAX] {
1135                let got: Vec<&[u8]> = splitn(&b, input, limit).collect();
1136                let want: Vec<&[u8]> = splitn(&u, input, limit).collect();
1137                assert_eq!(got, want, "{bound} against {bare}, limit {limit}");
1138            }
1139            let got: Vec<&[u8]> = split(&b, input).collect();
1140            let want: Vec<&[u8]> = split(&u, input).collect();
1141            assert_eq!(got, want, "{bound} against {bare}, unlimited");
1142        }
1143    }
1144
1145    #[test]
1146    fn split_and_a_limitless_splitn_reach_the_same_pieces_by_different_routes() {
1147        // split takes the scan's spans together; splitn keeps the resumable
1148        // walk, because a caller naming a limit may stop early. They are now
1149        // different code and only a test says they agree, over every route the
1150        // cursor can take and an input that matches nothing.
1151        let mut text = String::new();
1152        for i in 0..3_000u32 {
1153            text.push_str(&format!("let value_{i} = {} ;\n", i * 7));
1154            text.push_str(&format!("call_{i}(alpha, beta, {i}) ;\n"));
1155            text.push_str(&format!("if (cond_{i}) {{ do_{i}(x) ; }}\n"));
1156        }
1157        for input in [text.as_bytes(), b"", b"nothing matches in here"] {
1158            for src in PATTERNS {
1159                let p = match crate::parse(src) {
1160                    Ok(p) => p,
1161                    Err(e) => panic!("{src} does not parse: {e:?}"),
1162                };
1163                let eager: Vec<&[u8]> = split(&p, input).collect();
1164                let walked: Vec<&[u8]> = splitn(&p, input, usize::MAX).collect();
1165                assert_eq!(eager, walked, "{src}");
1166                // The pieces rejoin to the input with the separators removed,
1167                // which is what makes a piece list a split rather than a list.
1168                assert!(
1169                    eager.iter().map(|p| p.len()).sum::<usize>() <= input.len(),
1170                    "{src}: the pieces outgrew the input"
1171                );
1172            }
1173        }
1174    }
1175
1176    /// Patterns across every route the cursor can take: byte-routable
1177    /// literals, a word then punctuation, a byte pattern, a kind sequence,
1178    /// a windowed literal, the single-pass engine, and a balanced group that
1179    /// only the set engine advances.
1180    const PATTERNS: &[&str] = &[
1181        "\"alpha\"",
1182        "\"zzzqqq\"",
1183        "\\W",
1184        "\\N",
1185        "\\W \"=\"",
1186        "`cond_[0-9]+`",
1187        "(\"alpha\" | \"beta\")",
1188        "\"let\" \\W \"=\"",
1189        "\\W{2}",
1190        "^ \"let\"",
1191        "\\W:name \"=\"",
1192        "\\W:x \"=\" =x",
1193        "\\B(\\W)",
1194        "\\W \"=\" ~(\\N)",
1195    ];
1196
1197    fn corpus() -> Vec<u8> {
1198        let mut s = String::new();
1199        for i in 0..400 {
1200            match i % 4 {
1201                0 => s.push_str(&format!("let value_{i} = {} ;\n", i * 37)),
1202                1 => s.push_str(&format!("call_{i}(alpha, beta, {i}) ;\n")),
1203                2 => s.push_str(&format!("key_{i}: item_{i}, item_{} ;\n", i + 1)),
1204                _ => s.push_str(&format!("if (cond_{i}) {{ do_{i}(x) ; }}\n")),
1205            }
1206        }
1207        s.into_bytes()
1208    }
1209
1210    #[test]
1211    fn a_cursor_collects_to_what_the_scan_returns() {
1212        // The contract the lazy source is held to: the same spans, in the
1213        // same order. A cursor that stops early must not also select
1214        // differently, and only comparing the whole sequence shows that.
1215        let input = corpus();
1216        for src in PATTERNS {
1217            let p = crate::parse(src).expect("pattern parses");
1218            let want = crate::scan(&p, &input);
1219            let got: Vec<Span> = find_iter(&p, &input).collect();
1220            assert_eq!(got, want, "{src}");
1221        }
1222    }
1223
1224    /// [`corpus`] with quoted strings through it, including a string holding an
1225    /// escaped quote and one written with single quotes.
1226    ///
1227    /// The corpus above holds no quote of either kind, so an ascending walk over
1228    /// it asks the quote scan only to report that there is nothing. A string
1229    /// opening between two asks is the other case, and the one a resumed scan
1230    /// can answer differently from a fresh one.
1231    fn quoted_corpus() -> Vec<u8> {
1232        let mut s = String::new();
1233        for i in 0..400 {
1234            match i % 5 {
1235                0 => s.push_str(&format!("let value_{i} = \"text {i}\" ;\n")),
1236                1 => s.push_str(&format!("call_{i}(alpha, \"beta {i}\", {i}) ;\n")),
1237                2 => s.push_str(&format!("key_{i}: \"a \\\"quoted\\\" {i}\" ;\n")),
1238                3 => s.push_str(&format!("note_{i} = 'c' ; other_{i} = {} ;\n", i * 37)),
1239                _ => s.push_str(&format!("if (cond_{i}) {{ do_{i}(\"x\") ; }}\n")),
1240            }
1241        }
1242        s.into_bytes()
1243    }
1244
1245    /// A cursor holds its byte reader between matches and its quote scan reads
1246    /// quote bytes only as far as it has been asked, so what it carries from one
1247    /// ask to the next is state a fresh reader would not have. Over quoted text
1248    /// that state includes an open string, a closed one and an escaped quote
1249    /// inside one.
1250    ///
1251    /// The kind atoms are why this is asked over a whole pattern list rather
1252    /// than one pattern: a kind route probes at many candidate positions in
1253    /// ascending order, so it asks the quote scan far more often than a literal
1254    /// route, which asks only where a rare byte string occurs.
1255    #[test]
1256    fn a_cursor_collects_to_what_the_scan_returns_over_quoted_text() {
1257        let input = quoted_corpus();
1258        for src in PATTERNS {
1259            let p = crate::parse(src).expect("pattern parses");
1260            let want = crate::scan(&p, &input);
1261            let got: Vec<Span> = find_iter(&p, &input).collect();
1262            assert_eq!(got, want, "{src}");
1263        }
1264    }
1265
1266    #[test]
1267    fn find_is_the_first_match_the_scan_reports() {
1268        let input = corpus();
1269        for src in PATTERNS {
1270            let p = crate::parse(src).expect("pattern parses");
1271            assert_eq!(find(&p, &input), crate::scan(&p, &input).first().copied(), "{src}");
1272        }
1273    }
1274
1275    #[test]
1276    fn the_early_exit_reports_the_leftmost_match_not_the_first_literal_tried() {
1277        // The route reads each literal to its own first confirmation, so the
1278        // answer is the earliest of those. Taking whichever literal confirmed
1279        // first would report alpha at 11 over beta at 6, which is a different
1280        // match and not a slower way to the same one.
1281        let hay = b"gamma beta alpha gamma";
1282        let p = crate::parse("(\"alpha\" | \"beta\")").expect("pattern parses");
1283        assert_eq!(find(&p, hay).map(|s| s.start()), Some(6), "beta is leftmost");
1284        assert_eq!(find(&p, hay), crate::scan(&p, hay).first().copied());
1285
1286        // And the same when the pattern names them the other way round, since
1287        // leftmost is a property of the input.
1288        let q = crate::parse("(\"beta\" | \"alpha\")").expect("pattern parses");
1289        assert_eq!(find(&q, hay), find(&p, hay));
1290    }
1291
1292    #[test]
1293    fn the_widening_prefix_answers_what_the_whole_lex_answers() {
1294        // The two arms differ only in how much of the input is lexed, so they
1295        // must not differ in what they report. An input well past the first
1296        // prefix, so the widening actually runs more than one round.
1297        let mut input = corpus();
1298        while input.len() < 400_000 {
1299            let more = corpus();
1300            input.extend_from_slice(&more);
1301        }
1302        for src in PATTERNS {
1303            let p = crate::parse(src).expect("pattern parses");
1304            assert_eq!(find(&p, &input), find_by_full_lex(&p, &input), "{src}");
1305            assert_eq!(find(&p, &input), crate::scan(&p, &input).first().copied(), "{src}");
1306        }
1307    }
1308
1309    #[test]
1310    fn a_match_only_at_the_end_is_still_found() {
1311        // The case the widening is worst at and must still get right: every
1312        // round before the last says nothing, and the last one lexes to the
1313        // end of the input.
1314        let mut input = corpus();
1315        while input.len() < 400_000 {
1316            let more = corpus();
1317            input.extend_from_slice(&more);
1318        }
1319        input.extend_from_slice(b"\nsentinel_token = 99 ;\n");
1320        let p = crate::parse("\"sentinel_token\" \"=\" \\N").expect("pattern parses");
1321        let want = crate::scan(&p, &input).first().copied();
1322        assert!(want.is_some(), "the sentinel must be there to be found");
1323        assert_eq!(find(&p, &input), want);
1324        assert_eq!(find_by_full_lex(&p, &input), want);
1325    }
1326
1327    #[test]
1328    fn absence_over_a_widening_prefix_is_absence_over_the_input() {
1329        // A prefix that finds nothing proves nothing, so the widening has to
1330        // reach the end before it may answer no.
1331        let mut input = corpus();
1332        while input.len() < 400_000 {
1333            let more = corpus();
1334            input.extend_from_slice(&more);
1335        }
1336        for src in ["\"zzzqqq\"", "\"zzzqqq\" \"=\" \\N", "\\W \"@@\""] {
1337            let p = crate::parse(src).expect("pattern parses");
1338            assert_eq!(find(&p, &input), None, "{src}");
1339            assert_eq!(crate::scan(&p, &input).first().copied(), None, "{src}");
1340        }
1341    }
1342
1343    #[test]
1344    fn find_agrees_with_is_match_on_whether_there_is_one() {
1345        let input = corpus();
1346        for src in PATTERNS {
1347            let p = crate::parse(src).expect("pattern parses");
1348            assert_eq!(find(&p, &input).is_some(), crate::is_match(&p, &input), "{src}");
1349        }
1350    }
1351
1352    #[test]
1353    fn seeking_past_a_match_finds_the_next_one() {
1354        // Stepping the anchor to just past each match must walk the same
1355        // sequence the scan reports, which is what says the seek lands on a
1356        // token boundary and not inside one.
1357        let input = corpus();
1358        for src in PATTERNS {
1359            let p = crate::parse(src).expect("pattern parses");
1360            let want = crate::scan(&p, &input);
1361            let mut at = 0usize;
1362            for expected in &want {
1363                let got = find_at(&p, &input, at).expect("a match the scan found");
1364                assert_eq!(got, *expected, "{src} from {at}");
1365                at = got.end();
1366            }
1367            assert_eq!(find_at(&p, &input, at), None, "{src}: nothing past the last match");
1368        }
1369    }
1370
1371    #[test]
1372    fn seeking_from_zero_is_finding_from_the_start() {
1373        let input = corpus();
1374        for src in PATTERNS {
1375            let p = crate::parse(src).expect("pattern parses");
1376            assert_eq!(find_at(&p, &input, 0), find(&p, &input), "{src}");
1377            assert_eq!(is_match_at(&p, &input, 0), crate::is_match(&p, &input), "{src}");
1378        }
1379    }
1380
1381    #[test]
1382    fn the_pieces_and_the_matches_rebuild_the_input() {
1383        // The property that says the split is right without asserting any
1384        // particular piece: pieces and matched text, interleaved in order,
1385        // are the input back. It catches an off-by-one at either edge of a
1386        // match, which comparing piece counts alone would not.
1387        let input = corpus();
1388        for src in PATTERNS {
1389            let p = crate::parse(src).expect("pattern parses");
1390            let spans = crate::scan(&p, &input);
1391            let pieces: Vec<&[u8]> = split(&p, &input).collect();
1392            assert_eq!(pieces.len(), spans.len() + 1, "{src}: one more piece than matches");
1393            let mut rebuilt: Vec<u8> = Vec::with_capacity(input.len());
1394            for (i, piece) in pieces.iter().enumerate() {
1395                rebuilt.extend_from_slice(piece);
1396                if let Some(s) = spans.get(i) {
1397                    rebuilt.extend_from_slice(&input[s.range()]);
1398                }
1399            }
1400            assert_eq!(rebuilt, input, "{src}");
1401        }
1402    }
1403
1404    #[test]
1405    fn splitting_at_a_depth_ignores_the_separators_nested_deeper() {
1406        // The case a byte-level split cannot express: the commas inside
1407        // `g(...)` are the same bytes as the ones outside it, and telling
1408        // them apart needs counting, which a regular language cannot do.
1409        let input = b"f(a, g(b, c), d)" as &[u8];
1410        let comma = crate::parse("\",\"").expect("pattern parses");
1411        let flat: Vec<&[u8]> = split(&comma, input).collect();
1412        assert_eq!(flat.len(), 4, "every comma separates: {flat:?}");
1413
1414        let args: Vec<&[u8]> = split_at_depth(&comma, input, 1).collect();
1415        assert_eq!(args.len(), 3, "only the top-level commas separate: {args:?}");
1416        assert_eq!(args[0], b"f(a");
1417        assert_eq!(args[1], b" g(b, c)", "the nested comma stayed inside its piece");
1418        assert_eq!(args[2], b" d)");
1419
1420        // Whatever depth is asked for, the pieces and the separators that
1421        // were taken still rebuild the input.
1422        for d in 0..3u16 {
1423            let pieces: Vec<&[u8]> = split_at_depth(&comma, input, d).collect();
1424            let joined = pieces.join(b"," as &[u8]);
1425            assert_eq!(joined.len(), input.len(), "depth {d}: {pieces:?}");
1426        }
1427    }
1428
1429    #[test]
1430    fn a_limited_split_keeps_the_rest_whole() {
1431        let input = corpus();
1432        for src in PATTERNS {
1433            let p = crate::parse(src).expect("pattern parses");
1434            let all: Vec<&[u8]> = split(&p, &input).collect();
1435            assert_eq!(splitn(&p, &input, 0).count(), 0, "{src}: a limit of zero yields nothing");
1436            for k in 1..=3.min(all.len()) {
1437                let some: Vec<&[u8]> = splitn(&p, &input, k).collect();
1438                assert_eq!(some.len(), k, "{src}: {k} pieces");
1439                assert_eq!(some[..k - 1], all[..k - 1], "{src}: the pieces before the last agree");
1440                // The last piece runs to the end of the input, whatever is in
1441                // it, which is what distinguishes a limit from a truncation.
1442                let tail = some[k - 1];
1443                assert_eq!(tail.as_ptr_range().end, input.as_ptr_range().end, "{src}");
1444            }
1445        }
1446    }
1447
1448    #[test]
1449    fn rewriting_n_matches_rewrites_the_first_n() {
1450        let input = corpus();
1451        let names: Vec<String> = Vec::new();
1452        let tmpl = crate::Template::parse("X", &names).expect("the template parses");
1453        for src in PATTERNS {
1454            let p = crate::parse(src).expect("pattern parses");
1455            let spans = crate::scan(&p, &input);
1456            // Past the match count, a partial rewrite is the whole rewrite.
1457            assert_eq!(
1458                crate::rewrite_n(&p, &tmpl, &input, spans.len()),
1459                crate::rewrite(&p, &tmpl, &input),
1460                "{src}"
1461            );
1462            assert_eq!(crate::rewrite_n(&p, &tmpl, &input, 0), input, "{src}: none is untouched");
1463            if let Some(first) = spans.first() {
1464                let once = crate::rewrite_first(&p, &tmpl, &input);
1465                assert_eq!(&once[..first.start()], &input[..first.start()], "{src}: before");
1466                assert_eq!(&once[first.start()..first.start() + 1], b"X", "{src}: the replacement");
1467                assert_eq!(&once[first.start() + 1..], &input[first.end()..], "{src}: after");
1468            }
1469        }
1470    }
1471
1472    #[test]
1473    fn escaped_text_matches_exactly_itself() {
1474        // Each of these holds something the pattern language reads as syntax,
1475        // and each is more than one token, which is the case a byte-level
1476        // escape does not have to think about. Matched against the text
1477        // itself, so the surrounding bytes cannot change how it lexes.
1478        for text in ["alpha", "a+b", "x(y)", "let x = 1", "a\\b", "one|two", "p.q", "\\W"] {
1479            let pat = escape(text);
1480            let p = crate::parse(&pat).unwrap_or_else(|e| panic!("`{pat}` should parse: {e:?}"));
1481            let got = crate::find(&p, text.as_bytes());
1482            assert_eq!(
1483                got.map(|s| &text.as_bytes()[s.range()]),
1484                Some(text.as_bytes()),
1485                "`{pat}` over `{text}`"
1486            );
1487        }
1488    }
1489
1490    #[test]
1491    fn escaped_text_parses_even_where_it_cannot_match() {
1492        // A lone quote opens a string the lexer never sees closed, and an
1493        // empty text has no token at all. Neither is a match question: the
1494        // contract here is that escaping never builds a pattern that fails
1495        // to parse, because a caller splices the result into a larger one.
1496        for text in ["\"", "\\", "", "   ", "'", "`"] {
1497            let pat = escape(text);
1498            crate::parse(&pat).unwrap_or_else(|e| panic!("`{pat}` from `{text}`: {e:?}"));
1499        }
1500    }
1501
1502    #[test]
1503    fn escaping_text_that_is_not_ascii_does_not_panic() {
1504        // The lexer works in bytes and a `str` may only be indexed at a
1505        // character boundary, so reading a token out of the source by the
1506        // lexer's own offsets is a panic waiting for the first multi-byte
1507        // character. These carry two-, three- and four-byte ones.
1508        let texts =
1509            ["caf\u{e9}", "\u{3b8} = 1", "\u{4f60}\u{597d} world", "a \u{1f600} b", "na\u{ef}ve(x)"];
1510        for text in texts {
1511            let pat = escape(text);
1512            crate::parse(&pat).unwrap_or_else(|e| panic!("`{pat}` from `{text}`: {e:?}"));
1513        }
1514    }
1515
1516    #[test]
1517    fn the_first_match_captures_the_same_over_a_prefix_as_over_the_whole_input() {
1518        // captures_first settles from a widening prefix and resolves the
1519        // registers over that prefix's tokens; the cursor resolves them over
1520        // a full lex. The two readings must agree, on an input well past the
1521        // first prefix so the two really do read different token streams.
1522        let mut input = corpus();
1523        while input.len() < 400_000 {
1524            let more = corpus();
1525            input.extend_from_slice(&more);
1526        }
1527        for src in PATTERNS {
1528            let p = crate::parse(src).expect("pattern parses");
1529            assert_eq!(captures_first(&p, &input), captures_iter(&p, &input).next(), "{src}");
1530        }
1531    }
1532
1533    #[test]
1534    fn taking_captures_one_at_a_time_gives_what_taking_them_together_gives() {
1535        // The contract for the lazy form: same matches, same order, same
1536        // registers bound. Resolving from the walk's own save slots must not
1537        // differ from re-running an attempt at each span, which is what the
1538        // eager path does.
1539        let input = corpus();
1540        for src in PATTERNS {
1541            let p = crate::parse(src).expect("pattern parses");
1542            let spans = crate::scan(&p, &input);
1543            let want = crate::captures(&p, &input, &spans);
1544            let got: Vec<crate::engine::Match> = captures_iter(&p, &input).collect();
1545            assert_eq!(got, want, "{src}");
1546            assert_eq!(captures_first(&p, &input), want.first().cloned(), "{src}");
1547        }
1548    }
1549
1550    #[test]
1551    fn the_names_a_pattern_binds_are_the_names_its_matches_carry() {
1552        // Introspection has to agree with what a match actually holds, or a
1553        // caller sizing a buffer from it is sized wrong.
1554        let input = corpus();
1555        for src in PATTERNS {
1556            let p = crate::parse(src).expect("pattern parses");
1557            let names = capture_names(&p);
1558            assert_eq!(captures_len(&p), names.len(), "{src}");
1559            let spans = crate::scan(&p, &input);
1560            for m in crate::captures(&p, &input, &spans) {
1561                let mut bound: Vec<&String> = m.names().iter().collect();
1562                bound.sort_unstable();
1563                let mut want: Vec<&String> = names.iter().collect();
1564                want.sort_unstable();
1565                assert_eq!(bound, want, "{src}: the match carries what the pattern binds");
1566            }
1567        }
1568    }
1569
1570    #[test]
1571    fn a_matchs_token_extent_counts_the_tokens_it_spans() {
1572        // The two sources compute it differently - the walk subtracts the
1573        // indices it already ran between, a route indexes the significant
1574        // starts - so they are checked against one count neither of them
1575        // produced: the tokens of the whole input that fall inside the span.
1576        let input = corpus();
1577        let all = crate::lexer::lex(&input);
1578        for src in PATTERNS {
1579            let p = crate::parse(src).expect("pattern parses");
1580            let mut c = Cursor::new(&p, &input);
1581            let mut seen = 0usize;
1582            while let Some((s, extent)) = c.next_extent() {
1583                let want = all
1584                    .iter()
1585                    .filter(|t| t.is_significant() && t.start() >= s.start() && t.end() <= s.end())
1586                    .count();
1587                assert_eq!(extent, want, "{src} at {}..{}", s.start(), s.end());
1588                assert!(extent > 0, "{src}: a match spans at least one token");
1589                seen += 1;
1590            }
1591            assert_eq!(seen, crate::scan(&p, &input).len(), "{src}: every match was handed out");
1592        }
1593    }
1594
1595    #[test]
1596    fn a_token_extent_is_not_a_byte_length() {
1597        // The point of counting in tokens: a token is not bounded in bytes,
1598        // so a run of several hundred characters is one of them and a span's
1599        // byte length says nothing about how many tokens it holds.
1600        let mut input = String::from("prefix = ");
1601        input.push_str(&"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".repeat(8));
1602        input.push_str(" ;\n");
1603        let bytes = input.as_bytes();
1604        // `.` rather than `\W`: a long run with no whitespace in it reads as a
1605        // blob and not as a word, and which of the two it is does not bear on
1606        // the point. Either way it is one token.
1607        let p = crate::parse("\"prefix\" \"=\" .").expect("pattern parses");
1608        let mut c = Cursor::new(&p, bytes);
1609        let (s, extent) = c.next_extent().expect("the pattern matches this input");
1610        assert_eq!(extent, 3, "three tokens: the word, the equals and the run");
1611        assert!(
1612            s.end() - s.start() > 400,
1613            "three tokens spanning {} bytes",
1614            s.end() - s.start()
1615        );
1616    }
1617
1618    #[test]
1619    fn the_shortest_end_never_reaches_past_the_first_match() {
1620        // The two answer the same question about whether there is a match,
1621        // and the shortest end is at or before the end of the match `find`
1622        // reports. It may be earlier, which is the whole point of having it,
1623        // so this bounds it rather than asserting equality.
1624        let input = corpus();
1625        for src in PATTERNS {
1626            let p = crate::parse(src).expect("pattern parses");
1627            let first = find(&p, &input);
1628            let end = shortest_match(&p, &input);
1629            assert_eq!(end.is_some(), first.is_some(), "{src}: they agree on whether");
1630            if let (Some(e), Some(f)) = (end, first) {
1631                assert!(e <= f.end(), "{src}: shortest end {e} past the first match's {}", f.end());
1632                assert!(e >= f.start(), "{src}: shortest end {e} before the match begins");
1633            }
1634        }
1635    }
1636
1637    #[test]
1638    fn a_shorter_alternative_ends_sooner_than_the_preferred_match() {
1639        // Where a pattern has two ways to match from one place, `find`
1640        // reports the one the pattern prefers and this reports whichever ends
1641        // first. A greedy run of words prefers all of them; a match is known
1642        // to have occurred after the first.
1643        let input = b"alpha beta gamma delta ;" as &[u8];
1644        let p = crate::parse("\\W+").expect("pattern parses");
1645        let first = find(&p, input).expect("it matches");
1646        let end = shortest_match(&p, input).expect("it matches");
1647        assert_eq!(&input[first.range()], b"alpha beta gamma delta", "greedy takes all four");
1648        assert!(end < first.end(), "the soonest end is earlier: {end} against {}", first.end());
1649        assert_eq!(&input[..end], b"alpha", "and it is the end of the first word");
1650    }
1651
1652    #[test]
1653    fn the_anchored_shortest_end_is_bounded_by_the_anchored_first_match() {
1654        // The anchored pair stands where the unanchored pair stands: they
1655        // agree on whether there is a match at or after the offset, and the
1656        // end reported lies within the match `find_at` returns.
1657        let input = corpus();
1658        for src in PATTERNS {
1659            let p = crate::parse(src).expect("pattern parses");
1660            for at in [0, 1, 37, input.len() / 3, input.len() / 2, input.len()] {
1661                let first = find_at(&p, &input, at);
1662                let end = shortest_match_at(&p, &input, at);
1663                assert_eq!(end.is_some(), first.is_some(), "{src} at {at}: agree on whether");
1664                if let (Some(e), Some(f)) = (end, first) {
1665                    assert!(e <= f.end(), "{src} at {at}: end {e} past the match's {}", f.end());
1666                    assert!(e >= f.start(), "{src} at {at}: end {e} before the match begins");
1667                }
1668            }
1669        }
1670    }
1671
1672    #[test]
1673    fn a_limited_split_gives_what_an_unlimited_one_gives() {
1674        // splitn asks an anchored route for one separator at a time where
1675        // split takes the scan's spans together, so the two reach the same
1676        // pieces by different paths. The last input holds a quote the bytes
1677        // cannot settle partway through, which makes the route refuse there
1678        // and the source become a walk seeked to that offset - the transition
1679        // this is here to exercise rather than assume.
1680        for src in [
1681            "alpha beta alpha gamma alpha",
1682            "let a = 1 ; let b = 2 ; let c = 3 ;",
1683            "x alpha y alpha z",
1684            "no separator here at all",
1685            "alpha 'http://x/1' alpha beta alpha",
1686            "alpha \"q\" alpha 'http://y/2' alpha end",
1687        ] {
1688            let input = src.as_bytes();
1689            for pat in ["\"alpha\"", "^ \"let\"", "\\W \"=\"", "\\N"] {
1690                let p = crate::parse(pat).expect("pattern parses");
1691                let whole: Vec<&[u8]> = split(&p, input).collect();
1692                let unlimited: Vec<&[u8]> = splitn(&p, input, usize::MAX).collect();
1693                assert_eq!(unlimited, whole, "{pat} over {src:?}");
1694                // A limit takes a prefix of those pieces with the rest
1695                // unsplit, so every limit must agree with the whole split.
1696                for n in 1..=whole.len() + 1 {
1697                    let got: Vec<&[u8]> = splitn(&p, input, n).collect();
1698                    assert_eq!(got.len(), n.min(whole.len()), "{pat} over {src:?} at {n}");
1699                    for (i, piece) in got.iter().enumerate().take(got.len().saturating_sub(1)) {
1700                        assert_eq!(*piece, whole[i], "{pat} over {src:?} at {n}, piece {i}");
1701                    }
1702                }
1703            }
1704        }
1705    }
1706
1707    #[test]
1708    fn the_line_anchored_literal_stops_at_the_first_occurrence_that_leads_a_line() {
1709        // The route walks occurrences one at a time and stops at the first
1710        // that leads its line, where the spans route filters a whole set. Held
1711        // to the scan's first span over inputs where the earlier occurrences
1712        // are the rejected ones: indented leads a line, mid-line does not, and
1713        // an input whose every occurrence is mid-line has no match at all.
1714        let p = crate::parse("^ \"let\"").expect("pattern parses");
1715        for src in [
1716            "let a = 1 ;\nlet b = 2 ;\n",
1717            "x let a = 1 ;\n   let b = 2 ;\n",
1718            "x let a ;\ny let b ;\nz let c ;\n",
1719            "\n\n\tlet deep = 1 ;\n",
1720            "nothing here at all\n",
1721        ] {
1722            let input = src.as_bytes();
1723            let want = crate::scan(&p, input).first().copied();
1724            assert_eq!(find(&p, input), want, "{src:?}");
1725            assert_eq!(crate::is_match(&p, input), want.is_some(), "{src:?} is_match");
1726            // From an offset the answer is the first match at or after it, and
1727            // the occurrences the route steps over on the way are the ones
1728            // that do not lead a line.
1729            for at in 0..input.len() {
1730                let want_at = crate::scan(&p, input).into_iter().find(|s| s.start() >= at);
1731                assert_eq!(find_at(&p, input, at), want_at, "{src:?} from {at}");
1732            }
1733        }
1734    }
1735
1736    #[test]
1737    fn the_quoted_route_names_the_token_the_lexer_makes() {
1738        // The quote scan applies the lexer's own char_literal_end and
1739        // single_quoted_end, so a span it reports is the token rather than a
1740        // guess at one. Held to the lexer directly over the shapes that decide
1741        // it: a lifetime and a contraction open nothing, a char literal holding
1742        // a double quote is one token, a doubled quote leaves one string, and a
1743        // quote a URL could have taken is refused rather than guessed - where
1744        // the engine then answers and must agree all the same.
1745        let p = crate::parse("\\Q").expect("pattern parses");
1746        for src in [
1747            "let s = \"hello world\" ;",
1748            "&'static T and don't stop",
1749            "c = '\"' ; d = 'x'",
1750            "q = 'foo''bar' ;",
1751            "the '90s and 5'10 tall",
1752            "u = 'http://x/1' ; v = \"w\"",
1753            "plain words and 12 numbers",
1754            "'n dag 'n beer met 'n karakter",
1755            "x = \"a\\\"b\" ; y = 2",
1756            // A string no quote closes runs to the input's end, where the scan
1757            // carries that length in place of a closing quote's index.
1758            "s = \"unterminated",
1759            "t = 'also unterminated",
1760        ] {
1761            let input = src.as_bytes();
1762            let want = crate::lexer::lex(input)
1763                .iter()
1764                .find(|t| t.kind == crate::token::TokenKind::Quoted)
1765                .map(|t| (t.start(), t.end()));
1766            assert_eq!(find(&p, input).map(|s| (s.start(), s.end())), want, "{src}");
1767            // From an offset, the token reported must be the first the lexer
1768            // makes that begins at or after it - a quote inside a string that
1769            // opened earlier names that string and not a token of its own.
1770            for at in 0..input.len() {
1771                let want_at = crate::lexer::lex(input)
1772                    .iter()
1773                    .find(|t| t.kind == crate::token::TokenKind::Quoted && t.start() >= at)
1774                    .map(|t| (t.start(), t.end()));
1775                let got = find_at(&p, input, at).map(|s| (s.start(), s.end()));
1776                assert_eq!(got, want_at, "{src} from {at}");
1777            }
1778        }
1779    }
1780
1781    #[test]
1782    fn the_anchored_byte_pattern_route_agrees_with_the_walk() {
1783        // A byte pattern's match opens with its literal prefix, so bounding the
1784        // search at the offset is enough and the route needs no span filter.
1785        // Held to the walk at every offset the corpus reaches.
1786        let input = corpus();
1787        let p = crate::parse("`cond_[0-9]+`").expect("pattern parses");
1788        let mut compared = 0;
1789        for at in [0, 1, 37, input.len() / 3, input.len() / 2, input.len()] {
1790            let Some(routed) = crate::engine::routed_first_at(&p, &input, at) else {
1791                continue;
1792            };
1793            let Some(mut w) = crate::nfa::SerialWalk::over(&p, &input) else {
1794                continue;
1795            };
1796            w.seek(at);
1797            assert_eq!(routed, w.next_span(&input), "the byte pattern from {at}");
1798            compared += 1;
1799        }
1800        assert!(compared > 0, "the route never applied, so nothing was compared");
1801        // An input the route certainly matches in, so the agreement above is
1802        // not carried by a shared verdict of no match.
1803        let small = b"x cond_1 y cond_22 z cond_333" as &[u8];
1804        for at in [0, 1, 2, 9, 10, 19, 29] {
1805            let routed =
1806                crate::engine::routed_first_at(&p, small, at).expect("the route takes it");
1807            let mut w = crate::nfa::SerialWalk::over(&p, small).expect("the engine takes it");
1808            w.seek(at);
1809            assert_eq!(routed, w.next_span(small), "the byte pattern from {at} of the small input");
1810        }
1811        assert!(
1812            crate::engine::routed_first_at(&p, small, 0).expect("the route takes it").is_some(),
1813            "the small input must hold a match"
1814        );
1815    }
1816
1817    #[test]
1818    fn the_anchored_word_then_punct_route_skips_a_word_that_began_before_the_offset() {
1819        // The route searches for the punctuation, and the word behind an
1820        // occurrence may begin before the offset. "alpha =" starts at 0, so an
1821        // ask from 1 must report "beta =" at 12 and not "alpha =" - the search
1822        // cannot be bounded at the offset, so the span is filtered instead.
1823        let input = b"alpha = 1 ; beta = 2 ; gamma = 3" as &[u8];
1824        let p = crate::parse("\\W \"=\"").expect("pattern parses");
1825        for (at, want) in [(0, (0, 7)), (1, (12, 18)), (12, (12, 18)), (13, (23, 30))] {
1826            let mut w = crate::nfa::SerialWalk::over(&p, input).expect("the engine takes it");
1827            w.seek(at);
1828            let walked = w.next_span(input);
1829            assert_eq!(
1830                walked.map(|s| (s.start(), s.end())),
1831                Some(want),
1832                "the walk from {at}"
1833            );
1834            assert_eq!(find_at(&p, input, at), walked, "the ladder from {at}");
1835        }
1836    }
1837
1838    #[test]
1839    fn the_anchored_prefix_reruns_the_selection_rather_than_filtering_it() {
1840        // The leftmost, non-overlapping selection from an offset can hold a
1841        // match that overlaps one the selection from zero preferred. Over
1842        // "a b c d ..." with \W{2} the selection from zero is [a b] then
1843        // [c d]; asked from b, the answer is [b c], which no filter of that
1844        // selection contains. Both the walk and the prefix must give [b c].
1845        let input = b"a b c d e f g h" as &[u8];
1846        let p = crate::parse("\\W{2}").expect("pattern parses");
1847        let at = 2;
1848        let mut w = crate::nfa::SerialWalk::over(&p, input).expect("the engine takes it");
1849        w.seek(at);
1850        let by_walk = w.next_span(input);
1851        assert_eq!(
1852            by_walk.map(|s| (s.start(), s.end())),
1853            Some((2, 5)),
1854            "the walk re-runs the selection from the offset"
1855        );
1856        assert_eq!(find_at(&p, input, at), by_walk, "and the ladder gives the same");
1857    }
1858
1859    #[test]
1860    fn the_anchored_prefix_settles_what_the_whole_input_settles() {
1861        // The prefix is only allowed to read less if it decides the same
1862        // thing, and the thing it must decide is the walk's answer from the
1863        // offset, not the whole scan's filtered.
1864        let input = corpus();
1865        let mut compared = 0;
1866        for src in PATTERNS {
1867            let p = crate::parse(src).expect("pattern parses");
1868            for at in [0, 1, 37, input.len() / 3, input.len() / 2, input.len()] {
1869                let Some(from_prefix) = crate::prefilter::find_at_by_growing_prefix(&p, &input, at)
1870                else {
1871                    continue;
1872                };
1873                let Some(mut w) = crate::nfa::SerialWalk::over(&p, &input) else {
1874                    continue;
1875                };
1876                w.seek(at);
1877                assert_eq!(from_prefix, w.next_span(&input), "{src} at {at}: prefix against walk");
1878                compared += 1;
1879            }
1880        }
1881        assert!(compared > 0, "no pattern reached the prefix, so nothing was compared");
1882    }
1883
1884    #[test]
1885    fn the_prefix_settles_the_same_shortest_end_the_whole_input_does() {
1886        // The prefix is only allowed to read less if it decides the same
1887        // thing. Where it answers at all - an end, or a proof that there is
1888        // none - that answer must be the whole input's.
1889        let input = corpus();
1890        let mut compared = 0;
1891        for src in PATTERNS {
1892            let p = crate::parse(src).expect("pattern parses");
1893            for at in [0, 1, 37, input.len() / 3, input.len() / 2, input.len()] {
1894                let Some(from_prefix) =
1895                    crate::prefilter::shortest_end_by_growing_prefix_from(&p, &input, at)
1896                else {
1897                    continue;
1898                };
1899                let whole = crate::nfa::shortest_end_from_byte(&p, &input, at);
1900                assert_eq!(from_prefix, whole, "{src} at {at}: prefix and whole input differ");
1901                compared += 1;
1902            }
1903        }
1904        assert!(compared > 0, "no pattern reached the prefix, so nothing was compared");
1905    }
1906
1907    #[test]
1908    fn an_anchor_holds_against_the_input_and_not_against_the_offset() {
1909        // `at` selects where a match may begin and never what the input is, so
1910        // the stream read is the whole one from `at` rather than the input cut
1911        // there. `\A` therefore holds only at the input's own first token, and
1912        // an ask from past it has no match - which is what `find_at` answers.
1913        let input = b"alpha beta gamma delta ;" as &[u8];
1914        let p = crate::parse("\\A \\W").expect("pattern parses");
1915        assert_eq!(shortest_match_at(&p, input, 0), Some(5), "at the start it matches");
1916        assert_eq!(find_at(&p, input, 6), None, "find_at reads the anchor this way");
1917        assert_eq!(shortest_match_at(&p, input, 6), None, "and so does this");
1918    }
1919
1920    #[test]
1921    fn an_empty_input_has_no_match_to_take() {
1922        for src in PATTERNS {
1923            let p = crate::parse(src).expect("pattern parses");
1924            assert_eq!(find(&p, b""), None, "{src}");
1925            assert_eq!(find_iter(&p, b"").count(), 0, "{src}");
1926            assert_eq!(find_at(&p, b"", 0), None, "{src}");
1927        }
1928    }
1929}