1use std::{
2 error::Error,
3 fmt::Display,
4 mem::MaybeUninit,
5 ops::{BitAnd, BitOr, Not},
6 ptr::null_mut,
7 range::Range,
8};
9
10use minrx_sys::{
11 minrx_regcomp_flags_t, minrx_regcomp_flags_t_MINRX_REG_BRACE_COMPAT,
12 minrx_regcomp_flags_t_MINRX_REG_BRACK_ESCAPE, minrx_regcomp_flags_t_MINRX_REG_EXTENDED,
13 minrx_regcomp_flags_t_MINRX_REG_EXTENSIONS_BSD, minrx_regcomp_flags_t_MINRX_REG_EXTENSIONS_GNU,
14 minrx_regcomp_flags_t_MINRX_REG_ICASE, minrx_regcomp_flags_t_MINRX_REG_MINDISABLE,
15 minrx_regcomp_flags_t_MINRX_REG_MINIMAL, minrx_regcomp_flags_t_MINRX_REG_NATIVE1B,
16 minrx_regcomp_flags_t_MINRX_REG_NEWLINE, minrx_regcomp_flags_t_MINRX_REG_NOSUB, minrx_regerror,
17 minrx_regex_t, minrx_regexec_flags_t, minrx_regexec_flags_t_MINRX_REG_FIRSTSUB,
18 minrx_regexec_flags_t_MINRX_REG_NOFIRSTBYTES, minrx_regexec_flags_t_MINRX_REG_NOSUBRESET,
19 minrx_regexec_flags_t_MINRX_REG_NOTBOL, minrx_regexec_flags_t_MINRX_REG_NOTEOL,
20 minrx_regexec_flags_t_MINRX_REG_RESUME, minrx_regfree, minrx_regmatch_t, minrx_regncomp,
21 minrx_regnexec, minrx_result_t, minrx_result_t_MINRX_REG_BADBR,
22 minrx_result_t_MINRX_REG_BADPAT, minrx_result_t_MINRX_REG_BADRPT,
23 minrx_result_t_MINRX_REG_EBRACE, minrx_result_t_MINRX_REG_EBRACK,
24 minrx_result_t_MINRX_REG_ECOLLATE, minrx_result_t_MINRX_REG_ECTYPE,
25 minrx_result_t_MINRX_REG_EESCAPE, minrx_result_t_MINRX_REG_EPAREN,
26 minrx_result_t_MINRX_REG_ERANGE, minrx_result_t_MINRX_REG_ESPACE,
27 minrx_result_t_MINRX_REG_ESUBREG, minrx_result_t_MINRX_REG_NOMATCH,
28 minrx_result_t_MINRX_REG_SUCCESS, minrx_result_t_MINRX_REG_UNKNOWN,
29};
30
31#[repr(transparent)]
33pub struct Regex(minrx_regex_t);
34
35#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
38pub struct Match {
39 pub start: usize,
40 pub end: usize,
41}
42
43pub struct MatchIter<'r, 'h> {
45 regex: &'r mut Regex,
46 haystack: &'h [u8],
47 rm: minrx_regmatch_t,
48 options: MatchOptions,
49 resuming: bool,
50 is_done: bool,
51}
52
53#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
54pub struct RegexBuilder(minrx_regcomp_flags_t);
55
56#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
57pub struct MatchOptions(minrx_regexec_flags_t);
58
59#[repr(u32)]
60#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
61pub enum BuildError {
62 BadPattern(String) = minrx_result_t_MINRX_REG_BADPAT,
63 BadBracket(String) = minrx_result_t_MINRX_REG_BADBR,
64 BadRepetition(String) = minrx_result_t_MINRX_REG_BADRPT,
65 UnbalancedBrace(String) = minrx_result_t_MINRX_REG_EBRACE,
66 UnbalancedBracket(String) = minrx_result_t_MINRX_REG_EBRACK,
67 InvalidCollate(String) = minrx_result_t_MINRX_REG_ECOLLATE,
68 InvalidClass(String) = minrx_result_t_MINRX_REG_ECTYPE,
69 InvalidEscape(String) = minrx_result_t_MINRX_REG_EESCAPE,
70 UnbalancedParen(String) = minrx_result_t_MINRX_REG_EPAREN,
71 InvalidEndpoint(String) = minrx_result_t_MINRX_REG_ERANGE,
72 AllocError(String) = minrx_result_t_MINRX_REG_ESPACE,
73 InvalidDigitEscape(String) = minrx_result_t_MINRX_REG_ESUBREG,
74 Unknown(String) = minrx_result_t_MINRX_REG_UNKNOWN,
75}
76
77#[repr(u32)]
78#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
79pub enum MatchError {
80 AllocError(String) = minrx_result_t_MINRX_REG_ESPACE,
81 Unknown(String) = minrx_result_t_MINRX_REG_UNKNOWN,
82}
83
84impl Regex {
85 pub fn new(pattern: impl AsRef<[u8]>) -> Result<Self, BuildError> {
88 RegexBuilder::new().build(pattern)
89 }
90
91 pub fn capture_count(&self) -> usize {
93 self.0.re_nsub as _
94 }
95
96 pub fn find_matches(
99 &mut self,
100 subject: impl AsRef<[u8]>,
101 ) -> Result<Option<Box<[Option<Match>]>>, MatchError> {
102 self.find_matches_with(subject, MatchOptions::new())
103 }
104
105 pub fn is_match(&mut self, subject: impl AsRef<[u8]>) -> Result<bool, MatchError> {
110 self.is_match_with(subject, MatchOptions::new())
111 }
112
113 pub fn find_matches_with(
116 &mut self,
117 haystack: impl AsRef<[u8]>,
118 options: MatchOptions,
119 ) -> Result<Option<Box<[Option<Match>]>>, MatchError> {
120 let subject = haystack.as_ref();
121 let mut buf = Vec::with_capacity(self.0.re_nsub + 1);
122 let res = unsafe {
123 minrx_regnexec(
124 &raw mut self.0,
125 subject.len(),
126 subject.as_ptr(),
127 buf.capacity(),
128 buf.as_mut_ptr(),
129 options.0 as _,
130 )
131 } as minrx_result_t;
132
133 MatchError::from_raw(res, self).map(|found| {
134 found.then(|| {
135 unsafe { buf.set_len(buf.capacity()) };
136 buf.into_iter()
137 .map(|m| {
138 Some(Match {
139 start: m.rm_so.try_into().ok()?,
140 end: m.rm_eo.try_into().ok()?,
141 })
142 })
143 .collect()
144 })
145 })
146 }
147
148 pub fn is_match_with(
152 &mut self,
153 haystack: impl AsRef<[u8]>,
154 options: MatchOptions,
155 ) -> Result<bool, MatchError> {
156 let subject = haystack.as_ref();
157 let res = unsafe {
158 minrx_regnexec(
159 &raw mut self.0,
160 subject.len(),
161 subject.as_ptr(),
162 0,
163 null_mut(),
164 options.0 as _,
165 )
166 } as minrx_result_t;
167
168 MatchError::from_raw(res, self)
169 }
170
171 pub fn find_iter<'r, 'h>(
174 &'r mut self,
175 haystack: &'h (impl AsRef<[u8]> + ?Sized),
176 ) -> MatchIter<'r, 'h> {
177 self.find_iter_with_flags(haystack, MatchOptions::new())
178 }
179
180 pub fn find_iter_with_flags<'r, 'h>(
183 &'r mut self,
184 haystack: &'h (impl AsRef<[u8]> + ?Sized),
185 options: MatchOptions,
186 ) -> MatchIter<'r, 'h> {
187 MatchIter {
188 regex: self,
189 haystack: haystack.as_ref(),
190 rm: minrx_regmatch_t { rm_so: 0, rm_eo: 0 },
191 options,
192 resuming: false,
193 is_done: false,
194 }
195 }
196}
197
198impl RegexBuilder {
199 pub fn new() -> Self {
202 Self(minrx_regcomp_flags_t_MINRX_REG_EXTENDED)
203 }
204
205 pub fn build(&self, pattern: impl AsRef<[u8]>) -> Result<Regex, BuildError> {
207 let pattern = pattern.as_ref();
208 let mut regex = MaybeUninit::uninit();
209 let res = unsafe {
210 minrx_regncomp(
211 regex.as_mut_ptr(),
212 pattern.len(),
213 pattern.as_ptr(),
214 self.0 as _,
215 )
216 } as minrx_result_t;
217 BuildError::from_raw(res, &mut regex)?;
218
219 let regex = unsafe { regex.assume_init() };
220 Ok(Regex(regex))
221 }
222
223 pub fn extended(&mut self, _enable: bool) -> &mut Self {
227 self
228 }
229
230 pub fn case_insensitive(&mut self, enable: bool) -> &mut Self {
232 self.0 = mask(self.0, minrx_regcomp_flags_t_MINRX_REG_ICASE, enable);
233 self
234 }
235
236 pub fn swap_greed(&mut self, enable: bool) -> &mut Self {
238 self.0 = mask(self.0, minrx_regcomp_flags_t_MINRX_REG_MINIMAL, enable);
239 self
240 }
241
242 pub fn multi_line(&mut self, enable: bool) -> &mut Self {
244 self.0 = mask(self.0, minrx_regcomp_flags_t_MINRX_REG_NEWLINE, enable);
245 self
246 }
247
248 pub fn no_substrings(&mut self, enable: bool) -> &mut Self {
250 self.0 = mask(self.0, minrx_regcomp_flags_t_MINRX_REG_NOSUB, enable);
251 self
252 }
253
254 pub fn brace_compat(&mut self, enable: bool) -> &mut Self {
256 self.0 = mask(self.0, minrx_regcomp_flags_t_MINRX_REG_BRACE_COMPAT, enable);
257 self
258 }
259
260 pub fn escapes_in_brackets(&mut self, enable: bool) -> &mut Self {
262 self.0 = mask(self.0, minrx_regcomp_flags_t_MINRX_REG_BRACK_ESCAPE, enable);
263 self
264 }
265
266 pub fn bsd_extensions(&mut self, enable: bool) -> &mut Self {
268 self.0 = mask(
269 self.0,
270 minrx_regcomp_flags_t_MINRX_REG_EXTENSIONS_BSD,
271 enable,
272 );
273 self
274 }
275
276 pub fn gnu_extensions(&mut self, enable: bool) -> &mut Self {
278 self.0 = mask(
279 self.0,
280 minrx_regcomp_flags_t_MINRX_REG_EXTENSIONS_GNU,
281 enable,
282 );
283 self
284 }
285
286 pub fn native_encoding(&mut self, enable: bool) -> &mut Self {
288 self.0 = mask(self.0, minrx_regcomp_flags_t_MINRX_REG_NATIVE1B, enable);
289 self
290 }
291
292 pub fn disable_min_reps(&mut self, enable: bool) -> &mut Self {
294 self.0 = mask(self.0, minrx_regcomp_flags_t_MINRX_REG_MINDISABLE, enable);
295 self
296 }
297}
298
299impl MatchOptions {
300 pub fn new() -> Self {
305 Self(0)
306 }
307
308 pub fn not_bol(&mut self, enable: bool) -> &mut Self {
310 self.0 = mask(self.0, minrx_regexec_flags_t_MINRX_REG_NOTBOL, enable);
311 self
312 }
313
314 pub fn not_eol(&mut self, enable: bool) -> &mut Self {
316 self.0 = mask(self.0, minrx_regexec_flags_t_MINRX_REG_NOTEOL, enable);
317 self
318 }
319
320 pub fn first_subexpr(&mut self, enable: bool) -> &mut Self {
322 self.0 = mask(self.0, minrx_regexec_flags_t_MINRX_REG_FIRSTSUB, enable);
323 self
324 }
325
326 pub fn no_subexpr_reset(&mut self, enable: bool) -> &mut Self {
328 self.0 = mask(self.0, minrx_regexec_flags_t_MINRX_REG_NOSUBRESET, enable);
329 self
330 }
331
332 fn resume(&mut self, enable: bool) -> &mut Self {
335 self.0 = mask(self.0, minrx_regexec_flags_t_MINRX_REG_RESUME, enable);
336 self
337 }
338
339 pub fn no_first_bytes(&mut self, enable: bool) -> &mut Self {
341 self.0 = mask(self.0, minrx_regexec_flags_t_MINRX_REG_NOFIRSTBYTES, enable);
342 self
343 }
344}
345
346impl Default for RegexBuilder {
347 fn default() -> Self {
348 Self::new()
349 }
350}
351
352impl Default for MatchOptions {
353 fn default() -> Self {
354 Self::new()
355 }
356}
357
358impl BuildError {
359 fn from_raw(res: minrx_result_t, regex: &mut MaybeUninit<minrx_regex_t>) -> Result<(), Self> {
360 let err = || regerror(res, regex.as_ptr());
361 let err = match res {
362 res if res == minrx_result_t_MINRX_REG_SUCCESS => return Ok(()),
363 res if res == minrx_result_t_MINRX_REG_BADPAT => Err(Self::BadPattern(err())),
364 res if res == minrx_result_t_MINRX_REG_BADBR => Err(Self::BadBracket(err())),
365 res if res == minrx_result_t_MINRX_REG_BADRPT => Err(Self::BadRepetition(err())),
366 res if res == minrx_result_t_MINRX_REG_EBRACE => Err(Self::UnbalancedBrace(err())),
367 res if res == minrx_result_t_MINRX_REG_EBRACK => Err(Self::UnbalancedBracket(err())),
368 res if res == minrx_result_t_MINRX_REG_ECOLLATE => Err(Self::InvalidCollate(err())),
369 res if res == minrx_result_t_MINRX_REG_ECTYPE => Err(Self::InvalidClass(err())),
370 res if res == minrx_result_t_MINRX_REG_EESCAPE => Err(Self::InvalidEscape(err())),
371 res if res == minrx_result_t_MINRX_REG_EPAREN => Err(Self::UnbalancedParen(err())),
372 res if res == minrx_result_t_MINRX_REG_ERANGE => Err(Self::InvalidEndpoint(err())),
373 res if res == minrx_result_t_MINRX_REG_ESPACE => Err(Self::AllocError(err())),
374 res if res == minrx_result_t_MINRX_REG_ESUBREG => Err(Self::InvalidDigitEscape(err())),
375 _ => Err(Self::Unknown(err())),
376 };
377 drop(Regex(unsafe { regex.assume_init() }));
378 err
379 }
380}
381
382impl MatchError {
383 fn from_raw(res: minrx_result_t, regex: &Regex) -> Result<bool, Self> {
384 let err = || regerror(res, &raw const regex.0);
385 match res {
386 res if res == minrx_result_t_MINRX_REG_SUCCESS => Ok(true),
387 res if res == minrx_result_t_MINRX_REG_NOMATCH => Ok(false),
388 res if res == minrx_result_t_MINRX_REG_ESPACE => Err(Self::AllocError(err())),
389 _ => Err(Self::Unknown(err())),
390 }
391 }
392}
393
394impl<'r, 'h> Iterator for MatchIter<'r, 'h> {
395 type Item = Result<Match, MatchError>;
396
397 fn next(&mut self) -> Option<Self::Item> {
398 if self.is_done {
399 return None;
400 }
401
402 self.options.resume(self.resuming);
403
404 let res = unsafe {
405 minrx_regnexec(
406 &raw mut self.regex.0,
407 self.haystack.len(),
408 self.haystack.as_ptr(),
409 1,
410 &raw mut self.rm,
411 self.options.0 as _,
412 )
413 };
414
415 match MatchError::from_raw(res as _, self.regex) {
416 Ok(true) => {
417 let so = self.rm.rm_so as usize;
418 let eo = self.rm.rm_eo as usize;
419
420 if so == eo {
421 if eo >= self.haystack.len() {
422 self.is_done = true;
423 } else {
424 self.rm.rm_eo = (eo + 1) as _;
428 }
429 }
430
431 self.resuming = true;
432 Some(Ok(Match { start: so, end: eo }))
433 }
434 Ok(false) => {
435 self.is_done = true;
436 None
437 }
438 Err(e) => {
439 self.is_done = true;
440 Some(Err(e))
441 }
442 }
443 }
444}
445
446#[inline]
447fn mask<T>(set: T, bit: T, enable: bool) -> T
448where
449 T: BitOr<Output = T> + BitAnd<Output = T> + Not<Output = T>,
450{
451 if enable { set | bit } else { set & !bit }
452}
453
454fn regerror(res: minrx_result_t, regex: *const minrx_regex_t) -> String {
455 let mut buf = Vec::with_capacity(53);
456 let new_len = unsafe { minrx_regerror(res as _, regex, buf.as_mut_ptr(), buf.capacity()) };
457
458 if new_len > buf.capacity() {
459 buf.reserve_exact(new_len);
460 unsafe { minrx_regerror(res as _, regex, buf.as_mut_ptr(), buf.capacity()) };
461 }
462
463 unsafe { buf.set_len(new_len.saturating_sub(1)) }; String::from_utf8_lossy(&buf).to_string()
465}
466
467impl Drop for Regex {
468 fn drop(&mut self) {
469 unsafe { minrx_regfree(&raw mut self.0) };
470 }
471}
472
473unsafe impl Send for Regex {}
474
475impl From<Match> for std::ops::Range<usize> {
476 fn from(value: Match) -> Self {
477 value.start..value.end
478 }
479}
480
481impl Match {
482 pub fn range(&self) -> Range<usize> {
483 (self.start..self.end).into()
484 }
485}
486
487impl From<Match> for Range<usize> {
488 fn from(value: Match) -> Self {
489 value.range()
490 }
491}
492
493impl Display for BuildError {
494 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
495 match self {
496 BuildError::BadPattern(s)
497 | BuildError::BadBracket(s)
498 | BuildError::BadRepetition(s)
499 | BuildError::UnbalancedBrace(s)
500 | BuildError::UnbalancedBracket(s)
501 | BuildError::InvalidCollate(s)
502 | BuildError::InvalidClass(s)
503 | BuildError::InvalidEscape(s)
504 | BuildError::UnbalancedParen(s)
505 | BuildError::InvalidEndpoint(s)
506 | BuildError::AllocError(s)
507 | BuildError::InvalidDigitEscape(s)
508 | BuildError::Unknown(s) => f.write_str(s),
509 }
510 }
511}
512
513impl Display for MatchError {
514 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
515 match self {
516 MatchError::AllocError(s) | MatchError::Unknown(s) => f.write_str(s),
517 }
518 }
519}
520
521impl Error for BuildError {}
522impl Error for MatchError {}