Skip to main content

trex/
captures.rs

1//! A capture buffer a caller owns and refills, and the two pattern properties
2//! that are fixed before any input is seen.
3//!
4//! A match carries two different kinds of thing. How many registers there are
5//! and what they are called belongs to the pattern and is the same for every
6//! match of it; where each one landed belongs to one match. [`crate::Match`]
7//! carries both, so it allocates a vector and clones a name per register per
8//! match. Splitting them lets the names be built once and the positions be
9//! written into a buffer that outlives the match.
10//!
11//! This is the regex crate's `CaptureLocations` with two differences. The
12//! buffer names its own slots, so reading one back does not need the pattern
13//! again. And each slot carries a token extent beside its byte span: the
14//! engine's save slots hold significant-token indices and convert them to bytes
15//! on the way out, so the pair is already there and storing it costs nothing. A
16//! byte matcher has no such pair to report.
17
18use crate::ast::Pattern;
19use crate::engine::Span;
20
21/// Where each register landed in one match, in a buffer sized by the pattern
22/// and refilled in place.
23///
24/// Built once with [`CaptureSlots::of`] and passed to [`captures_read`] or
25/// [`captures_read_at`] for each match. Slot order is the order the pattern
26/// binds its names, which is the order [`crate::capture_names`] reports; it is
27/// not the order [`crate::Match::captures`] uses, which is sorted by name.
28///
29/// A register the match did not bind reads back as `None`, which is what
30/// distinguishes it from one that bound an empty span.
31#[derive(Clone, Debug, Default, PartialEq, Eq)]
32pub struct CaptureSlots {
33    names: Vec<String>,
34    spans: Vec<Option<Span>>,
35    extents: Vec<Option<(usize, usize)>>,
36    matched: Option<Span>,
37    matched_extent: Option<(usize, usize)>,
38}
39
40impl CaptureSlots {
41    /// A buffer shaped for `pattern`: one slot per name it binds.
42    #[must_use]
43    pub fn of(pattern: &Pattern) -> Self {
44        let names = crate::cursor::capture_names(pattern);
45        let n = names.len();
46        Self {
47            names,
48            spans: vec![None; n],
49            extents: vec![None; n],
50            matched: None,
51            matched_extent: None,
52        }
53    }
54
55    /// How many registers the buffer holds.
56    #[must_use]
57    pub fn len(&self) -> usize {
58        self.names.len()
59    }
60
61    /// Whether the pattern binds nothing.
62    #[must_use]
63    pub fn is_empty(&self) -> bool {
64        self.names.is_empty()
65    }
66
67    /// The register names, in slot order.
68    #[must_use]
69    pub fn names(&self) -> &[String] {
70        &self.names
71    }
72
73    /// The name of slot `i`.
74    #[must_use]
75    pub fn name(&self, i: usize) -> Option<&str> {
76        self.names.get(i).map(String::as_str)
77    }
78
79    /// The slot `name` occupies.
80    #[must_use]
81    pub fn index_of(&self, name: &str) -> Option<usize> {
82        self.names.iter().position(|n| n == name)
83    }
84
85    /// The byte span slot `i` bound, or `None` where it bound nothing.
86    #[must_use]
87    pub fn get(&self, i: usize) -> Option<Span> {
88        *self.spans.get(i)?
89    }
90
91    /// The byte span the register called `name` bound.
92    #[must_use]
93    pub fn by_name(&self, name: &str) -> Option<Span> {
94        self.get(self.index_of(name)?)
95    }
96
97    /// The significant tokens slot `i` bound, as a half-open index pair into
98    /// the token stream.
99    ///
100    /// The engine names a match by the token indices it runs between, so this
101    /// is what it had before it converted to bytes. A byte matcher reports no
102    /// such pair, because a byte offset does not say which token it is in.
103    #[must_use]
104    pub fn extent(&self, i: usize) -> Option<(usize, usize)> {
105        *self.extents.get(i)?
106    }
107
108    /// How many significant tokens slot `i` bound.
109    #[must_use]
110    pub fn tokens(&self, i: usize) -> Option<usize> {
111        self.extent(i).map(|(a, b)| b - a)
112    }
113
114    /// The span of the whole match the buffer was last filled from.
115    #[must_use]
116    pub fn matched(&self) -> Option<Span> {
117        self.matched
118    }
119
120    /// The significant tokens the whole match spanned.
121    #[must_use]
122    pub fn matched_extent(&self) -> Option<(usize, usize)> {
123        self.matched_extent
124    }
125
126    /// How many significant tokens the whole match spanned.
127    #[must_use]
128    pub fn matched_tokens(&self) -> Option<usize> {
129        self.matched_extent.map(|(a, b)| b - a)
130    }
131
132    /// Forget every position, keeping the names and the allocation.
133    pub fn clear(&mut self) {
134        self.spans.fill(None);
135        self.extents.fill(None);
136        self.matched = None;
137        self.matched_extent = None;
138    }
139
140    /// Fill from a resolved match, for the routes that hand back whole matches.
141    fn take_match(&mut self, m: &crate::engine::Match, extent: Option<(usize, usize)>) {
142        self.clear();
143        self.matched = Some(m.span());
144        self.matched_extent = extent;
145        for (name, span) in m.names().iter().zip(m.captures()) {
146            if let Some(i) = self.index_of(name) {
147                self.spans[i] = Some(*span);
148            }
149        }
150    }
151
152    /// Fill from a match whose registers arrived inline, `names` naming them in
153    /// the order it holds them.
154    ///
155    /// The whole reason the inline form exists: this path copies the registers
156    /// out and keeps nothing, so building a match to own them first is one
157    /// allocation a match for a value discarded on the next call.
158    fn take_flat(&mut self, m: &crate::nfa::FlatMatch, names: &[String]) {
159        self.clear();
160        self.matched = Some(m.span);
161        for (i, name) in names.iter().enumerate() {
162            if let Some(k) = self.index_of(name) {
163                self.spans[k] = Some(m.regs[i]);
164            }
165        }
166    }
167
168    /// Take the walk's next match straight into these slots.
169    ///
170    /// The fields are destructured rather than reached through `self` so the
171    /// name list and the two position lists are borrowed as the separate
172    /// places they are: the walk reads the first and writes the other two.
173    fn fill_from_walk(
174        &mut self,
175        w: &mut crate::nfa::SerialWalk<crate::nfa::OwnedStream>,
176        input: &[u8],
177    ) -> Option<Span> {
178        let Self { names, spans, extents, matched, matched_extent } = self;
179        let (span, ks, ke) =
180            w.next_into(input, names.as_slice(), spans.as_mut_slice(), extents.as_mut_slice())?;
181        *matched = Some(span);
182        *matched_extent = Some((ks, ke));
183        Some(span)
184    }
185}
186
187/// The first match of `pattern` in `input`, with its registers written into
188/// `slots`.
189///
190/// The counterpart of the regex crate's `captures_read`. Nothing is allocated
191/// per match where the single-pass engine takes the pattern, which is the loop
192/// this exists for: the walk already carries the save slots and writes them
193/// straight through. A pattern the walk declines is answered by the set engine,
194/// which builds a match before the slots are filled from it.
195pub fn captures_read(pattern: &Pattern, input: &[u8], slots: &mut CaptureSlots) -> Option<Span> {
196    captures_read_at(pattern, input, 0, slots)
197}
198
199/// [`captures_read`] anchored at byte `at`.
200///
201/// The counterpart of the regex crate's `captures_read_at`. The bytes before
202/// `at` are still read, so a backward assertion and a start anchor see what
203/// precedes the position.
204///
205/// It answers one anchored question and answers it from the start. The regex
206/// crate's version RESUMES: it searches forward from `at`, so calling it in a
207/// loop over every match costs one pass over the input in total. This one
208/// RESTARTS: it takes the whole route ladder again, which is a fresh scan for a
209/// byte route and a fresh lex for the walk, so the same loop costs one pass per
210/// match. Over fifty thousand matches that is fifty thousand passes.
211///
212/// Use [`captures_read_iter`] to walk every match with one buffer. This is for
213/// a single anchored ask.
214pub fn captures_read_at(
215    pattern: &Pattern,
216    input: &[u8],
217    at: usize,
218    slots: &mut CaptureSlots,
219) -> Option<Span> {
220    slots.clear();
221    if !pattern.binds() {
222        let span = crate::cursor::find_at(pattern, input, at)?;
223        slots.matched = Some(span);
224        return Some(span);
225    }
226    if let Some(first) = crate::engine::routed_first_at(pattern, input, at) {
227        let first = first?;
228        let m = crate::engine::captures(pattern, input, &[first]).into_iter().next()?;
229        slots.take_match(&m, None);
230        return Some(m.span());
231    }
232    if let Some(mut w) = crate::nfa::SerialWalk::over(pattern, input) {
233        w.seek(at);
234        return slots.fill_from_walk(&mut w, input);
235    }
236    let toks = crate::parallel_lex::lex_parallel(input);
237    let start = toks.partition_point(|t| t.start() < at);
238    let span = crate::engine::scan_tokens_from(pattern, input, &toks, start).into_iter().next()?;
239    let m = crate::engine::captures_over(pattern, input, &toks, &[span]).into_iter().next()?;
240    slots.take_match(&m, None);
241    Some(m.span())
242}
243
244/// Matches taken one at a time into a buffer the caller owns.
245///
246/// The other half of the reusing loop, and the half that makes it worth having.
247/// The walk is held between matches, so taking every match costs one lex and
248/// one allocation however many matches there are.
249pub struct SlotCursor<'h> {
250    input: &'h [u8],
251    source: SlotSource,
252}
253
254/// Where a slot cursor's matches come from.
255enum SlotSource {
256    /// A route answered and the pattern binds nothing, so the spans arrived
257    /// together and there is nothing to resolve. Nothing is allocated per
258    /// match here either: the span is copied into the buffer's whole-match
259    /// field and the register slots stay empty.
260    Spans { spans: Vec<Span>, at: usize },
261    /// A route answered a pattern that does bind, so the registers were
262    /// resolved together with the spans and filling the buffer is a copy.
263    Resolved { ms: Vec<crate::engine::Match>, at: usize },
264    /// A route answered a pattern whose program is a flat run of atoms, so the
265    /// registers came back inline. Nothing is allocated per match on this path
266    /// at all: the record is copied into the caller's buffer and no match is
267    /// ever built.
268    Flat { ms: Vec<crate::nfa::FlatMatch>, names: std::sync::Arc<[String]>, at: usize },
269    /// The single-pass engine, held between matches and writing its save slots
270    /// straight into the caller's buffer.
271    ///
272    /// Boxed for the reason a held walk is always boxed here: it owns the
273    /// tokens, the program and two thread lists, and an enum is as large as its
274    /// largest variant.
275    Walk(Box<crate::nfa::SerialWalk<crate::nfa::OwnedStream>>),
276}
277
278impl SlotCursor<'_> {
279    /// The next match, with its registers written into `slots`, or `None` where
280    /// there is none left.
281    ///
282    /// `slots` need not have come from the same call that made this cursor, but
283    /// it must be shaped for the same pattern: a register the pattern binds and
284    /// the buffer does not name is skipped rather than written to some other
285    /// slot.
286    pub fn next_into(&mut self, slots: &mut CaptureSlots) -> Option<Span> {
287        match &mut self.source {
288            SlotSource::Spans { spans, at } => {
289                let s = *spans.get(*at)?;
290                *at += 1;
291                slots.clear();
292                slots.matched = Some(s);
293                Some(s)
294            }
295            SlotSource::Resolved { ms, at } => {
296                let m = ms.get(*at)?;
297                *at += 1;
298                slots.take_match(m, None);
299                Some(m.span())
300            }
301            SlotSource::Flat { ms, names, at } => {
302                let m = ms.get(*at)?;
303                *at += 1;
304                slots.take_flat(m, names);
305                Some(m.span)
306            }
307            SlotSource::Walk(w) => {
308                slots.clear();
309                slots.fill_from_walk(w, self.input)
310            }
311        }
312    }
313}
314
315/// A cursor over every match of `pattern`, refilling one buffer, or `None` for
316/// a pattern nothing here takes.
317///
318/// It takes the same ladder [`crate::captures_iter`] takes, for the same
319/// reason: a pattern a byte route answers must not be walked, because that
320/// route never lexes and the walk always does. Going straight to the walk would
321/// make this the slowest way to ask rather than the fastest.
322///
323/// `None` is a refusal and never a verdict of no match. A caller that must take
324/// every pattern falls back to [`crate::captures_iter`], which allocates a
325/// capture list per match and accepts everything.
326#[must_use]
327pub fn captures_read_iter<'h>(pattern: &Pattern, input: &'h [u8]) -> Option<SlotCursor<'h>> {
328    // A pattern that binds nothing has no registers to resolve, so the spans
329    // are the whole answer and the windows below have nothing to keep. Without
330    // this they run anyway and build a match a window to carry an empty capture
331    // list, which measured 48 nanoseconds a match against the 5 the buffer
332    // copy costs. [`crate::captures_iter`] takes the same exit first.
333    if !pattern.binds()
334        && let Some(spans) = crate::engine::routed_spans(pattern, input)
335    {
336        return Some(SlotCursor { input, source: SlotSource::Spans { spans, at: 0 } });
337    }
338    // One pass over the windows, keeping what each attempt bound. Taking the
339    // spans from the ladder and resolving them afterward lexes every window
340    // twice, because the first pass throws the save slots away to report a span.
341    // Taken wherever `flat_shape` accepts the program, for the reason
342    // [`crate::captures_iter`] gives: a walk over a match's bytes being cheaper
343    // than a lex does not settle which rung is cheaper, and on the pattern
344    // measured there the byte route below costs half as much again.
345    if let Some((ms, names)) = crate::prefilter::scan_flat_by_literal_windows(pattern, input) {
346        return Some(SlotCursor { input, source: SlotSource::Flat { ms, names, at: 0 } });
347    }
348    if let Some(ms) = crate::prefilter::scan_captures_by_literal_windows(pattern, input) {
349        return Some(SlotCursor { input, source: SlotSource::Resolved { ms, at: 0 } });
350    }
351    if let Some(spans) = crate::engine::routed_spans(pattern, input) {
352        let source = if !pattern.binds() {
353            SlotSource::Spans { spans, at: 0 }
354        } else if let Some((ms, names)) =
355            crate::prefilter::flat_captures_by_byte_bounds(pattern, input, &spans)
356        {
357            SlotSource::Flat { ms, names, at: 0 }
358        } else if let Some((ms, names)) =
359            crate::prefilter::flat_captures_by_windows(pattern, input, &spans)
360        {
361            SlotSource::Flat { ms, names, at: 0 }
362        } else {
363            SlotSource::Resolved { ms: crate::engine::captures(pattern, input, &spans), at: 0 }
364        };
365        return Some(SlotCursor { input, source });
366    }
367    let walk = crate::nfa::SerialWalk::over(pattern, input)?;
368    Some(SlotCursor { input, source: SlotSource::Walk(Box::new(walk)) })
369}
370
371/// How many registers every match of `pattern` binds, or `None` where that
372/// number is not the same for every match.
373///
374/// The counterpart of the regex crate's `static_captures_len`. A name under a
375/// star, an optional, a repeat that may run zero times, or only some branches
376/// of an alternation is not bound by every match, and one under an assertion is
377/// never bound at all: an assertion runs its sub-pattern as a filter and the
378/// probe's own bindings are discarded.
379#[must_use]
380pub fn static_captures_len(pattern: &Pattern) -> Option<usize> {
381    let all = crate::cursor::capture_names(pattern);
382    let mut certain = Vec::new();
383    certain_names(pattern, &mut certain);
384    (certain.len() == all.len()).then_some(all.len())
385}
386
387/// The names every match of `pattern` binds.
388fn certain_names(pattern: &Pattern, out: &mut Vec<String>) {
389    match pattern {
390        Pattern::Bind(name, _, inner) => {
391            if !out.iter().any(|n| n == name) {
392                out.push(name.clone());
393            }
394            certain_names(inner, out);
395        }
396        Pattern::Plus(p, _)
397        | Pattern::Atomic(p)
398        | Pattern::Balanced(_, p)
399        | Pattern::Field(_, p) => certain_names(p, out),
400        // A body that must run at least once binds what one run binds.
401        Pattern::Repeat(p, lo, _, _) if *lo >= 1 => certain_names(p, out),
402        Pattern::Concat(v) => {
403            for p in v {
404                certain_names(p, out);
405            }
406        }
407        // Only a name every branch binds is bound by the alternation.
408        Pattern::Alt(v, _) => {
409            let Some((first, rest)) = v.split_first() else { return };
410            let mut common = Vec::new();
411            certain_names(first, &mut common);
412            for p in rest {
413                let mut theirs = Vec::new();
414                certain_names(p, &mut theirs);
415                common.retain(|n| theirs.contains(n));
416            }
417            for n in common {
418                if !out.contains(&n) {
419                    out.push(n);
420                }
421            }
422        }
423        // A body that may run zero times binds nothing, and an assertion's
424        // bindings are discarded with the probe that made them.
425        // An edit-distance group binds where its alignment matched, and an
426        // alignment within `k` may leave any of its atoms out, so none of its
427        // names is bound by every match.
428        Pattern::Within(..)
429        | Pattern::Star(..)
430        | Pattern::Opt(..)
431        | Pattern::Repeat(..)
432        | Pattern::Assert(..)
433        | Pattern::Empty
434        | Pattern::Atom(_)
435        | Pattern::Guard(..)
436        | Pattern::Anchor(_) => {}
437    }
438}
439
440/// How many significant tokens every match of `pattern` spans, or `None` where
441/// that width is not the same for every match.
442///
443/// The token stream's counterpart of [`static_captures_len`], and a property no
444/// byte matcher has: a regular expression's matches have no fixed width in
445/// bytes because a token is not bounded in bytes, so a byte stream has no
446/// answer to the same question.
447///
448/// It is what the route ladder already turns on. Matches that are whole tokens
449/// of a fixed count cannot overlap, so a caller anchored at a position may take
450/// such matches and keep the ones beginning at or after it, which is why
451/// [`crate::find_at`] may filter one set of routes and not the other.
452#[must_use]
453pub fn static_token_extent(pattern: &Pattern) -> Option<usize> {
454    match pattern {
455        Pattern::Empty | Pattern::Guard(..) | Pattern::Anchor(_) | Pattern::Assert(..) => Some(0),
456        Pattern::Atom(_) => Some(1),
457        // Every run from `k` short of the atoms to `k` past them is offered,
458        // so the width varies with the alignment unless the count is zero.
459        Pattern::Within(v, 0) => Some(v.len()),
460        Pattern::Within(..) => None,
461        Pattern::Bind(_, _, p) | Pattern::Atomic(p) | Pattern::Field(_, p) => {
462            static_token_extent(p)
463        }
464        // The open and the close are tokens of the match as well as the
465        // interior, so a group is two wider than what it encloses.
466        Pattern::Balanced(_, p) => static_token_extent(p)?.checked_add(2),
467        Pattern::Concat(v) => {
468            let mut total = 0usize;
469            for p in v {
470                total = total.checked_add(static_token_extent(p)?)?;
471            }
472            Some(total)
473        }
474        Pattern::Alt(v, _) => {
475            let (first, rest) = v.split_first()?;
476            let width = static_token_extent(first)?;
477            for p in rest {
478                if static_token_extent(p)? != width {
479                    return None;
480                }
481            }
482            Some(width)
483        }
484        Pattern::Repeat(p, lo, Some(hi), _) if lo == hi => {
485            static_token_extent(p)?.checked_mul(*lo)
486        }
487        Pattern::Star(..) | Pattern::Plus(..) | Pattern::Opt(..) | Pattern::Repeat(..) => None,
488    }
489}
490
491#[cfg(test)]
492mod tests {
493    use super::*;
494    use crate::parser::parse;
495
496    #[test]
497    fn a_refilled_buffer_answers_what_a_match_would_have() {
498        // The regex crate's reusing loop, written the same way: take a match,
499        // read the slots, call again from its end. The answers must be the
500        // ones the allocating iterator gives, match for match and register for
501        // register, or the cheap path is a different matcher.
502        let hay = b"a 1 b 2 c 3";
503        let p = parse("\\W:k \\N:v").expect("parses");
504        let want: Vec<_> = crate::captures_iter(&p, hay).collect();
505        assert_eq!(want.len(), 3, "three pairs: {want:?}");
506
507        let mut slots = CaptureSlots::of(&p);
508        let mut at = 0usize;
509        let mut seen = 0usize;
510        while let Some(span) = captures_read_at(&p, hay, at, &mut slots) {
511            assert_eq!(span, want[seen].span(), "match {seen}");
512            assert_eq!(
513                slots.by_name("k"),
514                want[seen].group_span("k"),
515                "register k of match {seen}"
516            );
517            assert_eq!(
518                slots.by_name("v"),
519                want[seen].group_span("v"),
520                "register v of match {seen}"
521            );
522            assert_eq!(slots.matched(), Some(span));
523            at = span.end();
524            seen += 1;
525        }
526        assert_eq!(seen, want.len(), "the loop took every match");
527    }
528
529    /// Both cursors report what the eager resolve reports on the patterns whose
530    /// registers now arrive inline. The record holds spans in the program's name
531    /// order and the buffer holds them in the pattern's, so a wrong pairing here
532    /// is a register reported under another's name.
533    #[test]
534    fn the_inline_registers_are_the_ones_the_eager_resolve_reports() {
535        let mut text = String::new();
536        for i in 0..80u32 {
537            text.push_str(&format!("let value_{i} = {} ; call_{i}(alpha, beta) ;\n", i * 37));
538        }
539        let hay = text.as_bytes();
540        // Two registers named out of their positional order, so a path pairing
541        // by position rather than by name reports them swapped.
542        for src in ["\"let\" \\W:v \"=\"", "\\W:name \"=\"", "\"let\" \\W:z \"=\" \\N:a"] {
543            let p = parse(src).expect("parses");
544            let spans = crate::scan(&p, hay);
545            let want = crate::captures(&p, hay, &spans);
546            assert!(!want.is_empty(), "{src} matches the corpus");
547
548            let got: Vec<_> = crate::captures_iter(&p, hay).collect();
549            assert_eq!(got, want, "captures_iter {src}");
550
551            let mut slots = CaptureSlots::of(&p);
552            let mut c = captures_read_iter(&p, hay).expect("a route or the walk takes this");
553            let mut seen = 0usize;
554            while let Some(span) = c.next_into(&mut slots) {
555                assert_eq!(span, want[seen].span(), "{src} match {seen}");
556                for name in slots.names().to_vec() {
557                    assert_eq!(
558                        slots.by_name(&name),
559                        want[seen].group_span(&name),
560                        "{src} register {name} of match {seen}"
561                    );
562                }
563                seen += 1;
564            }
565            assert_eq!(seen, want.len(), "{src} took every match");
566        }
567    }
568
569    #[test]
570    fn a_cursor_refilling_one_buffer_takes_the_same_matches() {
571        // Holding the walk between matches must select exactly what taking
572        // them one at a time selects. It is also what makes the loop linear:
573        // captures_read_at restarts the route ladder on every call, so a loop
574        // written with that costs one pass over the input per match.
575        let hay = b"a 1 b 2 c 3 d 4";
576        let p = parse("\\W:k \\N:v").expect("parses");
577        let want: Vec<_> = crate::captures_iter(&p, hay).collect();
578        assert_eq!(want.len(), 4, "four pairs: {want:?}");
579
580        let mut slots = CaptureSlots::of(&p);
581        let mut c = captures_read_iter(&p, hay).expect("the walk takes this pattern");
582        let mut seen = 0usize;
583        while let Some(span) = c.next_into(&mut slots) {
584            assert_eq!(span, want[seen].span(), "match {seen}");
585            assert_eq!(slots.by_name("k"), want[seen].group_span("k"), "k of match {seen}");
586            assert_eq!(slots.by_name("v"), want[seen].group_span("v"), "v of match {seen}");
587            seen += 1;
588        }
589        assert_eq!(seen, want.len(), "the cursor took every match");
590
591        // A byte-routable pattern must take the route, which never lexes,
592        // rather than the walk, which always does. The selection is the same
593        // either way and that is what makes the choice free to make.
594        let routed = parse("\"a\"").expect("parses");
595        let mut rs = CaptureSlots::of(&routed);
596        let mut rc = captures_read_iter(&routed, hay).expect("a route answers this");
597        let mut got = Vec::new();
598        while let Some(s) = rc.next_into(&mut rs) {
599            got.push(s);
600        }
601        assert_eq!(got, crate::scan(&routed, hay), "the routed cursor selects what the scan does");
602        assert!(rs.is_empty(), "the pattern binds nothing, so there are no slots");
603
604        // A refusal is not a verdict of no match: the set engine takes this one
605        // and a caller falls back rather than reading zero matches.
606        let balanced = parse("\\B(\\N:v)").expect("parses");
607        assert!(captures_read_iter(&balanced, b"(1) (2)").is_none());
608        assert_eq!(crate::captures_iter(&balanced, b"(1) (2)").count(), 2);
609    }
610
611    #[test]
612    fn binding_a_register_does_not_key_the_thread_list_but_reading_one_does() {
613        // The engine keys its thread list on whether a register is read back,
614        // not on whether one is written. Two threads at one counter holding
615        // different bindings match identically from there unless something
616        // reads a binding, so keying on a write keeps threads apart that
617        // nothing can tell apart, and hashes the save array per thread per
618        // step to do it.
619        let bind_only = parse("\\W:k \"=\"").expect("parses");
620        assert!(bind_only.binds(), "it writes a register");
621        assert!(!bind_only.reads_registers(), "and never reads one back");
622
623        let reads = parse("\\W:x \"=\" =x").expect("parses");
624        assert!(reads.reads_registers(), "a back-reference reads one");
625
626        // What the change must not alter: the captures reported. A pattern
627        // that binds and never reads reports what it always did, and the
628        // back-reference still matches only where the tokens agree.
629        let hay = b"alpha = beta ; gamma = gamma ;";
630        let got: Vec<_> = crate::captures_iter(&bind_only, hay)
631            .map(|m| m.group_span("k").map(|s| (s.start(), s.end())))
632            .collect();
633        assert_eq!(got, vec![Some((0, 5)), Some((15, 20))], "both assignments, both keys");
634
635        let back: Vec<_> = crate::captures_iter(&reads, hay)
636            .map(|m| (m.span().start(), m.span().end()))
637            .collect();
638        assert_eq!(back.len(), 1, "only gamma = gamma repeats its token: {back:?}");
639        assert_eq!(&hay[back[0].0..back[0].1], &b"gamma = gamma"[..]);
640    }
641
642    #[test]
643    fn a_slot_carries_the_tokens_as_well_as_the_bytes() {
644        // The save slots hold significant-token indices and every layer above
645        // converts them to bytes and drops the pair. A byte matcher has no such
646        // pair to keep: a byte offset does not say which token it is in.
647        let hay = b"alpha 42 beta 7";
648        let p = parse("\\W:k \\N:v").expect("parses");
649        let mut slots = CaptureSlots::of(&p);
650        assert!(captures_read(&p, hay, &mut slots).is_some(), "alpha 42 matches");
651
652        let k = slots.index_of("k").expect("k is a register");
653        let v = slots.index_of("v").expect("v is a register");
654        assert_eq!(slots.tokens(k), Some(1), "a word is one token");
655        assert_eq!(slots.tokens(v), Some(1), "a number is one token");
656        assert_eq!(slots.matched_tokens(), Some(2), "the match spans both");
657
658        // The extents index the significant-token stream, so the second
659        // register begins where the first ends.
660        let (ks, ke) = slots.extent(k).expect("k bound");
661        let (vs, ve) = slots.extent(v).expect("v bound");
662        assert_eq!(ke, vs, "adjacent tokens: k ends at {ke}, v starts at {vs}");
663        assert_eq!(slots.matched_extent(), Some((ks, ve)));
664    }
665
666    #[test]
667    fn clearing_keeps_the_names_and_forgets_the_positions() {
668        let p = parse("\\W:k \\N:v").expect("parses");
669        let mut slots = CaptureSlots::of(&p);
670        assert!(captures_read(&p, b"alpha 42", &mut slots).is_some());
671        slots.clear();
672        assert_eq!(slots.len(), 2, "the shape is the pattern's, not the match's");
673        assert_eq!(slots.get(0), None);
674        assert_eq!(slots.matched(), None);
675        assert_eq!(slots.names().to_vec(), vec!["k".to_string(), "v".to_string()]);
676    }
677
678    #[test]
679    fn a_register_that_need_not_bind_has_no_static_count() {
680        assert_eq!(static_captures_len(&parse("\\W:k \\N:v").expect("parses")), Some(2));
681        assert_eq!(
682            static_captures_len(&parse("\\W:k (\\N:v)?").expect("parses")),
683            None,
684            "v is bound by some matches and not others"
685        );
686        assert_eq!(
687            static_captures_len(&parse("\\W:k | \\N:k").expect("parses")),
688            Some(1),
689            "every branch binds k"
690        );
691        assert_eq!(
692            static_captures_len(&parse("\\W:k | \\N:v").expect("parses")),
693            None,
694            "neither name is bound by both branches"
695        );
696        // An assertion is a filter and the probe's bindings are discarded, so a
697        // name only under one is never bound by any match. Every match of this
698        // binds exactly none, which is a fixed count and not a varying one.
699        let asserted = parse("\\W ~(\\N:v)").expect("parses");
700        assert_eq!(crate::capture_names(&asserted), Vec::<String>::new(), "v cannot bind");
701        assert_eq!(static_captures_len(&asserted), Some(0), "every match binds none");
702    }
703
704    #[test]
705    fn a_fixed_token_width_is_a_property_no_byte_matcher_has() {
706        assert_eq!(static_token_extent(&parse("\\W \\N").expect("parses")), Some(2));
707        assert_eq!(static_token_extent(&parse("\\W{3}").expect("parses")), Some(3));
708        assert_eq!(
709            static_token_extent(&parse("\\B(\\N)").expect("parses")),
710            Some(3),
711            "the open and the close are tokens of the match"
712        );
713        assert_eq!(
714            static_token_extent(&parse("\\W ~(\\N)").expect("parses")),
715            Some(1),
716            "an assertion consumes nothing"
717        );
718        assert_eq!(static_token_extent(&parse("\\W | \\N").expect("parses")), Some(1));
719        assert_eq!(
720            static_token_extent(&parse("\\W | \\N \\N").expect("parses")),
721            None,
722            "the branches are one token and two"
723        );
724        assert_eq!(static_token_extent(&parse("\\W*").expect("parses")), None);
725        assert_eq!(static_token_extent(&parse("\\W{2,4}").expect("parses")), None);
726    }
727
728    #[test]
729    fn anchoring_selects_where_a_match_may_begin_and_never_what_the_input_is() {
730        let hay = b"a 1 b 2 c 3";
731        let p = parse("\\W:k \\N:v").expect("parses");
732        let all: Vec<_> = crate::captures_iter(&p, hay).collect();
733        assert_eq!(all.len(), 3);
734
735        let after_first = all[0].span().end();
736        let got = crate::captures_at(&p, hay, after_first).expect("a match follows the first");
737        assert_eq!(got.span(), all[1].span(), "the next match, not the first again");
738        assert_eq!(got.group_span("k"), all[1].group_span("k"));
739
740        assert_eq!(crate::shortest_match_at(&p, hay, 0), crate::shortest_match(&p, hay));
741        assert_eq!(
742            crate::shortest_match_at(&p, hay, after_first),
743            Some(all[1].span().end()),
744            "the soonest end at or after the position"
745        );
746        assert_eq!(crate::captures_at(&p, hay, hay.len()), None, "nothing begins past the end");
747    }
748
749    #[test]
750    fn a_set_reports_where_each_member_matched() {
751        // The regex crate's RegexSet reports which patterns match and states
752        // that it does not report where: one automaton carrying every pattern
753        // loses which of them reached an accepting state. A token set shares a
754        // lex rather than an automaton, so each member is walked as itself and
755        // keeps its span.
756        let hay = b"alpha 42";
757        let set = crate::PatternSet::new(vec![
758            parse("\\N").expect("parses"),
759            parse("\"zzzqqq\"").expect("parses"),
760            parse("\\W").expect("parses"),
761        ]);
762
763        let hits = set.matches_with_spans(hay);
764        assert_eq!(hits.iter().map(|&(i, _)| i).collect::<Vec<_>>(), vec![0, 2]);
765        assert_eq!(&hay[hits[0].1.start()..hits[0].1.end()], &b"42"[..]);
766        assert_eq!(&hay[hits[1].1.start()..hits[1].1.end()], &b"alpha"[..]);
767
768        let m = set.matched(hay);
769        assert!(m.matched(0) && !m.matched(1) && m.matched(2));
770        assert!(m.matched_any(), "two of three");
771        assert!(!m.matched_all(), "zzzqqq is nowhere in the input");
772        assert_eq!(m.iter().collect::<Vec<_>>(), vec![0, 2]);
773        assert_eq!(m.len(), 3, "every index carries a verdict, matched or not");
774
775        // Anchored past the word: the number is still ahead and the word is not.
776        let at = set.matches_at(hay, 5);
777        assert!(at.matched(0), "42 begins at or after byte 5");
778        assert!(!at.matched(2), "alpha ends before it");
779        assert!(set.is_match_at(hay, 5));
780        assert!(!set.is_match_at(hay, hay.len()), "nothing begins past the end");
781    }
782}