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