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