1use super::{
2 CaptureGroup, CompiledRegex, EngineError, EngineFlags, EngineKind, EngineResult, Match,
3 RegexEngine,
4};
5
6pub struct FancyRegexEngine;
7
8impl RegexEngine for FancyRegexEngine {
9 fn kind(&self) -> EngineKind {
10 EngineKind::FancyRegex
11 }
12
13 fn compile(&self, pattern: &str, flags: &EngineFlags) -> EngineResult<Box<dyn CompiledRegex>> {
14 let mut flag_prefix = String::new();
15 if flags.case_insensitive {
16 flag_prefix.push('i');
17 }
18 if flags.multi_line {
19 flag_prefix.push('m');
20 }
21 if flags.dot_matches_newline {
22 flag_prefix.push('s');
23 }
24 if flags.unicode {
25 flag_prefix.push('u');
26 }
27 if flags.extended {
28 flag_prefix.push('x');
29 }
30
31 let full_pattern = if flag_prefix.is_empty() {
32 pattern.to_string()
33 } else {
34 format!("(?{flag_prefix}){pattern}")
35 };
36
37 let re = fancy_regex::Regex::new(&full_pattern)
38 .map_err(|e| EngineError::CompileError(e.to_string()))?;
39
40 Ok(Box::new(FancyCompiledRegex { re }))
41 }
42}
43
44struct FancyCompiledRegex {
45 re: fancy_regex::Regex,
46}
47
48impl CompiledRegex for FancyCompiledRegex {
49 fn find_matches(&self, text: &str) -> EngineResult<Vec<Match>> {
50 let mut matches = Vec::new();
51
52 for result in self.re.captures_iter(text) {
53 let caps = result.map_err(|e| EngineError::MatchError(e.to_string()))?;
54 let overall = caps.get(0).unwrap();
55 let mut captures = Vec::new();
56
57 for i in 1..caps.len() {
59 if let Some(m) = caps.get(i) {
60 captures.push(CaptureGroup {
61 index: i,
62 name: None, start: m.start(),
64 end: m.end(),
65 text: m.as_str().to_string(),
66 });
67 }
68 }
69
70 matches.push(Match {
71 start: overall.start(),
72 end: overall.end(),
73 text: overall.as_str().to_string(),
74 captures,
75 });
76 }
77
78 Ok(matches)
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 #[test]
87 fn test_simple_match() {
88 let engine = FancyRegexEngine;
89 let flags = EngineFlags::default();
90 let compiled = engine.compile(r"\d+", &flags).unwrap();
91 let matches = compiled.find_matches("abc 123 def 456").unwrap();
92 assert_eq!(matches.len(), 2);
93 assert_eq!(matches[0].text, "123");
94 }
95
96 #[test]
97 fn test_lookahead() {
98 let engine = FancyRegexEngine;
99 let flags = EngineFlags::default();
100 let compiled = engine.compile(r"\w+(?=@)", &flags).unwrap();
101 let matches = compiled.find_matches("user@example.com").unwrap();
102 assert_eq!(matches.len(), 1);
103 assert_eq!(matches[0].text, "user");
104 }
105
106 #[test]
107 fn test_lookbehind() {
108 let engine = FancyRegexEngine;
109 let flags = EngineFlags::default();
110 let compiled = engine.compile(r"(?<=@)\w+", &flags).unwrap();
111 let matches = compiled.find_matches("user@example.com").unwrap();
112 assert_eq!(matches.len(), 1);
113 assert_eq!(matches[0].text, "example");
114 }
115}