use crate::classicalbacktrack; use crate::emit; use crate::exec; use crate::indexing; use crate::insn::CompiledRegex; use crate::optimizer; use crate::parse; use crate::types::MAX_CAPTURE_GROUPS; use std::iter::FusedIterator; #[cfg(feature = "utf16")] use crate::{ classicalbacktrack::MatchAttempter, indexing::{InputIndexer, Ucs2Input, Utf16Input}, }; #[cfg(feature = "backend-pikevm")] use crate::pikevm; use crate::util::to_char_sat; use std::sync::OnceLock; use core::{fmt, str::FromStr}; #[cfg(feature = "std")] #[cfg(not(feature = "std"))] use { alloc::{string::String, vec::Vec}, hashbrown::{HashMap, hash_map::Iter}, }; pub use parse::Error; /// Flags used to control regex parsing. /// The default flags are case-sensitive, not-multiline, and optimizing. #[derive(Debug, Copy, Clone, Default)] pub struct Flags { /// If set, make the regex case-insensitive. /// Equivalent to the 'i' flag in JavaScript. pub icase: bool, /// If set, ^ and $ match at line separators, not just the input boundaries. /// Equivalent to the 'm' flag in JavaScript. pub multiline: bool, /// If set, . matches at line separators as well as any other character. /// Equivalent to the 's' flag in JavaScript. pub dot_all: bool, /// If set, disable regex IR passes. pub no_opt: bool, /// If set, the regex is interpreted as a Unicode regex. /// Equivalent to the 'u' flag in JavaScript. pub unicode: bool, /// If set, the regex is interpreted as a UnicodeSets regex. /// Equivalent to the 'v' flag in JavaScript. pub unicode_sets: bool, } impl Flags { /// Construct a Flags from a Unicode codepoints iterator, using JavaScript field names. /// 'i' means to ignore case, 'm' means multiline, 'u' means unicode. /// Note the 'g' flag implies a stateful regex and is not supported. /// Other flags are not implemented and are ignored. #[inline] pub fn new>(chars: T) -> Self { let mut result = Self::default(); for c in chars { match to_char_sat(c) { 'm' => { result.multiline = true; } 'i' => { result.icase = true; } 's' => { result.dot_all = true; } 'u' => { result.unicode = true; } 'v' => { result.unicode_sets = true; } _ => { // Silently skip unsupported flags. } } } result } } impl From<&str> for Flags { /// Construct a Flags from a string, using JavaScript field names. /// /// See also: [`Flags::new`]. #[inline] fn from(s: &str) -> Self { Self::new(s.chars().map(u32::from)) } } impl fmt::Display for Flags { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.multiline { f.write_str("m")?; } if self.icase { f.write_str("i")?; } if self.dot_all { f.write_str("s")?; } if self.unicode { f.write_str("u")?; } Ok(()) } } /// Range is used to express the extent of a match, as indexes into the input /// string. pub type Range = core::ops::Range; /// An iterator type which yields `Match`es found in a string. pub type Matches<'r, 't> = exec::Matches>; /// An iterator type which yields `Match`es found in a string, supporting ASCII /// only. pub type AsciiMatches<'r, 't> = exec::Matches>; /// A Match represents a portion of a string which was found to match a Regex. #[derive(Debug, Clone)] pub struct Match { /// The total range of the match. Note this may be empty, if the regex /// matched an empty string. pub range: Range, /// The list of captures. This has length equal to the number of capturing /// groups in the regex. For each capture, if the value is None, that group /// did not match (for example, it was in a not-taken branch of an /// alternation). If the value is Some, the group did match with the /// enclosed range. pub captures: Vec>, // A list of capture group names. This is either: // - Empty, if there were no named capture groups. // - A list of names with length `captures.len()`, corresponding to the // capture group names in order. Groups without names have an empty string. pub(crate) group_names: Box<[Box]>, } impl Match { /// Access a group by index, using the convention of Python's group() /// function. Index 0 is the total match, index 1 is the first capture /// group. #[inline] pub fn group(&self, idx: usize) -> Option { if idx == 0 { Some(self.range.clone()) } else if idx <= self.captures.len() { self.captures[idx - 1].clone() } else { None } } /// Access a named group by name. #[inline] pub fn named_group(&self, name: &str) -> Option { // Empty strings are used as sentinels to indicate unnamed group. if name.is_empty() { return None; } let pos = self.group_names.iter().position(|s| s.as_ref() == name)?; self.captures[pos].clone() } /// Return an iterator over the named groups of a Match. #[inline] pub fn named_groups(&self) -> NamedGroups<'_> { NamedGroups::new(self) } /// Returns the range over the starting and ending byte offsets of the match in the haystack. /// /// This is a convenience function to work around /// the fact that Range does not support Copy. #[inline] pub fn range(&self) -> Range { self.range.clone() } /// Returns the starting byte offset of the match in the haystack. #[inline] pub fn start(&self) -> usize { self.range.start } /// Returns the ending byte offset of the match in the haystack. #[inline] pub fn end(&self) -> usize { self.range.end } /// Returns the matched text as a string slice. /// /// # Examples /// /// ```rust /// use regress::Regex; /// /// let re = Regex::new(r"\d+").unwrap(); /// let text = "Price: $123"; /// let m = re.find(text).unwrap(); /// assert_eq!(m.as_str(text), "123"); /// ``` #[inline] pub fn as_str<'t>(&self, text: &'t str) -> &'t str { &text[self.range()] } /// Return an iterator over a Match. The first returned value is the total /// match, and subsequent values represent the capture groups. #[inline] pub fn groups(&self) -> Groups<'_> { Groups::new(self) } } /// An iterator over the capture groups of a [`Match`] /// /// This struct is created by the [`groups`] method on [`Match`]. /// /// [`Match`]: ../struct.Match.html /// [`groups`]: ../struct.Match.html#method.groups #[derive(Clone)] pub struct Groups<'m> { mat: &'m Match, // The next group index to return, where 0 references the total match. next_group_idx: usize, // The maximum group index to return, with a +1 for the implicit total match. // For example, in a regex with 1 capture group, this will be 2. max: usize, } impl<'m> Groups<'m> { #[inline] fn new(mat: &'m Match) -> Self { Self { mat, next_group_idx: 0, max: mat.captures.len() + 1, // +1 for the total match } } } impl Iterator for Groups<'_> { type Item = Option; #[inline] fn next(&mut self) -> Option { let i = self.next_group_idx; if i < self.max { self.next_group_idx += 1; Some(self.mat.group(i)) } else { None } } fn size_hint(&self) -> (usize, Option) { let size = self.max.saturating_sub(self.next_group_idx); (size, Some(size)) } } impl<'m> ExactSizeIterator for Groups<'m> {} impl<'m> FusedIterator for Groups<'m> {} /// An iterator over the named capture groups of a [`Match`] /// /// This struct is created by the [`named_groups`] method on [`Match`]. /// /// [`Match`]: ../struct.Match.html /// [`named_groups`]: ../struct.Match.html#method.named_groups #[derive(Clone)] pub struct NamedGroups<'m> { mat: &'m Match, // The next group name index to return. // Note unlike `Groups` this does NOT include the implicit total match. // That is, group 0 is the first capture group, NOT the total match. next_group_idx: usize, } impl<'m> NamedGroups<'m> { #[inline] fn new(mat: &'m Match) -> Self { Self { mat, next_group_idx: 0, } } } impl<'m> Iterator for NamedGroups<'m> { type Item = (&'m str, Option); #[inline] fn next(&mut self) -> Option { // Increment next_group_idx until we find a non-empty name that we haven't seen yet. debug_assert!(self.next_group_idx <= self.mat.group_names.len()); let end = self.mat.group_names.len(); loop { let mut idx = self.next_group_idx; // Skip empty names while idx < end && self.mat.group_names[idx].is_empty() { idx += 1; } if idx == end { return None; } let name = self.mat.group_names[idx].as_ref(); // Check if we've already returned this name (by looking backwards) let already_seen = self.mat.group_names[..idx] .iter() .any(|n| n.as_ref() == name); if already_seen { // Skip this duplicate and continue to next self.next_group_idx = idx + 1; continue; } // This is the first occurrence of this name. Find the best range value. // Prefer a Some value over None when there are duplicate names. let mut best_range = self.mat.captures[idx].clone(); for check_idx in (idx + 1)..end { if self.mat.group_names[check_idx].as_ref() == name { // Found a duplicate name. Prefer a Some value over None. if best_range.is_none() && self.mat.captures[check_idx].is_some() { best_range = self.mat.captures[check_idx].clone(); break; // Stop once we find a Some value } } } self.next_group_idx = idx + 1; return Some((name, best_range)); } } fn size_hint(&self) -> (usize, Option) { let size = self.mat.group_names[self.next_group_idx..] .iter() .filter(|s| !s.is_empty()) .count(); (size, Some(size)) } } impl<'m> ExactSizeIterator for NamedGroups<'m> {} impl<'m> FusedIterator for NamedGroups<'m> {} /// A Regex is the compiled version of a pattern. #[derive(Debug, Clone)] pub struct Regex { cr: CompiledRegex, // 模式源码码点序列:str 路径编译一次消费,留存供单元向 IR 惰性重解析。 // 仅 `From` 构造无源码(None)。 #[cfg_attr(not(feature = "utf16"), allow(dead_code))] pattern: Option>, // 单元向编译产物(未跑字节字面量 pass),首次单元输入入口时惰性物化。 // 两路缓存:码点向入口(`find_from_utf16`)不拆非 BMP 码点字面量; // 码元向入口(`find_from_ucs2`)拆为 surrogate 对。两 IR 不得共用。 #[cfg_attr(not(feature = "utf16"), allow(dead_code))] units_cr: OnceLock, #[cfg_attr(not(feature = "utf16"), allow(dead_code))] units_cr_ucs2: OnceLock, } impl From for Regex { fn from(cr: CompiledRegex) -> Self { Self { cr, pattern: None, units_cr: OnceLock::new(), units_cr_ucs2: OnceLock::new(), } } } impl Regex { /// Construct a regex by parsing `pattern` using the default flags. /// An Error may be returned if the syntax is invalid. /// Note that this is rather expensive; prefer to cache a Regex which is /// intended to be used more than once. #[inline] pub fn new(pattern: &str) -> Result { Self::with_flags(pattern, Flags::default()) } /// Construct a regex by parsing `pattern` with `flags`. /// An Error may be returned if the syntax is invalid. // /// Note it is preferable to cache a Regex which is intended to be used more /// than once, as the parse may be expensive. For example: #[inline] pub fn with_flags(pattern: &str, flags: F) -> Result where F: Into, { Self::from_unicode(pattern.chars().map(u32::from), flags) } /// Construct a regex by parsing `pattern` with `flags`, where /// `pattern` is an iterator of `u32` Unicode codepoints. /// An Error may be returned if the syntax is invalid. /// This allows parsing regular expressions from exotic strings in /// other encodings, such as UTF-16 or UTF-32. pub fn from_unicode(pattern: I, flags: F) -> Result where I: Iterator + Clone, F: Into, { let flags = flags.into(); // 先收集源码:str 路径编译照旧消费一次,留存的码点序列供单元向 IR 惰性重解析。 let pattern: Vec = pattern.collect(); let mut ire = parse::try_parse(pattern.iter().copied(), flags)?; if !flags.no_opt { optimizer::optimize(&mut ire); } let cr = emit::emit(&ire); Ok(Regex { cr, pattern: Some(pattern), units_cr: OnceLock::new(), units_cr_ucs2: OnceLock::new(), }) } /// 编译本正则所用的 flags(u/v 标志决定码点语义,供调用方选择 /// 码点向或码元向的单元输入入口)。 pub fn flags(&self) -> &Flags { &self.cr.flags } /// 单元向编译产物(码点向):首次使用时按同源码同 flags 重解析、关字节 /// 字面量 pass 重新优化并 emit,得到可在 UTF-16 单元输入上执行的 IR。 /// 非 BMP 码点字面量保持单码点元素(码点向输入把代理对视作单码点)。 /// 无源码构造(`From`)无重解析来源,回退复用既有产物。 #[cfg(feature = "utf16")] fn units_cr(&self) -> &CompiledRegex { self.units_cr.get_or_init(|| self.compile_units(false)) } /// 单元向编译产物(码元向):同 `units_cr`,但非 BMP 码点字面量拆为 /// 高+低 surrogate 码元,使码元向输入(UCS-2,每单元独立元素)可命中。 #[cfg(feature = "utf16")] fn units_cr_ucs2(&self) -> &CompiledRegex { self.units_cr_ucs2.get_or_init(|| self.compile_units(true)) } /// 两路单元向缓存共用的惰性编译体:`decompose` 决定非 BMP 码点字面量 /// 是否拆为 surrogate 对。 #[cfg(feature = "utf16")] fn compile_units(&self, decompose: bool) -> CompiledRegex { let Some(pattern) = &self.pattern else { return self.cr.clone(); }; // 同源码同 flags 在构造期已成功解析编译,重解析不可能失败。 let mut ire = parse::try_parse(pattern.iter().copied(), self.cr.flags).expect("构造期已成功解析的模式重解析失败"); if !self.cr.flags.no_opt { optimizer::optimize_with_byte_literals(&mut ire, false, decompose); } emit::emit(&ire) } /// Searches `text` to find the first match. #[inline] pub fn find(&self, text: &str) -> Option { self.find_iter(text).next() } /// Searches `text`, returning an iterator over non-overlapping matches. /// Note that the resulting Iterator borrows both the regex `'r` and the /// input string as `'t`. #[inline] pub fn find_iter<'r, 't>(&'r self, text: &'t str) -> Matches<'r, 't> { self.find_from(text, 0) } /// Returns an iterator for matches found in 'text' starting at byte index /// `start`. Note this may be different from passing a sliced `text` in /// the case of lookbehind assertions. /// Example: /// /// ```rust /// use regress::Regex; /// let text = "xyxy"; /// let re = Regex::new(r"(?<=x)y").unwrap(); /// let t1 = re.find(&text[1..]).unwrap().range(); /// assert!(t1 == (2..3)); /// let t2 = re.find_from(text, 1).next().unwrap().range(); /// assert!(t2 == (1..2)); /// ``` #[inline] pub fn find_from<'r, 't>(&'r self, text: &'t str, start: usize) -> Matches<'r, 't> { backends::find(self, text, start) } /// Searches `text` to find the first match. /// The input text is expected to be ascii-only: only ASCII case-folding is /// supported. #[inline] pub fn find_ascii(&self, text: &str) -> Option { self.find_iter_ascii(text).next() } /// Searches `text`, returning an iterator over non-overlapping matches. /// The input text is expected to be ascii-only: only ASCII case-folding is /// supported. #[inline] pub fn find_iter_ascii<'r, 't>(&'r self, text: &'t str) -> AsciiMatches<'r, 't> { self.find_from_ascii(text, 0) } /// Returns an iterator for matches found in 'text' starting at byte index /// `start`. #[inline] pub fn find_from_ascii<'r, 't>(&'r self, text: &'t str, start: usize) -> AsciiMatches<'r, 't> { backends::find(self, text, start) } /// Returns an iterator for matches found in 'text' starting at index `start`. #[cfg(feature = "utf16")] pub fn find_from_utf16<'r, 't>( &'r self, text: &'t [u16], start: usize, ) -> exec::Matches>> { // 码点向 IR 无字节指令,UTF-16 单元输入可执行;代理对按单码点匹配。 let cr = self.units_cr(); let input = Utf16Input::new(text, cr.flags.unicode); exec::Matches::new( super::classicalbacktrack::BacktrackExecutor::new( input, MatchAttempter::new(cr, input.left_end()), ), start, ) } /// Returns an iterator for matches found in 'text' starting at index `start`. #[cfg(feature = "utf16")] pub fn find_from_ucs2<'r, 't>( &'r self, text: &'t [u16], start: usize, ) -> exec::Matches>> { // 码元向 IR:UCS-2 同为单元输入(str IR 的字节指令在单元索引器上 // 不可执行),且非 BMP 码点字面量须拆为 surrogate 对以命中码元。 let cr = self.units_cr_ucs2(); let input = Ucs2Input::new(text, cr.flags.unicode); exec::Matches::new( super::classicalbacktrack::BacktrackExecutor::new( input, MatchAttempter::new(cr, input.left_end()), ), start, ) } /// Replaces the first match of the regex in `text` with the replacement string. /// /// The replacement string may contain capture group references in the form `$1`, `$2`, etc., /// where `$1` refers to the first capture group, `$2` to the second, and so on. /// `$0` refers to the entire match. Use `$$` to insert a literal `$`. /// /// If no match is found, the original text is returned unchanged. /// /// # Examples /// /// ```rust /// use regress::Regex; /// /// let re = Regex::new(r"(\w+)\s+(\w+)").unwrap(); /// let result = re.replace("hello world", "$2 $1"); /// assert_eq!(result, "world hello"); /// /// let re = Regex::new(r"(\d{4})-(\d{2})-(\d{2})").unwrap(); /// let result = re.replace("2023-12-25", "$2/$3/$1"); /// assert_eq!(result, "12/25/2023"); /// ``` pub fn replace(&self, text: &str, replacement: &str) -> String { match self.find(text) { Some(m) => { let mut result = String::with_capacity(text.len()); result.push_str(&text[..m.start()]); self.expand_replacement(&m, text, replacement, &mut result); result.push_str(&text[m.end()..]); result } None => text.to_string(), } } /// Replaces all matches of the regex in `text` with the replacement string. /// /// The replacement string may contain capture group references in the form `$1`, `$2`, etc., /// where `$1` refers to the first capture group, `$2` to the second, and so on. /// `$0` refers to the entire match. Use `$$` to insert a literal `$`. /// /// # Examples /// /// ```rust /// use regress::Regex; /// /// let re = Regex::new(r"(\w+)\s+(\w+)").unwrap(); /// let result = re.replace_all("hello world foo bar", "$2-$1"); /// assert_eq!(result, "world-hello bar-foo"); /// /// let re = Regex::new(r"\b(\w)(\w+)").unwrap(); /// let result = re.replace_all("hello world", "$1.$2"); /// assert_eq!(result, "h.ello w.orld"); /// ``` pub fn replace_all(&self, text: &str, replacement: &str) -> String { let mut result = String::with_capacity(text.len()); let mut last_end = 0; for m in self.find_iter(text) { result.push_str(&text[last_end..m.start()]); self.expand_replacement(&m, text, replacement, &mut result); last_end = m.end(); } result.push_str(&text[last_end..]); result } /// Replaces the first match of the regex in `text` using a closure. /// /// The closure receives a `&Match` and should return the replacement string. /// This is useful for dynamic replacements that depend on the match details. /// /// If no match is found, the original text is returned unchanged. /// /// # Examples /// /// ```rust /// use regress::Regex; /// /// let re = Regex::new(r"\d+").unwrap(); /// let text = "Price: $123"; /// let result = re.replace_with(text, |m| { /// let num: i32 = m.as_str(text).parse().unwrap(); /// format!("{}", num * 2) /// }); /// assert_eq!(result, "Price: $246"); /// ``` pub fn replace_with(&self, text: &str, replacement: F) -> String where F: FnOnce(&Match) -> String, { match self.find(text) { Some(m) => { let mut result = String::with_capacity(text.len()); result.push_str(&text[..m.start()]); result.push_str(&replacement(&m)); result.push_str(&text[m.end()..]); result } None => text.to_string(), } } /// Replaces all matches of the regex in `text` using a closure. /// /// The closure receives a `&Match` and should return the replacement string. /// This is useful for dynamic replacements that depend on the match details. /// /// # Examples /// /// ```rust /// use regress::Regex; /// /// let re = Regex::new(r"\d+").unwrap(); /// let text = "Items: 5, 10, 15"; /// let result = re.replace_all_with(text, |m| { /// let num: i32 = m.as_str(text).parse().unwrap(); /// format!("[{}]", num * 10) /// }); /// assert_eq!(result, "Items: [50], [100], [150]"); /// ``` pub fn replace_all_with(&self, text: &str, replacement: F) -> String where F: Fn(&Match) -> String, { let mut result = String::with_capacity(text.len()); let mut last_end = 0; for m in self.find_iter(text) { result.push_str(&text[last_end..m.start()]); result.push_str(&replacement(&m)); last_end = m.end(); } result.push_str(&text[last_end..]); result } /// Helper method to expand replacement strings with capture group substitutions. fn expand_replacement(&self, m: &Match, text: &str, replacement: &str, output: &mut String) { let mut chars = replacement.chars().peekable(); while let Some(ch) = chars.next() { if ch == '$' { match chars.peek() { Some('$') => { // $$ -> literal $ chars.next(); output.push('$'); } Some(&digit) if digit.is_ascii_digit() => { // Parse the group number let mut group_num = 0; while let Some(&digit) = chars.peek() { if digit.is_ascii_digit() { chars.next(); group_num = group_num * 10 + (digit as u32 - '0' as u32) as usize; // Limit to reasonable group numbers to avoid overflow if group_num > MAX_CAPTURE_GROUPS { break; } } else { break; } } // Get the matched text for this group if let Some(range) = m.group(group_num) { output.push_str(&text[range]); } // If group doesn't exist or didn't match, add nothing } Some('{') => { // Handle ${name} syntax for named groups chars.next(); // consume '{' let mut name = String::new(); let mut found_closing_brace = false; for ch in chars.by_ref() { if ch == '}' { found_closing_brace = true; break; } name.push(ch); } if found_closing_brace { if let Some(range) = m.named_group(&name) { output.push_str(&text[range]); } } else { // Malformed ${...}, treat as literal output.push_str("${"); output.push_str(&name); } } _ => { // Just a $ at end or followed by non-digit output.push('$'); } } } else { output.push(ch); } } } } impl FromStr for Regex { type Err = Error; /// Attempts to parse a string into a regular expression #[inline] fn from_str(s: &str) -> Result { Self::new(s) } } // Pattern trait implementation for str::find, str::contains, etc. #[cfg(feature = "pattern")] mod pattern_impl { use super::*; use core::str::pattern::{Pattern, ReverseSearcher, SearchStep, Searcher}; /// A searcher for a regex pattern. pub struct RegexSearcher<'r, 't> { haystack: &'t str, regex: &'r Regex, current_pos: usize, done: bool, // For reverse searching reverse_pos: usize, reverse_done: bool, } impl<'r, 't> RegexSearcher<'r, 't> { fn new(regex: &'r Regex, haystack: &'t str) -> Self { Self { haystack, regex, current_pos: 0, done: false, reverse_pos: haystack.len(), reverse_done: false, } } fn find_last_match_before(&self, pos: usize) -> Option { // Find all matches up to the given position and return the last one let mut last_match = None; for m in self.regex.find_from(self.haystack, 0) { if m.end() <= pos { last_match = Some(m); } else { break; } } last_match } } unsafe impl<'r, 't> Searcher<'t> for RegexSearcher<'r, 't> { fn haystack(&self) -> &'t str { self.haystack } fn next(&mut self) -> SearchStep { if self.done { return SearchStep::Done; } // Try to find the next match starting from current position if let Some(m) = self.regex.find_from(self.haystack, self.current_pos).next() { let match_start = m.start(); let match_end = m.end(); // Handle any gap between current position and match start if self.current_pos < match_start { let reject_end = match_start; let reject_start = self.current_pos; self.current_pos = match_start; return SearchStep::Reject(reject_start, reject_end); } // Return the match self.current_pos = match_end; // Handle zero-width matches to avoid infinite loops if match_start == match_end { // For zero-width matches, we need to advance at least one byte // to avoid infinite loops if match_end < self.haystack.len() { // Find the next character boundary let mut next_pos = match_end + 1; while next_pos < self.haystack.len() && !self.haystack.is_char_boundary(next_pos) { next_pos += 1; } self.current_pos = next_pos; } else { // We're at the end of the string self.done = true; } } SearchStep::Match(match_start, match_end) } else { // No more matches, reject remaining text if any if self.current_pos < self.haystack.len() { let reject_start = self.current_pos; let reject_end = self.haystack.len(); self.current_pos = self.haystack.len(); self.done = true; SearchStep::Reject(reject_start, reject_end) } else { self.done = true; SearchStep::Done } } } } unsafe impl<'r, 't> ReverseSearcher<'t> for RegexSearcher<'r, 't> { fn next_back(&mut self) -> SearchStep { if self.reverse_done { return SearchStep::Done; } // Try to find the last match before current reverse position if let Some(m) = self.find_last_match_before(self.reverse_pos) { let match_start = m.start(); let match_end = m.end(); // Handle any gap between match end and current reverse position if match_end < self.reverse_pos { let reject_start = match_end; let reject_end = self.reverse_pos; self.reverse_pos = match_end; return SearchStep::Reject(reject_start, reject_end); } // Return the match self.reverse_pos = match_start; // Handle zero-width matches if match_start == match_end { // For zero-width matches, move back by one character if match_start > 0 { let mut prev_pos = match_start - 1; while prev_pos > 0 && !self.haystack.is_char_boundary(prev_pos) { prev_pos -= 1; } self.reverse_pos = prev_pos; } else { // We're at the beginning of the string self.reverse_done = true; } } SearchStep::Match(match_start, match_end) } else { // No more matches, reject remaining text if any if self.reverse_pos > 0 { let reject_start = 0; let reject_end = self.reverse_pos; self.reverse_pos = 0; self.reverse_done = true; SearchStep::Reject(reject_start, reject_end) } else { self.reverse_done = true; SearchStep::Done } } } } impl<'r> Pattern for &'r Regex { type Searcher<'a> = RegexSearcher<'r, 'a>; fn into_searcher(self, haystack: &str) -> Self::Searcher<'_> { RegexSearcher::new(self, haystack) } } } #[cfg(feature = "pattern")] pub use pattern_impl::*; // Support for using regress with different regex backends. // Currently there is only the classical backtracking, and PikeVM. #[doc(hidden)] pub mod backends { use super::Regex; use super::exec; use super::indexing; pub use crate::emit::emit; pub use crate::optimizer::{optimize, optimize_with_byte_literals}; pub use crate::parse::try_parse; /// An Executor using the classical backtracking algorithm. pub type BacktrackExecutor<'r, 't> = super::classicalbacktrack::BacktrackExecutor<'r, indexing::Utf8Input<'t>>; /// A Executor using the PikeVM executor. #[cfg(feature = "backend-pikevm")] pub type PikeVMExecutor<'r, 't> = super::pikevm::PikeVMExecutor<'r, indexing::Utf8Input<'t>>; /// An alias type to the default Executor. pub type DefaultExecutor<'r, 't> = BacktrackExecutor<'r, 't>; /// An alias type to the default executor's ASCII form. pub type DefaultAsciiExecutor<'r, 't> = as exec::Executor<'r, 't>>::AsAscii; /// Searches `text`, returning an iterator over non-overlapping matches. pub fn find<'r, 't, Executor: exec::Executor<'r, 't>>( re: &'r Regex, text: &'t str, start: usize, ) -> exec::Matches { exec::Matches::new(Executor::new(&re.cr, text), start) } /// Searches `text`, returning an iterator over non-overlapping matches. /// This is a convenience method to avoid E0223. pub fn find_ascii<'r, 't, Executor: exec::Executor<'r, 't>>( re: &'r Regex, text: &'t str, start: usize, ) -> exec::Matches { find::(re, text, start) } } /// Escapes all special regex characters in a string to make it a literal match. /// /// This function takes a string and returns a new string with all special /// regex characters escaped with backslashes, so the resulting string can be /// used as a literal pattern in a regular expression. /// /// # Example /// /// ``` /// use regress::escape; /// /// let escaped = escape("Hello. How are you?"); /// assert_eq!(escaped, "Hello\\. How are you\\?"); /// /// let escaped = escape("$100 + tax (15%)"); /// assert_eq!(escaped, "\\$100 \\+ tax \\(15%\\)"); /// ``` pub fn escape(text: &str) -> String { let mut result = String::with_capacity(text.len()); for c in text.chars() { match c { // Characters that have special meaning in regex and need escaping '\\' | '^' | '$' | '.' | '|' | '?' | '*' | '+' | '(' | ')' | '[' | ']' | '{' | '}' => { result.push('\\'); result.push(c); } // All other characters are literal _ => result.push(c), } } result } #[cfg(all(test, feature = "utf16"))] mod utf16_tests { use super::*; use crate::insn::{Insn, StartPredicate}; /// 单元序列解码为码点序列:代理对合并为单码点,孤立 surrogate 保留单元值。 fn decode_units(units: &[u16]) -> Vec { let mut out = Vec::with_capacity(units.len()); let mut i = 0; while i < units.len() { let u = units[i]; // 高 surrogate 后随低 surrogate 时合并为代理对码点。 if (0xD800..0xDC00).contains(&u) && i + 1 < units.len() { let lo = units[i + 1]; if (0xDC00..0xE000).contains(&lo) { out.push(0x10000 + ((u as u32 - 0xD800) << 10) + (lo as u32 - 0xDC00)); i += 2; continue; } } out.push(u as u32); i += 1; } out } fn to_units(text: &str) -> Vec { let mut out = Vec::with_capacity(text.len()); for c in text.chars() { let mut buf = [0u16; 2]; out.extend_from_slice(c.encode_utf16(&mut buf)); } out } /// 单元下标 → 码点下标映射:代理对的两个单元同指一个码点。 fn unit_to_cp_index(units: &[u16]) -> Vec { let mut map = Vec::with_capacity(units.len()); let mut cp = 0usize; let mut i = 0; while i < units.len() { let u = units[i]; if (0xD800..0xDC00).contains(&u) && i + 1 < units.len() && (0xDC00..0xE000).contains(&units[i + 1]) { map.push(cp); map.push(cp); cp += 1; i += 2; } else { map.push(cp); cp += 1; i += 1; } } map } /// 收集全部匹配(主匹配 + 捕获组,均码点序列)。 /// 两种输入形态的偏移口径(字节 / 单元)经码点归一后直接可比。 type MatchCps = (Vec, Vec>>); fn str_matches(re: &Regex, text: &str) -> Vec { let decode = |s: &str| s.chars().map(u32::from).collect::>(); re.find_iter(text) .map(|m| { let caps = m .captures .iter() .map(|c| c.as_ref().map(|r| decode(&text[r.clone()]))) .collect(); (decode(m.as_str(text)), caps) }) .collect() } fn utf16_matches(re: &Regex, units: &[u16]) -> Vec { let cps = decode_units(units); let idx = unit_to_cp_index(units); // 空匹配不占码点(含尾部位);非空匹配经单元→码点下标映射切片。 let cp_slice = |r: &core::ops::Range| -> Vec { if r.start == r.end { return Vec::new(); } cps[idx[r.start]..(idx[r.end - 1] + 1)].to_vec() }; re.find_from_utf16(units, 0) .map(|m| { let caps = m .captures .iter() .map(|c| c.as_ref().map(|r| cp_slice(&r))) .collect(); (cp_slice(&m.range()), caps) }) .collect() } fn is_byte_insn(i: &Insn) -> bool { matches!( i, Insn::ByteSet2(_) | Insn::ByteSet3(_) | Insn::ByteSet4(_) | Insn::ByteSeq1(_) | Insn::ByteSeq2(_) | Insn::ByteSeq3(_) | Insn::ByteSeq4(_) | Insn::ByteSeq5(_) | Insn::ByteSeq6(_) | Insn::ByteSeq7(_) | Insn::ByteSeq8(_) | Insn::ByteSeq9(_) | Insn::ByteSeq10(_) | Insn::ByteSeq11(_) | Insn::ByteSeq12(_) | Insn::ByteSeq13(_) | Insn::ByteSeq14(_) | Insn::ByteSeq15(_) | Insn::ByteSeq16(_) ) } /// well-formed 语料:units IR 与 str IR 匹配结果逐一相等。 #[test] fn units_ir_matches_equal_str_path_on_well_formed() { let cases = [ (r"\d+", "abc123x456"), (r"a[bc]d", "xabcdyabd"), (r"(f+)(o+)(r*)", "foo of foo"), (r"\w+\s+\w+", "héllo wörld"), (r"x*", "abab"), (r"(a)|(b)", "abab"), (r"nope", "nothing here"), ]; for (pattern, text) in cases { let re = Regex::new(pattern).unwrap(); let units = to_units(text); assert_eq!(str_matches(&re, text), utf16_matches(&re, &units), "pattern {pattern}"); } } /// 孤立 surrogate 单元在 \p{General_Category=Surrogate} 下命中, /// 普通字符不命中,代理对(单码点)不命中。 #[test] fn units_ir_matches_lone_surrogate_property() { let re = Regex::with_flags(r"\p{General_Category=Surrogate}", "u").unwrap(); let m = re.find_from_utf16(&[0xD800], 0).next(); assert!(m.is_some(), "孤立 surrogate 单元应命中 GC=Surrogate"); assert_eq!(m.unwrap().range(), 0..1); assert!(re.find_from_utf16(&[0x41], 0).next().is_none(), "A 不应命中"); // 代理对是单码点,不是两个孤立 surrogate。 assert!(re.find_from_utf16(&[0xD834, 0xDE00], 0).next().is_none()); } /// 代理对单元序列按单码点匹配,与 str 路径逐位等价。 #[test] fn units_ir_surrogate_pair_equals_str_path() { let cp: u32 = 0x1D11E; let re = Regex::from_unicode([cp].iter().copied(), "").unwrap(); let ch = char::from_u32(cp).unwrap(); let text: String = [ch, 'a', 'b', 'c'].iter().copied().collect(); let m_str = re.find(&text).expect("str 路径应命中"); assert_eq!(m_str.range(), 0..4, "str 路径命中 1 个码点(4 字节)"); let units = to_units(&text); let m_u16 = re.find_from_utf16(&units, 0).next().expect("单元路径应命中"); assert_eq!(m_u16.range(), 0..2, "单元路径命中 1 个码点(2 个单元)"); assert_eq!(decode_units(&units[m_u16.range()]), vec![cp]); assert_eq!(str_matches(&re, &text), utf16_matches(&re, &units)); } /// 字节字面量 pass 的运行时门控:str IR 含字节指令与字节形起始谓词, /// units IR 两者皆无;well-formed 输入上两 IR 匹配结果一致。 #[test] fn byte_literal_pass_gated_per_ir() { let re = Regex::new("abcdef").unwrap(); assert!( re.cr.insns.iter().any(is_byte_insn), "str IR 应含字节指令(字节字面量 pass 生效)" ); assert!( !matches!(re.cr.start_pred, StartPredicate::Arbitrary), "str 起始谓词应为字节形" ); let units = re.units_cr(); assert!(!units.insns.iter().any(is_byte_insn), "units IR 不得含字节指令"); assert!( matches!(units.start_pred, StartPredicate::Arbitrary), "units 起始谓词应为 Arbitrary" ); let text = "xxabcdefyy"; assert_eq!(str_matches(&re, text), utf16_matches(&re, &to_units(text))); } /// no_opt 标志贯穿单元向惰性编译:两 IR 均不跑优化 pass,指令流同长且匹配一致。 #[test] fn no_opt_flag_respected_by_units_ir() { let flags = Flags { no_opt: true, ..Default::default() }; let re = Regex::with_flags("abcdef", flags).unwrap(); assert!(!re.cr.insns.iter().any(is_byte_insn), "no_opt 下 str IR 不含字节指令"); let units = re.units_cr(); assert!(!units.insns.iter().any(is_byte_insn)); assert_eq!(re.cr.insns.len(), units.insns.len(), "no_opt 下两 IR 指令流同长"); let text = "xxabcdefyy"; assert_eq!(str_matches(&re, text), utf16_matches(&re, &to_units(text))); } }