fancy_regex/lib.rs
1// Copyright 2026 The Fancy Regex Authors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19// THE SOFTWARE.
20
21#![doc = include_str!("../docs/main.md")]
22#![doc = include_str!("../docs/features.md")]
23#![doc = include_str!("../docs/syntax.md")]
24#![doc = include_str!("../docs/subroutines/1_intro.md")]
25#![doc = include_str!("../docs/subroutines/2_flags.md")]
26#![doc = include_str!("../docs/subroutines/3_left_recursion.md")]
27#![doc = include_str!("../docs/subroutines/4_recursion.md")]
28#![doc = include_str!("../docs/absent.md")]
29#![deny(missing_docs)]
30#![deny(missing_debug_implementations)]
31#![cfg_attr(not(feature = "std"), no_std)]
32
33extern crate alloc;
34
35use alloc::borrow::Cow;
36use alloc::boxed::Box;
37use alloc::string::{String, ToString};
38use alloc::sync::Arc;
39use alloc::vec;
40use alloc::vec::Vec;
41
42use core::convert::TryFrom;
43use core::fmt;
44use core::fmt::{Debug, Formatter};
45use core::ops::{Index, Range};
46use core::str::FromStr;
47use regex_automata::meta::Regex as RaRegex;
48use regex_automata::util::captures::Captures as RaCaptures;
49use regex_automata::util::syntax::Config as SyntaxConfig;
50use regex_automata::Anchored as RaAnchored;
51use regex_automata::Input as RaInput;
52
53mod analyze;
54mod bytes;
55mod compile;
56mod error;
57mod expand;
58mod input;
59mod optimize;
60mod parse;
61mod parse_flags;
62mod regexset;
63mod replacer;
64mod seek;
65mod to_hir;
66mod vm;
67
68use crate::analyze::can_compile_as_anchored;
69use crate::analyze::{analyze, AnalyzeContext};
70use crate::compile::{compile, CompileOptions};
71use crate::optimize::optimize;
72use crate::parse::{ExprTree, NamedGroups, Parser};
73use crate::parse_flags::*;
74use crate::vm::{Prog, OPTION_FIND_NOT_EMPTY, OPTION_NOT_CONTINUED_FROM_PREVIOUS_MATCH};
75
76pub use crate::bytes::MatchBytes;
77pub use crate::error::{CompileError, Error, ParseError, Result, RuntimeError};
78pub use crate::expand::Expander;
79pub use crate::input::{Input, RegexInput};
80pub use crate::regexset::{RegexSet, RegexSetMatch, RegexSetOptions};
81pub use crate::replacer::{NoExpand, Replacer, ReplacerRef};
82pub use crate::seek::seek_pattern_is_useful;
83
84/// Controls how the regex engine handles input encoding.
85///
86/// This enum represents the three valid combinations of the `utf8` and `unicode`
87/// flags in the underlying regex engine. Each variant has different trade-offs
88/// between input flexibility and character class semantics.
89///
90/// The default is [`BytesMode::Unicode`].
91///
92/// # Variants
93///
94/// ## `BytesMode::Unicode` (default)
95///
96/// - Input is expected to be valid UTF-8
97/// - `.` matches any Unicode scalar value (except `\n` unless `dot_matches_new_line` is set)
98/// - `\w`, `\d`, `\s` match Unicode characters
99/// - Unicode properties like `\p{Letter}` are available
100/// - Word boundaries (`\b`) are Unicode-aware
101///
102/// ## `BytesMode::Ascii`
103///
104/// - Input can be arbitrary bytes (no UTF-8 requirement)
105/// - `.` matches any single **byte** (except `\n` unless `dot_matches_new_line` is set)
106/// - `\w` matches `[a-zA-Z0-9_]` only (ASCII)
107/// - `\d` matches `[0-9]` only (ASCII)
108/// - `\s` matches ASCII whitespace only
109/// - Unicode properties are **not available**
110/// - Word boundaries (`\b`) use ASCII-only word characters
111///
112/// Use this mode when matching raw binary data or filenames that may contain
113/// non-UTF-8 bytes and you don't need Unicode character classes.
114///
115/// ## `BytesMode::UnicodeBytes`
116///
117/// - Input can be arbitrary bytes (no UTF-8 requirement)
118/// - `.` matches Unicode scalar values (sequences of valid UTF-8 bytes)
119/// - `\w`, `\d`, `\s` match Unicode characters
120/// - Unicode properties like `\p{Letter}` are available
121///
122/// Use this mode when the input may contain non-UTF-8 bytes but you still want
123/// Unicode-aware character classes. Note that `.` will **not** match individual
124/// non-UTF-8 bytes — it only matches valid UTF-8 codepoint sequences.
125#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
126pub enum BytesMode {
127 /// Unicode mode: input must be valid UTF-8, full Unicode support.
128 /// (utf8=true, unicode=true)
129 #[default]
130 Unicode,
131 /// ASCII bytes mode: `.` matches any byte, character classes are ASCII-only.
132 /// (utf8=false, unicode=false)
133 Ascii,
134 /// Unicode-aware bytes mode: input can be non-UTF-8, character classes
135 /// remain Unicode-aware. `.` matches Unicode scalar values only.
136 /// (utf8=false, unicode=true)
137 UnicodeBytes,
138}
139
140const MAX_RECURSION: usize = 64;
141
142// the public API
143
144/// A builder for a `Regex` to allow configuring options.
145#[derive(Debug)]
146pub struct RegexBuilder {
147 pattern: String,
148 options: RegexOptionsBuilder,
149}
150
151/// A builder for a `Regex` to allow configuring options.
152#[derive(Debug)]
153pub struct RegexOptionsBuilder {
154 options: RegexOptions,
155}
156
157/// A compiled regular expression.
158#[derive(Clone)]
159pub struct Regex {
160 inner: RegexImpl,
161 named_groups: Arc<NamedGroups>,
162}
163
164// Separate enum because we don't want to expose any of this
165#[derive(Clone)]
166enum RegexImpl {
167 // Do we want to box this? It's pretty big...
168 Wrap {
169 inner: RaRegex,
170 /// The original pattern which the regex was constructed from
171 pattern: String,
172 /// Some optimizations avoid the VM, but need to use an extra capture group to represent the match boundaries
173 explicit_capture_group_0: bool,
174 /// The actual pattern passed to regex-automata for delegation
175 delegated_pattern: String,
176 },
177 Fancy {
178 prog: Arc<Prog>,
179 n_groups: usize,
180 /// The original pattern which the regex was constructed from
181 pattern: String,
182 options: HardRegexRuntimeOptions,
183 },
184}
185
186/// A single match of a regex or group in an input text
187#[derive(Copy, Clone, Debug, Eq, PartialEq)]
188pub struct Match<'t> {
189 text: &'t str,
190 start: usize,
191 end: usize,
192}
193
194/// An iterator over all non-overlapping matches for a particular input.
195///
196/// The iterator yields a `Result<S::Match>`. The iterator stops when no more
197/// matches can be found.
198///
199/// `'r` is the lifetime of the compiled regular expression and `'t` is the
200/// lifetime of the matched input.
201#[derive(Debug)]
202pub struct Matches<'r, 't, S: input::Input + ?Sized> {
203 re: &'r Regex,
204 input: RegexInput<'t, S>,
205 last_match: Option<usize>,
206 last_skipped_empty: bool,
207}
208
209impl<'r, 't, S: input::Input + ?Sized> Matches<'r, 't, S> {
210 /// Return the underlying regex.
211 pub fn regex(&self) -> &'r Regex {
212 self.re
213 }
214
215 /// Return the text being searched.
216 pub fn text(&self) -> &'t S {
217 self.input.haystack()
218 }
219
220 /// Return the current search input configuration.
221 pub fn input(&self) -> &RegexInput<'t, S> {
222 &self.input
223 }
224
225 /// Adapted from the `regex` crate. Calls `find_from_pos`/`captures_from_pos` repeatedly.
226 /// Ignores empty matches immediately after a match.
227 /// Also passes a flag when skipping an empty match, so that \G wouldn't match at the new start position.
228 fn next_with<F, R>(&mut self, mut search: F) -> Option<Result<R>>
229 where
230 F: FnMut(&Regex, &RegexInput<'t, S>, u32) -> Result<Option<(R, (usize, usize))>>,
231 {
232 if self.input.is_done() {
233 return None;
234 }
235
236 let option_flags = if self.last_skipped_empty {
237 OPTION_NOT_CONTINUED_FROM_PREVIOUS_MATCH
238 } else {
239 0
240 };
241
242 let pos = self.input.effective_start();
243 let (result, (match_start, match_end)) = match search(self.re, &self.input, option_flags) {
244 Err(error) => {
245 // Stop on first error: If an error is encountered, return it, and set the "last match position"
246 // to the string length, so that the next next() call will return None, to prevent an infinite loop.
247 self.input
248 .set_start(self.input.get_range().end.saturating_add(1));
249 return Some(Err(error));
250 }
251 Ok(None) => return None,
252 Ok(Some(pair)) => pair,
253 };
254
255 if match_start == match_end {
256 // This is an empty match. To ensure we make progress, start
257 // the next search at the smallest possible starting position
258 // of the next match following this one.
259 self.input
260 .set_start(self.input.haystack().advance_position(match_end));
261 // Only set OPTION_NOT_CONTINUED_FROM_PREVIOUS_MATCH on the next call if this was a
262 // truly zero-length match (the VM consumed no bytes from `pos`).
263 // This means that \K won't prevent \G from matching.
264 self.last_skipped_empty = match_end == pos;
265 // Don't accept empty matches immediately following a match.
266 // Just move on to the next match.
267 if Some(match_end) == self.last_match {
268 return self.next_with(search);
269 }
270 } else {
271 self.input.set_start(match_end);
272 self.last_skipped_empty = false;
273 }
274
275 self.last_match = Some(match_end);
276
277 Some(Ok(result))
278 }
279}
280
281impl<'r, 't, S: input::Input + ?Sized> Iterator for Matches<'r, 't, S> {
282 type Item = Result<S::Match<'t>>;
283
284 fn next(&mut self) -> Option<Self::Item> {
285 let text = self.input.haystack();
286 self.next_with(move |re, input, flags| {
287 re.find_input_raw(input, flags)
288 .map(|opt| opt.map(|(s, e)| (text.make_match(s, e), (s, e))))
289 })
290 }
291}
292
293/// An iterator that yields all non-overlapping capture groups matching a
294/// particular regular expression.
295///
296/// The iterator stops when no more matches can be found.
297///
298/// `'r` is the lifetime of the compiled regular expression and `'t` is the
299/// lifetime of the matched string.
300#[derive(Debug)]
301pub struct CaptureMatches<'r, 't, S: input::Input + ?Sized>(Matches<'r, 't, S>);
302
303impl<'r, 't, S: input::Input + ?Sized> CaptureMatches<'r, 't, S> {
304 /// Return the text being searched.
305 pub fn text(&self) -> &'t S {
306 self.0.input.haystack()
307 }
308
309 /// Return the underlying regex.
310 pub fn regex(&self) -> &'r Regex {
311 self.0.re
312 }
313}
314
315impl<'r, 't, S: input::Input + ?Sized> Iterator for CaptureMatches<'r, 't, S> {
316 type Item = Result<Captures<'t, S>>;
317
318 fn next(&mut self) -> Option<Self::Item> {
319 self.0.next_with(move |re, input, flags| {
320 let captures = re.captures_input_with_option_flags(input, flags)?;
321 Ok(captures.map(|c| {
322 let (start, end) = c
323 .inner
324 .get_span(0)
325 .expect("`Captures` is expected to have entire match at 0th position");
326 (c, (start, end))
327 }))
328 })
329 }
330}
331
332/// A set of capture groups found for a regex.
333///
334/// `S` is the input type (`str` or `[u8]`).
335#[derive(Debug)]
336pub struct Captures<'t, S: input::Input + ?Sized> {
337 inner: CapturesImpl,
338 named_groups: Arc<NamedGroups>,
339 input: &'t S,
340}
341
342#[derive(Debug)]
343enum CapturesImpl {
344 Wrap {
345 locations: RaCaptures,
346 /// Some optimizations avoid the VM but need an extra capture group to represent the match boundaries.
347 /// Therefore what is actually capture group 1 should be treated as capture group 0, and all other
348 /// capture groups should have their index reduced by one as well to line up with what the pattern specifies.
349 explicit_capture_group_0: bool,
350 },
351 Fancy {
352 saves: Vec<usize>,
353 },
354}
355
356impl CapturesImpl {
357 fn get_span(&self, i: usize) -> Option<(usize, usize)> {
358 match self {
359 CapturesImpl::Wrap {
360 locations,
361 explicit_capture_group_0,
362 } => locations
363 .get_group(i + if *explicit_capture_group_0 { 1 } else { 0 })
364 .map(|span| (span.start, span.end)),
365 CapturesImpl::Fancy { saves } => {
366 let slot = i * 2;
367 if slot >= saves.len() {
368 return None;
369 }
370 let lo = saves[slot];
371 if lo == usize::MAX {
372 return None;
373 }
374 let hi = saves[slot + 1];
375 Some((lo, hi))
376 }
377 }
378 }
379
380 fn len(&self) -> usize {
381 match self {
382 CapturesImpl::Wrap {
383 locations,
384 explicit_capture_group_0,
385 } => locations.group_len() - if *explicit_capture_group_0 { 1 } else { 0 },
386 CapturesImpl::Fancy { saves } => saves.len() / 2,
387 }
388 }
389}
390
391/// Iterator for captured groups in order in which they appear in the regex.
392#[derive(Debug)]
393pub struct SubCaptureMatches<'c, 't, S: input::Input + ?Sized> {
394 data: &'c Captures<'t, S>,
395 i: usize,
396}
397
398/// An iterator over all substrings delimited by a regex.
399///
400/// This iterator yields `Result<&'h str>`, where each item is a substring of the
401/// target string that is delimited by matches of the regular expression. It stops when there
402/// are no more substrings to yield.
403///
404/// `'r` is the lifetime of the compiled regular expression, and `'h` is the
405/// lifetime of the target string being split.
406///
407/// This iterator can be created by the [`Regex::split`] method.
408#[derive(Debug)]
409pub struct Split<'r, 'h> {
410 matches: Matches<'r, 'h, str>,
411 next_start: usize,
412 target: &'h str,
413}
414
415impl<'r, 'h> Iterator for Split<'r, 'h> {
416 type Item = Result<&'h str>;
417
418 /// Returns the next substring that results from splitting the target string by the regex.
419 ///
420 /// If no more matches are found, returns the remaining part of the string,
421 /// or `None` if all substrings have been yielded.
422 fn next(&mut self) -> Option<Result<&'h str>> {
423 match self.matches.next() {
424 None => {
425 let len = self.target.len();
426 if self.next_start > len {
427 // No more substrings to return
428 None
429 } else {
430 // Return the last part of the target string
431 // Next call will return None
432 let part = &self.target[self.next_start..len];
433 self.next_start = len + 1;
434 Some(Ok(part))
435 }
436 }
437 // Return the next substring
438 Some(Ok(m)) => {
439 let part = &self.target[self.next_start..m.start()];
440 self.next_start = m.end();
441 Some(Ok(part))
442 }
443 Some(Err(e)) => Some(Err(e)),
444 }
445 }
446}
447
448impl<'r, 'h> core::iter::FusedIterator for Split<'r, 'h> {}
449
450/// An iterator over at most `N` substrings delimited by a regex.
451///
452/// This iterator yields `Result<&'h str>`, where each item is a substring of the
453/// target that is delimited by matches of the regular expression. It stops either when
454/// there are no more substrings to yield, or after `N` substrings have been yielded.
455///
456/// The `N`th substring is the remaining part of the target.
457///
458/// `'r` is the lifetime of the compiled regular expression, and `'h` is the
459/// lifetime of the target string being split.
460///
461/// This iterator can be created by the [`Regex::splitn`] method.
462#[derive(Debug)]
463pub struct SplitN<'r, 'h> {
464 splits: Split<'r, 'h>,
465 limit: usize,
466}
467
468impl<'r, 'h> Iterator for SplitN<'r, 'h> {
469 type Item = Result<&'h str>;
470
471 /// Returns the next substring resulting from splitting the target by the regex,
472 /// limited to `N` splits.
473 ///
474 /// Returns `None` if no more matches are found or if the limit is reached after yielding
475 /// the remaining part of the target.
476 fn next(&mut self) -> Option<Result<&'h str>> {
477 if self.limit == 0 {
478 // Limit reached. No more substrings available.
479 return None;
480 }
481
482 // Decrement the limit for each split.
483 self.limit -= 1;
484 if self.limit > 0 {
485 return self.splits.next();
486 }
487
488 // Nth split
489 let len = self.splits.target.len();
490 if self.splits.next_start > len {
491 // No more substrings available.
492 None
493 } else {
494 // Return the remaining part of the target
495 let start = self.splits.next_start;
496 self.splits.next_start = len + 1;
497 Some(Ok(&self.splits.target[start..len]))
498 }
499 }
500
501 fn size_hint(&self) -> (usize, Option<usize>) {
502 (0, Some(self.limit))
503 }
504}
505
506impl<'r, 'h> core::iter::FusedIterator for SplitN<'r, 'h> {}
507
508#[derive(Clone)]
509struct RegexOptions {
510 syntaxc: SyntaxConfig,
511 delegate_size_limit: Option<usize>,
512 delegate_dfa_size_limit: Option<usize>,
513 oniguruma_mode: bool,
514 ignore_numbered_groups_when_named_groups_exist: bool,
515 hard_regex_runtime_options: HardRegexRuntimeOptions,
516 bytes_mode: BytesMode,
517 seek_filter: Option<fn(&str) -> bool>,
518 /// Whether the top-level engine of an easy (`Wrap`) pattern should build a
519 /// prefilter. `RegexSet` turns this off for its internally-built members:
520 /// the set only ever searches them anchored at candidate positions, where
521 /// a prefilter is never consulted, so building one wastes time and memory.
522 delegate_prefilter: bool,
523}
524
525impl fmt::Debug for RegexOptions {
526 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
527 let seek_filter_desc = match self.seek_filter {
528 None => "None",
529 Some(f_ptr) if (f_ptr as *const ()) == (seek_pattern_is_useful as *const ()) => {
530 "Some(seek_pattern_is_useful)"
531 }
532 Some(_) => "Some(<custom>)",
533 };
534 f.debug_struct("RegexOptions")
535 .field("syntaxc", &self.syntaxc)
536 .field("delegate_size_limit", &self.delegate_size_limit)
537 .field("delegate_dfa_size_limit", &self.delegate_dfa_size_limit)
538 .field("oniguruma_mode", &self.oniguruma_mode)
539 .field(
540 "ignore_numbered_groups_when_named_groups_exist",
541 &self.ignore_numbered_groups_when_named_groups_exist,
542 )
543 .field(
544 "hard_regex_runtime_options",
545 &self.hard_regex_runtime_options,
546 )
547 .field("seek_filter", &seek_filter_desc)
548 .field("delegate_prefilter", &self.delegate_prefilter)
549 .finish()
550 }
551}
552
553impl Default for RegexOptions {
554 fn default() -> Self {
555 RegexOptions {
556 syntaxc: SyntaxConfig::new().unicode(true),
557 delegate_size_limit: None,
558 delegate_dfa_size_limit: None,
559 oniguruma_mode: false,
560 ignore_numbered_groups_when_named_groups_exist: false,
561 hard_regex_runtime_options: HardRegexRuntimeOptions::default(),
562 bytes_mode: BytesMode::default(),
563 seek_filter: None, // when we are ready to enable seek by default, use: `Some(seek_pattern_is_useful)`
564 delegate_prefilter: true,
565 }
566 }
567}
568
569#[derive(Copy, Clone, Debug)]
570struct HardRegexRuntimeOptions {
571 backtrack_limit: usize,
572 find_not_empty: bool,
573 disallow_empty_match_at_eof_after_newline: bool,
574 allow_input_assertion_overrides: bool,
575}
576
577impl RegexOptions {
578 fn get_flag_value(flag_value: bool, enum_value: u32) -> u32 {
579 if flag_value {
580 enum_value
581 } else {
582 0
583 }
584 }
585
586 fn compute_flags(&self) -> u32 {
587 let insensitive = Self::get_flag_value(self.syntaxc.get_case_insensitive(), FLAG_CASEI);
588 let multiline = Self::get_flag_value(self.syntaxc.get_multi_line(), FLAG_MULTI);
589 let whitespace =
590 Self::get_flag_value(self.syntaxc.get_ignore_whitespace(), FLAG_IGNORE_SPACE);
591 let dotnl = Self::get_flag_value(self.syntaxc.get_dot_matches_new_line(), FLAG_DOTNL);
592 let unicode = Self::get_flag_value(
593 self.syntaxc.get_unicode() && !matches!(self.bytes_mode, BytesMode::Ascii),
594 FLAG_UNICODE,
595 );
596 let oniguruma_mode = Self::get_flag_value(self.oniguruma_mode, FLAG_ONIGURUMA_MODE);
597 let crlf = Self::get_flag_value(self.syntaxc.get_crlf(), FLAG_CRLF);
598 let named_groups_only = Self::get_flag_value(
599 self.ignore_numbered_groups_when_named_groups_exist,
600 FLAG_IGNORE_NUMBERED_GROUPS_WHEN_NAMED_GROUPS_EXIST,
601 );
602
603 insensitive
604 | multiline
605 | whitespace
606 | dotnl
607 | unicode
608 | oniguruma_mode
609 | crlf
610 | named_groups_only
611 }
612}
613
614impl Default for HardRegexRuntimeOptions {
615 fn default() -> Self {
616 HardRegexRuntimeOptions {
617 backtrack_limit: 1_000_000,
618 find_not_empty: false,
619 disallow_empty_match_at_eof_after_newline: false,
620 allow_input_assertion_overrides: false,
621 }
622 }
623}
624
625impl Default for RegexOptionsBuilder {
626 fn default() -> Self {
627 Self::new()
628 }
629}
630
631impl RegexOptionsBuilder {
632 /// Create a new regex options builder.
633 pub fn new() -> Self {
634 RegexOptionsBuilder {
635 options: RegexOptions::default(),
636 }
637 }
638
639 /// Build a `Regex` from the given pattern.
640 ///
641 /// Returns an [`Error`](enum.Error.html) if the pattern could not be parsed.
642 pub fn build(&self, pattern: String) -> Result<Regex> {
643 Regex::new_options(pattern, &self.options)
644 }
645
646 fn set_config(&mut self, func: impl Fn(SyntaxConfig) -> SyntaxConfig) -> &mut Self {
647 self.options.syntaxc = func(self.options.syntaxc);
648 self
649 }
650
651 /// Override default case insensitive
652 /// this is to enable/disable casing via builder instead of a flag within
653 /// the raw string pattern which will be parsed
654 ///
655 /// Default is false
656 pub fn case_insensitive(&mut self, yes: bool) -> &mut Self {
657 self.set_config(|x| x.case_insensitive(yes))
658 }
659
660 /// Enable multi-line regex
661 pub fn multi_line(&mut self, yes: bool) -> &mut Self {
662 self.set_config(|x| x.multi_line(yes))
663 }
664
665 /// Allow ignore whitespace
666 pub fn ignore_whitespace(&mut self, yes: bool) -> &mut Self {
667 self.set_config(|x| x.ignore_whitespace(yes))
668 }
669
670 /// Enable or disable the "dot matches any character" flag.
671 /// When this is enabled, `.` will match any character. When it's disabled, then `.` will match any character
672 /// except for a new line character.
673 pub fn dot_matches_new_line(&mut self, yes: bool) -> &mut Self {
674 self.set_config(|x| x.dot_matches_new_line(yes))
675 }
676
677 /// Enable or disable the CRLF mode flag (`R`).
678 ///
679 /// When enabled, `\r\n` is treated as a single line ending for the purposes of
680 /// `^` and `$` in multi-line mode, instead of treating `\r` and `\n` as separate
681 /// line endings.
682 ///
683 /// By default, this is disabled. It may be selectively enabled in the regular
684 /// expression by using the `R` flag, e.g. `(?mR)` or `(?Rm)`.
685 pub fn crlf(&mut self, yes: bool) -> &mut Self {
686 self.set_config(|x| x.crlf(yes))
687 }
688
689 /// Enable verbose mode in the regular expression.
690 ///
691 /// The same as ignore_whitespace
692 ///
693 /// When enabled, verbose mode permits insigificant whitespace in many
694 /// places in the regular expression, as well as comments. Comments are
695 /// started using `#` and continue until the end of the line.
696 ///
697 /// By default, this is disabled. It may be selectively enabled in the
698 /// regular expression by using the `x` flag regardless of this setting.
699 pub fn verbose_mode(&mut self, yes: bool) -> &mut Self {
700 self.set_config(|x| x.ignore_whitespace(yes))
701 }
702
703 /// Enable or disable the Unicode flag (`u`) by default.
704 ///
705 /// By default this is **enabled**. The inline `u` flag inside a pattern
706 /// is only accepted when it **matches** the current builder setting (e.g.
707 /// `(?u)` when unicode is already enabled, or `(?-u)` when it is already
708 /// disabled). Attempts to change the mode inline are rejected with a
709 /// [`ParseError::ChangingUnicodeModeUnsupported`] error. Use this builder
710 /// method to set the desired mode instead.
711 ///
712 /// ## Effect on `str` input (default)
713 ///
714 /// When matching against `&str` (the default), the underlying engine
715 /// requires that all matches respect UTF-8 boundaries. Disabling Unicode
716 /// therefore has the following effects:
717 ///
718 /// - **`\w`, `\d`, `\s`** become ASCII-only (`[a-zA-Z0-9_]`, `[0-9]`,
719 /// and ASCII whitespace respectively).
720 /// - **`\W`, `\D`, `\S`**, bare **`.`**, and **`\p{...}`** Unicode
721 /// properties **fail to compile**, because they could match byte
722 /// sequences that violate UTF-8 boundaries.
723 ///
724 /// If you need those constructs with `unicode_mode(false)`, use the bytes
725 /// API with [`BytesMode::Ascii`] instead.
726 ///
727 /// ## Effect on byte input
728 ///
729 /// When matching against `&[u8]` (via [`BytesMode::Ascii`]), all
730 /// constructs work as expected in ASCII mode (`.` matches any byte,
731 /// `\W`/`\D`/`\S` match non-ASCII byte values, etc.).
732 ///
733 /// **WARNING**: Unicode mode can greatly increase the size of the compiled
734 /// DFA, which can noticeably impact both memory usage and compilation
735 /// time. This is especially noticeable if your regex contains character
736 /// classes like `\w` that are impacted by whether Unicode is enabled or
737 /// not. If Unicode is not necessary, you are encouraged to disable it.
738 pub fn unicode_mode(&mut self, yes: bool) -> &mut Self {
739 self.set_config(|x| x.unicode(yes))
740 }
741
742 /// Limit for how many times backtracking should be attempted for fancy regexes (where
743 /// backtracking is used). If this limit is exceeded, execution returns an error with
744 /// [`Error::BacktrackLimitExceeded`](enum.Error.html#variant.BacktrackLimitExceeded).
745 /// This is for preventing a regex with catastrophic backtracking to run for too long.
746 ///
747 /// Default is `1_000_000` (1 million).
748 pub fn backtrack_limit(&mut self, limit: usize) -> &mut Self {
749 self.options.hard_regex_runtime_options.backtrack_limit = limit;
750 self
751 }
752
753 /// Set the approximate size limit of the compiled regular expression.
754 ///
755 /// This option is forwarded from the wrapped `regex` crate. Note that depending on the used
756 /// regex features there may be multiple delegated sub-regexes fed to the `regex` crate. As
757 /// such the actual limit is closer to `<number of delegated regexes> * delegate_size_limit`.
758 pub fn delegate_size_limit(&mut self, limit: usize) -> &mut Self {
759 self.options.delegate_size_limit = Some(limit);
760 self
761 }
762
763 /// Set the approximate size of the cache used by the DFA.
764 ///
765 /// This option is forwarded from the wrapped `regex` crate. Note that depending on the used
766 /// regex features there may be multiple delegated sub-regexes fed to the `regex` crate. As
767 /// such the actual limit is closer to `<number of delegated regexes> *
768 /// delegate_dfa_size_limit`.
769 pub fn delegate_dfa_size_limit(&mut self, limit: usize) -> &mut Self {
770 self.options.delegate_dfa_size_limit = Some(limit);
771 self
772 }
773
774 /// Require that matches are non-empty (i.e. match at least one character).
775 ///
776 /// When this is enabled, any match attempt that would result in a zero-length match is
777 /// rejected.
778 ///
779 /// Default is `false`.
780 ///
781 /// N.B. When `find_not_empty` is set and analysis determines the pattern will only ever
782 /// produce an empty match, compiling the regex will return
783 /// `CompileError::PatternCanNeverMatch` instead of silently constructing a regex that can never
784 /// return a result. This catches the user error at compile time rather than allowing the
785 /// combination to execute pointlessly at runtime.
786 pub fn find_not_empty(&mut self, yes: bool) -> &mut Self {
787 self.options.hard_regex_runtime_options.find_not_empty = yes;
788 self
789 }
790
791 /// Treat unnamed capture groups as non-capturing when named groups exist.
792 /// Prevents accessing capture groups by number from within the pattern
793 /// (backrefs, subroutine calls) when named groups are present.
794 pub fn ignore_numbered_groups_when_named_groups_exist(&mut self, yes: bool) -> &mut Self {
795 self.options.ignore_numbered_groups_when_named_groups_exist = yes;
796 self
797 }
798
799 /// ⚠️ Experimental: This API may change, be removed without notice, or cause matches to be
800 /// skipped. This requires more real-world testing to prove correctness and observe in which
801 /// circumstances it brings performance benefits and in which it has the opposite effect.
802 /// Feedback (and benchmarks on real-world patterns/haystacks) would be very welcome!
803 ///
804 /// Enable the Seek pre-filter optimization for hard (backtracking) patterns.
805 ///
806 /// When enabled, the compiler attempts to derive a regular approximation of the pattern
807 /// which is used to skip to the earliest plausible match position in the haystack before
808 /// invoking the backtracking VM. This can dramatically speed up searches in long haystacks
809 /// when the pattern can only match at infrequent positions.
810 ///
811 /// The seek pattern is always a conservative over-approximation — it may report false-positive
812 /// positions but will never skip a true match.
813 ///
814 /// When `yes` is `true`, uses the default [`seek_pattern_is_useful`] filter to decide
815 /// whether the derived pattern is worth using. When `false`, disables seek entirely.
816 ///
817 /// To supply a custom filter, use [`seek_filter`](Self::seek_filter) instead.
818 pub fn seek(&mut self, yes: bool) -> &mut Self {
819 self.options.seek_filter = if yes {
820 Some(seek_pattern_is_useful)
821 } else {
822 None
823 };
824 self
825 }
826
827 /// Set a custom filter function that decides whether the derived seek pattern is useful.
828 ///
829 /// The function receives the seek pattern string and returns `true` if the pattern should
830 /// be used as a pre-filter, or `false` to fall back to the standard unanchored search.
831 ///
832 /// Calling this method implicitly enables seek. Pass [`seek_pattern_is_useful`] to restore
833 /// the default behavior.
834 ///
835 /// # Example
836 ///
837 /// ```rust
838 /// use fancy_regex::{RegexOptionsBuilder, seek_pattern_is_useful};
839 ///
840 /// // Use the default filter (equivalent to seek(true))
841 /// let mut builder = RegexOptionsBuilder::new();
842 /// builder.seek_filter(seek_pattern_is_useful);
843 ///
844 /// // Use a custom filter that additionally requires the pattern to be longer than 3 bytes
845 /// let mut builder = RegexOptionsBuilder::new();
846 /// builder.seek_filter(|pat| pat.len() > 3 && seek_pattern_is_useful(pat));
847 /// ```
848 pub fn seek_filter(&mut self, filter: fn(&str) -> bool) -> &mut Self {
849 self.options.seek_filter = Some(filter);
850 self
851 }
852
853 /// Attempts to better match [Oniguruma](https://github.com/kkos/oniguruma)'s default parsing behavior
854 ///
855 /// Currently this amounts to changing behavior with:
856 ///
857 /// # Left and right word bounds
858 ///
859 /// `fancy-regex` follows the default of other regex engines such as the `regex` crate itself
860 /// where `\<` and `\>` correspond to a _left_ and _right_ word-bound respectively. This
861 /// differs from Oniguruma's defaults which treat them as matching the literals `<` and `>`.
862 /// When this option is set using `\<` and `\>` in the pattern will match the literals
863 /// `<` and `>` instead of word bounds.
864 ///
865 /// # Repetition/Quantifiers on empty groups
866 ///
867 /// `fancy-regex` would normally reject patterns like `(?:)+` because the `+` has nothing
868 /// to target. In Oniguruma mode, the empty repeat is silently dropped at parse time.
869 ///
870 /// # Swapped order quantifiers
871 ///
872 /// `fancy-regex` would normally treat `x{0,3}` as a syntax error because the minimum is
873 /// greater than the maximum. In Oniguruma mode, the limits are swapped (behaving as
874 /// `x{3,0}`) and the resulting repeat is treated as atomic (possessive), matching
875 /// Oniguruma's behavior.
876 ///
877 /// # Adjacent quantifiers
878 ///
879 /// `fancy-regex` would normally reject adjacent quantifiers like `a{3}{2}`, treating the
880 /// second as having nothing to target. In Oniguruma mode, each quantifier wraps the
881 /// previous result (e.g. `a{3}{2}` becomes `(?:a{3}){2}`).
882 ///
883 /// Outside Oniguruma mode, `+` after `{...}` is a possessive modifier (e.g. `x{2}+` is
884 /// possessive). In Oniguruma mode, `+` after a user-specified `{...}` repeat is treated
885 /// as another repeat modifier — `x{2}+` is equivalent to `(?:x{2})+`.
886 ///
887 /// # Start of line (`^`) in multiline mode
888 ///
889 /// In multiline mode (`(?m)`), `^` normally matches at the start of the input and after
890 /// any newline. In Oniguruma mode, it additionally rejects a match at the absolute end of
891 /// the input when it is preceded by a trailing newline.
892 /// Inside lookarounds, `^` behaves as a plain line start without the end-of-input rejection.
893 ///
894 /// ## Example
895 ///
896 /// ```
897 /// use fancy_regex::{Regex, RegexBuilder};
898 ///
899 /// let haystack = "turbo::<Fish>";
900 /// let regex = r"\<\w*\>";
901 ///
902 /// // By default `\<` and `\>` will match the start and end of a word boundary
903 /// let word_bounds_regex = Regex::new(regex).unwrap();
904 /// let word_bounds = word_bounds_regex.find(haystack).unwrap().unwrap();
905 /// assert_eq!(word_bounds.as_str(), "turbo");
906 ///
907 /// // With the option set they instead match the literal `<` and `>` characters
908 /// let literals_regex = RegexBuilder::new(regex).oniguruma_mode(true).build().unwrap();
909 /// let literals = literals_regex.find(haystack).unwrap().unwrap();
910 /// assert_eq!(literals.as_str(), "<Fish>");
911 /// ```
912 pub fn oniguruma_mode(&mut self, yes: bool) -> &mut Self {
913 self.options.oniguruma_mode = yes;
914 self
915 }
916
917 /// Set the input encoding mode for the regex.
918 ///
919 /// Controls how the regex engine handles input encoding. See [`BytesMode`]
920 /// for details on each variant.
921 ///
922 /// Default is [`BytesMode::Unicode`].
923 ///
924 /// # Example
925 ///
926 /// ```rust
927 /// use fancy_regex::{BytesMode, RegexBuilder};
928 ///
929 /// // ASCII bytes mode: . matches any byte including non-UTF-8
930 /// let re = RegexBuilder::new(r".+")
931 /// .bytes_mode(BytesMode::Ascii)
932 /// .build()
933 /// .unwrap();
934 /// assert!(re.is_match(b"\x80\x81\x82").unwrap());
935 ///
936 /// // Default Unicode mode: . only matches valid UTF-8 codepoints
937 /// let re = RegexBuilder::new(r".+")
938 /// .build()
939 /// .unwrap();
940 /// assert!(!re.is_match(b"\x80\x81\x82").unwrap());
941 /// ```
942 pub fn bytes_mode(&mut self, mode: BytesMode) -> &mut Self {
943 self.options.bytes_mode = mode;
944 self
945 }
946
947 /// Sometimes you want to pass in a haystack containing a trailing newline,
948 /// and have that last position ignored for the purposes of anchors like ^ and $
949 /// and even for lookarounds, unless \z is specifically used to anchor to the end of the string.
950 /// This is how Oniguruma works for example.
951 pub fn disallow_empty_match_at_eof_after_newline(&mut self, yes: bool) -> &mut Self {
952 self.options
953 .hard_regex_runtime_options
954 .disallow_empty_match_at_eof_after_newline = yes;
955 self
956 }
957
958 /// Allow [`RegexInput`] assertion suppression overrides at runtime.
959 ///
960 /// When enabled, patterns containing `\A` and `\z` are treated as hard and compiled to the VM
961 /// so that [`RegexInput::start_text`] and [`RegexInput::end_text`] can suppress those
962 /// assertions.
963 ///
964 /// When disabled (the default), those runtime overrides are ignored.
965 pub fn allow_input_assertion_overrides(&mut self, yes: bool) -> &mut Self {
966 self.options
967 .hard_regex_runtime_options
968 .allow_input_assertion_overrides = yes;
969 self
970 }
971}
972
973impl RegexBuilder {
974 /// Create a new regex builder.
975 pub fn new(pattern: &str) -> Self {
976 RegexBuilder {
977 pattern: pattern.to_string(),
978 options: RegexOptionsBuilder::new(),
979 }
980 }
981
982 /// Build a `Regex` from the given pattern.
983 ///
984 /// Returns an [`Error`](enum.Error.html) if the pattern could not be parsed.
985 pub fn build(&self) -> Result<Regex> {
986 self.options.build(self.pattern.clone())
987 }
988
989 /// Change the pattern to build. Useful when building multiple regexes from
990 /// many patterns.
991 pub fn pattern(&mut self, pattern: String) -> &mut Self {
992 self.pattern = pattern;
993 self
994 }
995
996 /// See [`RegexOptionsBuilder::case_insensitive`]
997 pub fn case_insensitive(&mut self, yes: bool) -> &mut Self {
998 self.options.case_insensitive(yes);
999 self
1000 }
1001
1002 /// See [`RegexOptionsBuilder::multi_line`]
1003 pub fn multi_line(&mut self, yes: bool) -> &mut Self {
1004 self.options.multi_line(yes);
1005 self
1006 }
1007
1008 /// See [`RegexOptionsBuilder::ignore_whitespace`]
1009 pub fn ignore_whitespace(&mut self, yes: bool) -> &mut Self {
1010 self.options.ignore_whitespace(yes);
1011 self
1012 }
1013
1014 /// See [`RegexOptionsBuilder::dot_matches_new_line`]
1015 pub fn dot_matches_new_line(&mut self, yes: bool) -> &mut Self {
1016 self.options.dot_matches_new_line(yes);
1017 self
1018 }
1019
1020 /// See [`RegexOptionsBuilder::verbose_mode`]
1021 pub fn verbose_mode(&mut self, yes: bool) -> &mut Self {
1022 self.options.ignore_whitespace(yes);
1023 self
1024 }
1025
1026 /// See [`RegexOptionsBuilder::unicode_mode`]
1027 pub fn unicode_mode(&mut self, yes: bool) -> &mut Self {
1028 self.options.unicode_mode(yes);
1029 self
1030 }
1031
1032 /// See [`RegexOptionsBuilder::backtrack_limit`]
1033 pub fn backtrack_limit(&mut self, limit: usize) -> &mut Self {
1034 self.options.backtrack_limit(limit);
1035 self
1036 }
1037
1038 /// See [`RegexOptionsBuilder::delegate_size_limit`]
1039 pub fn delegate_size_limit(&mut self, limit: usize) -> &mut Self {
1040 self.options.delegate_size_limit(limit);
1041 self
1042 }
1043
1044 /// See [`RegexOptionsBuilder::delegate_dfa_size_limit`]
1045 pub fn delegate_dfa_size_limit(&mut self, limit: usize) -> &mut Self {
1046 self.options.delegate_dfa_size_limit(limit);
1047 self
1048 }
1049
1050 /// See [`RegexOptionsBuilder::oniguruma_mode`]
1051 pub fn oniguruma_mode(&mut self, yes: bool) -> &mut Self {
1052 self.options.oniguruma_mode(yes);
1053 self
1054 }
1055
1056 /// See [`RegexOptionsBuilder::bytes_mode`]
1057 pub fn bytes_mode(&mut self, mode: BytesMode) -> &mut Self {
1058 self.options.bytes_mode(mode);
1059 self
1060 }
1061
1062 /// See [`RegexOptionsBuilder::crlf`]
1063 pub fn crlf(&mut self, yes: bool) -> &mut Self {
1064 self.options.crlf(yes);
1065 self
1066 }
1067
1068 /// See [`RegexOptionsBuilder::find_not_empty`]
1069 pub fn find_not_empty(&mut self, yes: bool) -> &mut Self {
1070 self.options.find_not_empty(yes);
1071 self
1072 }
1073
1074 /// See [`RegexOptionsBuilder::ignore_numbered_groups_when_named_groups_exist`]
1075 pub fn ignore_numbered_groups_when_named_groups_exist(&mut self, yes: bool) -> &mut Self {
1076 self.options
1077 .ignore_numbered_groups_when_named_groups_exist(yes);
1078 self
1079 }
1080
1081 /// See [`RegexOptionsBuilder::seek`]
1082 pub fn seek(&mut self, yes: bool) -> &mut Self {
1083 self.options.seek(yes);
1084 self
1085 }
1086
1087 /// See [`RegexOptionsBuilder::seek_filter`]
1088 pub fn seek_filter(&mut self, filter: fn(&str) -> bool) -> &mut Self {
1089 self.options.seek_filter(filter);
1090 self
1091 }
1092
1093 /// See [`RegexOptionsBuilder::disallow_empty_match_at_eof_after_newline`]
1094 pub fn disallow_empty_match_at_eof_after_newline(&mut self, yes: bool) -> &mut Self {
1095 self.options.disallow_empty_match_at_eof_after_newline(yes);
1096 self
1097 }
1098
1099 /// See [`RegexOptionsBuilder::allow_input_assertion_overrides`]
1100 pub fn allow_input_assertion_overrides(&mut self, yes: bool) -> &mut Self {
1101 self.options.allow_input_assertion_overrides(yes);
1102 self
1103 }
1104}
1105
1106impl fmt::Debug for Regex {
1107 /// Shows the original regular expression.
1108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1109 write!(f, "{}", self.as_str())
1110 }
1111}
1112
1113impl fmt::Display for Regex {
1114 /// Shows the original regular expression
1115 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1116 write!(f, "{}", self.as_str())
1117 }
1118}
1119
1120impl FromStr for Regex {
1121 type Err = Error;
1122
1123 /// Attempts to parse a string into a regular expression
1124 fn from_str(s: &str) -> Result<Regex> {
1125 Regex::new(s)
1126 }
1127}
1128
1129impl Regex {
1130 /// Parse and compile a regex with default options, see `RegexBuilder`.
1131 ///
1132 /// Returns an [`Error`](enum.Error.html) if the pattern could not be parsed.
1133 pub fn new(re: &str) -> Result<Regex> {
1134 Self::new_options(re.to_string(), &RegexOptions::default())
1135 }
1136
1137 pub(crate) fn new_options(pattern: String, options: &RegexOptions) -> Result<Regex> {
1138 let mut tree = Expr::parse_tree_with_flags(&pattern, options.compute_flags())?;
1139
1140 let find_not_empty = options.hard_regex_runtime_options.find_not_empty;
1141 let disallow_empty_match_at_eof_after_newline = options
1142 .hard_regex_runtime_options
1143 .disallow_empty_match_at_eof_after_newline;
1144 let allow_input_assertion_overrides = options
1145 .hard_regex_runtime_options
1146 .allow_input_assertion_overrides;
1147
1148 let requires_capture_group_fixup = if find_not_empty {
1149 // if the find_not_empty flag is set, we skip optimizations
1150 // partially because we have to go though the VM anyway
1151 // partially because having the last instruction of the expression not have
1152 // ix be at the end of capture group 0 ruins our empty match checking logic.
1153 false
1154 } else {
1155 // try to optimize the expression tree so that a hard pattern could become easy
1156 // with a fixup of the capture groups
1157 optimize(&mut tree)
1158 };
1159 let info = analyze(
1160 &tree,
1161 AnalyzeContext {
1162 explicit_capture_group_0: requires_capture_group_fixup,
1163 find_not_empty,
1164 disallow_empty_match_at_eof_after_newline,
1165 allow_input_assertion_overrides,
1166 },
1167 )?;
1168
1169 if find_not_empty && info.const_size && info.min_size == 0 {
1170 return Err(CompileError::PatternCanNeverMatch.into());
1171 }
1172
1173 if !info.hard {
1174 // easy case, wrap regex
1175
1176 // we do our own to_str because escapes are different
1177 // The cooked form is the pattern plus some flag-group decoration. It is
1178 // kept even though the engine is normally built from an Hir below,
1179 // because it doubles as the seek pattern for RegexSet membership and
1180 // as debug output.
1181 let mut re_cooked = String::with_capacity(pattern.len() + pattern.len() / 2);
1182 tree.expr.to_str(&mut re_cooked, 0);
1183 let compile_options = CompileOptions {
1184 bytes_mode: options.bytes_mode,
1185 unicode: options.syntaxc.get_unicode()
1186 && !matches!(options.bytes_mode, BytesMode::Ascii),
1187 delegate_size_limit: options.delegate_size_limit,
1188 delegate_dfa_size_limit: options.delegate_dfa_size_limit,
1189 // The remaining fields (anchored, contains_subroutines, seek_filter,
1190 // disallow_empty_match_at_eof_after_newline) are irrelevant for a plain
1191 // delegate compile in the easy path and their defaults are correct.
1192 ..CompileOptions::default()
1193 };
1194 // The whole pattern is delegated and searched unanchored, so it keeps
1195 // its prefilter and all capture groups (the user may request captures).
1196 // RegexSet members are the exception: they are only searched anchored,
1197 // so their prefilter build is skipped (see `delegate_prefilter`).
1198 let usage = if options.delegate_prefilter {
1199 compile::DelegateUsage::unanchored()
1200 } else {
1201 compile::DelegateUsage::unanchored_no_prefilter()
1202 };
1203 // Build the engine from an Hir translated directly from the tree, so
1204 // the engine doesn't re-parse the cooked pattern. Fall back to the
1205 // string path for anything the translator doesn't cover.
1206 let utf8 = matches!(compile_options.bytes_mode, BytesMode::Unicode);
1207 let mut hir_ctx = to_hir::HirCtx::new(compile_options.unicode, utf8);
1208 let inner = match to_hir::expr_to_hir(&tree.expr, &mut hir_ctx) {
1209 Some(hir) => compile::compile_inner_from_hir(&hir, &compile_options, usage)?,
1210 None => compile::compile_inner(&re_cooked, &compile_options, usage)?,
1211 };
1212 return Ok(Regex {
1213 inner: RegexImpl::Wrap {
1214 inner,
1215 pattern,
1216 explicit_capture_group_0: requires_capture_group_fixup,
1217 delegated_pattern: re_cooked,
1218 },
1219 named_groups: Arc::new(tree.named_groups),
1220 });
1221 }
1222
1223 let prog = compile(
1224 &info,
1225 CompileOptions {
1226 anchored: can_compile_as_anchored(&tree.expr),
1227 contains_subroutines: tree.contains_subroutines,
1228 seek_filter: options.seek_filter,
1229 disallow_empty_match_at_eof_after_newline,
1230 bytes_mode: options.bytes_mode,
1231 unicode: options.syntaxc.get_unicode()
1232 && !matches!(options.bytes_mode, BytesMode::Ascii),
1233 delegate_size_limit: options.delegate_size_limit,
1234 delegate_dfa_size_limit: options.delegate_dfa_size_limit,
1235 },
1236 )?;
1237 Ok(Regex {
1238 inner: RegexImpl::Fancy {
1239 prog: Arc::new(prog),
1240 n_groups: info.end_group(),
1241 options: options.hard_regex_runtime_options,
1242 pattern,
1243 },
1244 named_groups: Arc::new(tree.named_groups),
1245 })
1246 }
1247
1248 /// Returns the original string of this regex.
1249 pub fn as_str(&self) -> &str {
1250 match &self.inner {
1251 RegexImpl::Wrap { pattern, .. } => pattern,
1252 RegexImpl::Fancy { pattern, .. } => pattern,
1253 }
1254 }
1255
1256 /// Check if the regex matches the input.
1257 ///
1258 /// Accepts any type implementing [`Input`]: `&str`, `&String`, or `&[u8]`.
1259 ///
1260 /// # Example
1261 ///
1262 /// Test if some text contains the same word twice:
1263 ///
1264 /// ```rust
1265 /// # use fancy_regex::Regex;
1266 ///
1267 /// let re = Regex::new(r"(\w+) \1").unwrap();
1268 /// assert!(re.is_match("mirror mirror on the wall").unwrap());
1269 /// ```
1270 ///
1271 /// Match against raw bytes:
1272 ///
1273 /// ```rust
1274 /// # use fancy_regex::{BytesMode, RegexBuilder};
1275 ///
1276 /// let re = RegexBuilder::new(r"\d+")
1277 /// .bytes_mode(BytesMode::Ascii)
1278 /// .build()
1279 /// .unwrap();
1280 /// assert!(re.is_match(b"abc 123").unwrap());
1281 /// ```
1282 pub fn is_match<S: input::Input + ?Sized>(&self, input: &S) -> Result<bool> {
1283 self.is_match_input(RegexInput::new(input))
1284 }
1285
1286 /// Returns true if and only if this regex matches anywhere in the given
1287 /// search input.
1288 pub fn is_match_input<S: input::Input + ?Sized>(
1289 &self,
1290 input: RegexInput<'_, S>,
1291 ) -> Result<bool> {
1292 match &self.inner {
1293 RegexImpl::Wrap { inner, .. } => {
1294 if input.is_done() {
1295 Ok(false)
1296 } else {
1297 Ok(inner.is_match(ra_input(&input)))
1298 }
1299 }
1300 RegexImpl::Fancy { .. } => self.find_input_raw(&input, 0).map(|m| m.is_some()),
1301 }
1302 }
1303
1304 /// Returns an iterator for each successive non-overlapping match in `text`.
1305 ///
1306 /// If you have capturing groups in your regex that you want to extract, use the [Regex::captures_iter()]
1307 /// method.
1308 ///
1309 /// # Example
1310 ///
1311 /// Find all words followed by an exclamation point:
1312 ///
1313 /// ```rust
1314 /// # use fancy_regex::Regex;
1315 ///
1316 /// let re = Regex::new(r"\w+(?=!)").unwrap();
1317 /// let mut matches = re.find_iter("so fancy! even with! iterators!");
1318 /// assert_eq!(matches.next().unwrap().unwrap().as_str(), "fancy");
1319 /// assert_eq!(matches.next().unwrap().unwrap().as_str(), "with");
1320 /// assert_eq!(matches.next().unwrap().unwrap().as_str(), "iterators");
1321 /// assert!(matches.next().is_none());
1322 /// ```
1323 pub fn find_iter<'r, 't, S: input::Input + ?Sized>(
1324 &'r self,
1325 text: &'t S,
1326 ) -> Matches<'r, 't, S> {
1327 self.find_iter_input(RegexInput::new(text))
1328 }
1329
1330 /// Returns an iterator for each successive non-overlapping match in the
1331 /// given search input.
1332 pub fn find_iter_input<'r, 't, S: input::Input + ?Sized>(
1333 &'r self,
1334 input: RegexInput<'t, S>,
1335 ) -> Matches<'r, 't, S> {
1336 Matches {
1337 re: self,
1338 input,
1339 last_match: None,
1340 last_skipped_empty: false,
1341 }
1342 }
1343
1344 /// Find the first match in the input.
1345 ///
1346 /// Accepts any type implementing [`Input`]: `&str`, `&String`, or `&[u8]`.
1347 ///
1348 /// # Example
1349 ///
1350 /// Find a word that is followed by an exclamation point:
1351 ///
1352 /// ```rust
1353 /// # use fancy_regex::Regex;
1354 ///
1355 /// let re = Regex::new(r"\w+(?=!)").unwrap();
1356 /// assert_eq!(re.find("so fancy!").unwrap().unwrap().as_str(), "fancy");
1357 /// ```
1358 pub fn find<'t, S: input::Input + ?Sized>(&self, input: &'t S) -> Result<Option<S::Match<'t>>> {
1359 self.find_input(RegexInput::new(input))
1360 }
1361
1362 /// Find the first match in the given search input.
1363 pub fn find_input<'t, S: input::Input + ?Sized>(
1364 &self,
1365 input: RegexInput<'t, S>,
1366 ) -> Result<Option<S::Match<'t>>> {
1367 Ok(self
1368 .find_input_raw(&input, 0)?
1369 .map(|(s, e)| input.haystack().make_match(s, e)))
1370 }
1371
1372 /// Returns the first match in `input`, starting from the specified byte position `pos`.
1373 ///
1374 /// # Examples
1375 ///
1376 /// Finding match starting at a position:
1377 ///
1378 /// ```
1379 /// # use fancy_regex::Regex;
1380 /// let re = Regex::new(r"(?m:^)(\d+)").unwrap();
1381 /// let text = "1 test 123\n2 foo";
1382 /// let mat = re.find_from_pos(text, 7).unwrap().unwrap();
1383 ///
1384 /// assert_eq!(mat.start(), 11);
1385 /// assert_eq!(mat.end(), 12);
1386 /// ```
1387 ///
1388 /// Note that in some cases this is not the same as using the `find`
1389 /// method and passing a slice of the string, see [Regex::captures_from_pos()]
1390 /// for details. To constrain matching to a byte range without slicing, use
1391 /// [Regex::find_input()] with [`RegexInput`].
1392 pub fn find_from_pos<'t, S: input::Input + ?Sized>(
1393 &self,
1394 input: &'t S,
1395 pos: usize,
1396 ) -> Result<Option<S::Match<'t>>> {
1397 self.find_input(RegexInput::new(input).from_pos(pos))
1398 }
1399
1400 pub(crate) fn find_input_raw<S: input::Input + ?Sized>(
1401 &self,
1402 input: &RegexInput<'_, S>,
1403 option_flags: u32,
1404 ) -> Result<Option<(usize, usize)>> {
1405 if input.is_done() {
1406 return Ok(None);
1407 }
1408 match &self.inner {
1409 RegexImpl::Wrap {
1410 inner,
1411 explicit_capture_group_0,
1412 ..
1413 } => {
1414 let mut delegated_input = ra_input(input);
1415 if input.is_anchored() {
1416 delegated_input = delegated_input.anchored(RaAnchored::Yes);
1417 }
1418 let result = if !*explicit_capture_group_0 {
1419 inner.search(&delegated_input).map(|m| (m.start(), m.end()))
1420 } else {
1421 // Only group 1's span is needed (the real match bounds of
1422 // the rewritten pattern); a fixed 4-slot search avoids
1423 // allocating full captures on every find.
1424 let mut slots = [None; 4];
1425 if inner.search_slots(&delegated_input, &mut slots).is_some() {
1426 slots[2]
1427 .zip(slots[3])
1428 .map(|(start, end)| (start.get(), end.get()))
1429 } else {
1430 None
1431 }
1432 };
1433 Ok(result)
1434 }
1435 RegexImpl::Fancy { prog, options, .. } => {
1436 let option_flags = option_flags
1437 | if options.find_not_empty {
1438 OPTION_FIND_NOT_EMPTY
1439 } else {
1440 0
1441 };
1442 // Span-only VM entry: nothing is moved out of the pooled
1443 // scratch, so this path is allocation-free per call.
1444 vm::run_spans(prog, input, option_flags, options)
1445 }
1446 }
1447 }
1448
1449 /// Build a `Captures` value containing only group 0 for the given span.
1450 ///
1451 /// This is used by `RegexSet` as a fast path for patterns without capture
1452 /// groups, where we only need to preserve the overall match range.
1453 ///
1454 /// The caller must pass a valid `start..end` match span for `input`.
1455 /// Behavior is otherwise undefined for APIs reading the resulting captures.
1456 pub(crate) fn captures_for_span<'t, S: input::Input + ?Sized>(
1457 &self,
1458 input: &'t S,
1459 start: usize,
1460 end: usize,
1461 ) -> Captures<'t, S> {
1462 Captures {
1463 inner: CapturesImpl::Fancy {
1464 saves: vec![start, end],
1465 },
1466 named_groups: self.named_groups.clone(),
1467 input,
1468 }
1469 }
1470
1471 /// Returns an iterator over all the non-overlapping capture groups matched in `text`.
1472 ///
1473 /// # Examples
1474 ///
1475 /// Finding all matches and capturing parts of each:
1476 ///
1477 /// ```rust
1478 /// # use fancy_regex::Regex;
1479 ///
1480 /// let re = Regex::new(r"(\d{4})-(\d{2})").unwrap();
1481 /// let text = "It was between 2018-04 and 2020-01";
1482 /// let mut all_captures = re.captures_iter(text);
1483 ///
1484 /// let first = all_captures.next().unwrap().unwrap();
1485 /// assert_eq!(first.get(1).unwrap().as_str(), "2018");
1486 /// assert_eq!(first.get(2).unwrap().as_str(), "04");
1487 /// assert_eq!(first.get(0).unwrap().as_str(), "2018-04");
1488 ///
1489 /// let second = all_captures.next().unwrap().unwrap();
1490 /// assert_eq!(second.get(1).unwrap().as_str(), "2020");
1491 /// assert_eq!(second.get(2).unwrap().as_str(), "01");
1492 /// assert_eq!(second.get(0).unwrap().as_str(), "2020-01");
1493 ///
1494 /// assert!(all_captures.next().is_none());
1495 /// ```
1496 pub fn captures_iter<'r, 't, S: input::Input + ?Sized>(
1497 &'r self,
1498 text: &'t S,
1499 ) -> CaptureMatches<'r, 't, S> {
1500 self.captures_iter_input(RegexInput::new(text))
1501 }
1502
1503 /// Returns an iterator over all the non-overlapping capture groups matched
1504 /// in the given search input.
1505 pub fn captures_iter_input<'r, 't, S: input::Input + ?Sized>(
1506 &'r self,
1507 input: RegexInput<'t, S>,
1508 ) -> CaptureMatches<'r, 't, S> {
1509 CaptureMatches(self.find_iter_input(input))
1510 }
1511
1512 /// Returns the capture groups for the first match in `text`.
1513 ///
1514 /// If no match is found, then `Ok(None)` is returned.
1515 ///
1516 /// # Examples
1517 ///
1518 /// Finding matches and capturing parts of the match:
1519 ///
1520 /// ```rust
1521 /// # use fancy_regex::Regex;
1522 ///
1523 /// let re = Regex::new(r"(\d{4})-(\d{2})-(\d{2})").unwrap();
1524 /// let text = "The date was 2018-04-07";
1525 /// let captures = re.captures(text).unwrap().unwrap();
1526 ///
1527 /// assert_eq!(captures.get(1).unwrap().as_str(), "2018");
1528 /// assert_eq!(captures.get(2).unwrap().as_str(), "04");
1529 /// assert_eq!(captures.get(3).unwrap().as_str(), "07");
1530 /// assert_eq!(captures.get(0).unwrap().as_str(), "2018-04-07");
1531 /// ```
1532 pub fn captures<'t, S: input::Input + ?Sized>(
1533 &self,
1534 text: &'t S,
1535 ) -> Result<Option<Captures<'t, S>>> {
1536 self.captures_input(RegexInput::new(text))
1537 }
1538
1539 /// Returns the capture groups for the first match in the given search
1540 /// input.
1541 pub fn captures_input<'t, S: input::Input + ?Sized>(
1542 &self,
1543 input: RegexInput<'t, S>,
1544 ) -> Result<Option<Captures<'t, S>>> {
1545 self.captures_input_with_option_flags(&input, 0)
1546 }
1547
1548 /// Returns the capture groups for the first match in `text`, starting from
1549 /// the specified byte position `pos`.
1550 ///
1551 /// # Examples
1552 ///
1553 /// Finding captures starting at a position:
1554 ///
1555 /// ```
1556 /// # use fancy_regex::Regex;
1557 /// let re = Regex::new(r"(?m:^)(\d+)").unwrap();
1558 /// let text = "1 test 123\n2 foo";
1559 /// let captures = re.captures_from_pos(text, 7).unwrap().unwrap();
1560 ///
1561 /// let group = captures.get(1).unwrap();
1562 /// assert_eq!(group.as_str(), "2");
1563 /// assert_eq!(group.start(), 11);
1564 /// assert_eq!(group.end(), 12);
1565 /// ```
1566 ///
1567 /// Note that in some cases this is not the same as using the `captures`
1568 /// method and passing a slice of the string, see the capture that we get
1569 /// when we do this:
1570 ///
1571 /// ```
1572 /// # use fancy_regex::Regex;
1573 /// let re = Regex::new(r"(?m:^)(\d+)").unwrap();
1574 /// let text = "1 test 123\n2 foo";
1575 /// let captures = re.captures(&text[7..]).unwrap().unwrap();
1576 /// assert_eq!(captures.get(1).unwrap().as_str(), "123");
1577 /// ```
1578 ///
1579 /// This matched the number "123" because it's at the beginning of the text
1580 /// of the string slice.
1581 ///
1582 /// To constrain matching to a byte range without slicing, use
1583 /// [Regex::captures_input()] with [`RegexInput`].
1584 ///
1585 pub fn captures_from_pos<'t, S: input::Input + ?Sized>(
1586 &self,
1587 text: &'t S,
1588 pos: usize,
1589 ) -> Result<Option<Captures<'t, S>>> {
1590 self.captures_input(RegexInput::new(text).from_pos(pos))
1591 }
1592
1593 pub(crate) fn captures_input_with_option_flags<'t, S: input::Input + ?Sized>(
1594 &self,
1595 input: &RegexInput<'t, S>,
1596 option_flags: u32,
1597 ) -> Result<Option<Captures<'t, S>>> {
1598 if input.is_done() {
1599 return Ok(None);
1600 }
1601 let named_groups = self.named_groups.clone();
1602 let haystack = input.haystack();
1603 match &self.inner {
1604 RegexImpl::Wrap {
1605 inner,
1606 explicit_capture_group_0,
1607 ..
1608 } => {
1609 // find_not_empty patterns are always compiled as Fancy, so find_not_empty is
1610 // always false here.
1611 let explicit = *explicit_capture_group_0;
1612 let mut locations = inner.create_captures();
1613 let mut delegated_input = ra_input(input);
1614 if input.is_anchored() {
1615 delegated_input = delegated_input.anchored(RaAnchored::Yes);
1616 }
1617 inner.captures(delegated_input, &mut locations);
1618 Ok(locations.is_match().then_some(Captures {
1619 inner: CapturesImpl::Wrap {
1620 locations,
1621 explicit_capture_group_0: explicit,
1622 },
1623 named_groups,
1624 input: haystack,
1625 }))
1626 }
1627 RegexImpl::Fancy {
1628 prog,
1629 n_groups,
1630 options,
1631 ..
1632 } => {
1633 let option_flags = option_flags
1634 | if options.find_not_empty {
1635 OPTION_FIND_NOT_EMPTY
1636 } else {
1637 0
1638 };
1639 let result = vm::run(prog, input, option_flags, options)?;
1640 Ok(result.map(|mut saves| {
1641 saves.truncate(n_groups * 2);
1642 Captures {
1643 inner: CapturesImpl::Fancy { saves },
1644 named_groups,
1645 input: haystack,
1646 }
1647 }))
1648 }
1649 }
1650 }
1651
1652 pub(crate) fn seek_pattern(&self) -> &str {
1653 match &self.inner {
1654 RegexImpl::Wrap {
1655 delegated_pattern, ..
1656 } => delegated_pattern,
1657 RegexImpl::Fancy { prog, .. } => &prog.seek_pattern,
1658 }
1659 }
1660
1661 /// Returns the number of captures, including the implicit capture of the entire expression.
1662 pub fn captures_len(&self) -> usize {
1663 match &self.inner {
1664 RegexImpl::Wrap {
1665 inner,
1666 explicit_capture_group_0,
1667 ..
1668 } => inner.captures_len() - if *explicit_capture_group_0 { 1 } else { 0 },
1669 RegexImpl::Fancy { n_groups, .. } => *n_groups,
1670 }
1671 }
1672
1673 /// Returns an iterator over the capture names.
1674 pub fn capture_names(&self) -> CaptureNames<'_> {
1675 let mut names = Vec::new();
1676 names.resize(self.captures_len(), None);
1677 for (name, &i) in self.named_groups.iter() {
1678 names[i] = Some(name.as_str());
1679 }
1680 CaptureNames(names.into_iter())
1681 }
1682
1683 // for debugging only
1684 #[doc(hidden)]
1685 pub fn debug_print(&self, writer: &mut Formatter<'_>) -> fmt::Result {
1686 match &self.inner {
1687 RegexImpl::Wrap {
1688 delegated_pattern,
1689 explicit_capture_group_0,
1690 ..
1691 } => {
1692 write!(
1693 writer,
1694 "wrapped Regex {:?}, explicit_capture_group_0: {:}",
1695 delegated_pattern, *explicit_capture_group_0
1696 )
1697 }
1698 RegexImpl::Fancy { prog, .. } => prog.debug_print(writer),
1699 }
1700 }
1701
1702 /// Replaces the leftmost-first match with the replacement provided.
1703 /// The replacement can be a regular string (where `$N` and `$name` are
1704 /// expanded to match capture groups) or a function that takes the matches'
1705 /// `Captures` and returns the replaced string.
1706 ///
1707 /// If no match is found, then a copy of the string is returned unchanged.
1708 ///
1709 /// # Replacement string syntax
1710 ///
1711 /// All instances of `$name` in the replacement text is replaced with the
1712 /// corresponding capture group `name`.
1713 ///
1714 /// `name` may be an integer corresponding to the index of the
1715 /// capture group (counted by order of opening parenthesis where `0` is the
1716 /// entire match) or it can be a name (consisting of letters, digits or
1717 /// underscores) corresponding to a named capture group.
1718 ///
1719 /// If `name` isn't a valid capture group (whether the name doesn't exist
1720 /// or isn't a valid index), then it is replaced with the empty string.
1721 ///
1722 /// The longest possible name is used. e.g., `$1a` looks up the capture
1723 /// group named `1a` and not the capture group at index `1`. To exert more
1724 /// precise control over the name, use braces, e.g., `${1}a`.
1725 ///
1726 /// To write a literal `$` use `$$`.
1727 ///
1728 /// # Examples
1729 ///
1730 /// Note that this function is polymorphic with respect to the replacement.
1731 /// In typical usage, this can just be a normal string:
1732 ///
1733 /// ```rust
1734 /// # use fancy_regex::Regex;
1735 /// let re = Regex::new("[^01]+").unwrap();
1736 /// assert_eq!(re.replace("1078910", ""), "1010");
1737 /// ```
1738 ///
1739 /// But anything satisfying the `Replacer` trait will work. For example,
1740 /// a closure of type `|&Captures| -> String` provides direct access to the
1741 /// captures corresponding to a match. This allows one to access
1742 /// capturing group matches easily:
1743 ///
1744 /// ```rust
1745 /// # use fancy_regex::{Regex, Captures};
1746 /// let re = Regex::new(r"([^,\s]+),\s+(\S+)").unwrap();
1747 /// let result = re.replace("Springsteen, Bruce", |caps: &Captures<'_, str>| {
1748 /// format!("{} {}", &caps[2], &caps[1])
1749 /// });
1750 /// assert_eq!(result, "Bruce Springsteen");
1751 /// ```
1752 ///
1753 /// But this is a bit cumbersome to use all the time. Instead, a simple
1754 /// syntax is supported that expands `$name` into the corresponding capture
1755 /// group. Here's the last example, but using this expansion technique
1756 /// with named capture groups:
1757 ///
1758 /// ```rust
1759 /// # use fancy_regex::Regex;
1760 /// let re = Regex::new(r"(?P<last>[^,\s]+),\s+(?P<first>\S+)").unwrap();
1761 /// let result = re.replace("Springsteen, Bruce", "$first $last");
1762 /// assert_eq!(result, "Bruce Springsteen");
1763 /// ```
1764 ///
1765 /// Note that using `$2` instead of `$first` or `$1` instead of `$last`
1766 /// would produce the same result. To write a literal `$` use `$$`.
1767 ///
1768 /// Sometimes the replacement string requires use of curly braces to
1769 /// delineate a capture group replacement and surrounding literal text.
1770 /// For example, if we wanted to join two words together with an
1771 /// underscore:
1772 ///
1773 /// ```rust
1774 /// # use fancy_regex::Regex;
1775 /// let re = Regex::new(r"(?P<first>\w+)\s+(?P<second>\w+)").unwrap();
1776 /// let result = re.replace("deep fried", "${first}_$second");
1777 /// assert_eq!(result, "deep_fried");
1778 /// ```
1779 ///
1780 /// Without the curly braces, the capture group name `first_` would be
1781 /// used, and since it doesn't exist, it would be replaced with the empty
1782 /// string.
1783 ///
1784 /// Finally, sometimes you just want to replace a literal string with no
1785 /// regard for capturing group expansion. This can be done by wrapping a
1786 /// byte string with `NoExpand`:
1787 ///
1788 /// ```rust
1789 /// # use fancy_regex::Regex;
1790 /// use fancy_regex::NoExpand;
1791 ///
1792 /// let re = Regex::new(r"(?P<last>[^,\s]+),\s+(\S+)").unwrap();
1793 /// let result = re.replace("Springsteen, Bruce", NoExpand("$2 $last"));
1794 /// assert_eq!(result, "$2 $last");
1795 /// ```
1796 pub fn replace<'t, R: Replacer>(&self, text: &'t str, rep: R) -> Cow<'t, str> {
1797 self.replacen(text, 1, rep)
1798 }
1799
1800 /// Replaces all non-overlapping matches in `text` with the replacement
1801 /// provided. This is the same as calling `replacen` with `limit` set to
1802 /// `0`.
1803 ///
1804 /// See the documentation for `replace` for details on how to access
1805 /// capturing group matches in the replacement string.
1806 pub fn replace_all<'t, R: Replacer>(&self, text: &'t str, rep: R) -> Cow<'t, str> {
1807 self.replacen(text, 0, rep)
1808 }
1809
1810 /// Replaces at most `limit` non-overlapping matches in `text` with the
1811 /// replacement provided. If `limit` is 0, then all non-overlapping matches
1812 /// are replaced.
1813 ///
1814 /// Will panic if any errors are encountered. Use `try_replacen`, which this
1815 /// function unwraps, if you want to handle errors.
1816 ///
1817 /// See the documentation for `replace` for details on how to access
1818 /// capturing group matches in the replacement string.
1819 ///
1820 pub fn replacen<'t, R: Replacer>(&self, text: &'t str, limit: usize, rep: R) -> Cow<'t, str> {
1821 self.try_replacen(text, limit, rep).unwrap()
1822 }
1823
1824 /// Replaces at most `limit` non-overlapping matches in `text` with the
1825 /// replacement provided. If `limit` is 0, then all non-overlapping matches
1826 /// are replaced.
1827 ///
1828 /// Propagates any errors encountered, such as `RuntimeError::BacktrackLimitExceeded`.
1829 ///
1830 /// See the documentation for `replace` for details on how to access
1831 /// capturing group matches in the replacement string.
1832 pub fn try_replacen<'t, R: Replacer>(
1833 &self,
1834 text: &'t str,
1835 limit: usize,
1836 mut rep: R,
1837 ) -> Result<Cow<'t, str>> {
1838 // If we know that the replacement doesn't have any capture expansions,
1839 // then we can fast path. The fast path can make a tremendous
1840 // difference:
1841 //
1842 // 1) We use `find_iter` instead of `captures_iter`. Not asking for
1843 // captures generally makes the regex engines faster.
1844 // 2) We don't need to look up all of the capture groups and do
1845 // replacements inside the replacement string. We just push it
1846 // at each match and be done with it.
1847 if let Some(rep) = rep.no_expansion() {
1848 let mut it = self.find_iter(text).enumerate().peekable();
1849 if it.peek().is_none() {
1850 return Ok(Cow::Borrowed(text));
1851 }
1852 let mut new = String::with_capacity(text.len());
1853 let mut last_match = 0;
1854 for (i, m) in it {
1855 let m = m?;
1856
1857 if limit > 0 && i >= limit {
1858 break;
1859 }
1860 new.push_str(&text[last_match..m.start()]);
1861 new.push_str(&rep);
1862 last_match = m.end();
1863 }
1864 new.push_str(&text[last_match..]);
1865 return Ok(Cow::Owned(new));
1866 }
1867
1868 // The slower path, which we use if the replacement needs access to
1869 // capture groups.
1870 let mut it = self.captures_iter(text).enumerate().peekable();
1871 if it.peek().is_none() {
1872 return Ok(Cow::Borrowed(text));
1873 }
1874 let mut new = String::with_capacity(text.len());
1875 let mut last_match = 0;
1876 for (i, cap) in it {
1877 let cap = cap?;
1878
1879 if limit > 0 && i >= limit {
1880 break;
1881 }
1882 // unwrap on 0 is OK because captures only reports matches
1883 let m = cap.get(0).unwrap();
1884 new.push_str(&text[last_match..m.start()]);
1885 rep.replace_append(&cap, &mut new);
1886 last_match = m.end();
1887 }
1888 new.push_str(&text[last_match..]);
1889 Ok(Cow::Owned(new))
1890 }
1891
1892 /// Splits the string by matches of the regex.
1893 ///
1894 /// Returns an iterator over the substrings of the target string
1895 /// that *aren't* matched by the regex.
1896 ///
1897 /// # Example
1898 ///
1899 /// To split a string delimited by arbitrary amounts of spaces or tabs:
1900 ///
1901 /// ```rust
1902 /// # use fancy_regex::Regex;
1903 /// let re = Regex::new(r"[ \t]+").unwrap();
1904 /// let target = "a b \t c\td e";
1905 /// let fields: Vec<&str> = re.split(target).map(|x| x.unwrap()).collect();
1906 /// assert_eq!(fields, vec!["a", "b", "c", "d", "e"]);
1907 /// ```
1908 pub fn split<'r, 'h>(&'r self, target: &'h str) -> Split<'r, 'h> {
1909 Split {
1910 matches: self.find_iter(target),
1911 next_start: 0,
1912 target,
1913 }
1914 }
1915
1916 /// Splits the string by matches of the regex at most `limit` times.
1917 ///
1918 /// Returns an iterator over the substrings of the target string
1919 /// that *aren't* matched by the regex.
1920 ///
1921 /// The `N`th substring is the remaining part of the target.
1922 ///
1923 /// # Example
1924 ///
1925 /// To split a string delimited by arbitrary amounts of spaces or tabs
1926 /// 3 times:
1927 ///
1928 /// ```rust
1929 /// # use fancy_regex::Regex;
1930 /// let re = Regex::new(r"[ \t]+").unwrap();
1931 /// let target = "a b \t c\td e";
1932 /// let fields: Vec<&str> = re.splitn(target, 3).map(|x| x.unwrap()).collect();
1933 /// assert_eq!(fields, vec!["a", "b", "c\td e"]);
1934 /// ```
1935 pub fn splitn<'r, 'h>(&'r self, target: &'h str, limit: usize) -> SplitN<'r, 'h> {
1936 SplitN {
1937 splits: self.split(target),
1938 limit,
1939 }
1940 }
1941}
1942
1943/// `Display` adapter that prints a [`Regex`]'s internal representation via
1944/// [`Regex::debug_print`]. Intended for debugging and test output only.
1945#[doc(hidden)]
1946#[derive(Debug)]
1947#[allow(dead_code)]
1948pub struct DebugRegex<'a>(pub &'a Regex);
1949
1950impl fmt::Display for DebugRegex<'_> {
1951 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
1952 self.0.debug_print(f)
1953 }
1954}
1955
1956fn ra_input<'a, S: input::Input + ?Sized>(input: &'a RegexInput<'a, S>) -> RaInput<'a> {
1957 let mut ra_input = RaInput::new(input.haystack().as_bytes()).range(input.get_range());
1958 ra_input.set_start(input.effective_start());
1959 ra_input
1960}
1961
1962impl TryFrom<&str> for Regex {
1963 type Error = Error;
1964
1965 /// Attempts to parse a string into a regular expression
1966 fn try_from(s: &str) -> Result<Self> {
1967 Self::new(s)
1968 }
1969}
1970
1971impl TryFrom<String> for Regex {
1972 type Error = Error;
1973
1974 /// Attempts to parse a string into a regular expression
1975 fn try_from(s: String) -> Result<Self> {
1976 Self::new(&s)
1977 }
1978}
1979
1980impl<'t> Match<'t> {
1981 /// Returns the starting byte offset of the match in the text.
1982 #[inline]
1983 pub fn start(&self) -> usize {
1984 self.start
1985 }
1986
1987 /// Returns the ending byte offset of the match in the text.
1988 #[inline]
1989 pub fn end(&self) -> usize {
1990 self.end
1991 }
1992
1993 /// Returns the range over the starting and ending byte offsets of the match in text.
1994 #[inline]
1995 pub fn range(&self) -> Range<usize> {
1996 self.start..self.end
1997 }
1998
1999 /// Returns the matched text.
2000 #[inline]
2001 pub fn as_str(&self) -> &'t str {
2002 &self.text[self.start..self.end]
2003 }
2004
2005 /// Creates a new match from the given text and byte offsets.
2006 pub(crate) fn new(text: &'t str, start: usize, end: usize) -> Match<'t> {
2007 Match { text, start, end }
2008 }
2009}
2010
2011impl<'t> From<Match<'t>> for &'t str {
2012 fn from(m: Match<'t>) -> &'t str {
2013 m.as_str()
2014 }
2015}
2016
2017impl<'t> From<Match<'t>> for Range<usize> {
2018 fn from(m: Match<'t>) -> Range<usize> {
2019 m.range()
2020 }
2021}
2022
2023#[allow(clippy::len_without_is_empty)] // follow regex's API
2024impl<'t, S: input::Input + ?Sized> Captures<'t, S> {
2025 pub(crate) fn get_span(&self, i: usize) -> Option<(usize, usize)> {
2026 self.inner.get_span(i)
2027 }
2028
2029 /// Get the capture group by its index in the regex.
2030 ///
2031 /// If there is no match for that group or the index does not correspond to a group, `None` is
2032 /// returned. The index 0 returns the whole match.
2033 pub fn get(&self, i: usize) -> Option<S::Match<'t>> {
2034 self.inner
2035 .get_span(i)
2036 .map(|(start, end)| self.input.make_match(start, end))
2037 }
2038
2039 /// Returns the match for a named capture group. Returns `None` the capture
2040 /// group did not match or if there is no group with the given name.
2041 pub fn name(&self, name: &str) -> Option<S::Match<'t>> {
2042 self.named_groups.get(name).and_then(|i| self.get(*i))
2043 }
2044
2045 /// Iterate over the captured groups in order in which they appeared in the regex. The first
2046 /// capture corresponds to the whole match.
2047 pub fn iter<'c>(&'c self) -> SubCaptureMatches<'c, 't, S> {
2048 SubCaptureMatches { data: self, i: 0 }
2049 }
2050
2051 /// How many groups were captured. This is always at least 1 because group 0 returns the whole
2052 /// match.
2053 pub fn len(&self) -> usize {
2054 self.inner.len()
2055 }
2056
2057 /// Returns the byte slice of the entire input that was searched.
2058 pub fn input_as_bytes(&self) -> &'t [u8] {
2059 self.input.as_bytes()
2060 }
2061}
2062
2063// str-only methods
2064impl<'t> Captures<'t, str> {
2065 /// Expands all instances of `$group` in `replacement` to the corresponding
2066 /// capture group `name`, and writes them to the `dst` buffer given.
2067 ///
2068 /// `group` may be an integer corresponding to the index of the
2069 /// capture group (counted by order of opening parenthesis where `\0` is the
2070 /// entire match) or it can be a name (consisting of letters, digits or
2071 /// underscores) corresponding to a named capture group.
2072 ///
2073 /// If `group` isn't a valid capture group (whether the name doesn't exist
2074 /// or isn't a valid index), then it is replaced with the empty string.
2075 ///
2076 /// The longest possible name is used. e.g., `$1a` looks up the capture
2077 /// group named `1a` and not the capture group at index `1`. To exert more
2078 /// precise control over the name, use braces, e.g., `${1}a`.
2079 ///
2080 /// To write a literal `$`, use `$$`.
2081 ///
2082 /// For more control over expansion, see [`Expander`].
2083 ///
2084 /// [`Expander`]: expand/struct.Expander.html
2085 pub fn expand(&self, replacement: &str, dst: &mut String) {
2086 Expander::default().append_expansion(dst, replacement, self);
2087 }
2088}
2089
2090/// Get a group by index.
2091///
2092/// `'t` is the lifetime of the matched text.
2093///
2094/// The text can't outlive the `Captures` object if this method is
2095/// used, because of how `Index` is defined (normally `a[i]` is part
2096/// of `a` and can't outlive it); to do that, use `get()` instead.
2097///
2098/// # Panics
2099///
2100/// If there is no group at the given index.
2101impl<'t> Index<usize> for Captures<'t, str> {
2102 type Output = str;
2103
2104 fn index(&self, i: usize) -> &str {
2105 self.get(i)
2106 .map(|m| m.as_str())
2107 .unwrap_or_else(|| panic!("no group at index '{}'", i))
2108 }
2109}
2110
2111/// Get a group by name.
2112///
2113/// `'t` is the lifetime of the matched text and `'i` is the lifetime
2114/// of the group name (the index).
2115///
2116/// The text can't outlive the `Captures` object if this method is
2117/// used, because of how `Index` is defined (normally `a[i]` is part
2118/// of `a` and can't outlive it); to do that, use `name` instead.
2119///
2120/// # Panics
2121///
2122/// If there is no group named by the given value.
2123impl<'t, 'i> Index<&'i str> for Captures<'t, str> {
2124 type Output = str;
2125
2126 fn index<'a>(&'a self, name: &'i str) -> &'a str {
2127 self.name(name)
2128 .map(|m| m.as_str())
2129 .unwrap_or_else(|| panic!("no group named '{}'", name))
2130 }
2131}
2132
2133impl<'c, 't, S: input::Input + ?Sized> Iterator for SubCaptureMatches<'c, 't, S> {
2134 type Item = Option<S::Match<'t>>;
2135
2136 fn next(&mut self) -> Option<Option<S::Match<'t>>> {
2137 if self.i < self.data.len() {
2138 let result = self.data.get(self.i);
2139 self.i += 1;
2140 Some(result)
2141 } else {
2142 None
2143 }
2144 }
2145}
2146
2147// TODO: might be nice to implement ExactSizeIterator etc for SubCaptures
2148
2149/// Regular expression AST. This is public for now but may change.
2150#[derive(Debug, PartialEq, Eq, Clone)]
2151pub enum Expr {
2152 /// An empty expression, e.g. the last branch in `(a|b|)`
2153 Empty,
2154 /// Any character, regex `.`
2155 Any {
2156 /// Whether it also matches newlines or not
2157 newline: bool,
2158 /// Whether CRLF mode is enabled (`\r` also counts as a newline, so dot
2159 /// excludes both `\r` and `\n`)
2160 crlf: bool,
2161 },
2162 /// An assertion
2163 Assertion(Assertion),
2164 /// General newline sequence, `\R`
2165 /// Matches `\r\n` or any single newline character (\n, \v, \f, \r)
2166 /// In Unicode mode, also matches U+0085, U+2028, U+2029
2167 GeneralNewline {
2168 /// Whether Unicode mode is enabled
2169 unicode: bool,
2170 },
2171 /// The string as a literal, e.g. `a`
2172 Literal {
2173 /// The string to match
2174 val: String,
2175 /// Whether match is case-insensitive or not
2176 casei: bool,
2177 },
2178 /// Concatenation of multiple expressions, must match in order, e.g. `a.` is a concatenation of
2179 /// the literal `a` and `.` for any character
2180 Concat(Vec<Expr>),
2181 /// Alternative of multiple expressions, one of them must match, e.g. `a|b` is an alternative
2182 /// where either the literal `a` or `b` must match
2183 Alt(Vec<Expr>),
2184 /// Capturing group of expression, e.g. `(a.)` matches `a` and any character and "captures"
2185 /// (remembers) the match
2186 Group(Arc<Expr>),
2187 /// Look-around (e.g. positive/negative look-ahead or look-behind) with an expression, e.g.
2188 /// `(?=a)` means the next character must be `a` (but the match is not consumed)
2189 LookAround(Box<Expr>, LookAround),
2190 /// Repeat of an expression, e.g. `a*` or `a+` or `a{1,3}`
2191 Repeat {
2192 /// The expression that is being repeated
2193 child: Box<Expr>,
2194 /// The minimum number of repetitions
2195 lo: usize,
2196 /// The maximum number of repetitions (or `usize::MAX`)
2197 hi: usize,
2198 /// Greedy means as much as possible is matched, e.g. `.*b` would match all of `abab`.
2199 /// Non-greedy means as little as possible, e.g. `.*?b` would match only `ab` in `abab`.
2200 greedy: bool,
2201 },
2202 /// Delegate a regex to the regex crate. This is used as a simplification so that we don't have
2203 /// to represent all the expressions in the AST, e.g. character classes.
2204 ///
2205 /// **Constraint**: All Delegate expressions must match exactly 1 character. This ensures
2206 /// consistent analysis and compilation behavior. For zero-width or multi-character patterns,
2207 /// use the appropriate Expr variants instead (e.g., Assertion, Repeat, Concat).
2208 Delegate {
2209 /// The regex
2210 inner: String,
2211 /// Whether the matching is case-insensitive or not
2212 casei: bool,
2213 },
2214 /// Back reference to a capture group, e.g. `\1` in `(abc|def)\1` references the captured group
2215 /// and the whole regex matches either `abcabc` or `defdef`.
2216 Backref {
2217 /// The capture group number being referenced
2218 group: usize,
2219 /// Whether the matching is case-insensitive or not
2220 casei: bool,
2221 },
2222 /// Back reference to a capture group at the given specified relative recursion level.
2223 BackrefWithRelativeRecursionLevel {
2224 /// The capture group number being referenced
2225 group: usize,
2226 /// Relative recursion level
2227 relative_level: isize,
2228 /// Whether the matching is case-insensitive or not
2229 casei: bool,
2230 },
2231 /// Atomic non-capturing group, e.g. `(?>ab|a)` in text that contains `ab` will match `ab` and
2232 /// never backtrack and try `a`, even if matching fails after the atomic group.
2233 AtomicGroup(Box<Expr>),
2234 /// Keep matched text so far out of overall match
2235 KeepOut,
2236 /// Anchor to match at the position where the previous match ended
2237 ContinueFromPreviousMatchEnd,
2238 /// Conditional expression based on whether the numbered capture group matched or not.
2239 /// The optional `relative_recursion_level` qualifies which recursion level's capture is
2240 /// tested (Oniguruma `(?(name+N)...)` syntax).
2241 BackrefExistsCondition {
2242 /// The resolved capture group number
2243 group: usize,
2244 /// Optional relative recursion level (e.g. `+0`, `-1`)
2245 relative_recursion_level: Option<isize>,
2246 },
2247 /// If/Then/Else Condition. If there is no Then/Else, these will just be empty expressions.
2248 Conditional {
2249 /// The conditional expression to evaluate
2250 condition: Box<Expr>,
2251 /// What to execute if the condition is true
2252 true_branch: Box<Expr>,
2253 /// What to execute if the condition is false
2254 false_branch: Box<Expr>,
2255 },
2256 /// Subroutine call to the specified group number
2257 SubroutineCall(usize),
2258 /// Backtracking control verb
2259 BacktrackingControlVerb(BacktrackingControlVerb),
2260 /// Match while the given expression is absent from the haystack
2261 Absent(Absent),
2262 /// DEFINE group - defines capture groups for subroutines without matching anything
2263 /// The expressions inside are parsed and assigned group numbers, but no VM instructions
2264 /// are generated for the DEFINE block itself.
2265 DefineGroup {
2266 /// The expressions/groups being defined
2267 definitions: Box<Expr>,
2268 },
2269 /// Abstract Syntax Tree node - will be resolved into an Expr before analysis.
2270 /// Contains the position in the pattern where the node was parsed from
2271 AstNode(AstNode, usize),
2272}
2273
2274/// Target of a backreference or subroutine call
2275#[derive(Debug, PartialEq, Eq, Clone)]
2276pub enum CaptureGroupTarget {
2277 /// Direct numbered reference
2278 ByNumber(usize),
2279
2280 /// Named reference
2281 ByName(String),
2282
2283 /// Relative reference (e.g., -1, -2, etc.)
2284 Relative(isize),
2285}
2286
2287/// Abstract Syntax Tree node - will be resolved into an Expr before analysis
2288#[derive(Debug, PartialEq, Eq, Clone)]
2289pub enum AstNode {
2290 /// Group with optional name - name is only present if explicitly specified in pattern
2291 AstGroup {
2292 /// Optional name of the capture group, present only when explicitly named in the pattern
2293 name: Option<String>,
2294 /// The inner expression of the group
2295 inner: Box<Expr>,
2296 },
2297 /// Backreference
2298 Backref {
2299 /// The target capture group being referenced
2300 target: CaptureGroupTarget,
2301 /// Whether the matching is case-insensitive or not
2302 // TODO: move out of Backref and prefer a Flags AstNode. The resolver can then track the flags and set casei on the resolved Expr accordingly
2303 casei: bool,
2304 /// Optional relative recursion level for the backreference
2305 relative_recursion_level: Option<isize>,
2306 },
2307 /// Subroutine Call
2308 SubroutineCall(CaptureGroupTarget),
2309 /// Backreference exists condition `(?(name)...)` or `(?(1)...)` - unresolved target.
2310 /// The optional `relative_recursion_level` corresponds to the Oniguruma `+N`/`-N` suffix
2311 /// (e.g. `(?(name+0)...)`) which qualifies which recursion level's capture is tested.
2312 BackrefExistsCondition {
2313 /// The target capture group being tested for existence
2314 target: CaptureGroupTarget,
2315 /// Optional relative recursion level qualifier (e.g. `+0`, `-1`)
2316 relative_recursion_level: Option<isize>,
2317 },
2318}
2319
2320/// Type of look-around assertion as used for a look-around expression.
2321#[derive(Debug, PartialEq, Eq, Clone, Copy)]
2322pub enum LookAround {
2323 /// Look-ahead assertion, e.g. `(?=a)`
2324 LookAhead,
2325 /// Negative look-ahead assertion, e.g. `(?!a)`
2326 LookAheadNeg,
2327 /// Look-behind assertion, e.g. `(?<=a)`
2328 LookBehind,
2329 /// Negative look-behind assertion, e.g. `(?<!a)`
2330 LookBehindNeg,
2331}
2332
2333/// Type of absent operator as used for Oniguruma's absent functionality.
2334#[derive(Debug, PartialEq, Eq, Clone)]
2335pub enum Absent {
2336 /// Absent repeater `(?~absent)` - works like `\O*` (match any character including newline, repeated)
2337 /// but is limited by the range that does not include the string match with `absent`.
2338 /// This is a written abbreviation of `(?~|absent|\O*)`.
2339 Repeater(Box<Expr>),
2340 /// Absent expression `(?~|absent|exp)` - works like `exp`, but is limited by the range
2341 /// that does not include the string match with `absent`.
2342 Expression {
2343 /// The expression to avoid matching
2344 absent: Box<Expr>,
2345 /// The expression to match
2346 exp: Box<Expr>,
2347 },
2348 /// Absent stopper `(?~|absent)` - after this operator, haystack range is limited
2349 /// up to the point where `absent` matches.
2350 Stopper(Box<Expr>),
2351 /// Range clear `(?~|)` - clears the effects caused by absent stoppers.
2352 Clear,
2353}
2354
2355/// Type of backtracking control verb which affects how backtracking will behave.
2356/// See <https://www.regular-expressions.info/verb.html>
2357#[derive(Debug, PartialEq, Eq, Clone, Copy)]
2358pub enum BacktrackingControlVerb {
2359 /// Fail this branch immediately
2360 Fail,
2361 /// Treat match so far as successful overall match
2362 Accept,
2363 /// Abort the entire match on failure
2364 Commit,
2365 /// Restart the entire match attempt at the current position
2366 Skip,
2367 /// Prune all backtracking states and restart the entire match attempt at the next position
2368 Prune,
2369}
2370
2371/// An iterator over capture names in a [Regex]. The iterator
2372/// returns the name of each group, or [None] if the group has
2373/// no name. Because capture group 0 cannot have a name, the
2374/// first item returned is always [None].
2375pub struct CaptureNames<'r>(vec::IntoIter<Option<&'r str>>);
2376
2377impl Debug for CaptureNames<'_> {
2378 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
2379 f.write_str("<CaptureNames>")
2380 }
2381}
2382
2383impl<'r> Iterator for CaptureNames<'r> {
2384 type Item = Option<&'r str>;
2385
2386 fn next(&mut self) -> Option<Self::Item> {
2387 self.0.next()
2388 }
2389}
2390
2391// silly to write my own, but this is super-fast for the common 1-digit
2392// case.
2393fn push_usize(s: &mut String, x: usize) {
2394 if x >= 10 {
2395 push_usize(s, x / 10);
2396 s.push((b'0' + (x % 10) as u8) as char);
2397 } else {
2398 s.push((b'0' + (x as u8)) as char);
2399 }
2400}
2401
2402/// Emit a repeat quantifier `?`, `*`, `+`, or `{lo,hi}` (optionally non-greedy) into `buf`.
2403///
2404/// This is shared between [`Expr::to_str`] and `build_seek_pattern` in the compiler.
2405pub(crate) fn write_quantifier(buf: &mut String, lo: usize, hi: usize, greedy: bool) {
2406 match (lo, hi) {
2407 (0, 1) => buf.push('?'),
2408 (0, usize::MAX) => buf.push('*'),
2409 (1, usize::MAX) => buf.push('+'),
2410 (lo, hi) => {
2411 buf.push('{');
2412 push_usize(buf, lo);
2413 if lo != hi {
2414 buf.push(',');
2415 if hi != usize::MAX {
2416 push_usize(buf, hi);
2417 }
2418 }
2419 buf.push('}');
2420 }
2421 }
2422 if !greedy {
2423 buf.push('?');
2424 }
2425}
2426
2427fn is_special(c: char) -> bool {
2428 matches!(
2429 c,
2430 '\\' | '.' | '+' | '*' | '?' | '(' | ')' | '|' | '[' | ']' | '{' | '}' | '^' | '$' | '#'
2431 )
2432}
2433
2434pub(crate) fn push_quoted(buf: &mut String, s: &str) {
2435 for c in s.chars() {
2436 if is_special(c) {
2437 buf.push('\\');
2438 }
2439 buf.push(c);
2440 }
2441}
2442
2443/// Escapes special characters in `text` with '\\'. Returns a string which, when interpreted
2444/// as a regex, matches exactly `text`.
2445pub fn escape(text: &str) -> Cow<'_, str> {
2446 // Using bytes() is OK because all special characters are single bytes.
2447 match text.bytes().filter(|&b| is_special(b as char)).count() {
2448 0 => Cow::Borrowed(text),
2449 n => {
2450 // The capacity calculation is exact because '\\' is a single byte.
2451 let mut buf = String::with_capacity(text.len() + n);
2452 push_quoted(&mut buf, text);
2453 Cow::Owned(buf)
2454 }
2455 }
2456}
2457
2458/// Type of assertions
2459#[derive(Debug, PartialEq, Eq, Clone, Copy)]
2460pub enum Assertion {
2461 /// Start of input text
2462 StartText,
2463 /// End of input text
2464 EndText,
2465 /// End of input text, or before any trailing newlines at the end (Oniguruma's `\Z`)
2466 EndTextIgnoreTrailingNewlines {
2467 /// Whether CRLF mode is enabled.
2468 /// If `true`, trailing `\r\n` pairs (in addition to bare `\n`) are also ignored.
2469 crlf: bool,
2470 },
2471 /// Start of a line
2472 StartLine {
2473 /// CRLF mode.
2474 /// If true, this assertion matches at the starting position of the input text, or at the position immediately
2475 /// following either a `\r` or `\n` character, but never after a `\r` when a `\n` follows.
2476 crlf: bool,
2477 },
2478 /// Start of a line in Oniguruma mode.
2479 /// Behaves like [`Assertion::StartLine`], but additionally rejects matches at the end of the input
2480 /// when it is preceded by a newline.
2481 StartLineOniguruma {
2482 /// CRLF mode.
2483 /// If true, this assertion matches at the starting position of the input text, or at the position immediately
2484 /// following either a `\r` or `\n` character, but never after a `\r` when a `\n` follows.
2485 crlf: bool,
2486 },
2487 /// End of a line
2488 EndLine {
2489 /// CRLF mode
2490 /// If true, this assertion matches at the ending position of the input text, or at the position immediately
2491 /// preceding either a `\r` or `\n` character, but never after a `\r` when a `\n` follows.
2492 crlf: bool,
2493 },
2494 /// Left word boundary
2495 LeftWordBoundary,
2496 /// Left word half boundary
2497 LeftWordHalfBoundary,
2498 /// Right word boundary
2499 RightWordBoundary,
2500 /// Right word half boundary
2501 RightWordHalfBoundary,
2502 /// Both word boundaries
2503 WordBoundary,
2504 /// Not word boundary
2505 NotWordBoundary,
2506}
2507
2508impl Assertion {
2509 pub(crate) fn is_always_hard(&self) -> bool {
2510 use Assertion::*;
2511 matches!(
2512 self,
2513 // these will make regex-automata use PikeVM and are not compabible with certain regex-automata features we use
2514 LeftWordBoundary
2515 | LeftWordHalfBoundary
2516 | RightWordBoundary
2517 | RightWordHalfBoundary
2518 | WordBoundary
2519 | NotWordBoundary
2520 // `\Z` needs custom trailing-newline handling.
2521 | EndTextIgnoreTrailingNewlines { .. }
2522 )
2523 }
2524}
2525
2526/// An iterator over the immediate children of an [`Expr`].
2527///
2528/// This iterator yields references to child expressions but does not recurse into them.
2529#[derive(Debug)]
2530pub enum ExprChildrenIter<'a> {
2531 /// No children (leaf node)
2532 Empty,
2533 /// A single child (Group, LookAround, AtomicGroup, Repeat)
2534 Single(Option<&'a Expr>),
2535 /// Multiple children in a Vec (Concat, Alt)
2536 Vec(alloc::slice::Iter<'a, Expr>),
2537 /// Three children (Conditional)
2538 Triple {
2539 /// First child
2540 first: Option<&'a Expr>,
2541 /// Second child
2542 second: Option<&'a Expr>,
2543 /// Third child
2544 third: Option<&'a Expr>,
2545 },
2546}
2547
2548/// An iterator over the immediate children of an [`Expr`] for mutable access.
2549///
2550/// This iterator yields mutable references to child expressions but does not recurse into them.
2551#[derive(Debug)]
2552pub enum ExprChildrenIterMut<'a> {
2553 /// No children (leaf node)
2554 Empty,
2555 /// A single child (Group, LookAround, AtomicGroup, Repeat)
2556 Single(Option<&'a mut Expr>),
2557 /// Multiple children in a Vec (Concat, Alt)
2558 Vec(alloc::slice::IterMut<'a, Expr>),
2559 /// Three children (Conditional)
2560 Triple {
2561 /// First child
2562 first: Option<&'a mut Expr>,
2563 /// Second child
2564 second: Option<&'a mut Expr>,
2565 /// Third child
2566 third: Option<&'a mut Expr>,
2567 },
2568}
2569
2570impl<'a> Iterator for ExprChildrenIter<'a> {
2571 type Item = &'a Expr;
2572
2573 fn next(&mut self) -> Option<Self::Item> {
2574 match self {
2575 ExprChildrenIter::Empty => None,
2576 ExprChildrenIter::Single(ref mut child) => child.take(),
2577 ExprChildrenIter::Vec(ref mut iter) => iter.next(),
2578 ExprChildrenIter::Triple {
2579 ref mut first,
2580 ref mut second,
2581 ref mut third,
2582 } => first
2583 .take()
2584 .or_else(|| second.take())
2585 .or_else(|| third.take()),
2586 }
2587 }
2588}
2589
2590impl<'a> Iterator for ExprChildrenIterMut<'a> {
2591 type Item = &'a mut Expr;
2592
2593 fn next(&mut self) -> Option<Self::Item> {
2594 match self {
2595 ExprChildrenIterMut::Empty => None,
2596 ExprChildrenIterMut::Single(ref mut child) => child.take(),
2597 ExprChildrenIterMut::Vec(ref mut iter) => iter.next(),
2598 ExprChildrenIterMut::Triple {
2599 ref mut first,
2600 ref mut second,
2601 ref mut third,
2602 } => first
2603 .take()
2604 .or_else(|| second.take())
2605 .or_else(|| third.take()),
2606 }
2607 }
2608}
2609
2610macro_rules! children_iter_match {
2611 ($self:expr, $iter:ident, $vec_method:ident, $single_method:ident, $group_method:ident) => {
2612 match $self {
2613 Expr::Concat(children) | Expr::Alt(children) => $iter::Vec(children.$vec_method()),
2614 Expr::Group(child) => $iter::Single(Some(Arc::$group_method(child))),
2615 Expr::Absent(Absent::Repeater(child))
2616 | Expr::Absent(Absent::Stopper(child))
2617 | Expr::LookAround(child, _)
2618 | Expr::AtomicGroup(child)
2619 | Expr::Repeat { child, .. } => $iter::Single(Some(child.$single_method())),
2620 Expr::Conditional {
2621 condition,
2622 true_branch,
2623 false_branch,
2624 } => $iter::Triple {
2625 first: Some(condition.$single_method()),
2626 second: Some(true_branch.$single_method()),
2627 third: Some(false_branch.$single_method()),
2628 },
2629 Expr::Absent(Absent::Expression { absent, exp }) => $iter::Triple {
2630 first: Some(absent.$single_method()),
2631 second: Some(exp.$single_method()),
2632 third: None,
2633 },
2634 Expr::DefineGroup { definitions } => $iter::Single(Some(definitions.$single_method())),
2635 _ if $self.is_leaf_node() => $iter::Empty,
2636 _ => unimplemented!(),
2637 }
2638 };
2639}
2640impl Expr {
2641 /// Parse the regex and return an expression (AST) and a bit set with the indexes of groups
2642 /// that are referenced by backrefs.
2643 pub fn parse_tree(re: &str) -> Result<ExprTree> {
2644 Expr::parse_tree_with_flags(re, RegexOptions::default().compute_flags())
2645 }
2646
2647 /// Parse the regex and return an expression (AST)
2648 /// Flags should be bit based based on flags
2649 pub fn parse_tree_with_flags(re: &str, flags: u32) -> Result<ExprTree> {
2650 Parser::parse_with_flags(re, flags)
2651 }
2652
2653 /// Returns `true` if this expression is a leaf node (has no children).
2654 ///
2655 /// Leaf nodes include literals, assertions, backreferences, and other atomic expressions.
2656 /// Non-leaf nodes include groups, concatenations, alternations, and repetitions.
2657 pub fn is_leaf_node(&self) -> bool {
2658 matches!(
2659 self,
2660 Expr::Empty
2661 | Expr::Any { .. }
2662 | Expr::Assertion(_)
2663 | Expr::GeneralNewline { .. }
2664 | Expr::Literal { .. }
2665 | Expr::Delegate { .. }
2666 | Expr::Backref { .. }
2667 | Expr::BackrefWithRelativeRecursionLevel { .. }
2668 | Expr::KeepOut
2669 | Expr::ContinueFromPreviousMatchEnd
2670 | Expr::BackrefExistsCondition { .. }
2671 | Expr::BacktrackingControlVerb(_)
2672 | Expr::SubroutineCall(_)
2673 | Expr::Absent(Absent::Clear)
2674 // An unresolved AstNode has no separate child Expr to iterate; the resolver
2675 // should have replaced it before analysis, so treat it as a leaf so that
2676 // collection/iteration doesn't panic, and let the analyzer emit the error.
2677 | Expr::AstNode(..),
2678 )
2679 }
2680
2681 /// Returns `true` if any descendant of this expression (not including itself)
2682 /// satisfies the given predicate.
2683 ///
2684 /// This performs an iterative depth-first search using [`children_iter`](Self::children_iter).
2685 pub fn has_descendant(&self, predicate: impl Fn(&Expr) -> bool) -> bool {
2686 let mut stack: Vec<&Expr> = self.children_iter().collect();
2687 while let Some(expr) = stack.pop() {
2688 if predicate(expr) {
2689 return true;
2690 }
2691 stack.extend(expr.children_iter());
2692 }
2693 false
2694 }
2695
2696 /// Returns an iterator over the immediate children of this expression.
2697 ///
2698 /// For leaf nodes, this returns an empty iterator. For non-leaf nodes, it returns
2699 /// references to their immediate children (non-recursive).
2700 pub fn children_iter(&self) -> ExprChildrenIter<'_> {
2701 children_iter_match!(self, ExprChildrenIter, iter, as_ref, as_ref)
2702 }
2703
2704 /// Returns an iterator over the immediate children of this expression for mutable access.
2705 ///
2706 /// For leaf nodes, this returns an empty iterator. For non-leaf nodes, it returns
2707 /// mutable references to their immediate children (non-recursive).
2708 pub fn children_iter_mut(&mut self) -> ExprChildrenIterMut<'_> {
2709 children_iter_match!(self, ExprChildrenIterMut, iter_mut, as_mut, make_mut)
2710 }
2711
2712 /// Convert expression to a regex string in the regex crate's syntax.
2713 ///
2714 /// # Panics
2715 ///
2716 /// Panics for expressions that are hard, i.e. can not be handled by the regex crate.
2717 pub fn to_str(&self, buf: &mut String, precedence: u8) {
2718 match *self {
2719 Expr::Empty => (),
2720 Expr::Any { newline, crlf } => buf.push_str(match (newline, crlf) {
2721 (true, _) => "(?s:.)",
2722 (false, true) => "(?R-s:.)",
2723 (false, false) => ".",
2724 }),
2725 Expr::Literal { ref val, casei } => {
2726 if casei {
2727 buf.push_str("(?i:");
2728 }
2729 push_quoted(buf, val);
2730 if casei {
2731 buf.push(')');
2732 }
2733 }
2734 Expr::Assertion(Assertion::StartText) => buf.push('^'),
2735 Expr::Assertion(Assertion::EndText) => buf.push('$'),
2736 Expr::Assertion(
2737 Assertion::StartLine { crlf: false }
2738 | Assertion::StartLineOniguruma { crlf: false },
2739 ) => buf.push_str("(?m:^)"),
2740 Expr::Assertion(Assertion::EndLine { crlf: false }) => buf.push_str("(?m:$)"),
2741 Expr::Assertion(
2742 Assertion::StartLine { crlf: true } | Assertion::StartLineOniguruma { crlf: true },
2743 ) => buf.push_str("(?Rm:^)"),
2744 Expr::Assertion(Assertion::EndLine { crlf: true }) => buf.push_str("(?Rm:$)"),
2745 Expr::Concat(ref children) => {
2746 if precedence > 1 {
2747 buf.push_str("(?:");
2748 }
2749 for child in children {
2750 child.to_str(buf, 2);
2751 }
2752 if precedence > 1 {
2753 buf.push(')')
2754 }
2755 }
2756 Expr::Alt(_) => {
2757 if precedence > 0 {
2758 buf.push_str("(?:");
2759 }
2760 let mut children = self.children_iter();
2761 if let Some(first) = children.next() {
2762 first.to_str(buf, 1);
2763 for child in children {
2764 buf.push('|');
2765 child.to_str(buf, 1);
2766 }
2767 }
2768 if precedence > 0 {
2769 buf.push(')');
2770 }
2771 }
2772 Expr::Group(ref child) => {
2773 buf.push('(');
2774 child.to_str(buf, 0);
2775 buf.push(')');
2776 }
2777 Expr::Repeat {
2778 ref child,
2779 lo,
2780 hi,
2781 greedy,
2782 } => {
2783 if precedence > 2 {
2784 buf.push_str("(?:");
2785 }
2786 child.to_str(buf, 3);
2787 write_quantifier(buf, lo, hi, greedy);
2788 if precedence > 2 {
2789 buf.push(')');
2790 }
2791 }
2792 Expr::Delegate {
2793 ref inner, casei, ..
2794 } => {
2795 // at the moment, delegate nodes are just atoms
2796 if casei {
2797 buf.push_str("(?i:");
2798 }
2799 buf.push_str(inner);
2800 if casei {
2801 buf.push(')');
2802 }
2803 }
2804 Expr::DefineGroup { .. } => {
2805 // DEFINE groups match nothing - output empty string for delegation
2806 }
2807 _ => panic!("attempting to format hard expr {:?}", self),
2808 }
2809 }
2810}
2811
2812// precondition: ix > 0
2813fn prev_codepoint_ix(s: &str, mut ix: usize) -> usize {
2814 let bytes = s.as_bytes();
2815 loop {
2816 ix -= 1;
2817 // fancy bit magic for ranges 0..0x80 + 0xc0..
2818 if (bytes[ix] as i8) >= -0x40 {
2819 break;
2820 }
2821 }
2822 ix
2823}
2824
2825fn codepoint_len(b: u8) -> usize {
2826 match b {
2827 b if b < 0x80 => 1,
2828 b if b < 0xe0 => 2,
2829 b if b < 0xf0 => 3,
2830 _ => 4,
2831 }
2832}
2833
2834// If this returns false, then there is no possible backref in the re
2835
2836// Both potential implementations are turned off, because we currently
2837// always need to do a deeper analysis because of 1-character
2838// look-behind. If we could call a find_from_pos method of regex::Regex,
2839// it would make sense to bring this back.
2840/*
2841pub fn detect_possible_backref(re: &str) -> bool {
2842 let mut last = b'\x00';
2843 for b in re.as_bytes() {
2844 if b'0' <= *b && *b <= b'9' && last == b'\\' { return true; }
2845 last = *b;
2846 }
2847 false
2848}
2849
2850pub fn detect_possible_backref(re: &str) -> bool {
2851 let mut bytes = re.as_bytes();
2852 loop {
2853 match memchr::memchr(b'\\', &bytes[..bytes.len() - 1]) {
2854 Some(i) => {
2855 bytes = &bytes[i + 1..];
2856 let c = bytes[0];
2857 if b'0' <= c && c <= b'9' { return true; }
2858 }
2859 None => return false
2860 }
2861 }
2862}
2863*/
2864
2865/// The internal module only exists so that the toy example can access internals for debugging and
2866/// experimenting.
2867#[doc(hidden)]
2868pub mod internal {
2869 pub use crate::analyze::{analyze, can_compile_as_anchored, AnalyzeContext, Info};
2870 pub use crate::compile::{compile, CompileOptions};
2871 pub use crate::optimize::optimize;
2872 pub use crate::parse_flags::{
2873 FLAG_CASEI, FLAG_CRLF, FLAG_DOTNL, FLAG_IGNORE_NUMBERED_GROUPS_WHEN_NAMED_GROUPS_EXIST,
2874 FLAG_IGNORE_SPACE, FLAG_MULTI, FLAG_ONIGURUMA_MODE, FLAG_UNICODE,
2875 };
2876 pub use crate::vm::{run_default, run_trace, Insn, Prog, Seek};
2877}
2878
2879#[cfg(test)]
2880mod tests {
2881 use alloc::borrow::Cow;
2882 use alloc::boxed::Box;
2883 use alloc::string::{String, ToString};
2884 use alloc::sync::Arc;
2885 use alloc::vec::Vec;
2886 use alloc::{format, vec};
2887
2888 use crate::parse::{make_group, make_literal};
2889 use crate::{Absent, Expr, Regex, RegexBuilder, RegexImpl, RegexInput};
2890
2891 //use detect_possible_backref;
2892
2893 // tests for to_str
2894
2895 fn to_str(e: Expr) -> String {
2896 let mut s = String::new();
2897 e.to_str(&mut s, 0);
2898 s
2899 }
2900
2901 #[test]
2902 fn to_str_concat_alt() {
2903 let e = Expr::Concat(vec![
2904 Expr::Alt(vec![make_literal("a"), make_literal("b")]),
2905 make_literal("c"),
2906 ]);
2907 assert_eq!(to_str(e), "(?:a|b)c");
2908 }
2909
2910 #[test]
2911 fn to_str_rep_concat() {
2912 let e = Expr::Repeat {
2913 child: Box::new(Expr::Concat(vec![make_literal("a"), make_literal("b")])),
2914 lo: 2,
2915 hi: 3,
2916 greedy: true,
2917 };
2918 assert_eq!(to_str(e), "(?:ab){2,3}");
2919 }
2920
2921 #[test]
2922 fn to_str_group_alt() {
2923 let e = Expr::Group(Arc::new(Expr::Alt(vec![
2924 make_literal("a"),
2925 make_literal("b"),
2926 ])));
2927 assert_eq!(to_str(e), "(a|b)");
2928 }
2929
2930 #[test]
2931 fn as_str_debug() {
2932 let s = r"(a+)b\1";
2933 let regex = Regex::new(s).unwrap();
2934 assert_eq!(s, regex.as_str());
2935 assert_eq!(s, format!("{:?}", regex));
2936 }
2937
2938 #[test]
2939 fn display() {
2940 let s = r"(a+)b\1";
2941 let regex = Regex::new(s).unwrap();
2942 assert_eq!(s, format!("{}", regex));
2943 }
2944
2945 #[test]
2946 fn from_str() {
2947 let s = r"(a+)b\1";
2948 let regex = s.parse::<Regex>().unwrap();
2949 assert_eq!(regex.as_str(), s);
2950 }
2951
2952 #[test]
2953 fn to_str_repeat() {
2954 fn repeat(lo: usize, hi: usize, greedy: bool) -> Expr {
2955 Expr::Repeat {
2956 child: Box::new(make_literal("a")),
2957 lo,
2958 hi,
2959 greedy,
2960 }
2961 }
2962
2963 assert_eq!(to_str(repeat(2, 2, true)), "a{2}");
2964 assert_eq!(to_str(repeat(2, 2, false)), "a{2}?");
2965 assert_eq!(to_str(repeat(2, 3, true)), "a{2,3}");
2966 assert_eq!(to_str(repeat(2, 3, false)), "a{2,3}?");
2967 assert_eq!(to_str(repeat(2, usize::MAX, true)), "a{2,}");
2968 assert_eq!(to_str(repeat(2, usize::MAX, false)), "a{2,}?");
2969 assert_eq!(to_str(repeat(0, 1, true)), "a?");
2970 assert_eq!(to_str(repeat(0, 1, false)), "a??");
2971 assert_eq!(to_str(repeat(0, usize::MAX, true)), "a*");
2972 assert_eq!(to_str(repeat(0, usize::MAX, false)), "a*?");
2973 assert_eq!(to_str(repeat(1, usize::MAX, true)), "a+");
2974 assert_eq!(to_str(repeat(1, usize::MAX, false)), "a+?");
2975 }
2976
2977 #[test]
2978 fn escape() {
2979 // Check that strings that need no quoting are borrowed, and that non-special punctuation
2980 // is not quoted.
2981 match crate::escape("@foo") {
2982 Cow::Borrowed(s) => assert_eq!(s, "@foo"),
2983 _ => panic!("Value should be borrowed."),
2984 }
2985
2986 // Check typical usage.
2987 assert_eq!(crate::escape("fo*o").into_owned(), "fo\\*o");
2988
2989 // Check that multibyte characters are handled correctly.
2990 assert_eq!(crate::escape("fø*ø").into_owned(), "fø\\*ø");
2991 }
2992
2993 #[test]
2994 fn trailing_positive_lookahead_wrap_capture_group_fixup() {
2995 let s = r"a+(?=c)";
2996 let regex = s.parse::<Regex>().unwrap();
2997 assert!(matches!(regex.inner,
2998 RegexImpl::Wrap { explicit_capture_group_0: true, .. }),
2999 "trailing positive lookahead for an otherwise easy pattern should avoid going through the VM");
3000 assert_eq!(s, regex.as_str());
3001 assert_eq!(s, format!("{:?}", regex));
3002 }
3003
3004 #[test]
3005 fn easy_regex() {
3006 let s = r"(a+)b";
3007 let regex = s.parse::<Regex>().unwrap();
3008 assert!(
3009 matches!(regex.inner, RegexImpl::Wrap { explicit_capture_group_0: false, .. }),
3010 "easy pattern should avoid going through the VM, and capture group 0 should be implicit"
3011 );
3012
3013 assert_eq!(s, regex.as_str());
3014 assert_eq!(s, format!("{:?}", regex));
3015 }
3016
3017 #[test]
3018 fn hard_regex() {
3019 let s = r"(a+)(?>c)";
3020 let regex = s.parse::<Regex>().unwrap();
3021 assert!(
3022 matches!(regex.inner, RegexImpl::Fancy { .. }),
3023 "hard regex should be compiled into a VM"
3024 );
3025 assert_eq!(s, regex.as_str());
3026 assert_eq!(s, format!("{:?}", regex));
3027 }
3028
3029 #[test]
3030 fn start_end_text_assertions_can_stay_wrap_without_override_opt_in() {
3031 let regex = Regex::new(r"\Afoo\z").unwrap();
3032 assert!(
3033 matches!(regex.inner, RegexImpl::Wrap { .. }),
3034 r"\A...\z should stay on the wrap path unless input assertion overrides are enabled"
3035 );
3036 }
3037
3038 #[test]
3039 fn start_end_text_assertions_become_fancy_with_override_opt_in() {
3040 let regex = RegexBuilder::new(r"\Afoo\z")
3041 .allow_input_assertion_overrides(true)
3042 .build()
3043 .unwrap();
3044 assert!(
3045 matches!(regex.inner, RegexImpl::Fancy { .. }),
3046 r"\A...\z should use the VM when input assertion overrides are enabled"
3047 );
3048 }
3049
3050 /*
3051 #[test]
3052 fn detect_backref() {
3053 assert_eq!(detect_possible_backref("a0a1a2"), false);
3054 assert_eq!(detect_possible_backref("a0a1\\a2"), false);
3055 assert_eq!(detect_possible_backref("a0a\\1a2"), true);
3056 assert_eq!(detect_possible_backref("a0a1a2\\"), false);
3057 }
3058 */
3059
3060 #[test]
3061 fn test_is_leaf_node_leaf_nodes() {
3062 // Test all leaf node variants
3063 assert!(Expr::Empty.is_leaf_node());
3064 assert!(Expr::Any {
3065 newline: false,
3066 crlf: false
3067 }
3068 .is_leaf_node());
3069 assert!(Expr::Any {
3070 newline: true,
3071 crlf: false
3072 }
3073 .is_leaf_node());
3074 assert!(Expr::Assertion(crate::Assertion::StartText).is_leaf_node());
3075 assert!(Expr::Literal {
3076 val: "test".to_string(),
3077 casei: false
3078 }
3079 .is_leaf_node());
3080 assert!(Expr::Delegate {
3081 inner: "[0-9]".to_string(),
3082 casei: false,
3083 }
3084 .is_leaf_node());
3085 assert!(Expr::Backref {
3086 group: 1,
3087 casei: false
3088 }
3089 .is_leaf_node());
3090 assert!(Expr::BackrefWithRelativeRecursionLevel {
3091 group: 1,
3092 relative_level: -1,
3093 casei: false
3094 }
3095 .is_leaf_node());
3096 assert!(Expr::KeepOut.is_leaf_node());
3097 assert!(Expr::ContinueFromPreviousMatchEnd.is_leaf_node());
3098 assert!(Expr::BackrefExistsCondition {
3099 group: 1,
3100 relative_recursion_level: None
3101 }
3102 .is_leaf_node());
3103 assert!(Expr::BacktrackingControlVerb(crate::BacktrackingControlVerb::Fail).is_leaf_node());
3104 assert!(Expr::SubroutineCall(1).is_leaf_node());
3105
3106 assert!(Expr::Absent(Absent::Clear).is_leaf_node());
3107 }
3108
3109 #[test]
3110 fn test_is_leaf_node_non_leaf_nodes() {
3111 // Test all non-leaf node variants
3112 assert!(!Expr::Concat(vec![make_literal("a")]).is_leaf_node());
3113 assert!(!Expr::Alt(vec![make_literal("a"), make_literal("b")]).is_leaf_node());
3114 assert!(!make_group(make_literal("a")).is_leaf_node());
3115 assert!(
3116 !Expr::LookAround(Box::new(make_literal("a")), crate::LookAround::LookAhead)
3117 .is_leaf_node()
3118 );
3119 assert!(!Expr::Repeat {
3120 child: Box::new(make_literal("a")),
3121 lo: 0,
3122 hi: 1,
3123 greedy: true
3124 }
3125 .is_leaf_node());
3126 assert!(!Expr::AtomicGroup(Box::new(make_literal("a"))).is_leaf_node());
3127 assert!(!Expr::Conditional {
3128 condition: Box::new(Expr::BackrefExistsCondition {
3129 group: 1,
3130 relative_recursion_level: None
3131 }),
3132 true_branch: Box::new(make_literal("a")),
3133 false_branch: Box::new(Expr::Empty)
3134 }
3135 .is_leaf_node());
3136
3137 assert!(!Expr::Absent(Absent::Repeater(Box::new(make_literal("a")))).is_leaf_node());
3138 assert!(!Expr::Absent(Absent::Expression {
3139 absent: Box::new(make_literal("/*")),
3140 exp: Box::new(Expr::Repeat {
3141 child: Box::new(Expr::Any {
3142 newline: true,
3143 crlf: false
3144 }),
3145 lo: 0,
3146 hi: usize::MAX,
3147 greedy: true
3148 })
3149 })
3150 .is_leaf_node());
3151 assert!(!Expr::Absent(Absent::Stopper(Box::new(make_literal("/*")))).is_leaf_node());
3152 }
3153
3154 #[test]
3155 fn test_children_iter_empty() {
3156 // Leaf nodes should return empty iterator
3157 let expr = Expr::Empty;
3158 let mut iter = expr.children_iter();
3159 assert!(iter.next().is_none());
3160
3161 let expr = make_literal("test");
3162 let mut iter = expr.children_iter();
3163 assert!(iter.next().is_none());
3164 }
3165
3166 #[test]
3167 fn test_children_iter_single() {
3168 // Group, LookAround, AtomicGroup, Repeat should return single child
3169 let child = make_literal("a");
3170 let expr = make_group(child.clone());
3171 let children: Vec<_> = expr.children_iter().collect();
3172 assert_eq!(children.len(), 1);
3173
3174 let expr = Expr::Repeat {
3175 child: Box::new(child.clone()),
3176 lo: 0,
3177 hi: 1,
3178 greedy: true,
3179 };
3180 let children: Vec<_> = expr.children_iter().collect();
3181 assert_eq!(children.len(), 1);
3182 }
3183
3184 #[test]
3185 fn test_children_iter_vec() {
3186 // Concat and Alt should return all children
3187 let children_vec = vec![make_literal("a"), make_literal("b"), make_literal("c")];
3188 let expr = Expr::Concat(children_vec.clone());
3189 let children: Vec<_> = expr.children_iter().collect();
3190 assert_eq!(children.len(), 3);
3191
3192 let expr = Expr::Alt(children_vec);
3193 let children: Vec<_> = expr.children_iter().collect();
3194 assert_eq!(children.len(), 3);
3195 }
3196
3197 #[test]
3198 fn test_children_iter_triple() {
3199 // Conditional should return three children
3200 let expr = Expr::Conditional {
3201 condition: Box::new(Expr::BackrefExistsCondition {
3202 group: 1,
3203 relative_recursion_level: None,
3204 }),
3205 true_branch: Box::new(make_literal("a")),
3206 false_branch: Box::new(make_literal("b")),
3207 };
3208 let children: Vec<_> = expr.children_iter().collect();
3209 assert_eq!(children.len(), 3);
3210
3211 // Absent expression should return two children
3212 let expr = Expr::Absent(Absent::Expression {
3213 absent: Box::new(make_literal("/*")),
3214 exp: Box::new(Expr::Repeat {
3215 child: Box::new(Expr::Any {
3216 newline: true,
3217 crlf: false,
3218 }),
3219 lo: 0,
3220 hi: usize::MAX,
3221 greedy: true,
3222 }),
3223 });
3224 let children: Vec<_> = expr.children_iter().collect();
3225 assert_eq!(children.len(), 2);
3226 }
3227
3228 #[test]
3229 fn find_input_raw_honors_anchored_flag_for_wrapped_regex() {
3230 let regex = Regex::new("abc").unwrap();
3231
3232 // Unanchored search finds match at position 1
3233 let input = RegexInput::new("zabc");
3234 assert_eq!(
3235 Some((1, 4)),
3236 regex
3237 .find_input(input)
3238 .unwrap()
3239 .map(|m| (m.start(), m.end()))
3240 );
3241
3242 // Anchored at position 0 returns None (abc doesn't match at pos 0)
3243 let anchored_at_start = RegexInput::new("zabc").anchored(true);
3244 assert!(regex.find_input(anchored_at_start).unwrap().is_none());
3245
3246 // Anchored at position 1 finds match
3247 let anchored_at_match = RegexInput::new("zabc").from_pos(1).anchored(true);
3248 assert_eq!(
3249 regex
3250 .find_input(anchored_at_match)
3251 .unwrap()
3252 .map(|m| (m.start(), m.end())),
3253 Some((1, 4))
3254 );
3255 }
3256}