1use alloc::boxed::Box;
112use alloc::string::ToString;
113use alloc::sync::Arc;
114use alloc::vec::Vec;
115
116use crate::CompileOptions;
117use crate::Input;
118use crate::RegexInput;
119use crate::RegexOptionsBuilder;
120use regex_automata::hybrid::dfa;
121use regex_automata::meta::Builder as RaBuilder;
122use regex_automata::meta::Config as RaConfig;
123use regex_automata::meta::Regex as RaRegex;
124use regex_automata::nfa::thompson;
125use regex_automata::nfa::thompson::WhichCaptures;
126use regex_automata::util::pool::Pool;
127use regex_automata::util::syntax::Config as SyntaxConfig;
128use regex_automata::Anchored;
129use regex_automata::Input as RaInput;
130use regex_automata::MatchErrorKind;
131use regex_automata::MatchKind;
132use regex_automata::PatternID;
133use regex_automata::PatternSet;
134
135use crate::vm::OPTION_NOT_CONTINUED_FROM_PREVIOUS_MATCH;
136use crate::CompileError;
137use crate::Error;
138use crate::RegexOptions;
139use crate::{BytesMode, Captures, Regex, Result};
140
141type DfaCachePoolFactory = alloc::boxed::Box<
142 dyn Fn() -> dfa::Cache + Send + Sync + core::panic::UnwindSafe + core::panic::RefUnwindSafe,
143>;
144
145const BYTES_PER_MIB: usize = 1 << 20;
146const DEFAULT_META_NFA_SIZE_LIMIT: usize = 64 * BYTES_PER_MIB;
147const DEFAULT_META_HYBRID_CACHE_CAPACITY: usize = 64 * BYTES_PER_MIB;
148const DEFAULT_OVERLAPPING_DFA_CACHE_CAPACITY: usize = 64 * BYTES_PER_MIB;
149
150#[derive(Clone, Debug)]
151pub struct RegexSet {
153 regexes: Vec<Arc<Regex>>,
154 earliest_match_finder: RaRegex,
155 overlapping_dfa: Arc<dfa::DFA>,
156 overlapping_cache_pool: Arc<Pool<dfa::Cache, DfaCachePoolFactory>>,
157}
158
159#[derive(Clone, Debug)]
160pub struct RegexSetOptions {
162 syntaxc: SyntaxConfig,
163 delegate_size_limit: Option<usize>,
164 delegate_dfa_size_limit: Option<usize>,
165 meta_nfa_size_limit: Option<usize>,
166 meta_hybrid_cache_capacity: usize,
167 overlapping_dfa_cache_capacity: usize,
168 overlapping_dfa_skip_cache_capacity_check: bool,
169 bytes_mode: BytesMode,
170}
171
172impl Default for RegexSetOptions {
173 fn default() -> Self {
174 let default_options = RegexOptions::default();
175 RegexSetOptions {
176 syntaxc: default_options.syntaxc,
177 delegate_size_limit: default_options.delegate_size_limit,
178 delegate_dfa_size_limit: default_options.delegate_dfa_size_limit,
179 meta_nfa_size_limit: Some(DEFAULT_META_NFA_SIZE_LIMIT),
180 meta_hybrid_cache_capacity: DEFAULT_META_HYBRID_CACHE_CAPACITY,
181 overlapping_dfa_cache_capacity: DEFAULT_OVERLAPPING_DFA_CACHE_CAPACITY,
182 overlapping_dfa_skip_cache_capacity_check: true,
183 bytes_mode: default_options.bytes_mode,
184 }
185 }
186}
187
188impl RegexSetOptions {
189 pub fn new() -> Self {
191 Self::default()
192 }
193
194 pub fn delegate_size_limit(mut self, limit: usize) -> Self {
200 self.delegate_size_limit = Some(limit);
201 self
202 }
203
204 pub fn delegate_dfa_size_limit(mut self, limit: usize) -> Self {
212 self.delegate_dfa_size_limit = Some(limit);
213 self
214 }
215
216 pub fn meta_nfa_size_limit(mut self, limit: Option<usize>) -> Self {
221 self.meta_nfa_size_limit = limit;
222 self
223 }
224
225 pub fn meta_hybrid_cache_capacity(mut self, limit: usize) -> Self {
230 self.meta_hybrid_cache_capacity = limit;
231 self
232 }
233
234 pub fn overlapping_dfa_cache_capacity(mut self, limit: usize) -> Self {
239 self.overlapping_dfa_cache_capacity = limit;
240 self
241 }
242
243 pub fn overlapping_dfa_skip_cache_capacity_check(mut self, yes: bool) -> Self {
248 self.overlapping_dfa_skip_cache_capacity_check = yes;
249 self
250 }
251}
252
253impl RegexSet {
254 pub fn new<I, S>(patterns: I) -> Result<Self>
266 where
267 I: IntoIterator<Item = S>,
268 S: AsRef<str>,
269 {
270 let builder = RegexOptionsBuilder::new();
271 Self::new_with_options(patterns, &builder)
272 }
273
274 pub fn new_with_options<I, S>(
280 patterns: I,
281 options_builder: &RegexOptionsBuilder,
282 ) -> Result<Self>
283 where
284 I: IntoIterator<Item = S>,
285 S: AsRef<str>,
286 {
287 let mut member_options = options_builder.options.clone();
291 member_options.delegate_prefilter = false;
292 let regexes = patterns
293 .into_iter()
294 .map(|pattern| {
295 Regex::new_options(pattern.as_ref().to_string(), &member_options).map(Arc::new)
296 })
297 .collect::<Result<Vec<_>>>()?;
298
299 let config = RegexSetOptions {
300 syntaxc: options_builder.options.syntaxc,
301 delegate_size_limit: options_builder.options.delegate_size_limit,
302 delegate_dfa_size_limit: options_builder.options.delegate_dfa_size_limit,
303 meta_nfa_size_limit: Some(DEFAULT_META_NFA_SIZE_LIMIT),
304 meta_hybrid_cache_capacity: DEFAULT_META_HYBRID_CACHE_CAPACITY,
305 overlapping_dfa_cache_capacity: DEFAULT_OVERLAPPING_DFA_CACHE_CAPACITY,
306 overlapping_dfa_skip_cache_capacity_check: true,
307 bytes_mode: options_builder.options.bytes_mode,
308 };
309 Self::from_regexes(regexes, config)
310 }
311
312 pub fn from_regexes<I>(regexes: I, config: RegexSetOptions) -> Result<Self>
348 where
349 I: IntoIterator<Item = Arc<Regex>>,
350 {
351 let regexes_vec: Vec<Arc<Regex>> = regexes.into_iter().collect();
352
353 let mut patterns = Vec::with_capacity(regexes_vec.len());
354
355 for regex in ®exes_vec {
356 patterns.push(regex.seek_pattern());
357 }
358
359 let compile_options = CompileOptions {
360 bytes_mode: config.bytes_mode,
361 unicode: config.syntaxc.get_unicode() && !matches!(config.bytes_mode, BytesMode::Ascii),
362 delegate_size_limit: config.delegate_size_limit,
363 delegate_dfa_size_limit: config.delegate_dfa_size_limit,
364 ..CompileOptions::default()
365 };
366
367 let utf8 = matches!(compile_options.bytes_mode, BytesMode::Unicode);
368
369 let syntax_config = SyntaxConfig::new()
373 .utf8(utf8)
374 .unicode(compile_options.unicode);
375 let hirs = regex_automata::util::syntax::parse_many_with(&patterns, &syntax_config)
376 .map_err(|e| {
377 Error::CompileError(Box::new(CompileError::UnexpectedGeneralError(
378 alloc::format!("failed to parse regex set pattern: {}", e),
379 )))
380 })?;
381
382 let mut earliest_builder = RaBuilder::new();
385 earliest_builder.configure(
386 RaConfig::new()
387 .match_kind(MatchKind::LeftmostFirst)
388 .nfa_size_limit(config.meta_nfa_size_limit)
389 .hybrid_cache_capacity(config.meta_hybrid_cache_capacity),
390 );
391 let earliest_match_finder = earliest_builder
392 .build_many_from_hir(&hirs)
393 .map_err(CompileError::InnerError)
394 .map_err(|e| Error::CompileError(Box::new(e)))?;
395
396 let format_patterns = || {
397 patterns
398 .iter()
399 .enumerate()
400 .map(|(i, p)| alloc::format!("[{}]: {}", i, p))
401 .collect::<Vec<_>>()
402 .join("\n---\n")
403 };
404
405 let mut thompson_config = thompson::Config::new().which_captures(WhichCaptures::None);
410 if let Some(limit) = compile_options.delegate_size_limit {
411 thompson_config = thompson_config.nfa_size_limit(Some(limit));
412 }
413 let nfa = thompson::Compiler::new()
414 .configure(thompson_config)
415 .build_many_from_hir(&hirs)
416 .map_err(|e| {
417 Error::CompileError(Box::new(CompileError::DfaBuildError(
418 format_patterns(),
419 e.to_string(),
420 )))
421 })?;
422
423 let mut overlapping_dfa_builder = dfa::DFA::builder();
424 let mut overlapping_config = dfa::Config::new()
425 .match_kind(MatchKind::All)
426 .unicode_word_boundary(compile_options.unicode)
427 .cache_capacity(config.overlapping_dfa_cache_capacity);
428 if config.overlapping_dfa_skip_cache_capacity_check {
429 overlapping_config = overlapping_config.skip_cache_capacity_check(true);
430 }
431 overlapping_dfa_builder.configure(overlapping_config);
432 let overlapping_dfa =
433 Arc::new(overlapping_dfa_builder.build_from_nfa(nfa).map_err(|e| {
434 Error::CompileError(Box::new(CompileError::DfaBuildError(
435 format_patterns(),
436 e.to_string(),
437 )))
438 })?);
439 let create: DfaCachePoolFactory = alloc::boxed::Box::new({
440 let dfa = Arc::clone(&overlapping_dfa);
441 move || dfa.create_cache()
442 });
443 let overlapping_cache_pool = Arc::new(Pool::new(create));
444
445 Ok(Self {
446 regexes: regexes_vec,
447 earliest_match_finder,
448 overlapping_dfa,
449 overlapping_cache_pool,
450 })
451 }
452
453 pub fn len(&self) -> usize {
455 self.regexes.len()
456 }
457
458 pub fn is_empty(&self) -> bool {
460 self.len() == 0
461 }
462
463 pub fn find_input<'r, 't, S: Input + ?Sized>(
466 &'r self,
467 input: RegexInput<'t, S>,
468 ) -> Result<Option<RegexSetMatchesAt<'r, 't, S>>> {
469 if input.is_done() {
470 return Ok(None);
471 }
472
473 let haystack = input.haystack();
474 let match_range = input.get_range();
475 let mut search_start = input.effective_start();
476 let mut seen_pattern_indices = PatternSet::new(self.regexes.len());
477
478 while search_start <= match_range.end {
479 let ra_input = RaInput::new(haystack.as_bytes())
480 .range(search_start..match_range.end)
481 .anchored(if input.is_anchored() {
482 Anchored::Yes
483 } else {
484 Anchored::No
485 });
486 let Some(candidate) = self.earliest_match_finder.search(&ra_input) else {
487 return Ok(None);
488 };
489 let match_start = candidate.start();
490 let overlapping_input = RaInput::new(haystack.as_bytes())
491 .anchored(Anchored::Yes)
492 .range(match_start..match_range.end);
493 seen_pattern_indices.clear();
494 {
495 let mut cache_guard = self.overlapping_cache_pool.get();
496 if let Err(e) = self.overlapping_dfa.try_which_overlapping_matches(
497 &mut cache_guard,
498 &overlapping_input,
499 &mut seen_pattern_indices,
500 ) {
501 match e.kind() {
502 MatchErrorKind::Quit { .. } | MatchErrorKind::GaveUp { .. } => {
503 seen_pattern_indices.clear();
508 for i in 0..self.regexes.len() {
509 seen_pattern_indices.insert(PatternID::must(i));
510 }
511 }
512 _ => panic!("unexpected overlapping DFA error: {:?}", e),
513 }
514 }
515 } let mut candidate_pattern_indices = seen_pattern_indices
521 .iter()
522 .map(|pattern| pattern.as_usize());
523 let mut first_match = None;
524 for pattern_index in &mut candidate_pattern_indices {
525 if let Some(candidate_match) =
526 self.match_pattern_at_input_position(pattern_index, &input, match_start)?
527 {
528 first_match = Some(candidate_match);
529 break;
530 }
531 }
532
533 if let Some(first_match) = first_match {
534 let pending_pattern_indices = candidate_pattern_indices.collect::<Vec<_>>();
535 return Ok(Some(RegexSetMatchesAt {
536 regex_set: self,
537 input,
538 haystack,
539 match_start,
540 first_match: Some(first_match),
541 pending_pattern_indices: pending_pattern_indices.into_iter(),
542 }));
543 }
544
545 search_start = haystack.advance_position(match_start);
546 }
547
548 Ok(None)
549 }
550
551 fn match_pattern_at_input_position<'t, S: Input + ?Sized>(
552 &self,
553 pattern_index: usize,
554 input: &RegexInput<'t, S>,
555 match_start: usize,
556 ) -> Result<Option<RegexSetMatch<'t, S>>> {
557 let candidate_input = input.clone().from_pos(match_start).anchored(true);
558 let regex = &self.regexes[pattern_index];
559 let mut option_flags = 0;
560 if input.start() < match_start {
561 option_flags |= OPTION_NOT_CONTINUED_FROM_PREVIOUS_MATCH;
562 }
563 if regex.captures_len() == 1 {
564 return Ok(regex.find_input_raw(&candidate_input, option_flags)?.map(
565 |(start, end)| RegexSetMatch {
566 pattern_index,
567 captures: regex.captures_for_span(input.haystack(), start, end),
568 },
569 ));
570 }
571 Ok(regex
572 .captures_input_with_option_flags(&candidate_input, option_flags)?
573 .map(|captures| RegexSetMatch {
574 pattern_index,
575 captures,
576 }))
577 }
578}
579
580#[derive(Debug)]
607pub struct RegexSetMatch<'t, S: Input + ?Sized> {
608 pattern_index: usize,
609 captures: Captures<'t, S>,
610}
611
612impl<'t, S: Input + ?Sized> RegexSetMatch<'t, S> {
613 pub fn pattern(&self) -> usize {
615 self.pattern_index
616 }
617
618 pub fn captures(&self) -> &Captures<'t, S> {
620 &self.captures
621 }
622
623 pub fn get(&self) -> S::Match<'t> {
625 self.captures
626 .get(0)
627 .expect("`RegexSetMatch` must always contain the overall match")
628 }
629
630 pub fn start(&self) -> usize {
632 self.captures
633 .get_span(0)
634 .expect("`RegexSetMatch` must always contain the overall match")
635 .0
636 }
637
638 pub fn end(&self) -> usize {
640 self.captures
641 .get_span(0)
642 .expect("`RegexSetMatch` must always contain the overall match")
643 .1
644 }
645}
646
647impl<'t> RegexSetMatch<'t, str> {
648 pub fn as_str(&self) -> &'t str {
650 self.captures
651 .get(0)
652 .expect("`RegexSetMatch` must always contain the overall match")
653 .as_str()
654 }
655}
656
657#[derive(Debug)]
658pub struct RegexSetMatchesAt<'r, 't, S: Input + ?Sized> {
659 regex_set: &'r RegexSet,
660 input: RegexInput<'t, S>,
661 haystack: &'t S,
662 match_start: usize,
663 first_match: Option<RegexSetMatch<'t, S>>,
664 pending_pattern_indices: alloc::vec::IntoIter<usize>,
665}
666
667impl<'r, 't, S: Input + ?Sized> RegexSetMatchesAt<'r, 't, S> {
668 pub fn regex_set(&self) -> &'r RegexSet {
670 self.regex_set
671 }
672
673 pub fn haystack(&self) -> &'t S {
675 self.haystack
676 }
677
678 pub fn start(&self) -> usize {
680 self.match_start
681 }
682}
683
684impl<'r, 't, S: Input + ?Sized> Iterator for RegexSetMatchesAt<'r, 't, S> {
685 type Item = Result<RegexSetMatch<'t, S>>;
686
687 fn next(&mut self) -> Option<Self::Item> {
688 if let Some(first_match) = self.first_match.take() {
689 return Some(Ok(first_match));
690 }
691
692 for pattern_index in self.pending_pattern_indices.by_ref() {
693 match self.regex_set.match_pattern_at_input_position(
694 pattern_index,
695 &self.input,
696 self.match_start,
697 ) {
698 Ok(Some(regex_set_match)) => return Some(Ok(regex_set_match)),
699 Ok(None) => continue,
700 Err(err) => return Some(Err(err)),
701 }
702 }
703
704 None
705 }
706}
707
708#[cfg(test)]
709mod tests {
710 use super::{
711 RegexSet, RegexSetOptions, DEFAULT_META_HYBRID_CACHE_CAPACITY, DEFAULT_META_NFA_SIZE_LIMIT,
712 DEFAULT_OVERLAPPING_DFA_CACHE_CAPACITY,
713 };
714 use crate::{Error, RegexInput, RegexOptionsBuilder, RuntimeError};
715
716 #[test]
717 fn regex_set_options_defaults_to_larger_regex_automata_limits() {
718 let options = RegexSetOptions::default();
719
720 assert_eq!(
721 options.meta_nfa_size_limit,
722 Some(DEFAULT_META_NFA_SIZE_LIMIT)
723 );
724 assert_eq!(
725 options.meta_hybrid_cache_capacity,
726 DEFAULT_META_HYBRID_CACHE_CAPACITY
727 );
728 assert_eq!(
729 options.overlapping_dfa_cache_capacity,
730 DEFAULT_OVERLAPPING_DFA_CACHE_CAPACITY
731 );
732 assert!(options.overlapping_dfa_skip_cache_capacity_check);
733 }
734
735 #[test]
736 fn regex_set_options_setters_update_regex_automata_limits() {
737 let options = RegexSetOptions::new()
738 .delegate_size_limit(11)
739 .delegate_dfa_size_limit(22)
740 .meta_nfa_size_limit(None)
741 .meta_hybrid_cache_capacity(33)
742 .overlapping_dfa_cache_capacity(44)
743 .overlapping_dfa_skip_cache_capacity_check(false);
744
745 assert_eq!(options.delegate_size_limit, Some(11));
746 assert_eq!(options.delegate_dfa_size_limit, Some(22));
747 assert_eq!(options.meta_nfa_size_limit, None);
748 assert_eq!(options.meta_hybrid_cache_capacity, 33);
749 assert_eq!(options.overlapping_dfa_cache_capacity, 44);
750 assert!(!options.overlapping_dfa_skip_cache_capacity_check);
751 }
752
753 #[test]
754 fn find_input_returns_all_matches_at_earliest_position_in_pattern_order() {
755 let set = RegexSet::new(&[r"\d+", r"\w+", r"(?<=\$)\d+\.\d+"]).unwrap();
756 let mut matches = set.find_input(RegexInput::new("$29.99")).unwrap().unwrap();
757
758 let first = matches.next().unwrap().unwrap();
759 assert_eq!(0, first.pattern());
760 assert_eq!(1, first.start());
761 assert_eq!(3, first.end());
762 assert_eq!("29", first.as_str());
763
764 let second = matches.next().unwrap().unwrap();
765 assert_eq!(1, second.pattern());
766 assert_eq!(1, second.start());
767 assert_eq!(3, second.end());
768 assert_eq!("29", second.as_str());
769
770 let third = matches.next().unwrap().unwrap();
771 assert_eq!(2, third.pattern());
772 assert_eq!(1, third.start());
773 assert_eq!(6, third.end());
774 assert_eq!("29.99", third.as_str());
775
776 assert!(matches.next().is_none());
777 }
778
779 #[test]
780 fn find_input_skips_false_positive_candidate_positions() {
781 let set = RegexSet::new(&[r"(?<=foo)bar"]).unwrap();
782 let mut matches = set
783 .find_input(RegexInput::new("barfoobar"))
784 .unwrap()
785 .unwrap();
786
787 let only = matches.next().unwrap().unwrap();
788 assert_eq!(0, only.pattern());
789 assert_eq!(6, only.start());
790 assert_eq!(9, only.end());
791 assert_eq!("bar", only.as_str());
792 assert!(matches.next().is_none());
793 }
794
795 #[test]
796 fn find_input_returns_none_when_input_is_done() {
797 let set = RegexSet::new(&[r"."]).unwrap();
798
799 assert!(set
800 .find_input(RegexInput::new("a").from_pos(2))
801 .unwrap()
802 .is_none());
803 }
804
805 #[test]
806 fn find_input_returns_none_when_input_is_anchored_and_match_not_at_start_position() {
807 let set = RegexSet::new(&[r"b"]).unwrap();
808
809 assert!(set
810 .find_input(RegexInput::new("ab").from_pos(0).anchored(true))
811 .unwrap()
812 .is_none());
813 }
814
815 #[test]
816 fn find_input_returns_match_when_input_is_anchored_and_match_at_start_position() {
817 let set = RegexSet::new(&[r"b"]).unwrap();
818
819 let mut matches = set
820 .find_input(RegexInput::new("ab").from_pos(1).anchored(true))
821 .unwrap()
822 .unwrap();
823
824 let only = matches.next().unwrap().unwrap();
825 assert_eq!(0, only.pattern());
826 assert_eq!(1, only.start());
827 assert_eq!(2, only.end());
828 assert_eq!("b", only.as_str());
829 assert!(matches.next().is_none());
830 }
831
832 #[test]
833 fn find_input_defers_later_pattern_evaluation_until_iteration() {
834 let mut options_builder = RegexOptionsBuilder::new();
835 options_builder.backtrack_limit(0);
836 let set = RegexSet::new_with_options(&[r"a", r"(?:(a|aa)+)\1"], &options_builder).unwrap();
837
838 let mut matches = set.find_input(RegexInput::new("aa")).unwrap().unwrap();
839
840 let first = matches.next().unwrap().unwrap();
841 assert_eq!(0, first.pattern());
842 assert_eq!(0, first.start());
843 assert_eq!(1, first.end());
844
845 let second = matches.next().unwrap();
846 assert!(matches!(
847 second,
848 Err(Error::RuntimeError(RuntimeError::BacktrackLimitExceeded))
849 ));
850 }
851
852 #[test]
853 fn find_input_picks_earliest_start_position_before_iterating_pattern_order() {
854 let mut options_builder = RegexOptionsBuilder::new();
855 options_builder.multi_line(true);
856 let set = RegexSet::new_with_options(
857 &[
858 r"//.*$",
859 r#""(?:[^"\\]|\\.)*""#,
860 r"\b(fn|let|mut|if|else)\b",
861 r"\b[0-9]+\b",
862 r"[a-zA-Z_][a-zA-Z0-9_]*",
863 ],
864 &options_builder,
865 )
866 .unwrap();
867
868 let mut matches = set
869 .find_input(RegexInput::new(
870 "let x = 42; // a comment\nlet s = \"hello world\";",
871 ))
872 .unwrap()
873 .unwrap();
874
875 let first = matches.next().unwrap().unwrap();
876 assert_eq!(2, first.pattern());
877 assert_eq!(0, first.start());
878 assert_eq!(3, first.end());
879 assert_eq!("let", first.as_str());
880 }
881
882 #[test]
883 fn find_input_yields_each_pattern_at_match_start_once() {
884 let set = RegexSet::new(&[r"a+", r"a"]).unwrap();
885 let mut matches = set.find_input(RegexInput::new("aaa")).unwrap().unwrap();
886
887 let first = matches.next().unwrap().unwrap();
888 assert_eq!(0, first.pattern());
889 assert_eq!(0, first.start());
890 assert_eq!(3, first.end());
891 assert_eq!("aaa", first.as_str());
892
893 let second = matches.next().unwrap().unwrap();
894 assert_eq!(1, second.pattern());
895 assert_eq!(0, second.start());
896 assert_eq!(1, second.end());
897 assert_eq!("a", second.as_str());
898
899 assert!(matches.next().is_none());
900 }
901
902 #[test]
903 fn test_no_captures_returns_group_0() {
904 let set = RegexSet::new(&[r"\w+"]).unwrap();
905 let mut matches = set.find_input(RegexInput::new("abc")).unwrap().unwrap();
906
907 let only = matches.next().unwrap().unwrap();
908 assert_eq!(0, only.pattern());
909 assert_eq!(1, only.captures().len());
910 assert_eq!("abc", only.as_str());
911 }
912
913 #[test]
914 fn word_boundary_matches_correctly_with_unicode_text() {
915 let set = RegexSet::new([r"foo", r"\bbar\b"]).unwrap();
919 let mut matches = set
920 .find_input(RegexInput::new("fooé bar"))
921 .unwrap()
922 .unwrap();
923
924 let first = matches.next().unwrap().unwrap();
926 assert_eq!(0, first.pattern());
927 assert_eq!("foo", first.as_str());
928 assert!(matches.next().is_none());
929
930 let mut matches2 = set
932 .find_input(RegexInput::new("fooé bar").from_pos(first.end()))
933 .unwrap()
934 .unwrap();
935 let bar_match = matches2.next().unwrap().unwrap();
936 assert_eq!(1, bar_match.pattern());
937 assert_eq!("bar", bar_match.as_str());
938 assert!(matches2.next().is_none());
939 }
940
941 #[test]
942 fn find_input_continue_from_prev_match_inside_negative_lookbehind() {
943 let options = &mut RegexOptionsBuilder::new();
944 let options = options.allow_input_assertion_overrides(true);
945 let set = RegexSet::new_with_options([r"(?<!\G)b"], &options).unwrap();
946 let mut matches = set
947 .find_input(RegexInput::new("ab").continue_from_previous_match_end(false))
948 .unwrap()
949 .unwrap();
950
951 let first = matches.next().unwrap().unwrap();
952 assert_eq!(0, first.pattern());
953 assert_eq!("b", first.as_str());
954 assert!(matches.next().is_none());
955 }
956
957 #[test]
958 fn continue_from_prev_match_works_as_expected_when_match_is_not_at_search_start() {
959 use crate::Arc;
960 use crate::RegexBuilder;
961
962 let (pat, hay) = (r"\Gx", "yx");
963
964 let re = RegexBuilder::new(pat).build().unwrap();
966 let single = re
967 .find_input(RegexInput::new(hay).from_pos(0))
968 .unwrap()
969 .map(|m| (m.start(), m.end()));
970
971 let set = RegexSet::from_regexes([Arc::new(re)], Default::default()).unwrap();
973 let via_set = set
974 .find_input(RegexInput::new(hay).from_pos(0))
975 .unwrap()
976 .and_then(|mut it| it.next())
977 .map(|m| {
978 let m = m.unwrap();
979 (m.start(), m.end())
980 });
981
982 assert_eq!(
983 single, via_set,
984 "RegexSet should behave the same as a standalone regex"
985 );
986 assert_eq!(single, None);
987 }
988
989 #[test]
990 fn continue_from_prev_match_works_as_expected_when_match_is_at_search_start() {
991 use crate::Arc;
992 use crate::RegexBuilder;
993
994 let (pat, hay) = (r"\Gx", "yx");
995
996 let re = RegexBuilder::new(pat).build().unwrap();
997 let single = re
998 .find_input(RegexInput::new(hay).from_pos(1))
999 .unwrap()
1000 .map(|m| (m.start(), m.end()));
1001
1002 let set = RegexSet::from_regexes([Arc::new(re)], Default::default()).unwrap();
1004 let via_set = set
1005 .find_input(RegexInput::new(hay).from_pos(1))
1006 .unwrap()
1007 .and_then(|mut it| it.next())
1008 .map(|m| {
1009 let m = m.unwrap();
1010 (m.start(), m.end())
1011 });
1012
1013 assert_eq!(
1014 single, via_set,
1015 "RegexSet should behave the same as a standalone regex"
1016 );
1017 assert_eq!(single, Some((1, 2)));
1018 }
1019}