Skip to main content

trex/
pattern_set.rs

1//! Many patterns asked of one input, over one lex.
2//!
3//! The regex crate's `RegexSet` answers which of its patterns match in a
4//! single pass, and the saving there is that one automaton carries all of
5//! them: the input is read once instead of once per pattern.
6//!
7//! Over tokens the saving is larger and comes from somewhere else. Matching
8//! here is a lex and then a walk, the lex runs at a pattern-independent rate
9//! and dominates, and the walk is the cheap half. So a set does not need one
10//! automaton to win - it needs one lex. Twenty patterns over a shared token
11//! stream cost one lex and twenty walks, where twenty separate calls cost
12//! twenty of each.
13//!
14//! A pattern that a byte route answers costs neither: those routes read the
15//! input's bytes and never reach the lexer, so a set whose patterns are all
16//! byte-routable never lexes at all, and one with a mix lexes once for the
17//! rest.
18//!
19//! The lex the rest share is a prefix that widens, not the whole input. That
20//! is the difference between a set being worth having and being worse than
21//! not having one: a member asked on its own settles from a prefix, so a set
22//! that lexes everything up front would lose to the same members asked one at
23//! a time. Members drop out of the widening as they are settled, so the
24//! prefix reached is the one the hardest member needed rather than the sum of
25//! what each needed.
26
27use crate::ast::Pattern;
28use crate::token::Token;
29
30/// A set of patterns, asked together.
31pub struct PatternSet {
32    pats: Vec<Pattern>,
33    /// The members' names, one each, where the set was built from a pattern
34    /// file: a `let` under its name, a bare line under its line number.
35    /// Empty for a set built from patterns alone, whose members go by index.
36    names: Vec<String>,
37    /// The shapes and kinds a pattern file declared for the members, which
38    /// a member naming one is lexed under; empty for a set built from
39    /// patterns alone.
40    shapes: crate::custom::ShapeSet,
41    /// Whether the members the single-pass engine takes are walked as one
42    /// program over the shared lex, or each as itself.
43    as_one: bool,
44    /// Whether an input's literals are probed once for every member through
45    /// a filter, or searched for once per member.
46    probed: bool,
47    /// Those members as one program, built on first use.
48    together: std::sync::OnceLock<Together>,
49}
50
51impl std::fmt::Debug for PatternSet {
52    /// The members and their names; the union built over them is not shown.
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        f.debug_struct("PatternSet").field("pats", &self.pats).field("names", &self.names).finish_non_exhaustive()
55    }
56}
57
58impl Clone for PatternSet {
59    /// The same members, names and declarations; the union is built again
60    /// on first use.
61    fn clone(&self) -> Self {
62        PatternSet {
63            pats: self.pats.clone(),
64            names: self.names.clone(),
65            shapes: self.shapes.clone(),
66            as_one: self.as_one,
67            probed: self.probed,
68            together: std::sync::OnceLock::new(),
69        }
70    }
71}
72
73/// The members walked as one: their union, and each set index's place in
74/// it, `None` for a member walked as itself.
75struct Together {
76    union: Option<crate::nfa::Union>,
77    place: Vec<Option<u32>>,
78    /// How many literals the members require between them, which decides
79    /// whether an input is filtered once or searched once per literal.
80    required_literals: usize,
81}
82
83/// How a member of a set was read, which is how its matches' registers are
84/// resolved: over the bytes a route read, over the lex the members shared,
85/// or over a lex of its own under its library kinds.
86enum Lexed<'t> {
87    Bytes,
88    Shared(&'t [crate::token::Token]),
89    Own,
90}
91
92/// What one input answers for every member at once: a filter over its
93/// n-grams, built once, that says which literals are absent with no search.
94/// Absent when the members require few literals, which one search each
95/// answers for less than the filter costs to build.
96struct Probe {
97    filter: Option<crate::prefilter::BloomFilter>,
98}
99
100impl Probe {
101    /// Whether `lits` are all absent from the input by the filter alone.
102    fn refuses_all(&self, lits: &[&str]) -> bool {
103        self.filter.as_ref().is_some_and(|f| crate::prefilter::all_absent_by(f, lits))
104    }
105}
106
107/// Which patterns of a set matched, one verdict per index.
108///
109/// The counterpart of the regex crate's `SetMatches`. Every index carries a
110/// verdict, so asking about one pattern is a lookup rather than a search
111/// through the list [`PatternSet::matches`] returns.
112#[derive(Clone, Debug, Default, PartialEq, Eq)]
113pub struct SetMatches {
114    bits: Vec<bool>,
115}
116
117impl SetMatches {
118    /// Whether the pattern at `i` matched. An index past the set reads false.
119    #[must_use]
120    pub fn matched(&self, i: usize) -> bool {
121        self.bits.get(i).copied().unwrap_or(false)
122    }
123
124    /// Whether any pattern matched.
125    #[must_use]
126    pub fn matched_any(&self) -> bool {
127        self.bits.iter().any(|b| *b)
128    }
129
130    /// Whether every pattern matched.
131    #[must_use]
132    pub fn matched_all(&self) -> bool {
133        self.bits.iter().all(|b| *b)
134    }
135
136    /// How many patterns the set held.
137    #[must_use]
138    pub fn len(&self) -> usize {
139        self.bits.len()
140    }
141
142    /// Whether the set held none.
143    #[must_use]
144    pub fn is_empty(&self) -> bool {
145        self.bits.is_empty()
146    }
147
148    /// The indices that matched, in order.
149    pub fn iter(&self) -> impl Iterator<Item = usize> + '_ {
150        self.bits.iter().enumerate().filter_map(|(i, b)| b.then_some(i))
151    }
152}
153
154/// How one member of a set is to be answered.
155enum Plan {
156    /// A byte route settled it without reaching the lexer.
157    Settled(bool),
158    /// It needs a lex of its own: an anchor no prefix can settle, or a
159    /// pattern the single-pass engine does not take.
160    Alone,
161    /// It can be walked over the prefix the rest of the set shares, with the
162    /// program already compiled and the token bound a match cannot exceed.
163    Shared(crate::nfa::Compiled, usize),
164    /// It is walked over the shared prefix as a member of the union, at this
165    /// place in it, with the token bound a match cannot exceed.
166    Together(u32, usize),
167}
168
169impl PatternSet {
170    /// A set over `pats`, in the order given. That order is the index every
171    /// answer is reported under.
172    #[must_use]
173    pub fn new(pats: Vec<Pattern>) -> Self {
174        PatternSet {
175            pats,
176            names: Vec::new(),
177            shapes: crate::custom::ShapeSet::new(),
178            as_one: true,
179            probed: false,
180            together: std::sync::OnceLock::new(),
181        }
182    }
183
184    /// A set over `pats` with a name each, in the order given; a name short
185    /// of the count is the member's index as text.
186    #[must_use]
187    pub fn named(pats: Vec<Pattern>, mut names: Vec<String>) -> Self {
188        let count = pats.len();
189        names.truncate(count);
190        while names.len() < count {
191            names.push(names.len().to_string());
192        }
193        let mut set = PatternSet::new(pats);
194        set.names = names;
195        set
196    }
197
198    /// The members a pattern file declares, into `shapes`: a `let NAME =
199    /// PATTERN` line is a member under its name, and any line that is not a
200    /// declaration is a member under its line number, parsed against the
201    /// declarations so far; `kind`, `shape`, `shape-after` and `test` lines
202    /// are declared as `ShapeSet::declare_text` declares them, so the file's
203    /// kinds and shapes serve the members and `lib --test` checks them.
204    /// Every member is lexed under the file's declarations, as a scan under
205    /// `--lib` is.
206    ///
207    /// # Errors
208    ///
209    /// The first line that is neither a declaration nor a pattern, or whose
210    /// declaration is refused, with its line number in the message.
211    pub fn from_text(
212        text: &str,
213        shapes: &mut crate::custom::ShapeSet,
214    ) -> Result<PatternSet, crate::custom::ShapeError> {
215        let mut members = Vec::new();
216        shapes.declare_lines(text, Some(&mut members))?;
217        Ok(PatternSet::of_members(members, shapes))
218    }
219
220    /// [`Self::from_text`] over the pattern file at `path`, a relative
221    /// `@file` set in it read from beside the file.
222    ///
223    /// # Errors
224    ///
225    /// The file cannot be read, or a line of it is refused.
226    pub fn from_file(
227        path: &std::path::Path,
228        shapes: &mut crate::custom::ShapeSet,
229    ) -> Result<PatternSet, crate::custom::ShapeError> {
230        let members = shapes.declare_file_members(path)?;
231        Ok(PatternSet::of_members(members, shapes))
232    }
233
234    /// A set over named members, lexed under `shapes`.
235    fn of_members(members: Vec<(String, Pattern)>, shapes: &crate::custom::ShapeSet) -> PatternSet {
236        let (names, pats): (Vec<String>, Vec<Pattern>) = members.into_iter().unzip();
237        PatternSet::named(pats, names).under(shapes.clone())
238    }
239
240    /// This set with its members lexed under `shapes`, as a set built from a
241    /// pattern file is lexed under the file's declarations.
242    #[must_use]
243    pub fn under(mut self, shapes: crate::custom::ShapeSet) -> Self {
244        self.shapes = shapes;
245        self
246    }
247
248    /// The members' names, one each, where the set was built from a pattern
249    /// file, and nothing where it was built from patterns alone.
250    #[must_use]
251    pub fn names(&self) -> &[String] {
252        &self.names
253    }
254
255    /// The name the member at `i` goes by: its name where the set has them,
256    /// else its index as text.
257    #[must_use]
258    pub fn name(&self, i: usize) -> String {
259        self.names.get(i).cloned().unwrap_or_else(|| i.to_string())
260    }
261
262    /// The declarations the members are lexed under.
263    #[must_use]
264    pub fn shapes(&self) -> &crate::custom::ShapeSet {
265        &self.shapes
266    }
267
268    /// Every match of every member: each member's leftmost, non-overlapping
269    /// matches over `input`, tagged with the member's index and ordered by
270    /// position, then by member. The members a byte route answers never
271    /// reach the lexer; the rest share one lex and each is walked over it as
272    /// itself, so every span keeps the member that made it. A set built from
273    /// a pattern file lexes every member under the file's declarations,
274    /// since a shape decides boundaries a byte route never sees.
275    #[must_use]
276    pub fn scan(&self, input: &[u8]) -> Vec<(usize, crate::engine::Span)> {
277        self.scan_from(input, 0)
278    }
279
280    /// [`Self::scan`] from the first token starting at or after byte `at`,
281    /// each member's leftmost, non-overlapping selection re-run from there.
282    #[must_use]
283    pub fn scan_from(&self, input: &[u8], at: usize) -> Vec<(usize, crate::engine::Span)> {
284        let mut out: Vec<(usize, crate::engine::Span)> = Vec::new();
285        self.each_member(input, at, false, None, |i, spans, _| {
286            out.extend(spans.into_iter().map(|s| (i, s)));
287        });
288        out.sort_unstable_by_key(|&(i, s)| (s.start(), s.end(), i));
289        out
290    }
291
292    /// [`Self::scan_from`] over a lex of `input` the caller already holds.
293    ///
294    /// A streaming push lexes its retained buffer before it scans, and a set
295    /// scanning the same bytes would otherwise lex them again - measured at
296    /// 7.3 MB in 57 pushes, the stream's lex ran 57 times and the set's 56
297    /// more over the same buffer. Lending the tokens removes the second.
298    ///
299    /// `toks` must be a lex of the whole of `input` taken under no declared
300    /// shapes, which is what [`crate::lexer::lex_into`] gives. A set that
301    /// declares shapes ignores them and lexes under its own, since those
302    /// decide boundaries the caller's lex never saw.
303    #[must_use]
304    pub fn scan_from_over(
305        &self,
306        input: &[u8],
307        at: usize,
308        toks: &[Token],
309    ) -> Vec<(usize, crate::engine::Span)> {
310        let mut out: Vec<(usize, crate::engine::Span)> = Vec::new();
311        self.each_member(input, at, false, Some(toks), |i, spans, _| {
312            out.extend(spans.into_iter().map(|s| (i, s)));
313        });
314        out.sort_unstable_by_key(|&(i, s)| (s.start(), s.end(), i));
315        out
316    }
317
318    /// [`Self::scan`] with each match's registers resolved under its own
319    /// member's names, over the lex the member was found on where it shared
320    /// one, so a set of binding members pays no lex beyond the scan's; under
321    /// `lists`, with every binding a register made under a repetition, as
322    /// [`crate::captures_with_lists`] resolves them.
323    #[must_use]
324    pub fn scan_matches(&self, input: &[u8], lists: bool) -> Vec<(usize, crate::engine::Match)> {
325        self.resolved(input, false, lists)
326    }
327
328    /// Each member's first match over `input`, with its registers resolved
329    /// as [`Self::scan_matches`] resolves them, ordered by position, then by
330    /// member. A member stops at its first match and the lexer stops with
331    /// it: a byte route reads no token, a member that lexes alone reads
332    /// through a cursor that lexes as it goes, and the rest share one prefix
333    /// that widens only while a member is still open. A set built from a
334    /// pattern file with declarations lexes every member under them.
335    #[must_use]
336    pub fn first_matches(&self, input: &[u8], lists: bool) -> Vec<(usize, crate::engine::Match)> {
337        let shaped = !self.shapes.is_empty();
338        let spans = if shaped { self.first_spans_shaped(input, 0, false) } else { self.first_spans(input) };
339        let mut out = Vec::with_capacity(spans.len());
340        for (i, s) in spans {
341            let p = &self.pats[i];
342            let resolved = match (lists, shaped) {
343                (true, false) => crate::engine::captures_with_lists(p, input, &[s]),
344                (false, false) => crate::engine::captures(p, input, &[s]),
345                (true, true) => crate::engine::captures_with_shapes_and_lists(p, input, &self.shapes, &[s]),
346                (false, true) => crate::engine::captures_with_shapes(p, input, &self.shapes, &[s]),
347            };
348            out.extend(resolved.into_iter().map(|m| (i, m)));
349        }
350        out.sort_by_key(|(i, m)| (m.start, m.end, *i));
351        out
352    }
353
354    /// Each member's first match, found as [`Self::matches`] finds whether
355    /// there is one: a route over the bytes where one answers, a cursor that
356    /// lexes as it goes for a member that lexes alone, and one widening
357    /// prefix for the rest, so no member reads past what its first match
358    /// needs.
359    fn first_spans(&self, input: &[u8]) -> Vec<(usize, crate::engine::Span)> {
360        let mut out = Vec::new();
361        let mut shared = Vec::new();
362        let mut together = Vec::new();
363        let probe = self.probe(input);
364        for (i, p) in self.pats.iter().enumerate() {
365            match self.plan(i, p, input, &probe) {
366                Plan::Settled(false) => {}
367                // A route said there is a match: the route that reports
368                // where answers, and the cursor where none of them does.
369                Plan::Settled(true) => {
370                    let first = match crate::engine::routed_first(p, input) {
371                        Some(first) => first,
372                        None => crate::cursor::find(p, input),
373                    };
374                    if let Some(s) = first {
375                        out.push((i, s));
376                    }
377                }
378                Plan::Alone => {
379                    if let Some(s) = crate::cursor::find(p, input) {
380                        out.push((i, s));
381                    }
382                }
383                Plan::Shared(c, max_len) => shared.push((i, c, max_len)),
384                Plan::Together(j, max_len) => together.push((i, j, max_len)),
385            }
386        }
387        out.extend(self.over_a_shared_prefix(input, &shared, &together, false));
388        out
389    }
390
391    /// The matches of every member, or its first alone under `first`, each
392    /// resolved over the lex it was found on.
393    fn resolved(&self, input: &[u8], first: bool, lists: bool) -> Vec<(usize, crate::engine::Match)> {
394        let mut out: Vec<(usize, crate::engine::Match)> = Vec::new();
395        self.each_member(input, 0, first, None, |i, spans, lexed| {
396            let p = &self.pats[i];
397            let matches = match lexed {
398                Lexed::Bytes if lists => crate::engine::captures_with_lists(p, input, &spans),
399                Lexed::Bytes => crate::engine::captures(p, input, &spans),
400                Lexed::Shared(toks) if lists => crate::engine::captures_over_with_lists(p, input, toks, &spans),
401                Lexed::Shared(toks) => crate::engine::captures_over(p, input, toks, &spans),
402                Lexed::Own if lists => crate::engine::captures_with_shapes_and_lists(p, input, &self.shapes, &spans),
403                Lexed::Own => crate::engine::captures_with_shapes(p, input, &self.shapes, &spans),
404            };
405            out.extend(matches.into_iter().map(|m| (i, m)));
406        });
407        out.sort_by_key(|(i, m)| (m.start, m.end, *i));
408        out
409    }
410
411    /// Hand `emit` each member's matches at or after byte `at`, or its
412    /// first alone under `first`, with how the member was read: the members
413    /// a byte route answers never reach the lexer, the rest share one lex
414    /// and each is walked over it as itself, and a member naming a library
415    /// kind takes a lex of its own under that kind's shapes. Under the
416    /// declared shapes of a set built from a pattern file every member is
417    /// lexed, once, since a shape decides boundaries a byte route never
418    /// sees and a literal's absence from the bytes settles nothing it could
419    /// have fused.
420    fn each_member<F>(
421        &self,
422        input: &[u8],
423        at: usize,
424        first: bool,
425        reuse: Option<&[Token]>,
426        mut emit: F,
427    ) where
428        F: FnMut(usize, Vec<crate::engine::Span>, Lexed<'_>),
429    {
430        let shaped = !self.shapes.is_empty();
431        let mut shared = Vec::new();
432        let mut own = Vec::new();
433        let probe = self.probe(input);
434        for (i, p) in self.pats.iter().enumerate() {
435            if !p.library_kinds().is_empty() {
436                own.push(i);
437                continue;
438            }
439            if shaped {
440                shared.push(i);
441                continue;
442            }
443            if crate::prefilter::requires_absent_with(p, input, probe.filter.as_ref()) {
444                continue;
445            }
446            // From the start any route answers; from a later position only
447            // the routes whose matches cannot overlap may be cut at it.
448            let routed = if first {
449                crate::engine::routed_first(p, input).map(|s| s.into_iter().collect())
450            } else if at == 0 {
451                crate::engine::routed_spans(p, input)
452            } else {
453                crate::engine::routed_spans_positional(p, input)
454            };
455            match routed {
456                Some(spans) => emit(i, spans.into_iter().filter(|s| s.start() >= at).collect(), Lexed::Bytes),
457                None => shared.push(i),
458            }
459        }
460        if !shared.is_empty() {
461            // One lex for every member that needs one, read by each rather
462            // than copied to it.
463            // A caller holding a lex of these same bytes can lend it, and a
464            // streaming set is handed the one its push already took. The
465            // offer is refused where this set declares shapes, because those
466            // decide boundaries the caller's lex never saw, and a set must
467            // read its members under its own declarations whatever it is
468            // given.
469            let lexed;
470            let toks: &[Token] = match (shaped, reuse) {
471                (true, _) => {
472                    let blobs = crate::lexer::blob_runs(input);
473                    lexed = crate::lexer::lex_with_shapes(input, &blobs, &self.shapes, 0);
474                    &lexed
475                }
476                (false, Some(lent)) => lent,
477                (false, None) => {
478                    lexed = crate::parallel_lex::lex_parallel(input);
479                    &lexed
480                }
481            };
482            let start = toks.partition_point(|t| t.start() < at);
483            let sig = crate::nfa::Stitched::significant_of(toks);
484            for i in shared {
485                let p = &self.pats[i];
486                let stream = crate::nfa::Stitched::new(toks, &sig);
487                let spans = if let Some(mut w) = crate::nfa::SerialWalk::over_stream(p, input, stream) {
488                    w.seek(at);
489                    let mut spans = Vec::new();
490                    while let Some(s) = w.next_span(input) {
491                        spans.push(s);
492                        if first {
493                            break;
494                        }
495                    }
496                    spans
497                } else {
498                    let mut spans = crate::engine::scan_tokens_from(p, input, toks, start);
499                    if first {
500                        spans.truncate(1);
501                    }
502                    spans
503                };
504                emit(i, spans, Lexed::Shared(toks));
505            }
506        }
507        for i in own {
508            let mut spans = crate::engine::scan_with_shapes_from(&self.pats[i], input, &self.shapes, at);
509            if first {
510                spans.truncate(1);
511            }
512            emit(i, spans, Lexed::Own);
513        }
514    }
515
516    /// Each member's first match at or after byte `at` under the declared
517    /// shapes of a set built from a pattern file, over one lex under them,
518    /// in member order; the first of them alone under `any`.
519    fn first_spans_shaped(&self, input: &[u8], at: usize, any: bool) -> Vec<(usize, crate::engine::Span)> {
520        let mut out = Vec::new();
521        self.each_member(input, at, true, None, |i, spans, _| {
522            if let Some(&s) = spans.first() {
523                out.push((i, s));
524            }
525        });
526        out.sort_unstable_by_key(|&(i, _)| i);
527        if any {
528            out.truncate(1);
529        }
530        out
531    }
532
533    /// The most tokens a match of any member can span, for a set every
534    /// member of which is bounded: what a stream over the set commits under.
535    #[must_use]
536    pub fn max_tokens(&self) -> Option<usize> {
537        self.pats.iter().map(Pattern::max_tokens).try_fold(0usize, |best, m| m.map(|m| best.max(m)))
538    }
539
540    /// Whether any member depends on input outside a single match span, so a
541    /// stream over the set commits nothing before its end.
542    #[must_use]
543    pub fn depends_on_whole_input(&self) -> bool {
544        self.pats.iter().any(Pattern::depends_on_whole_input)
545    }
546
547    /// Whether any member depends on input beyond the lines its match spans,
548    /// so a stream over the set, which cuts only just after a newline,
549    /// commits nothing before its end.
550    #[must_use]
551    pub fn depends_on_more_than_its_lines(&self) -> bool {
552        self.pats.iter().any(Pattern::depends_on_more_than_its_lines)
553    }
554
555    /// Whether any member reads whitespace, so a stream over the set cannot
556    /// take a line's end as a boundary.
557    #[must_use]
558    pub fn reads_whitespace(&self) -> bool {
559        self.pats.iter().any(Pattern::reads_whitespace)
560    }
561
562    /// Whether the literals the members require are probed once per input
563    /// through a filter over its n-grams, or searched for once per member,
564    /// which is how a set is built: measured on a thousand members over
565    /// 2 MiB, the filter cost 40 ms more than the searches when the literals
566    /// were present and saved 13 ms when they were absent. The answers are
567    /// the same either way; a caller whose lists are mostly absent turns the
568    /// probe on.
569    #[must_use]
570    pub fn probed(mut self, on: bool) -> Self {
571        self.probed = on;
572        self
573    }
574
575    /// Whether the members the single-pass engine takes are walked as one
576    /// program over the shared lex, which is how a set is built, or each as
577    /// itself. The answers are the same either way; this is the switch the
578    /// two forms are timed against each other through.
579    #[must_use]
580    pub fn walked_as_one(mut self, on: bool) -> Self {
581        self.as_one = on;
582        self.together = std::sync::OnceLock::new();
583        self
584    }
585
586    /// This set with the members' names replaced.
587    #[must_use]
588    pub fn with_names(mut self, names: Vec<String>) -> Self {
589        let count = self.pats.len();
590        self.names = names;
591        self.names.truncate(count);
592        while self.names.len() < count {
593            self.names.push(self.names.len().to_string());
594        }
595        self
596    }
597
598    /// The union of the members it takes, built once.
599    fn together(&self) -> &Together {
600        self.together.get_or_init(|| {
601            let mut place = vec![None; self.pats.len()];
602            let mut members: Vec<&Pattern> = Vec::new();
603            if self.as_one {
604                for (i, p) in self.pats.iter().enumerate() {
605                    if crate::nfa::union_eligible(p) {
606                        place[i] = Some(u32::try_from(members.len()).expect("a set holds fewer than four billion patterns"));
607                        members.push(p);
608                    }
609                }
610            }
611            let union = (!members.is_empty()).then(|| crate::nfa::Union::of(&members));
612            let required_literals =
613                self.pats.iter().map(crate::prefilter::required_literal_count).sum();
614            Together { union, place, required_literals }
615        })
616    }
617
618    /// The probe of `input` the members share: a filter over the input's
619    /// n-grams where the members require more literals than one search each
620    /// is worth, and nothing otherwise.
621    fn probe(&self, input: &[u8]) -> Probe {
622        let many = self.probed
623            && self.together().required_literals > crate::prefilter::direct_search_max_literals();
624        Probe { filter: many.then(|| crate::prefilter::BloomFilter::build(input)) }
625    }
626
627    /// The guard literals a prefilter proves absent from `input` for every
628    /// member in `indices`, so one union walk can carry them all.
629    fn absent_for(&self, indices: impl Iterator<Item = usize>, input: &[u8]) -> std::collections::HashSet<Vec<u8>> {
630        let mut absent = std::collections::HashSet::new();
631        for i in indices {
632            absent.extend(crate::prefilter::absent_guard_literals(&self.pats[i], input));
633        }
634        absent
635    }
636
637    /// Each member of `asked` (a set index and its place in the union) with
638    /// its first match at or after token `from`, from one walk of the union.
639    fn first_spans_together(
640        &self,
641        asked: &[(usize, u32)],
642        input: &[u8],
643        toks: &[crate::token::Token],
644        from: usize,
645    ) -> Vec<(usize, Option<crate::engine::Span>)> {
646        if asked.is_empty() {
647            return Vec::new();
648        }
649        let Some(u) = self.together().union.as_ref() else {
650            return Vec::new();
651        };
652        let mut active = vec![false; u.len()];
653        for &(_, j) in asked {
654            active[j as usize] = true;
655        }
656        let absent = self.absent_for(asked.iter().map(|&(i, _)| i), input);
657        let firsts = crate::nfa::first_spans_union(u, &active, input, toks, from, &absent);
658        asked.iter().map(|&(i, j)| (i, firsts[j as usize])).collect()
659    }
660
661    /// How many patterns the set holds.
662    #[must_use]
663    pub fn len(&self) -> usize {
664        self.pats.len()
665    }
666
667    /// Whether the set holds none.
668    #[must_use]
669    pub fn is_empty(&self) -> bool {
670        self.pats.is_empty()
671    }
672
673    /// The patterns themselves, in index order.
674    #[must_use]
675    pub fn patterns(&self) -> &[Pattern] {
676        &self.pats
677    }
678
679    /// The indices of the patterns that match `input`, in index order.
680    ///
681    /// Every pattern is asked. A route that answers without the lexer answers
682    /// first and costs nothing; what is left shares one lex.
683    #[must_use]
684    pub fn matches(&self, input: &[u8]) -> Vec<usize> {
685        if !self.shapes.is_empty() {
686            return self.first_spans_shaped(input, 0, false).into_iter().map(|(i, _)| i).collect();
687        }
688        let mut out = Vec::new();
689        let mut shared = Vec::new();
690        let mut together = Vec::new();
691        let probe = self.probe(input);
692        for (i, p) in self.pats.iter().enumerate() {
693            match self.plan(i, p, input, &probe) {
694                Plan::Settled(true) => out.push(i),
695                Plan::Settled(false) => {}
696                Plan::Alone => {
697                    if crate::engine::is_match(p, input) {
698                        out.push(i);
699                    }
700                }
701                Plan::Shared(c, max_len) => shared.push((i, c, max_len)),
702                Plan::Together(j, max_len) => together.push((i, j, max_len)),
703            }
704        }
705        out.extend(self.over_a_shared_prefix(input, &shared, &together, false).into_iter().map(|(i, _)| i));
706        out.sort_unstable();
707        out
708    }
709
710    /// Whether any pattern in the set matches `input`.
711    ///
712    /// Stops at the first that does, so a set whose early members are
713    /// byte-routable can answer without lexing even when later ones would
714    /// have needed it, and a set that must lex stops widening its prefix the
715    /// moment one member matches.
716    #[must_use]
717    pub fn is_match(&self, input: &[u8]) -> bool {
718        if !self.shapes.is_empty() {
719            return !self.first_spans_shaped(input, 0, true).is_empty();
720        }
721        let mut shared = Vec::new();
722        let mut together = Vec::new();
723        let mut alone = Vec::new();
724        let probe = self.probe(input);
725        for (i, p) in self.pats.iter().enumerate() {
726            match self.plan(i, p, input, &probe) {
727                Plan::Settled(true) => return true,
728                Plan::Settled(false) => {}
729                Plan::Alone => alone.push(p),
730                Plan::Shared(c, max_len) => shared.push((i, c, max_len)),
731                Plan::Together(j, max_len) => together.push((i, j, max_len)),
732            }
733        }
734        // The members that need a lex of their own are asked after the ones a
735        // byte route settled and before the shared prefix runs, so a set that
736        // one of them answers never widens a prefix at all.
737        if alone.iter().any(|p| crate::engine::is_match(p, input)) {
738            return true;
739        }
740        !self.over_a_shared_prefix(input, &shared, &together, true).is_empty()
741    }
742
743    /// Which patterns match `input`, as a bitset over the set's indices.
744    ///
745    /// The counterpart of the regex crate's `matches`, which returns its
746    /// `SetMatches`. [`Self::matches`] answers the same question as a list of
747    /// the indices that matched; this reports every index with its verdict, so
748    /// a caller asking about one pattern does not scan a list to find it.
749    #[must_use]
750    pub fn matched(&self, input: &[u8]) -> SetMatches {
751        let mut bits = vec![false; self.pats.len()];
752        for i in self.matches(input) {
753            bits[i] = true;
754        }
755        SetMatches { bits }
756    }
757
758    /// Which patterns match at or after byte `at`, as a bitset.
759    ///
760    /// The counterpart of the regex crate's `matches_at`. The bytes before `at`
761    /// are still read, so an assertion that looks back sees them.
762    ///
763    /// This lexes once and whole rather than widening the prefix
764    /// [`Self::matches`] shares. A prefix grows forward from the start of the
765    /// input, which is the wrong shape for a question anchored partway through
766    /// it: the bytes a member would settle from are the ones already passed.
767    #[must_use]
768    pub fn matches_at(&self, input: &[u8], at: usize) -> SetMatches {
769        let mut bits = vec![false; self.pats.len()];
770        if !self.shapes.is_empty() {
771            for (i, _) in self.first_spans_shaped(input, at, false) {
772                bits[i] = true;
773            }
774            return SetMatches { bits };
775        }
776        let mut open = Vec::new();
777        let probe = self.probe(input);
778        for (i, p) in self.pats.iter().enumerate() {
779            if !p.library_kinds().is_empty() {
780                open.push(i);
781                continue;
782            }
783            if crate::prefilter::requires_absent_with(p, input, probe.filter.as_ref()) {
784                continue;
785            }
786            // Only the routes whose matches cannot overlap may be filtered by
787            // the caller's position, and they answer without any lex at all.
788            if let Some(spans) = crate::engine::routed_spans_positional(p, input) {
789                bits[i] = spans.iter().any(|s| s.start() >= at);
790            } else {
791                open.push(i);
792            }
793        }
794        if open.is_empty() {
795            return SetMatches { bits };
796        }
797        let toks = crate::parallel_lex::lex_parallel(input);
798        let start = toks.partition_point(|t| t.start() < at);
799        // One lex for every member, read by each rather than copied to it: the
800        // walk borrows this stream, so a set of eight pays one lex and no
801        // member's tokens are its own.
802        let sig = crate::nfa::Stitched::significant_of(&toks);
803        let (asked, apart) = self.split_together(open);
804        for (i, first) in self.first_spans_together(&asked, input, &toks, start) {
805            bits[i] = first.is_some();
806        }
807        for i in apart {
808            let p = &self.pats[i];
809            let stream = crate::nfa::Stitched::new(&toks, &sig);
810            if !p.library_kinds().is_empty() {
811                bits[i] = crate::cursor::find_at(p, input, at).is_some();
812            } else if let Some(mut w) = crate::nfa::SerialWalk::over_stream(p, input, stream) {
813                w.seek(at);
814                bits[i] = w.next_span(input).is_some();
815            } else {
816                bits[i] = !crate::engine::scan_tokens_from(p, input, &toks, start).is_empty();
817            }
818        }
819        SetMatches { bits }
820    }
821
822    /// `open` split into the members the union walks, each with its place,
823    /// and the members walked as themselves.
824    fn split_together(&self, open: Vec<usize>) -> (Vec<(usize, u32)>, Vec<usize>) {
825        let place = &self.together().place;
826        let mut asked = Vec::new();
827        let mut apart = Vec::new();
828        for i in open {
829            match place[i] {
830                Some(j) => asked.push((i, j)),
831                None => apart.push(i),
832            }
833        }
834        (asked, apart)
835    }
836
837    /// Whether any pattern in the set matches at or after byte `at`.
838    #[must_use]
839    pub fn is_match_at(&self, input: &[u8], at: usize) -> bool {
840        self.matches_at(input, at).matched_any()
841    }
842
843    /// Which patterns match `input`, and where each one first does.
844    ///
845    /// The regex crate's `RegexSet` cannot answer this: it reports which
846    /// patterns match and states that it does not report where. The reason is
847    /// that its saving comes from carrying every pattern in one automaton,
848    /// which loses the identity of the pattern that reached an accepting state.
849    ///
850    /// A token set's saving is the shared lex rather than a shared automaton,
851    /// so each member is still walked as itself and its match keeps its span.
852    /// The position costs nothing beyond the walk that decided the verdict.
853    #[must_use]
854    pub fn matches_with_spans(&self, input: &[u8]) -> Vec<(usize, crate::engine::Span)> {
855        if !self.shapes.is_empty() {
856            return self.first_spans_shaped(input, 0, false);
857        }
858        let mut out = Vec::new();
859        let mut open = Vec::new();
860        let probe = self.probe(input);
861        for (i, p) in self.pats.iter().enumerate() {
862            if !p.library_kinds().is_empty() {
863                open.push(i);
864                continue;
865            }
866            if crate::prefilter::requires_absent_with(p, input, probe.filter.as_ref()) {
867                continue;
868            }
869            // The first-match form, because that is the question: the whole-set
870            // form reads the input to the end to report matches this discards.
871            // A route answering with no span is a verdict of no match, which is
872            // not the same as no route answering, so these cannot collapse.
873            match crate::engine::routed_first(p, input) {
874                Some(first) => {
875                    if let Some(s) = first {
876                        out.push((i, s));
877                    }
878                }
879                None => open.push(i),
880            }
881        }
882        if !open.is_empty() {
883            let toks = crate::parallel_lex::lex_parallel(input);
884            // Every member reads this one lex rather than taking a copy of it.
885            let sig = crate::nfa::Stitched::significant_of(&toks);
886            let (asked, apart) = self.split_together(open);
887            for (i, first) in self.first_spans_together(&asked, input, &toks, 0) {
888                if let Some(s) = first {
889                    out.push((i, s));
890                }
891            }
892            for i in apart {
893                let p = &self.pats[i];
894                let stream = crate::nfa::Stitched::new(&toks, &sig);
895                let found = if !p.library_kinds().is_empty() {
896                    crate::cursor::find(p, input)
897                } else if let Some(mut w) =
898                    crate::nfa::SerialWalk::over_stream(p, input, stream)
899                {
900                    w.next_span(input)
901                } else {
902                    crate::engine::scan_tokens_from(p, input, &toks, 0).into_iter().next()
903                };
904                if let Some(s) = found {
905                    out.push((i, s));
906                }
907            }
908        }
909        out.sort_unstable_by_key(|&(i, _)| i);
910        out
911    }
912
913    /// How a member is to be answered, decided once so the compile that
914    /// decides it is also the compile that runs.
915    fn plan(&self, index: usize, pattern: &Pattern, input: &[u8], probe: &Probe) -> Plan {
916        // A library kind lives only in a lex under the library's shapes, which
917        // neither a route nor the shared lex produces.
918        if !pattern.library_kinds().is_empty() {
919            return Plan::Alone;
920        }
921        if let Some(settled) = self.without_a_lex(pattern, input, probe) {
922            return Plan::Settled(settled);
923        }
924        if !crate::prefilter::settles_from_a_prefix(pattern) {
925            return Plan::Alone;
926        }
927        if let Some(j) = self.together().place[index] {
928            return Plan::Together(j, crate::nfa::bounded_max_len(pattern).unwrap_or(1).max(1));
929        }
930        match crate::nfa::compile_pattern(pattern) {
931            Some(c) => {
932                Plan::Shared(c, crate::nfa::bounded_max_len(pattern).unwrap_or(1).max(1))
933            }
934            // A balanced group or a field node, which only the
935            // set-reachability engine advances and which lexes its own stream.
936            None => Plan::Alone,
937        }
938    }
939
940    /// The members of `open` that match, found over one prefix that widens
941    /// until every one of them is settled.
942    ///
943    /// This is where a set pays for itself. Asked separately, each member
944    /// widens a prefix of its own and lexes those bytes again; asked together
945    /// they widen one prefix and each round's tokens are walked once per
946    /// member still open. A member that matches early drops out and the
947    /// widening continues only for the rest, so the prefix reached is the one
948    /// the hardest member needed and not the sum of what each needed.
949    ///
950    /// `stop_at_the_first` ends the whole walk as soon as any member matches,
951    /// for a caller asking whether rather than which. Each member found is
952    /// handed back with its first match, which a prefix's edge cannot have
953    /// cut short: the first span clear of the edge is the leftmost, since a
954    /// leftmost match reaching past the edge leaves none clear behind it.
955    fn over_a_shared_prefix(
956        &self,
957        input: &[u8],
958        shared: &[(usize, crate::nfa::Compiled, usize)],
959        together: &[(usize, u32, usize)],
960        stop_at_the_first: bool,
961    ) -> Vec<(usize, crate::engine::Span)> {
962        let mut found: Vec<(usize, crate::engine::Span)> = Vec::new();
963        if shared.is_empty() && together.is_empty() {
964            return found;
965        }
966        let seen = |found: &[(usize, crate::engine::Span)], i: usize| found.iter().any(|&(k, _)| k == i);
967        let asked: Vec<(usize, u32)> = together.iter().map(|&(i, j, _)| (i, j)).collect();
968        let bound: std::collections::HashMap<usize, usize> =
969            together.iter().map(|&(i, _, max_len)| (i, max_len)).collect();
970        crate::prefilter::over_widening_prefixes(input, |toks, whole| {
971            // The union's members still open, walked as one over this
972            // prefix; a member found drops out of the next round's walk.
973            let still: Vec<(usize, u32)> =
974                asked.iter().filter(|(i, _)| !seen(&found, *i)).copied().collect();
975            for (i, first) in self.first_spans_together(&still, input, toks, 0) {
976                let hit = if whole {
977                    first
978                } else {
979                    first.and_then(|s| {
980                        crate::prefilter::settled_clear_of_the_cut(toks, &[s], bound[&i])
981                    })
982                };
983                if let Some(s) = hit {
984                    found.push((i, s));
985                    if stop_at_the_first {
986                        return false;
987                    }
988                }
989            }
990            for (i, c, max_len) in shared {
991                if seen(&found, *i) {
992                    continue;
993                }
994                let spans = crate::nfa::scan_nfa_over_compiled(c, &self.pats[*i], input, toks);
995                // On the last round the prefix is the whole input, so any
996                // match is a match and none means none. Before that only a
997                // match clear of the cut is one the cut cannot have made.
998                let hit = if whole {
999                    spans.first().copied()
1000                } else {
1001                    crate::prefilter::settled_clear_of_the_cut(toks, &spans, *max_len)
1002                };
1003                if let Some(s) = hit {
1004                    found.push((*i, s));
1005                    if stop_at_the_first {
1006                        return false;
1007                    }
1008                }
1009            }
1010            found.len() < shared.len() + together.len()
1011        });
1012        found
1013    }
1014
1015    /// Whether every pattern in the set matches `input`.
1016    #[must_use]
1017    pub fn matched_all(&self, input: &[u8]) -> bool {
1018        self.matches(input).len() == self.pats.len()
1019    }
1020
1021    /// Whether `pattern` matches `input` by a route that never lexes, or
1022    /// `None` where only the lexer can say.
1023    ///
1024    /// The absent-literal refusal is the one that pays here: a set of many
1025    /// patterns over one input usually has most of them absent, and each
1026    /// absence is settled by a byte search rather than by a share of a lex.
1027    fn without_a_lex(&self, pattern: &Pattern, input: &[u8], probe: &Probe) -> Option<bool> {
1028        if crate::prefilter::requires_absent_with(pattern, input, probe.filter.as_ref()) {
1029            return Some(false);
1030        }
1031        if let Some(lits) = crate::prefilter::byte_routable_literals(pattern) {
1032            if probe.refuses_all(&lits) {
1033                return Some(false);
1034            }
1035            if let Some(found) = crate::prefilter::byte_route_any_word_literal(&lits, input) {
1036                return Some(found);
1037            }
1038        }
1039        if let Some(punct) = crate::prefilter::byte_routable_word_then_punct(pattern)
1040            && let Some(found) = crate::prefilter::byte_route_any_word_then_punct(punct, input)
1041        {
1042            return Some(found);
1043        }
1044        if let Some((bp, prefix)) = crate::prefilter::byte_routable_byte_pattern(pattern)
1045            && let Some(found) = crate::prefilter::byte_route_any_byte_pattern(bp, &prefix, input)
1046        {
1047            return Some(found);
1048        }
1049        None
1050    }
1051}
1052
1053#[cfg(test)]
1054mod tests {
1055    use super::*;
1056
1057    /// A spread over every route a set member can take: byte-routable
1058    /// literals present and absent, a word then punctuation, a byte pattern,
1059    /// a kind sequence, the single-pass engine, and a balanced group that
1060    /// only the set engine advances.
1061    const SOURCES: &[&str] = &[
1062        "\"alpha\"",
1063        "\"zzzqqq\"",
1064        "\\W",
1065        "\\N",
1066        "\\W \"=\"",
1067        "`cond_[0-9]+`",
1068        "\"let\" \\W \"=\"",
1069        "\\W:x \"=\" =x",
1070        "\\B(\\W)",
1071        "\"nowhere_at_all\" \"=\" \\N",
1072    ];
1073
1074    fn corpus() -> Vec<u8> {
1075        let mut s = String::new();
1076        for i in 0..300 {
1077            match i % 4 {
1078                0 => s.push_str(&format!("let value_{i} = {} ;\n", i * 37)),
1079                1 => s.push_str(&format!("call_{i}(alpha, beta, {i}) ;\n")),
1080                2 => s.push_str(&format!("key_{i}: item_{i}, item_{} ;\n", i + 1)),
1081                _ => s.push_str(&format!("if (cond_{i}) {{ do_{i}(x) ; }}\n")),
1082            }
1083        }
1084        s.into_bytes()
1085    }
1086
1087    fn built() -> PatternSet {
1088        PatternSet::new(
1089            SOURCES.iter().map(|s| crate::parse(s).expect("pattern parses")).collect(),
1090        )
1091    }
1092
1093    #[test]
1094    fn the_set_reports_what_each_pattern_reports_alone() {
1095        // The whole contract: sharing a lex must not change any answer. Asked
1096        // one at a time through the ordinary entry point, and together.
1097        let input = corpus();
1098        let set = built();
1099        let want: Vec<usize> = SOURCES
1100            .iter()
1101            .enumerate()
1102            .filter(|(_, s)| {
1103                let p = crate::parse(s).expect("pattern parses");
1104                crate::is_match(&p, &input)
1105            })
1106            .map(|(i, _)| i)
1107            .collect();
1108        assert_eq!(set.matches(&input), want);
1109        assert_eq!(set.is_match(&input), !want.is_empty());
1110        assert_eq!(set.matched_all(&input), want.len() == SOURCES.len());
1111    }
1112
1113    #[test]
1114    fn the_position_row_reports_where_each_pattern_first_matches_alone() {
1115        // The spans must be the ones each pattern's own first-match path gives.
1116        // The rung that answers inside the set is not always the one that
1117        // answers a lone ask - the set takes the first-match ladder and shares
1118        // a lex for what falls through it - and the span must not depend on
1119        // which of them answered.
1120        let input = corpus();
1121        let set = built();
1122        let want: Vec<(usize, crate::engine::Span)> = SOURCES
1123            .iter()
1124            .enumerate()
1125            .filter_map(|(i, s)| {
1126                let p = crate::parse(s).expect("pattern parses");
1127                crate::find(&p, &input).map(|span| (i, span))
1128            })
1129            .collect();
1130        assert_eq!(set.matches_with_spans(&input), want);
1131        // The first-match row takes a ladder that stops the lexer rather
1132        // than walking a whole lex, and must reach the same spans.
1133        let mut firsts: Vec<(usize, crate::engine::Span)> = set
1134            .first_matches(&input, false)
1135            .into_iter()
1136            .map(|(i, m)| {
1137                let at = |o: usize| u32::try_from(o).expect("a corpus offset fits a span");
1138                (i, crate::engine::Span { start: at(m.start), end: at(m.end) })
1139            })
1140            .collect();
1141        firsts.sort_unstable_by_key(|&(i, _)| i);
1142        assert_eq!(firsts, want);
1143    }
1144
1145    /// `n` patterns spread over every route: literal-led sequences the
1146    /// single-pass engine walks, kind-led ones with a typed predicate,
1147    /// literal runs a byte route settles, balanced groups the set engine owns,
1148    /// and a guard that keeps a member whole.
1149    fn generated(n: usize) -> Vec<Pattern> {
1150        (0..n)
1151            .map(|i| {
1152                let src = match i % 8 {
1153                    0 => format!("\"value_{i}\" \"=\" \\N"),
1154                    1 => format!("\"call_{i}\" \\B(\\W \",\" \\W \",\" \\N)"),
1155                    2 => format!("\"key_{i}\" \":\" \\W"),
1156                    3 => format!("\"cond_{i}\" \")\" \"{{\""),
1157                    4 => format!("\\W \"=\" \\N{{={}}}", i * 37),
1158                    5 => format!("\"item_{i}\" ~\"alpha\""),
1159                    6 => format!("\\N{{>={i}}} \";\""),
1160                    _ => format!("\"do_{i}\" \\B(\\W)"),
1161                };
1162                crate::parse(&src).expect("pattern parses")
1163            })
1164            .collect()
1165    }
1166
1167    #[test]
1168    fn walked_as_one_agrees_with_walked_apart_at_every_size() {
1169        // The union changes how the members the single-pass engine takes are
1170        // walked and nothing about what they answer: every verdict, first
1171        // span and positional verdict must equal the per-member walk's, over
1172        // sets small enough to read and large enough to matter.
1173        let input = corpus();
1174        for n in [10usize, 100, 1000] {
1175            let together = PatternSet::new(generated(n));
1176            let apart = PatternSet::new(generated(n)).walked_as_one(false);
1177            assert_eq!(together.matches(&input), apart.matches(&input), "matches, {n} patterns");
1178            assert!(!together.matches(&input).is_empty(), "the generated set has members that match");
1179            assert_eq!(
1180                together.matches_with_spans(&input),
1181                apart.matches_with_spans(&input),
1182                "first spans, {n} patterns"
1183            );
1184            assert_eq!(together.is_match(&input), apart.is_match(&input), "is_match, {n} patterns");
1185            for at in [0usize, 1000, input.len() / 2, input.len()] {
1186                assert_eq!(together.matches_at(&input, at), apart.matches_at(&input, at), "at {at}, {n} patterns");
1187            }
1188        }
1189    }
1190
1191    #[test]
1192    fn an_empty_set_matches_nothing_and_matches_all_of_it() {
1193        // `matched_all` over no patterns is vacuously true, which is the same
1194        // reading the regex crate takes and worth pinning so it cannot drift.
1195        let set = PatternSet::new(Vec::new());
1196        assert!(set.is_empty());
1197        assert_eq!(set.len(), 0);
1198        assert_eq!(set.matches(b"anything"), Vec::<usize>::new());
1199        assert!(!set.is_match(b"anything"));
1200        assert!(set.matched_all(b"anything"));
1201    }
1202
1203    #[test]
1204    fn a_set_of_only_absent_patterns_matches_none() {
1205        let input = corpus();
1206        let set = PatternSet::new(
1207            ["\"zzzqqq\"", "\"nowhere_at_all\"", "\"absent_word\" \"=\""]
1208                .iter()
1209                .map(|s| crate::parse(s).expect("pattern parses"))
1210                .collect(),
1211        );
1212        assert_eq!(set.matches(&input), Vec::<usize>::new());
1213        assert!(!set.is_match(&input));
1214        assert!(!set.matched_all(&input));
1215    }
1216
1217    #[test]
1218    fn the_index_follows_the_order_the_set_was_built_in() {
1219        // The index is the only handle a caller has on which pattern matched,
1220        // so it must be the position given and not the order answers arrive
1221        // in - and answers do not arrive in order here, since the routed
1222        // patterns are settled before the lexed ones.
1223        let input = corpus();
1224        let set = PatternSet::new(
1225            ["\\B(\\W)", "\"alpha\"", "\"zzzqqq\"", "\\W \"=\""]
1226                .iter()
1227                .map(|s| crate::parse(s).expect("pattern parses"))
1228                .collect(),
1229        );
1230        assert_eq!(set.matches(&input), vec![0, 1, 3]);
1231        assert_eq!(set.patterns().len(), 4);
1232    }
1233
1234    #[test]
1235    fn a_shared_prefix_that_widens_answers_what_a_single_ask_answers() {
1236        // An input well past the first prefix, so the widening runs more than
1237        // one round and members drop out of it at different rounds. Members
1238        // that match nowhere force it all the way to the end, which is the
1239        // case where sharing has to still be correct rather than merely fast.
1240        let mut input = corpus();
1241        while input.len() < 400_000 {
1242            let more = corpus();
1243            input.extend_from_slice(&more);
1244        }
1245        input.extend_from_slice(b"\nonly_at_the_very_end = 7 ;\n");
1246        let sources = [
1247            "\"alpha\"",
1248            "\"nowhere_at_all\"",
1249            "\\W \"=\" \\N",
1250            "\"only_at_the_very_end\" \"=\" \\N",
1251            "\\B(\\W)",
1252            "\"zzzqqq\" \"=\"",
1253        ];
1254        let set = PatternSet::new(
1255            sources.iter().map(|s| crate::parse(s).expect("pattern parses")).collect(),
1256        );
1257        let want: Vec<usize> = sources
1258            .iter()
1259            .enumerate()
1260            .filter(|(_, s)| {
1261                let p = crate::parse(s).expect("pattern parses");
1262                crate::is_match(&p, &input)
1263            })
1264            .map(|(i, _)| i)
1265            .collect();
1266        assert_eq!(set.matches(&input), want);
1267        assert_eq!(set.is_match(&input), !want.is_empty());
1268        assert!(want.contains(&3), "the member that matches only at the end is found");
1269        assert!(!want.contains(&1), "the member that matches nowhere is not");
1270    }
1271
1272    #[test]
1273    fn an_empty_input_matches_nothing() {
1274        let set = built();
1275        assert_eq!(set.matches(b""), Vec::<usize>::new());
1276        assert!(!set.is_match(b""));
1277    }
1278}