1#![feature(arbitrary_self_types_pointers)]
2
3use std::vec;
4
5use anyhow::{Result, bail};
6use bincode::{
7 Decode, Encode,
8 de::Decoder,
9 enc::Encoder,
10 error::{DecodeError, EncodeError},
11 impl_borrow_decode,
12};
13
14#[derive(Debug, Clone)]
18#[turbo_tasks::value(eq = "manual", shared, serialization = "custom")]
19pub struct EsRegex {
20 #[turbo_tasks(trace_ignore)]
21 delegate: EsRegexImpl,
22 pub pattern: String,
25 pub flags: String,
26}
27
28#[derive(Debug, Clone)]
29enum EsRegexImpl {
30 Regex(regex::Regex),
31 Regress(regress::Regex),
32}
33
34impl PartialEq for EsRegex {
39 fn eq(&self, other: &Self) -> bool {
40 self.pattern == other.pattern && self.flags == other.flags
41 }
42}
43impl Eq for EsRegex {}
44
45impl Encode for EsRegex {
46 fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
47 self.pattern.encode(encoder)?;
48 self.flags.encode(encoder)?;
49 Ok(())
50 }
51}
52
53impl<Context> Decode<Context> for EsRegex {
54 fn decode<D: Decoder<Context = Context>>(decoder: &mut D) -> Result<Self, DecodeError> {
55 let pattern: String = Decode::decode(decoder)?;
56 let flags: String = Decode::decode(decoder)?;
57 EsRegex::new(&pattern, &flags).map_err(|err| DecodeError::OtherString(err.to_string()))
59 }
60}
61
62impl_borrow_decode!(EsRegex);
63
64impl EsRegex {
65 pub fn new(pattern: &str, flags: &str) -> Result<Self> {
68 let pattern = pattern.replace("\\/", "/");
70
71 let mut applied_flags = String::new();
72 for flag in flags.chars() {
73 match flag {
74 'd' => {}
76 'g' => {}
78 'i' => applied_flags.push('i'),
80 'm' => applied_flags.push('m'),
82 's' => applied_flags.push('s'),
84 'u' => applied_flags.push('u'),
86 'y' => {}
88 _ => bail!("unsupported flag `{flag}` in regex: `{pattern}` with flags: `{flags}`"),
89 }
90 }
91
92 let regex = if !applied_flags.is_empty() {
93 regex::Regex::new(&format!("(?{applied_flags}){pattern}"))
94 } else {
95 regex::Regex::new(&pattern)
96 };
97
98 let delegate = match regex {
99 Ok(reg) => Ok(EsRegexImpl::Regex(reg)),
100 Err(_e) => {
101 match regress::Regex::with_flags(&pattern, regress::Flags::from(flags)) {
104 Ok(reg) => Ok(EsRegexImpl::Regress(reg)),
105 Err(e) => Err(e),
107 }
108 }
109 }?;
110 Ok(Self {
111 delegate,
112 pattern,
113 flags: flags.to_string(),
114 })
115 }
116
117 pub fn is_match(&self, haystack: &str) -> bool {
119 match &self.delegate {
120 EsRegexImpl::Regex(r) => r.is_match(haystack),
121 EsRegexImpl::Regress(r) => r.find(haystack).is_some(),
122 }
123 }
124
125 pub fn as_regex_str(&self) -> Option<&str> {
129 match &self.delegate {
130 EsRegexImpl::Regex(r) => Some(r.as_str()),
131 EsRegexImpl::Regress(_) => None,
132 }
133 }
134
135 pub fn captures<'h>(&self, haystack: &'h str) -> Option<Captures<'h>> {
146 let delegate = match &self.delegate {
147 EsRegexImpl::Regex(r) => CapturesImpl::Regex {
148 captures: r.captures(haystack)?,
149 idx: 0,
150 },
151 EsRegexImpl::Regress(r) => {
152 let re_match = r.find(haystack)?;
153 CapturesImpl::Regress {
154 captures_iter: re_match.captures.into_iter(),
155 haystack,
156 match_range: Some(re_match.range),
157 }
158 }
159 };
160 Some(Captures { delegate })
161 }
162}
163
164pub struct Captures<'h> {
165 delegate: CapturesImpl<'h>,
166}
167
168enum CapturesImpl<'h> {
169 Regex {
176 captures: regex::Captures<'h>,
177 idx: usize,
178 },
179 Regress {
181 captures_iter: vec::IntoIter<Option<regress::Range>>,
182 haystack: &'h str,
183 match_range: Option<regress::Range>,
184 },
185}
186
187impl<'h> Iterator for Captures<'h> {
188 type Item = Option<&'h str>;
189
190 fn next(&mut self) -> Option<Self::Item> {
191 match &mut self.delegate {
192 CapturesImpl::Regex { captures, idx } => {
193 if *idx >= captures.len() {
194 None
195 } else {
196 let capture = Some(captures.get(*idx).map(|sub_match| sub_match.as_str()));
197 *idx += 1;
198 capture
199 }
200 }
201 CapturesImpl::Regress {
202 captures_iter,
203 haystack,
204 match_range,
205 } => {
206 if let Some(range) = match_range.take() {
207 Some(Some(&haystack[range]))
209 } else {
210 Some(captures_iter.next()?.map(|range| &haystack[range]))
211 }
212 }
213 }
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::{EsRegex, EsRegexImpl};
220
221 #[test]
222 fn round_trip_bincode() {
223 let regex = EsRegex::new("[a-z]", "i").unwrap();
224 let config = bincode::config::standard();
225 let encoded = bincode::encode_to_vec(®ex, config).unwrap();
226 let (decoded, len) = bincode::decode_from_slice::<EsRegex, _>(&encoded, config).unwrap();
227 assert_eq!(regex, decoded);
228 assert_eq!(len, encoded.len());
229 }
230
231 #[test]
232 fn es_regex_matches_simple() {
233 let regex = EsRegex::new("a", "").unwrap();
234 assert!(matches!(regex.delegate, EsRegexImpl::Regex { .. }));
235 assert!(regex.is_match("a"));
236 }
237
238 #[test]
239 fn es_regex_matches_negative_lookahead() {
240 let regex = EsRegex::new("a(?!b)", "").unwrap();
242 assert!(matches!(regex.delegate, EsRegexImpl::Regress { .. }));
243 assert!(!regex.is_match("ab"));
244 assert!(regex.is_match("ac"));
245 }
246
247 #[test]
248 fn invalid_regex() {
249 assert!(matches!(EsRegex::new("*", ""), Err { .. }))
253 }
254
255 #[test]
256 fn captures_with_regex() {
257 let regex = EsRegex::new(r"(notmatched)|(\d{4})-(\d{2})-(\d{2})", "").unwrap();
258 assert!(matches!(regex.delegate, EsRegexImpl::Regex { .. }));
259
260 let captures = regex.captures("Today is 2024-01-15");
261 assert!(captures.is_some());
262 let caps: Vec<_> = captures.unwrap().collect();
263 assert_eq!(caps.len(), 5); assert_eq!(caps[0], Some("2024-01-15")); assert_eq!(caps[1], None); assert_eq!(caps[2], Some("2024")); assert_eq!(caps[3], Some("01")); assert_eq!(caps[4], Some("15")); }
270
271 #[test]
272 fn captures_with_regress() {
273 let regex = EsRegex::new(r"(\w+)(?=baz)", "").unwrap();
274 assert!(matches!(regex.delegate, EsRegexImpl::Regress { .. }));
275
276 let captures = regex.captures("foobar");
277 assert!(captures.is_none());
278
279 let captures = regex.captures("foobaz");
280 assert!(captures.is_some());
281 let caps: Vec<_> = captures.unwrap().collect();
282 assert_eq!(caps.len(), 2); assert_eq!(caps[0], Some("foo")); assert_eq!(caps[1], Some("foo")); }
286}