1use std::num::NonZeroUsize;
2
3use smallvec::SmallVec;
4
5use crate::{Literal, Options, Ranges, Token, Tokens};
6
7pub fn is_match(haystack: &str, tokens: &Tokens, options: Options) -> bool {
15 match options.case_insensitive {
16 false => is_match_impl::<CaseSensitive>(haystack, tokens.as_slice()),
17 true => is_match_impl::<CaseInsensitive>(haystack, tokens.as_slice()),
18 }
19}
20
21#[inline(always)]
22fn is_match_impl<'a, M>(haystack: &'a str, tokens: &'a [Token]) -> bool
23where
24 M: Matcher,
25{
26 if tokens.is_empty() {
28 return false;
29 }
30
31 let mut frames: SmallVec<[Frame<'a>; 5]> = smallvec::smallvec![Frame::root(haystack, tokens)];
37
38 let mut matched = true;
40
41 loop {
42 let Some(frame) = frames.last_mut() else {
43 return false;
45 };
46
47 let new_frame = loop {
54 if !matched {
55 if frame.t_revert == 0 {
56 break None;
59 }
60 frame.h_current = frame.h_revert;
61 frame.t_next = frame.t_revert;
62
63 match n_chars_to_bytes(NonZeroUsize::MIN, frame.h_current) {
65 Some(n) => frame.h_current = &frame.h_current[n..],
66 None => break None,
68 }
69
70 if !frame.skip_to_next_token::<M>() {
71 break None;
72 }
73 }
74
75 if frame.t_next == frame.stream.len() {
76 if frame.h_current.is_empty() {
77 return true;
79 }
80 matched = false;
82 continue;
83 }
84
85 let token = frame.stream.get(frame.t_next);
86 frame.t_next += 1;
87
88 matched = match token {
89 Token::Literal(literal) => match M::is_prefix(frame.h_current, literal) {
90 Some(n) => {
91 frame.h_current = &frame.h_current[n..];
92 true
93 }
94 None => false,
99 },
100 Token::Any(n) => match n_chars_to_bytes(*n, frame.h_current) {
101 Some(n) => {
102 frame.h_current = &frame.h_current[n..];
103 true
104 }
105 None => break None,
109 },
110 Token::Wildcard => {
111 if frame.t_next == frame.stream.len() {
113 return true;
114 }
115
116 frame.t_revert = frame.t_next;
117
118 if !frame.skip_to_next_token::<M>() {
119 break None;
120 }
121 true
122 }
123 Token::Class { negated, ranges } => match frame.h_current.chars().next() {
124 Some(next) if M::ranges_match(next, *negated, ranges) => {
125 frame.h_current = &frame.h_current[next.len_utf8()..];
126 true
127 }
128 _ => false,
129 },
130 Token::Alternates(alternates) => {
133 break Some(frame.new_branch(alternates.as_slice(), false));
134 }
135 Token::Optional(optional) => {
136 break Some(frame.new_branch(std::slice::from_ref(optional), true));
137 }
138 };
139 };
140
141 match new_frame {
142 Some(mut new_frame) => {
144 if new_frame.enter_next_alternate() {
145 frames.push(new_frame);
146 matched = true;
147 } else {
148 matched = new_frame.optional;
154 }
155 }
156 None if frame.enter_next_alternate() => {
158 matched = true;
159 }
160 None => {
162 matched = frame.optional;
168 frames.pop();
169 }
170 }
171 }
172}
173
174trait Matcher {
176 fn is_prefix(haystack: &str, needle: &Literal) -> Option<usize>;
178 fn find(haystack: &str, needle: &Literal) -> Option<(usize, usize)>;
181 fn ranges_match(c: char, negated: bool, ranges: &Ranges) -> bool;
183 #[inline(always)]
188 fn ranges_find(haystack: &str, negated: bool, ranges: &Ranges) -> Option<(usize, char)> {
189 haystack
192 .char_indices()
193 .find(|&(_, c)| Self::ranges_match(c, negated, ranges))
194 }
195}
196
197struct CaseSensitive;
199
200impl Matcher for CaseSensitive {
201 #[inline(always)]
202 fn is_prefix(haystack: &str, needle: &Literal) -> Option<usize> {
203 let needle = needle.as_case_converted_bytes();
204 memchr::arch::all::is_prefix(haystack.as_bytes(), needle).then_some(needle.len())
205 }
206
207 #[inline(always)]
208 fn find(haystack: &str, needle: &Literal) -> Option<(usize, usize)> {
209 let needle = needle.as_case_converted_bytes();
210 memchr::memmem::find(haystack.as_bytes(), needle).map(|offset| (offset, needle.len()))
211 }
212
213 #[inline(always)]
214 fn ranges_match(c: char, negated: bool, ranges: &Ranges) -> bool {
215 ranges.contains(c) ^ negated
216 }
217}
218
219struct CaseInsensitive;
221
222impl Matcher for CaseInsensitive {
223 #[inline(always)]
224 fn is_prefix(haystack: &str, needle: &Literal) -> Option<usize> {
225 let needle = needle.as_case_converted_bytes();
233 let lower_haystack = haystack.to_lowercase();
234
235 memchr::arch::all::is_prefix(lower_haystack.as_bytes(), needle)
236 .then(|| recover_offset_len(haystack, 0, needle.len()).1)
237 }
238
239 #[inline(always)]
240 fn find(haystack: &str, needle: &Literal) -> Option<(usize, usize)> {
241 let needle = needle.as_case_converted_bytes();
246 let lower_haystack = haystack.to_lowercase();
247
248 let offset = memchr::memmem::find(lower_haystack.as_bytes(), needle)?;
249
250 Some(recover_offset_len(haystack, offset, offset + needle.len()))
253 }
254
255 #[inline(always)]
256 fn ranges_match(c: char, negated: bool, ranges: &Ranges) -> bool {
257 let matches = exactly_one(c.to_lowercase()).is_some_and(|c| ranges.contains(c))
258 || exactly_one(c.to_uppercase()).is_some_and(|c| ranges.contains(c));
259 matches ^ negated
260 }
261}
262
263#[inline(always)]
269fn skip_to_token<'a, M>(next: &Token, haystack: &'a str) -> Option<(bool, &'a str, &'a str)>
270where
271 M: Matcher,
272{
273 Some(match next {
277 Token::Literal(literal) => {
278 match M::find(haystack, literal) {
279 Some((offset, len)) => (true, &haystack[offset..], &haystack[offset + len..]),
282 None => return None,
285 }
286 }
287 Token::Class { negated, ranges } => {
288 match M::ranges_find(haystack, *negated, ranges) {
289 Some((offset, c)) => (
290 true,
291 &haystack[offset..],
292 &haystack[offset + c.len_utf8()..],
293 ),
294 None => return None,
297 }
298 }
299 _ => {
300 (false, haystack, haystack)
303 }
304 })
305}
306
307#[inline(always)]
311fn n_chars_to_bytes(n: NonZeroUsize, s: &str) -> Option<usize> {
312 if n.get() > s.len() {
314 return None;
315 }
316 s.char_indices()
317 .nth(n.get() - 1)
318 .map(|(i, c)| i + c.len_utf8())
319}
320
321#[inline(always)]
323fn exactly_one<T>(mut iter: impl Iterator<Item = T>) -> Option<T> {
324 let item = iter.next()?;
325 match iter.next() {
326 Some(_) => None,
327 None => Some(item),
328 }
329}
330
331#[inline(always)]
339fn recover_offset_len(
340 haystack: &str,
341 lower_offset: usize,
342 lower_offset_end: usize,
343) -> (usize, usize) {
344 haystack
345 .chars()
346 .try_fold((0, 0, 0), |(lower, h_offset, h_len), c| {
347 let lower = lower + c.to_lowercase().map(|c| c.len_utf8()).sum::<usize>();
348
349 if lower <= lower_offset {
350 Ok((lower, h_offset + c.len_utf8(), 0))
351 } else if lower <= lower_offset_end {
352 Ok((lower, h_offset, h_len + c.len_utf8()))
353 } else {
354 Err((h_offset, h_len))
355 }
356 })
357 .map_or_else(|e| e, |(_, offset, len)| (offset, len))
358}
359
360#[derive(Default, Clone, Copy, Debug)]
362struct TokenStream<'a> {
363 alternate: &'a [Token],
367 tokens: &'a [Token],
369}
370
371impl<'a> TokenStream<'a> {
372 #[inline(always)]
374 fn len(&self) -> usize {
375 self.alternate.len() + self.tokens.len()
376 }
377
378 #[inline(always)]
382 fn get(&self, index: usize) -> &'a Token {
383 match self.alternate.get(index) {
384 Some(token) => token,
385 None => &self.tokens[index - self.alternate.len()],
386 }
387 }
388
389 fn base_suffix(&self, t_next: usize) -> &'a [Token] {
391 match t_next.checked_sub(self.alternate.len()) {
392 Some(offset) => &self.tokens[offset..],
393 None => unreachable!("No nested alternates"),
396 }
397 }
398}
399
400struct Frame<'a> {
410 stream: TokenStream<'a>,
412 h_current: &'a str,
414 h_revert: &'a str,
416 t_next: usize,
418 t_revert: usize,
422
423 haystack: &'a str,
426 alternates: std::slice::Iter<'a, Tokens>,
430 optional: bool,
435 base: &'a [Token],
439}
440
441impl<'a> Frame<'a> {
442 fn root(haystack: &'a str, tokens: &'a [Token]) -> Self {
444 Self {
445 stream: TokenStream {
446 alternate: &[],
447 tokens,
448 },
449 h_current: haystack,
450 h_revert: haystack,
451 t_next: 0,
452 t_revert: 0,
453 alternates: Default::default(),
454 optional: false,
455 haystack,
456 base: &[],
457 }
458 }
459
460 fn new_branch(&self, alternates: &'a [Tokens], optional: bool) -> Self {
465 Self {
466 stream: TokenStream::default(),
467 h_current: self.h_current,
468 h_revert: self.h_current,
469 t_next: 0,
470 t_revert: 0,
471 alternates: alternates.iter(),
472 optional,
473 haystack: self.h_current,
474 base: self.stream.base_suffix(self.t_next),
475 }
476 }
477
478 fn enter_next_alternate(&mut self) -> bool {
482 let Some(branch) = self.alternates.next() else {
483 return false;
484 };
485
486 self.stream = TokenStream {
487 alternate: branch.as_slice(),
488 tokens: self.base,
489 };
490 self.h_current = self.haystack;
491 self.h_revert = self.haystack;
492 self.t_next = 0;
493 self.t_revert = 0;
494
495 true
496 }
497
498 #[inline(always)]
504 fn skip_to_next_token<M: Matcher>(&mut self) -> bool {
505 let next = self.stream.get(self.t_next);
506
507 match skip_to_token::<M>(next, self.h_current) {
508 Some((consumed, revert, remaining)) => {
509 self.t_next += consumed as usize;
510 self.h_revert = revert;
511 self.h_current = remaining;
512 true
513 }
514 None => false,
515 }
516 }
517}
518
519#[cfg(test)]
521mod tests {
522 use super::*;
523 use crate::{Range, Ranges};
524
525 fn literal(s: &str) -> Literal {
526 Literal::new(s.to_owned(), Default::default())
527 }
528
529 fn literal_ci(s: &str) -> Literal {
530 Literal::new(
531 s.to_owned(),
532 Options {
533 case_insensitive: true,
534 },
535 )
536 }
537
538 fn range(start: char, end: char) -> Ranges {
539 Ranges::Single(Range { start, end })
540 }
541
542 #[test]
543 fn test_exactly_one() {
544 assert_eq!(exactly_one([].into_iter()), None::<i32>);
545 assert_eq!(exactly_one([1].into_iter()), Some(1));
546 assert_eq!(exactly_one([1, 2].into_iter()), None);
547 assert_eq!(exactly_one([1, 2, 3].into_iter()), None);
548 }
549
550 #[test]
551 fn test_literal() {
552 let mut tokens = Tokens::default();
553 tokens.push(Token::Literal(literal("abc")));
554 assert!(is_match("abc", &tokens, Default::default()));
555 assert!(!is_match("abcd", &tokens, Default::default()));
556 assert!(!is_match("bc", &tokens, Default::default()));
557 }
558
559 #[test]
560 fn test_class() {
561 let mut tokens = Tokens::default();
562 tokens.push(Token::Literal(literal("a")));
563 tokens.push(Token::Class {
564 negated: false,
565 ranges: Ranges::Single(Range::single('b')),
566 });
567 tokens.push(Token::Literal(literal("c")));
568 assert!(is_match("abc", &tokens, Default::default()));
569 assert!(!is_match("aac", &tokens, Default::default()));
570 assert!(!is_match("abbc", &tokens, Default::default()));
571 }
572
573 #[test]
574 fn test_class_negated() {
575 let mut tokens = Tokens::default();
576 tokens.push(Token::Literal(literal("a")));
577 tokens.push(Token::Class {
578 negated: true,
579 ranges: Ranges::Single(Range::single('b')),
580 });
581 tokens.push(Token::Literal(literal("c")));
582 assert!(!is_match("abc", &tokens, Default::default()));
583 assert!(is_match("aac", &tokens, Default::default()));
584 assert!(!is_match("abbc", &tokens, Default::default()));
585 }
586
587 #[test]
588 fn test_any_one() {
589 let mut tokens = Tokens::default();
590 tokens.push(Token::Literal(literal("a")));
591 tokens.push(Token::Any(NonZeroUsize::MIN));
592 tokens.push(Token::Literal(literal("c")));
593 assert!(is_match("abc", &tokens, Default::default()));
594 assert!(is_match("aඞc", &tokens, Default::default()));
595 assert!(!is_match("abbc", &tokens, Default::default()));
596 }
597
598 #[test]
599 fn test_any_many() {
600 let mut tokens = Tokens::default();
601 tokens.push(Token::Literal(literal("a")));
602 tokens.push(Token::Any(NonZeroUsize::new(2).unwrap()));
603 tokens.push(Token::Literal(literal("d")));
604 assert!(is_match("abcd", &tokens, Default::default()));
605 assert!(is_match("aඞ_d", &tokens, Default::default()));
606 assert!(!is_match("abbc", &tokens, Default::default()));
607 assert!(!is_match("abcde", &tokens, Default::default()));
608 assert!(!is_match("abc", &tokens, Default::default()));
609 assert!(!is_match("bcd", &tokens, Default::default()));
610 }
611
612 #[test]
613 fn test_any_unicode() {
614 let mut tokens = Tokens::default();
615 tokens.push(Token::Literal(literal("a")));
616 tokens.push(Token::Any(NonZeroUsize::new(3).unwrap()));
617 tokens.push(Token::Literal(literal("a")));
618 assert!(is_match("abbba", &tokens, Default::default()));
619 assert!(is_match("aඞbඞa", &tokens, Default::default()));
620 assert!(is_match("aඞi̇a", &tokens, Default::default()));
622 assert!(!is_match("aඞi̇ඞa", &tokens, Default::default()));
623 }
624
625 #[test]
626 fn test_wildcard_start() {
627 let mut tokens = Tokens::default();
628 tokens.push(Token::Wildcard);
629 tokens.push(Token::Literal(literal("b")));
630 assert!(is_match("b", &tokens, Default::default()));
631 assert!(is_match("aaaab", &tokens, Default::default()));
632 assert!(is_match("ඞb", &tokens, Default::default()));
633 assert!(is_match("bbbbbbbbb", &tokens, Default::default()));
634 assert!(!is_match("", &tokens, Default::default()));
635 assert!(!is_match("a", &tokens, Default::default()));
636 assert!(!is_match("aa", &tokens, Default::default()));
637 assert!(!is_match("aaa", &tokens, Default::default()));
638 assert!(!is_match("ba", &tokens, Default::default()));
639 }
640
641 #[test]
642 fn test_wildcard_end() {
643 let mut tokens = Tokens::default();
644 tokens.push(Token::Literal(literal("a")));
645 tokens.push(Token::Wildcard);
646 assert!(is_match("a", &tokens, Default::default()));
647 assert!(is_match("aaaab", &tokens, Default::default()));
648 assert!(is_match("aඞ", &tokens, Default::default()));
649 assert!(!is_match("", &tokens, Default::default()));
650 assert!(!is_match("b", &tokens, Default::default()));
651 assert!(!is_match("bb", &tokens, Default::default()));
652 assert!(!is_match("bbb", &tokens, Default::default()));
653 assert!(!is_match("ba", &tokens, Default::default()));
654 }
655
656 #[test]
657 fn test_wildcard_end_unicode_case_insensitive() {
658 let options = Options {
659 case_insensitive: true,
660 };
661 let mut tokens = Tokens::default();
662 tokens.push(Token::Literal(Literal::new("İ".to_owned(), options)));
663 tokens.push(Token::Wildcard);
664
665 assert!(is_match("İ___", &tokens, options));
666 assert!(is_match("İ", &tokens, options));
667 assert!(is_match("i̇", &tokens, options));
668 assert!(is_match("i\u{307}___", &tokens, options));
669 assert!(!is_match("i____", &tokens, options));
670 }
671
672 #[test]
673 fn test_alternate() {
674 let mut tokens = Tokens::default();
675 tokens.push(Token::Literal(literal("a")));
676 tokens.push(Token::Alternates(vec![
677 {
678 let mut tokens = Tokens::default();
679 tokens.push(Token::Literal(literal("b")));
680 tokens
681 },
682 {
683 let mut tokens = Tokens::default();
684 tokens.push(Token::Literal(literal("c")));
685 tokens
686 },
687 ]));
688 tokens.push(Token::Literal(literal("a")));
689 assert!(is_match("aba", &tokens, Default::default()));
690 assert!(is_match("aca", &tokens, Default::default()));
691 assert!(!is_match("ada", &tokens, Default::default()));
692 }
693
694 #[test]
695 fn test_optional() {
696 let mut tokens = Tokens::default();
697
698 tokens.push(Token::Optional(Tokens(vec![Token::Literal(literal(
699 "foo",
700 ))])));
701 assert!(is_match("foo", &tokens, Default::default()));
702 assert!(is_match("", &tokens, Default::default()));
703 }
704
705 #[test]
706 fn test_optional_alternate() {
707 let mut tokens = Tokens::default();
708 let alternates = Token::Alternates(vec![
709 {
710 let mut tokens = Tokens::default();
711 tokens.push(Token::Literal(literal("foo")));
712 tokens
713 },
714 {
715 let mut tokens = Tokens::default();
716 tokens.push(Token::Literal(literal("bar")));
717 tokens
718 },
719 ]);
720
721 tokens.push(Token::Optional(Tokens(vec![alternates])));
722 assert!(is_match("foo", &tokens, Default::default()));
723 assert!(is_match("bar", &tokens, Default::default()));
724 assert!(is_match("", &tokens, Default::default()));
725 }
726
727 #[test]
728 fn test_matcher_case_sensitive_prefix() {
729 macro_rules! test {
730 ($haystack:expr, $needle:expr, $result:expr) => {
731 assert_eq!(
732 CaseSensitive::is_prefix($haystack, &literal($needle)),
733 $result
734 );
735 };
736 }
737
738 test!("foobar", "f", Some(1));
739 test!("foobar", "foo", Some(3));
740 test!("foobar", "foobar", Some(6));
741 test!("foobar", "oobar", None);
742 test!("foobar", "foobar2", None);
743 test!("İ", "İ", Some(2));
744 test!("İ", "i", None);
745 test!("i", "İ", None);
746 test!("i", "i", Some(1));
747 test!("i̇", "i", Some(1));
748 test!("i̇", "i\u{307}", Some(3));
749 test!("i̇x", "i\u{307}", Some(3));
750 test!("i̇x", "i\u{307}x", Some(4));
751 test!("i̇x", "i\u{307}_", None);
752 }
753
754 #[test]
755 fn test_matcher_case_sensitive_find() {
756 macro_rules! test {
757 ($haystack:expr, $needle:expr, $result:expr) => {
758 assert_eq!(CaseSensitive::find($haystack, &literal($needle)), $result);
759 };
760 }
761
762 test!("foobar", "f", Some((0, 1)));
763 test!("foobar", "foo", Some((0, 3)));
764 test!("foobar", "foobar", Some((0, 6)));
765 test!("foobar", "bar", Some((3, 3)));
766 test!("foobar", "oobar", Some((1, 5)));
767 test!("foobar", "foobar2", None);
768 test!("İ", "İ", Some((0, 2)));
769 test!("i", "i", Some((0, 1)));
770 test!("i̇", "i\u{307}", Some((0, 3)));
771 test!("i̇x", "i\u{307}x", Some((0, 4)));
772 test!("i̇x", "i\u{307}_", None);
773 test!("xi̇x", "i\u{307}", Some((1, 3)));
774 test!("xi̇ඞi̇x", "ඞ", Some((4, 3)));
775 test!("xi̇ඞi̇x", "ඞi̇", Some((4, 6)));
776 }
777
778 #[test]
779 fn test_matcher_case_sensitive_ranges_match() {
780 macro_rules! test {
781 ($c:expr, $negated:expr, [$start:literal - $end:literal], $result:expr) => {
782 assert_eq!(
783 CaseSensitive::ranges_match($c, $negated, &range($start, $end)),
784 $result
785 );
786 };
787 }
788
789 test!('a', false, ['a' - 'a'], true);
790 test!('a', true, ['a' - 'a'], false);
791 test!('b', false, ['a' - 'a'], false);
792 test!('b', true, ['a' - 'a'], true);
793 test!('b', false, ['a' - 'c'], true);
794 test!('b', false, ['b' - 'c'], true);
795 test!('b', false, ['a' - 'b'], true);
796
797 test!('A', false, ['a' - 'a'], false);
798 test!('ඞ', false, ['ඞ' - 'ඞ'], true);
799 }
800
801 #[test]
802 fn test_matcher_case_sensitive_ranges_find() {
803 macro_rules! test {
804 ($haystack:expr, $negated:expr, [$start:literal - $end:literal], $result:expr) => {
805 assert_eq!(
806 CaseSensitive::ranges_find($haystack, $negated, &range($start, $end)),
807 $result
808 );
809 };
810 }
811
812 test!("ඞaඞ", false, ['a' - 'a'], Some((3, 'a')));
813 test!("a", true, ['a' - 'a'], None);
814 test!("ඞaඞ", true, ['a' - 'a'], Some((0, 'ඞ')));
815 test!("aඞaඞ", true, ['a' - 'a'], Some((1, 'ඞ')));
816 test!("ඞbඞ", false, ['a' - 'a'], None);
817 test!("ඞbඞ", true, ['ඞ' - 'ඞ'], Some((3, 'b')));
818 test!("ඞbඞ", false, ['a' - 'c'], Some((3, 'b')));
819 test!("ඞbඞ", false, ['b' - 'c'], Some((3, 'b')));
820 test!("ඞbඞ", false, ['a' - 'b'], Some((3, 'b')));
821 test!("AAAAA", false, ['a' - 'a'], None);
822 test!("aaaaaaabb", true, ['a' - 'a'], Some((7, 'b')));
823 test!("AaaaaaAbb", false, ['b' - 'b'], Some((7, 'b')));
824 }
825
826 #[test]
827 fn test_matcher_case_insensitive_prefix() {
828 macro_rules! test {
829 ($haystack:expr, $needle:expr, $result:expr) => {
830 assert_eq!(
831 CaseInsensitive::is_prefix($haystack, &literal_ci($needle)),
832 $result
833 );
834 };
835 }
836
837 test!("foobar", "f", Some(1));
838 test!("foobar", "F", Some(1));
839 test!("fOobar", "foo", Some(3));
840 test!("fooBAR", "foobar", Some(6));
841 test!("foobar", "oobar", None);
842 test!("FOOBAR", "oobar", None);
843 test!("foobar", "foobar2", None);
844 test!("İ", "İ", Some(2));
845 test!("İ", "i", Some(0));
846 test!("İ", "i̇", Some(2));
847 test!("i", "İ", None);
848 test!("i", "i", Some(1));
849 test!("i̇", "i", Some(1));
850 test!("i̇", "i\u{307}", Some(3));
851 test!("i̇x", "i\u{307}", Some(3));
852 test!("i̇x", "i\u{307}x", Some(4));
853 test!("i̇x", "i\u{307}_", None);
854 }
855
856 #[test]
857 fn test_matcher_case_insensitive_find() {
858 macro_rules! test {
859 ($haystack:expr, $needle:expr, $result:expr) => {
860 assert_eq!(
861 CaseInsensitive::find($haystack, &literal_ci($needle)),
862 $result
863 );
864 };
865 }
866
867 test!("Foobar", "f", Some((0, 1)));
868 test!("foObar", "FOO", Some((0, 3)));
869 test!("foObar", "Foobar", Some((0, 6)));
870 test!("foObar", "bar", Some((3, 3)));
871 test!("foObarx", "bar", Some((3, 3)));
872 test!("foObarbarbar", "bar", Some((3, 3)));
873 test!("foObar", "Oobar", Some((1, 5)));
874 test!("foObar", "Foobar2", None);
875 test!("İ", "İ", Some((0, 2)));
876 test!("i", "i", Some((0, 1)));
877 test!("i̇", "i\u{307}", Some((0, 3)));
878 test!("i̇x", "i\u{307}x", Some((0, 4)));
879 test!("i̇x", "i\u{307}_", None);
880 test!("xi̇x", "i\u{307}", Some((1, 3)));
881 test!("xi̇ඞi̇x", "ඞ", Some((4, 3)));
882 test!("xi̇ඞi̇x", "ඞi̇", Some((4, 6)));
883 test!("xi̇ඞİx", "ඞi̇", Some((4, 5)));
884 }
885
886 #[test]
887 fn test_matcher_case_insensitive_ranges_match() {
888 macro_rules! test {
889 ($c:expr, $negated:expr, [$start:literal - $end:literal], $result:expr) => {
890 assert_eq!(
891 CaseInsensitive::ranges_match($c, $negated, &range($start, $end)),
892 $result
893 );
894 };
895 }
896
897 test!('a', false, ['a' - 'a'], true);
898 test!('a', true, ['a' - 'a'], false);
899 test!('b', false, ['a' - 'a'], false);
900 test!('b', true, ['a' - 'a'], true);
901 test!('b', false, ['a' - 'c'], true);
902 test!('b', false, ['b' - 'c'], true);
903 test!('b', false, ['a' - 'b'], true);
904
905 test!('b', false, ['A' - 'A'], false);
906 test!('b', true, ['A' - 'A'], true);
907 test!('b', false, ['A' - 'C'], true);
908 test!('b', false, ['B' - 'C'], true);
909 test!('b', false, ['A' - 'B'], true);
910
911 test!('B', false, ['a' - 'a'], false);
912 test!('B', true, ['a' - 'a'], true);
913 test!('B', false, ['a' - 'c'], true);
914 test!('B', false, ['b' - 'c'], true);
915 test!('B', false, ['a' - 'b'], true);
916
917 test!('ǧ', false, ['Ǧ' - 'Ǧ'], true);
918 test!('Ǧ', false, ['ǧ' - 'ǧ'], true);
919 test!('ǧ', true, ['Ǧ' - 'Ǧ'], false);
920 test!('Ǧ', true, ['ǧ' - 'ǧ'], false);
921
922 test!('ඞ', false, ['ඞ' - 'ඞ'], true);
923 }
924
925 #[test]
926 fn test_matcher_case_insensitive_ranges_find() {
927 macro_rules! test {
928 ($haystack:expr, $negated:expr, [$start:literal - $end:literal], $result:expr) => {
929 assert_eq!(
930 CaseInsensitive::ranges_find($haystack, $negated, &range($start, $end)),
931 $result
932 );
933 };
934 }
935
936 test!("ඞaඞ", false, ['a' - 'a'], Some((3, 'a')));
937 test!("a", true, ['a' - 'a'], None);
938 test!("ඞaඞ", true, ['a' - 'a'], Some((0, 'ඞ')));
939 test!("aඞaඞ", true, ['a' - 'a'], Some((1, 'ඞ')));
940 test!("ඞbඞ", false, ['a' - 'a'], None);
941 test!("ඞbඞ", true, ['ඞ' - 'ඞ'], Some((3, 'b')));
942 test!("ඞbඞ", false, ['a' - 'c'], Some((3, 'b')));
943 test!("ඞbඞ", false, ['b' - 'c'], Some((3, 'b')));
944 test!("ඞbඞ", false, ['a' - 'b'], Some((3, 'b')));
945 test!("AAAAA", false, ['a' - 'a'], Some((0, 'A')));
946 test!("aaaaaaabb", true, ['a' - 'a'], Some((7, 'b')));
947 test!("AaaaaaAbb", false, ['b' - 'b'], Some((7, 'b')));
948
949 test!("ඞBඞ", false, ['a' - 'a'], None);
950 test!("ඞBඞ", true, ['ඞ' - 'ඞ'], Some((3, 'B')));
951 test!("ඞBඞ", false, ['a' - 'c'], Some((3, 'B')));
952 test!("ඞBඞ", false, ['b' - 'c'], Some((3, 'B')));
953 test!("ඞBඞ", false, ['a' - 'b'], Some((3, 'B')));
954
955 test!("ඞbඞ", false, ['A' - 'A'], None);
956 test!("ඞbඞ", true, ['ඞ' - 'ඞ'], Some((3, 'b')));
957 test!("ඞbඞ", false, ['A' - 'C'], Some((3, 'b')));
958 test!("ඞbඞ", false, ['B' - 'C'], Some((3, 'b')));
959 test!("ඞbඞ", false, ['A' - 'B'], Some((3, 'b')));
960
961 test!("fඞoǧbar", false, ['ǧ' - 'ǧ'], Some((5, 'ǧ')));
962 test!("fඞoǧbar", false, ['Ǧ' - 'Ǧ'], Some((5, 'ǧ')));
963 test!("fඞoǦbar", false, ['ǧ' - 'ǧ'], Some((5, 'Ǧ')));
964 }
965}