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