1#![forbid(unsafe_code)]
2
3use core::ffi::CStr;
4
5use crate::raw;
6
7pub use crate::raw::{RegexBuf, DEFAULT_CCL, DEFAULT_MEMO, DEFAULT_NODES};
8
9pub type Regex = raw::RegexBuf<DEFAULT_NODES, DEFAULT_CCL, DEFAULT_MEMO>;
14
15pub type TinyRegex = raw::RegexBuf<DEFAULT_NODES, DEFAULT_CCL, 0>;
19
20pub struct Match<'a> {
26 bytes: &'a [u8],
27 start: usize,
28 end: usize,
29}
30
31impl<'a> Match<'a> {
32 pub fn start(&self) -> usize {
34 self.start
35 }
36
37 pub fn end(&self) -> usize {
41 self.end
42 }
43
44 pub fn as_bytes(&self) -> &'a [u8] {
46 self.bytes
47 }
48}
49
50pub struct Matches<'a, 'b, const N: usize, const CCL: usize, const MEMO: usize> {
54 pattern: &'a raw::RegexBuf<N, CCL, MEMO>,
55 haystack: &'b CStr,
56 offset: usize,
57}
58
59impl<'a, 'b, const N: usize, const CCL: usize, const MEMO: usize>
60 Iterator for Matches<'a, 'b, N, CCL, MEMO>
61{
62 type Item = Match<'b>;
63
64 fn next(&mut self) -> Option<Match<'b>> {
65 if self.offset > self.haystack.to_bytes().len() {
66 return None;
67 }
68 let m = self.pattern.find_at(self.haystack, self.offset)?;
69 self.offset = if m.end > m.start { m.end } else { m.start + 1 };
70 Some(m)
71 }
72}
73
74impl<const N: usize, const CCL: usize, const MEMO: usize> raw::RegexBuf<N, CCL, MEMO> {
75 pub fn find_at<'a>(&self, haystack: &'a CStr, start: usize) -> Option<Match<'a>> {
82 let mut matchlen: i32 = 0;
83 let idx = self.matchp_at(haystack, start, &mut matchlen);
84 if idx < 0 {
85 return None;
86 }
87 let start = idx as usize;
88 let end = start + matchlen as usize;
89 Some(Match {
90 bytes: &haystack.to_bytes()[start..end],
91 start,
92 end,
93 })
94 }
95
96 pub fn find_iter<'a, 'b>(&'a self, haystack: &'b CStr) -> Matches<'a, 'b, N, CCL, MEMO> {
98 Matches { pattern: self, haystack, offset: 0 }
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 extern crate alloc;
105 extern crate std;
106 use super::*;
107 use alloc::vec::Vec;
108
109 #[test]
110 fn basic_match() {
111 let re = Regex::new(c"foo").unwrap();
112 let m = re.find_at(c"foo bar", 0).unwrap();
113 assert_eq!(m.start(), 0);
114 assert_eq!(m.end(), 3);
115 }
116
117 #[test]
118 fn find_at_offset() {
119 let re = Regex::new(c"foo").unwrap();
120 let m = re.find_at(c"foo bar foo", 1).unwrap();
121 assert_eq!(m.start(), 8);
122 assert_eq!(m.end(), 11);
123 }
124
125 #[test]
126 fn find_at_past_end_returns_none() {
127 let re = Regex::new(c"foo").unwrap();
128 assert!(re.find_at(c"foo", 4).is_none());
129 }
130
131 #[test]
132 fn find_iter_all_matches() {
133 let re = Regex::new(c"foo").unwrap();
134 let matches: Vec<_> = re.find_iter(c"foo bar foo").collect();
135 assert_eq!(matches.len(), 2);
136 assert_eq!((matches[0].start(), matches[0].end()), (0, 3));
137 assert_eq!((matches[1].start(), matches[1].end()), (8, 11));
138 }
139
140 #[test]
141 fn find_iter_empty_haystack() {
142 let re = Regex::new(c"foo").unwrap();
143 assert_eq!(re.find_iter(c"").count(), 0);
144 }
145
146 #[test]
147 fn new_invalid_pattern_returns_none() {
148 assert!(Regex::new(c"[abc").is_none());
149 }
150
151 #[test]
152 fn char_class_match() {
153 let re = Regex::new(c"[abc]+").unwrap();
154 let m = re.find_at(c"xyzabcxyz", 0).unwrap();
155 assert_eq!(m.start(), 3);
156 assert_eq!(m.end(), 6);
157 }
158
159 #[test]
162 fn move_preserves_char_class_match() {
163 fn compile() -> Regex { Regex::new(c"[a-z]+").unwrap() }
164 let re = compile(); let m = re.find_at(c"123abc", 0).unwrap();
166 assert_eq!((m.start(), m.end()), (3, 6));
167 }
168
169 #[test]
170 fn char_class_no_match() {
171 let re = Regex::new(c"[abc]").unwrap();
172 assert!(re.find_at(c"xyz", 0).is_none());
173 }
174
175 #[test]
176 fn char_range_match() {
177 let re = Regex::new(c"[a-z]+").unwrap();
178 let matches: Vec<_> = re.find_iter(c"123abc 456def").collect();
179 assert_eq!(matches.len(), 2);
180 assert_eq!((matches[0].start(), matches[0].end()), (3, 6));
181 assert_eq!((matches[1].start(), matches[1].end()), (10, 13));
182 }
183
184 #[test]
185 fn recompile_updates_pattern() {
186 let re = Regex::new(c"[abc]+").unwrap();
187 assert!(re.find_at(c"a", 0).is_some());
188 let re = re.recompile(c"[xyz]+").unwrap();
189 assert!(re.find_at(c"a", 0).is_none());
190 assert!(re.find_at(c"x", 0).is_some());
191 }
192
193 #[test]
194 fn recompile_invalid_pattern_returns_none() {
195 let re = Regex::new(c"foo").unwrap();
196 assert!(re.recompile(c"\\").is_none());
197 }
198
199 #[test]
200 fn new_empty_pattern_succeeds() {
201 assert!(Regex::new(c"").is_some());
202 }
203
204 #[test]
205 fn recompile_empty_pattern_succeeds() {
206 let re = Regex::new(c"foo").unwrap();
207 assert!(re.recompile(c"").is_some());
208 }
209
210 #[test]
211 fn concurrent_compile_then_match() {
212 use std::thread;
213
214 let handles: Vec<_> = (0..8)
215 .map(|i| {
216 thread::spawn(move || {
217 let pattern = if i % 2 == 0 { c"[a-z]+" } else { c"[0-9]+" };
218 let re = Regex::new(pattern).unwrap();
219 if i % 2 == 0 {
220 assert_eq!(re.find_iter(c"abc 123 def").count(), 2);
221 } else {
222 assert_eq!(re.find_iter(c"abc 123 def").count(), 1);
223 }
224 })
225 })
226 .collect();
227
228 for h in handles {
229 h.join().unwrap();
230 }
231 }
232
233 #[test]
234 fn concurrent_match_shared_regex() {
235 use std::sync::Arc;
236 use std::thread;
237
238 let re = Arc::new(Regex::new(c"[a-z]+").unwrap());
239
240 let handles: Vec<_> = (0..8)
241 .map(|_| {
242 let re = Arc::clone(&re);
243 thread::spawn(move || {
244 assert_eq!(re.find_iter(c"abc 123 def").count(), 2);
245 })
246 })
247 .collect();
248
249 for h in handles {
250 h.join().unwrap();
251 }
252 }
253
254 #[test]
255 fn new_trailing_backslash_returns_none() {
256 assert!(Regex::new(c"\\").is_none());
257 }
258
259 #[test]
260 fn compile_long_pattern_returns_none() {
261 let pattern = alloc::ffi::CString::new("a".repeat(DEFAULT_NODES)).unwrap();
264 assert!(Regex::new(pattern.as_c_str()).is_none());
265 }
266
267 #[test]
272 fn max_length_pattern_matches() {
273 let pattern = alloc::ffi::CString::new("a".repeat(DEFAULT_NODES - 1)).unwrap();
274 let haystack = alloc::ffi::CString::new("a".repeat(DEFAULT_NODES - 1)).unwrap();
275 let re = Regex::new(pattern.as_c_str()).unwrap();
276 let m = re.find_at(haystack.as_c_str(), 0).unwrap();
277 assert_eq!(m.start(), 0);
278 assert_eq!(m.end(), DEFAULT_NODES - 1);
279 }
280
281 #[test]
286 fn dollar_anchor_alone_matches_end_of_string() {
287 let re = Regex::new(c"$").unwrap();
288 let m = re.find_at(c"foo", 0).unwrap();
289 assert_eq!(m.start(), 3);
290 assert_eq!(m.end(), 3);
291 }
292
293 #[test]
294 fn dollar_anchor_with_prefix_still_works() {
295 let re = Regex::new(c"foo$").unwrap();
296 let m = re.find_at(c"foo", 0).unwrap();
297 assert_eq!(m.start(), 0);
298 assert_eq!(m.end(), 3);
299 assert!(re.find_at(c"foo bar", 0).is_none());
300 }
301
302 #[test]
303 fn caret_anchor_matches_only_at_start() {
304 let re = Regex::new(c"^foo").unwrap();
305 let m = re.find_at(c"foobar", 0).unwrap();
306 assert_eq!((m.start(), m.end()), (0, 3));
307 assert!(re.find_at(c"barfoo", 0).is_none());
308 }
309
310 #[test]
311 fn dot_does_not_match_newline_by_default() {
312 let haystack = std::ffi::CString::new("a\nb").unwrap();
313 let re = Regex::new(c"a.b").unwrap();
314 assert!(re.find_at(haystack.as_c_str(), 0).is_none());
315 }
316
317 #[test]
318 fn dot_matches_any_char() {
319 let re = Regex::new(c"f.o").unwrap();
320 assert!(re.find_at(c"foo", 0).is_some());
321 assert!(re.find_at(c"fzo", 0).is_some());
322 assert!(re.find_at(c"fo", 0).is_none());
323 }
324
325 #[test]
326 fn star_matches_zero_or_more() {
327 let re = Regex::new(c"a*b").unwrap();
328 let m = re.find_at(c"b", 0).unwrap();
329 assert_eq!((m.start(), m.end()), (0, 1));
330 let m = re.find_at(c"aaab", 0).unwrap();
331 assert_eq!((m.start(), m.end()), (0, 4));
332 assert!(re.find_at(c"aaa", 0).is_none());
333 }
334
335 #[test]
336 fn plus_no_match_exercises_backtrack_failure() {
337 let re = Regex::new(c"a+b").unwrap();
338 assert!(re.find_at(c"aaa", 0).is_none());
339 }
340
341 #[test]
342 fn question_matches_zero_or_one() {
343 let re = Regex::new(c"colou?r").unwrap();
344 assert!(re.find_at(c"color", 0).is_some());
345 assert!(re.find_at(c"colour", 0).is_some());
346 assert!(re.find_at(c"colouur", 0).is_none());
347 }
348
349 #[test]
350 fn metachar_digit_matches() {
351 let re = Regex::new(c"\\d+").unwrap();
352 let m = re.find_at(c"abc123def", 0).unwrap();
353 assert_eq!((m.start(), m.end()), (3, 6));
354 assert!(re.find_at(c"abc", 0).is_none());
355 }
356
357 #[test]
358 fn metachar_non_digit_matches() {
359 let re = Regex::new(c"\\D+").unwrap();
360 let m = re.find_at(c"123abc", 0).unwrap();
361 assert_eq!((m.start(), m.end()), (3, 6));
362 }
363
364 #[test]
365 fn metachar_word_matches_including_underscore() {
366 let re = Regex::new(c"\\w+").unwrap();
367 let m = re.find_at(c"...abc_123", 0).unwrap();
368 assert_eq!((m.start(), m.end()), (3, 10));
369 }
370
371 #[test]
372 fn metachar_non_word_matches() {
373 let re = Regex::new(c"\\W+").unwrap();
374 let m = re.find_at(c"abc...", 0).unwrap();
375 assert_eq!((m.start(), m.end()), (3, 6));
376 }
377
378 #[test]
379 fn metachar_whitespace_matches() {
380 let re = Regex::new(c"\\s+").unwrap();
381 let m = re.find_at(c"abc def", 0).unwrap();
382 assert_eq!((m.start(), m.end()), (3, 6));
383 }
384
385 #[test]
386 fn metachar_non_whitespace_matches() {
387 let re = Regex::new(c"\\S+").unwrap();
388 let m = re.find_at(c" abc", 0).unwrap();
389 assert_eq!((m.start(), m.end()), (3, 6));
390 }
391
392 #[test]
393 fn escaped_literal_matches_exact_char() {
394 let re = Regex::new(c"\\.").unwrap();
395 let m = re.find_at(c"abc.def", 0).unwrap();
396 assert_eq!((m.start(), m.end()), (3, 4));
397 assert!(re.find_at(c"abc", 0).is_none());
398 }
399
400 #[test]
401 fn inverted_char_class_matches_complement() {
402 let re = Regex::new(c"[^abc]+").unwrap();
403 let m = re.find_at(c"abcxyz", 0).unwrap();
404 assert_eq!((m.start(), m.end()), (3, 6));
405 assert!(re.find_at(c"aaa", 0).is_none());
406 }
407
408 #[test]
409 fn metachar_inside_char_class_matches() {
410 let re = Regex::new(c"[\\d]+").unwrap();
411 let m = re.find_at(c"abc123", 0).unwrap();
412 assert_eq!((m.start(), m.end()), (3, 6));
413 assert!(re.find_at(c"abc", 0).is_none());
414 }
415
416 #[test]
417 fn char_class_post_loop_overflow_returns_none() {
418 let pattern = alloc::ffi::CString::new(alloc::format!("[{}]", "a".repeat(DEFAULT_CCL - 1))).unwrap();
421 assert!(Regex::new(pattern.as_c_str()).is_none());
422 }
423
424 #[test]
425 fn char_class_in_loop_overflow_returns_none() {
426 let pattern = alloc::ffi::CString::new(alloc::format!("[{}]", "a".repeat(DEFAULT_CCL))).unwrap();
429 assert!(Regex::new(pattern.as_c_str()).is_none());
430 }
431
432 #[test]
433 fn char_class_backslash_overflow_returns_none() {
434 let pattern = alloc::ffi::CString::new(alloc::format!("[{}\\d]", "a".repeat(DEFAULT_CCL - 2))).unwrap();
438 assert!(Regex::new(pattern.as_c_str()).is_none());
439 }
440
441 #[test]
442 fn inverted_char_class_incomplete_returns_none() {
443 assert!(Regex::new(c"[^").is_none());
444 }
445
446 #[test]
447 fn char_class_backslash_at_end_returns_none() {
448 assert!(Regex::new(c"[a\\").is_none());
449 }
450
451 #[test]
452 fn metachar_non_digit_in_char_class_matches() {
453 let re = Regex::new(c"[\\D]+").unwrap();
454 let m = re.find_at(c"123abc", 0).unwrap();
455 assert_eq!((m.start(), m.end()), (3, 6));
456 }
457
458 #[test]
459 fn metachar_word_in_char_class_matches() {
460 let re = Regex::new(c"[\\w]+").unwrap();
461 let m = re.find_at(c"...abc_1", 0).unwrap();
462 assert_eq!((m.start(), m.end()), (3, 8));
463 }
464
465 #[test]
466 fn metachar_non_word_in_char_class_matches() {
467 let re = Regex::new(c"[\\W]+").unwrap();
468 let m = re.find_at(c"abc...", 0).unwrap();
469 assert_eq!((m.start(), m.end()), (3, 6));
470 }
471
472 #[test]
473 fn metachar_whitespace_in_char_class_matches() {
474 let re = Regex::new(c"[\\s]+").unwrap();
475 let m = re.find_at(c"abc ", 0).unwrap();
476 assert_eq!((m.start(), m.end()), (3, 6));
477 }
478
479 #[test]
480 fn metachar_non_whitespace_in_char_class_matches() {
481 let re = Regex::new(c"[\\S]+").unwrap();
482 let m = re.find_at(c" abc", 0).unwrap();
483 assert_eq!((m.start(), m.end()), (3, 6));
484 }
485
486 #[test]
487 fn escaped_literal_in_char_class_matches() {
488 let re = Regex::new(c"[\\.]+").unwrap();
489 let m = re.find_at(c"abc...", 0).unwrap();
490 assert_eq!((m.start(), m.end()), (3, 6));
491 assert!(re.find_at(c"abc", 0).is_none());
492 }
493
494 #[test]
495 fn find_iter_zero_length_match_at_end_covers_offset_guard() {
496 let re = Regex::new(c"$").unwrap();
497 let matches: Vec<_> = re.find_iter(c"foo").collect();
498 assert_eq!(matches.len(), 1);
499 assert_eq!((matches[0].start(), matches[0].end()), (3, 3));
500 }
501
502 #[test]
503 fn char_class_trailing_dash_covers_matchrange_empty_str2() {
504 let re = Regex::new(c"[a-]+").unwrap();
505 let m = re.find_at(c"!a-!", 0).unwrap();
506 assert_eq!((m.start(), m.end()), (1, 3));
507 }
508
509 #[test]
510 fn dash_does_not_match_char_range() {
511 let re = Regex::new(c"[a-z]+").unwrap();
512 assert!(re.find_at(c"-", 0).is_none());
513 }
514
515 #[test]
516 fn question_no_match_covers_matchquestion_branches() {
517 let re = Regex::new(c"x?y").unwrap();
518 assert!(re.find_at(c"z", 0).is_none());
519 }
520
521 #[test]
522 fn end_anchor_mid_pattern_covers_end_non_unused_branch() {
523 let re = Regex::new(c"$a").unwrap();
524 assert!(re.find_at(c"a", 0).is_none());
525 }
526
527 #[test]
528 fn memo_bounds_pathological_backtrack() {
529 let re = Regex::new(c"a*a*b").unwrap();
530 let haystack = alloc::ffi::CString::new("a".repeat(32)).unwrap();
531 assert!(re.find_at(haystack.as_c_str(), 0).is_none());
532 }
533
534 #[test]
537 fn per_attempt_memo_reset_finds_match_past_long_prefix() {
538 let re = Regex::new(c"a*a*b").unwrap();
539 let haystack = alloc::ffi::CString::new("a".repeat(200) + "cb").unwrap();
540 let m = re.find_at(haystack.as_c_str(), 0).unwrap();
541 assert_eq!(m.start(), 201);
542 assert_eq!(m.end(), 202);
543 }
544
545 #[test]
546 fn bplus_c_skips_long_decoy_brun() {
547 let re = Regex::new(c"b+c").unwrap();
548 let haystack = alloc::ffi::CString::new(
549 "a".repeat(50) + &"b".repeat(100) + &"a".repeat(50) + &"b".repeat(5) + "c" + &"a".repeat(10)
550 ).unwrap();
551 let m = re.find_at(haystack.as_c_str(), 0).unwrap();
552 assert_eq!(m.start(), 200);
553 assert_eq!(m.end(), 206);
554 }
555
556 #[test]
557 fn memo_finds_match_on_pathological_haystack() {
558 let haystack = alloc::ffi::CString::new("a".repeat(32) + "cb").unwrap();
559 let re = Regex::new(c"a*a*b").unwrap();
560 let m = re.find_at(haystack.as_c_str(), 0).unwrap();
561 assert_eq!(m.start(), 33);
562 assert_eq!(m.end(), 34);
563 }
564
565 #[test]
566 fn memo_correctly_returns_none_when_no_match_exists() {
567 let haystack = alloc::ffi::CString::new("a".repeat(32)).unwrap();
568 let re = Regex::new(c"a*a*b").unwrap();
569 assert!(re.find_at(haystack.as_c_str(), 0).is_none());
570 }
571
572 #[test]
581 fn regexbuf_parameter_variants() {
582 fn check<const N: usize, const CCL: usize, const MEMO: usize>(
583 pattern: &CStr,
584 haystack: &CStr,
585 expected: Option<(usize, usize)>,
586 ) {
587 let re = RegexBuf::<N, CCL, MEMO>::new(pattern).unwrap_or_else(|| {
588 panic!("compile failed: {pattern:?} N={N} CCL={CCL} MEMO={MEMO}")
589 });
590 let got = re.find_at(haystack, 0).map(|m| (m.start(), m.end()));
591 assert_eq!(
592 got, expected,
593 "N={N} CCL={CCL} MEMO={MEMO} pattern={pattern:?} haystack={haystack:?}"
594 );
595 }
596
597 check::<2, 2, 0>(c"a", c"a", Some((0, 1)));
600 check::<2, 2, 0>(c"a", c"b", None);
601 check::<2, 2, 2>(c"a", c"xax", Some((1, 2))); check::<3, 2, 0>(c"^a", c"ax", Some((0, 1)));
606 check::<3, 2, 0>(c"^a", c"ba", None);
607 check::<3, 2, 0>(c"a+", c"aaa", Some((0, 3)));
609 check::<3, 2, 3>(c"a+", c"bbb", None); check::<4, 2, 0>(c"a*b", c"b", Some((0, 1)));
614 check::<4, 2, 0>(c"a*b", c"aaab", Some((0, 4)));
615 check::<4, 2, 0>(c"a*b", c"aaa", None);
616 check::<4, 2, 4>(c"a*b", c"b", Some((0, 1))); check::<4, 2, 4>(c"a*b", c"aaab", Some((0, 4)));
618 check::<4, 2, 4>(c"a*b", c"aaa", None);
619
620 check::<5, 2, 0>(c"a+b+", c"ab", Some((0, 2)));
622 check::<5, 2, 5>(c"a+b+", c"aabb", Some((0, 4))); check::<5, 2, 5>(c"a+b+", c"aa", None);
624
625 check::<6, 2, 0>(c"a*b+c", c"bc", Some((0, 2)));
627 check::<6, 2, 6>(c"a*b+c", c"abbc", Some((0, 4))); check::<6, 2, 6>(c"a*b+c", c"ac", None);
629
630 check::<7, 2, 0>(c"a*a*b", c"b", Some((0, 1)));
632 check::<7, 2, 7>(c"a*a*b", c"aaab", Some((0, 4))); check::<7, 2, 7>(c"a*a*b", c"aaa", None);
634
635 check::<3, 2, 2>(c"a+", c"aaa", Some((0, 3)));
638 check::<3, 2, 2>(c"a+", c"bbb", None);
639
640 check::<8, 2, 1>(c"a*b", c"b", Some((0, 1)));
643 check::<8, 2, 1>(c"a*b", c"aaab", Some((0, 4)));
644 check::<8, 2, 1>(c"a*b", c"aaa", None);
645
646 check::<8, 2, 64>(c"a+b+", c"aabb", Some((0, 4)));
648 check::<8, 2, 64>(c"a+b+", c"bbbbb", None);
649
650 check::<3, 8, 0>(c"[a-z]+", c"hello", Some((0, 5)));
653 check::<3, 8, 3>(c"[a-z]+", c"123", None); check::<4, 8, 4>(c"[0-9]+", c"abc42", Some((3, 5))); check::<3, 8, 0>(c"[^abc]+", c"abcxyz", Some((3, 6)));
657 check::<3, 8, 0>(c"[^abc]+", c"aaa", None);
658
659 check::<8, 16, 64>(c"[a-z]+", c"hello world", Some((0, 5)));
661 check::<8, 16, 64>(c"[a-z]+", c"123", None);
662 check::<8, 16, 0>(c"[a-z]+", c"hello", Some((0, 5))); check::<32, 64, 256>(c"[a-z]+[0-9]*", c"abc123", Some((0, 6)));
666 check::<32, 64, 256>(c"[a-z]+[0-9]*", c"abc", Some((0, 3)));
667 check::<32, 64, 256>(c"[a-z]+[0-9]*", c"123", None);
668 }
669
670 #[test]
671 fn memo_is_fresh_each_call() {
672 let h1 = alloc::ffi::CString::new("a".repeat(32) + "cb").unwrap();
673 let h2 = alloc::ffi::CString::new("xb").unwrap();
674 let re = Regex::new(c"a*a*b").unwrap();
675 let m1 = re.find_at(h1.as_c_str(), 0).unwrap();
676 assert_eq!(m1.start(), 33);
677 let m2 = re.find_at(h2.as_c_str(), 0).unwrap();
678 assert_eq!(m2.start(), 1);
679 }
680
681 #[test]
682 fn non_ascii_literal_byte_matches() {
683 let re = Regex::new(c"é").unwrap();
684 let m = re.find_at(c"caf\xc3\xa9", 0).unwrap();
685 assert_eq!(m.start(), 3);
686 assert_eq!(m.end(), 5);
687 }
688
689 #[test]
690 fn question_is_greedy() {
691 let re = Regex::new(c"a?").unwrap();
692 let m = re.find_at(c"a", 0).unwrap();
693 assert_eq!((m.start(), m.end()), (0, 1));
694 }
695
696 #[test]
697 fn matchlength_not_leaked_across_failed_attempts() {
698 let re = Regex::new(c".0+").unwrap();
699 let m = re.find_at(c"aa0", 0).unwrap();
700 assert_eq!(m.start(), 1);
701 assert_eq!(m.end(), 3);
702 }
703
704 #[test]
705 fn tiny_regex_no_memo_matches_correctly() {
706 let re = TinyRegex::new(c"[0-9]+").unwrap();
707 let m = re.find_at(c"foo42bar", 0).unwrap();
708 assert_eq!((m.start(), m.end()), (3, 5));
709 assert!(re.find_at(c"abc", 0).is_none());
710 }
711}