1use paste::paste;
2
3use crate::{
4 CharStringExt, Lrc, Span, Token, TokenKind,
5 expr::{AsBoxedExpr, FirstMatchOf, FixedPhrase, LongestMatchOf},
6 patterns::{AnyPattern, IndefiniteArticle, RelativePronoun, WhitespacePattern, Word, WordSet},
7};
8
9use super::{Expr, Optional, OwnedExprExt, Repeating, Step, UnlessStep};
10
11pub struct SequenceExpr {
12 exprs: Vec<Box<dyn Expr>>,
13 capture_range: Span<Box<dyn Expr>>,
14}
15
16impl Default for SequenceExpr {
17 fn default() -> Self {
18 Self {
19 exprs: Default::default(),
20 capture_range: Span::FULL,
21 }
22 }
23}
24
25macro_rules! gen_then_from_is {
27 ($quality:ident) => {
28 paste! {
29 #[doc = concat!("Adds a step matching a token where [`TokenKind::is_", stringify!($quality), "()`] returns true.")]
30 pub fn [< then_$quality >] (self) -> Self{
31 self.then_kind_where(|kind| {
32 kind.[< is_$quality >]()
33 })
34 }
35
36 #[doc = concat!("Adds an optional step matching a token where [`TokenKind::is_", stringify!($quality), "()`] returns true.")]
37 pub fn [< then_optional_$quality >] (self) -> Self{
38 self.then_optional(|tok: &Token, _source: &[char]| {
39 tok.kind.[< is_$quality >]()
40 })
41 }
42
43 #[doc = concat!("Adds a step matching one or more consecutive tokens where [`TokenKind::is_", stringify!($quality), "()`] returns true.")]
44 pub fn [< then_one_or_more_$quality s >] (self) -> Self{
45 self.then_one_or_more(Box::new(|tok: &Token, _source: &[char]| {
46 tok.kind.[< is_$quality >]()
47 }))
48 }
49
50 #[doc = concat!("Adds a step matching a token where [`TokenKind::is_", stringify!($quality), "()`] returns false.")]
51 pub fn [< then_anything_but_$quality >] (self) -> Self{
52 self.then_kind_where(|kind| {
53 !kind.[< is_$quality >]()
54 })
55 }
56 }
57 };
58}
59
60impl Expr for SequenceExpr {
61 fn run(&self, mut cursor: usize, tokens: &[Token], source: &[char]) -> Option<Span<Token>> {
65 let mut window = Span::empty(cursor);
66
67 for (i, cur_expr) in self.exprs.iter().enumerate() {
68 let out = cur_expr.run(cursor, tokens, source)?;
69
70 if out.is_empty() {
73 continue;
75 }
76
77 if out.end > cursor {
79 cursor = out.end;
80 } else if out.start < cursor {
81 cursor = out.start;
82 }
83
84 if self.capture_range.start == i {
85 window = Span::empty(cursor);
90 } else if !self.capture_range.contains(i) {
91 continue;
93 }
94
95 window.expand_to_include(out.start);
96 window.expand_to_include(out.end - 1);
97 }
98
99 Some(window)
100 }
101}
102
103impl SequenceExpr {
104 pub fn with(expr: impl Expr + 'static) -> Self {
108 Self::default().then(expr)
109 }
110
111 pub fn anything() -> Self {
115 Self::default().then_anything()
116 }
117
118 pub fn any_capitalization_of(word: &'static str) -> Self {
122 Self::default().then_any_capitalization_of(word)
123 }
124
125 pub fn aco(word: &'static str) -> Self {
127 Self::any_capitalization_of(word)
128 }
129
130 pub fn word_set<I, S>(words: I) -> Self
132 where
133 I: IntoIterator<Item = S>,
134 S: AsRef<str>,
135 {
136 Self::default().then_word_set(words)
137 }
138
139 pub fn any_word() -> Self {
141 Self::default().then_any_word()
142 }
143
144 pub fn number() -> Self {
146 Self::default().then_number()
147 }
148
149 pub fn optional(expr: impl Expr + 'static) -> Self {
153 Self::default().then_optional(expr)
154 }
155
156 pub fn word_seq(words: &'static [&'static str]) -> Self {
158 Self::default().then_word_seq(words)
159 }
160
161 pub fn fixed_phrase(phrase: &'static str) -> Self {
163 Self::default().then_fixed_phrase(phrase)
164 }
165
166 pub fn any_of(exprs: impl IntoIterator<Item = impl AsBoxedExpr>) -> Self {
170 Self::default().then_any_of(exprs)
171 }
172
173 pub fn longest_of(exprs: impl IntoIterator<Item = impl AsBoxedExpr>) -> Self {
175 Self::default().then_longest_of(exprs)
176 }
177
178 pub fn whitespace() -> Self {
179 Self::default().then_whitespace()
180 }
181
182 pub fn unless(condition: impl Expr + 'static) -> Self {
184 Self::default().then_unless(condition)
185 }
186
187 pub fn then(mut self, expr: impl Expr + 'static) -> Self {
191 self.exprs.push(Box::new(expr));
192 self
193 }
194
195 pub fn then_boxed(mut self, expr: Box<dyn Expr>) -> Self {
197 self.exprs.push(expr);
198 self
199 }
200
201 pub fn then_optional(mut self, expr: impl Expr + 'static) -> Self {
203 self.exprs.push(Box::new(Optional::new(expr)));
204 self
205 }
206
207 pub fn then_any_of(mut self, exprs: impl IntoIterator<Item = impl AsBoxedExpr>) -> Self {
213 self.exprs.push(Box::new(FirstMatchOf::new(exprs)));
214 self
215 }
216
217 pub fn then_longest_of(mut self, exprs: impl IntoIterator<Item = impl AsBoxedExpr>) -> Self {
222 self.exprs.push(Box::new(LongestMatchOf::new(exprs)));
223 self
224 }
225
226 pub fn then_seq(mut self, mut other: Self) -> Self {
229 self.exprs.append(&mut other.exprs);
230 self
231 }
232
233 pub fn then_word_set<I, S>(self, words: I) -> Self
235 where
236 I: IntoIterator<Item = S>,
237 S: AsRef<str>,
238 {
239 self.then(WordSet::new(words))
240 }
241
242 pub fn t_set<I, S>(self, words: I) -> Self
244 where
245 I: IntoIterator<Item = S>,
246 S: AsRef<str>,
247 {
248 self.then_word_set(words)
249 }
250
251 pub fn then_whitespace(self) -> Self {
253 self.then(WhitespacePattern)
254 }
255
256 pub fn t_ws(self) -> Self {
258 self.then_whitespace()
259 }
260
261 pub fn then_whitespace_or_hyphen(self) -> Self {
263 self.then(WhitespacePattern.or(|tok: &Token, _: &[char]| tok.kind.is_hyphen()))
264 }
265
266 pub fn t_ws_h(self) -> Self {
268 self.then_whitespace_or_hyphen()
269 }
270
271 pub fn then_optional_whitespace(self) -> Self {
273 self.then_optional(WhitespacePattern)
274 }
275
276 pub fn t_ows(self) -> Self {
278 self.then_optional_whitespace()
279 }
280
281 pub fn then_zero_or_more(self, expr: impl Expr + 'static) -> Self {
283 self.then(Repeating::new(Box::new(expr), 0))
284 }
285
286 pub fn then_one_or_more(self, expr: impl Expr + 'static) -> Self {
288 self.then(Repeating::new(Box::new(expr), 1))
289 }
290
291 pub fn then_zero_or_more_spaced(self, expr: impl Expr + 'static) -> Self {
293 let expr = Lrc::new(expr);
294 self.then(SequenceExpr::with(expr.clone()).then(Repeating::new(
295 Box::new(SequenceExpr::default().t_ws().then(expr)),
296 0,
297 )))
298 }
299
300 pub fn then_unless(self, condition: impl Expr + 'static) -> Self {
307 self.then(UnlessStep::new(condition, |_tok: &Token, _src: &[char]| {
308 true
309 }))
310 }
311
312 pub fn then_anything(self) -> Self {
316 self.then(AnyPattern)
317 }
318
319 pub fn t_any(self) -> Self {
323 self.then_anything()
324 }
325
326 pub fn then_any_word(self) -> Self {
330 self.then_kind_where(|kind| kind.is_word())
331 }
332
333 pub fn then_any_capitalization_of(self, word: &'static str) -> Self {
335 self.then(Word::new(word))
336 }
337
338 pub fn t_aco(self, word: &'static str) -> Self {
340 self.then_any_capitalization_of(word)
341 }
342
343 pub fn then_exact_word(self, word: &'static str) -> Self {
345 self.then(Word::new_exact(word))
346 }
347
348 pub fn then_word_seq(self, words: &'static [&'static str]) -> Self {
350 if let Some((first, rest)) = words.split_first() {
351 let mut expr = self.t_aco(first);
352 for word in rest {
353 expr = expr.t_ws().t_aco(word);
354 }
355 expr
356 } else {
357 self
358 }
359 }
360
361 pub fn then_fixed_phrase(self, phrase: &'static str) -> Self {
363 self.then(FixedPhrase::from_phrase(phrase))
364 }
365
366 pub fn then_word_except(self, words: &'static [&'static str]) -> Self {
368 self.then(move |tok: &Token, src: &[char]| {
369 !tok.kind.is_word() || !words.iter().any(|&word| tok.get_ch(src).eq_str(word))
370 })
371 }
372
373 pub fn then_kind(self, kind: TokenKind) -> Self {
379 self.then_kind_where(move |k| kind == *k)
380 }
381
382 pub fn then_kind_where<F>(mut self, predicate: F) -> Self
384 where
385 F: Fn(&TokenKind) -> bool + Send + Sync + 'static,
386 {
387 self.exprs
388 .push(Box::new(move |tok: &Token, _source: &[char]| {
389 predicate(&tok.kind)
390 }));
391 self
392 }
393
394 pub fn then_kind_except<F>(self, pred_is: F, ex: &'static [&'static str]) -> Self
396 where
397 F: Fn(&TokenKind) -> bool + Send + Sync + 'static,
398 {
399 self.then(move |tok: &Token, src: &[char]| {
400 pred_is(&tok.kind) && !ex.iter().any(|&word| tok.get_ch(src).eq_str(word))
401 })
402 }
403
404 pub fn then_kind_both<F1, F2>(self, pred_is_1: F1, pred_is_2: F2) -> Self
409 where
410 F1: Fn(&TokenKind) -> bool + Send + Sync + 'static,
411 F2: Fn(&TokenKind) -> bool + Send + Sync + 'static,
412 {
413 self.then_kind_where(move |k| pred_is_1(k) && pred_is_2(k))
414 }
415
416 pub fn then_kind_either<F1, F2>(self, pred_is_1: F1, pred_is_2: F2) -> Self
419 where
420 F1: Fn(&TokenKind) -> bool + Send + Sync + 'static,
421 F2: Fn(&TokenKind) -> bool + Send + Sync + 'static,
422 {
423 self.then_kind_where(move |k| pred_is_1(k) || pred_is_2(k))
424 }
425
426 pub fn then_kind_neither<F1, F2>(self, pred_isnt_1: F1, pred_isnt_2: F2) -> Self
429 where
430 F1: Fn(&TokenKind) -> bool + Send + Sync + 'static,
431 F2: Fn(&TokenKind) -> bool + Send + Sync + 'static,
432 {
433 self.then_kind_where(move |k| !pred_isnt_1(k) && !pred_isnt_2(k))
434 }
435
436 pub fn then_kind_is_but_is_not<F1, F2>(self, pred_is: F1, pred_not: F2) -> Self
439 where
440 F1: Fn(&TokenKind) -> bool + Send + Sync + 'static,
441 F2: Fn(&TokenKind) -> bool + Send + Sync + 'static,
442 {
443 self.then_kind_where(move |k| pred_is(k) && !pred_not(k))
444 }
445
446 pub fn then_kind_is_but_is_not_except<F1, F2>(
449 self,
450 pred_is: F1,
451 pred_not: F2,
452 ex: &'static [&'static str],
453 ) -> Self
454 where
455 F1: Fn(&TokenKind) -> bool + Send + Sync + 'static,
456 F2: Fn(&TokenKind) -> bool + Send + Sync + 'static,
457 {
458 self.then(move |tok: &Token, src: &[char]| {
459 pred_is(&tok.kind)
460 && !pred_not(&tok.kind)
461 && !ex.iter().any(|&word| tok.get_ch(src).eq_str(word))
462 })
463 }
464
465 pub fn then_kind_is_but_isnt_any_of<F1, F2>(
468 self,
469 pred_is: F1,
470 preds_isnt: &'static [F2],
471 ) -> Self
472 where
473 F1: Fn(&TokenKind) -> bool + Send + Sync + 'static,
474 F2: Fn(&TokenKind) -> bool + Send + Sync + 'static,
475 {
476 self.then_kind_where(move |k| pred_is(k) && !preds_isnt.iter().any(|pred| pred(k)))
477 }
478
479 pub fn then_kind_is_but_isnt_any_of_except<F1, F2>(
483 self,
484 pred_is: F1,
485 preds_isnt: &'static [F2],
486 ex: &'static [&'static str],
487 ) -> Self
488 where
489 F1: Fn(&TokenKind) -> bool + Send + Sync + 'static,
490 F2: Fn(&TokenKind) -> bool + Send + Sync + 'static,
491 {
492 self.then(move |tok: &Token, src: &[char]| {
493 pred_is(&tok.kind)
494 && !preds_isnt.iter().any(|pred| pred(&tok.kind))
495 && !ex.iter().any(|&word| tok.get_ch(src).eq_str(word))
496 })
497 }
498
499 pub fn then_kind_both_but_not<F1, F2, F3>(
505 self,
506 (pred_is_1, pred_is_2): (F1, F2),
507 pred_not: F3,
508 ) -> Self
509 where
510 F1: Fn(&TokenKind) -> bool + Send + Sync + 'static,
511 F2: Fn(&TokenKind) -> bool + Send + Sync + 'static,
512 F3: Fn(&TokenKind) -> bool + Send + Sync + 'static,
513 {
514 self.then_kind_where(move |k| pred_is_1(k) && pred_is_2(k) && !pred_not(k))
515 }
516
517 pub fn then_kind_any<F>(self, preds_is: &'static [F]) -> Self
520 where
521 F: Fn(&TokenKind) -> bool + Send + Sync + 'static,
522 {
523 self.then_kind_where(move |k| preds_is.iter().any(|pred| pred(k)))
524 }
525
526 pub fn then_kind_none_of<F>(self, preds_isnt: &'static [F]) -> Self
529 where
530 F: Fn(&TokenKind) -> bool + Send + Sync + 'static,
531 {
532 self.then_kind_where(move |k| preds_isnt.iter().all(|pred| !pred(k)))
533 }
534
535 pub fn then_kind_any_except<F>(
538 self,
539 preds_is: &'static [F],
540 ex: &'static [&'static str],
541 ) -> Self
542 where
543 F: Fn(&TokenKind) -> bool + Send + Sync + 'static,
544 {
545 self.then(move |tok: &Token, src: &[char]| {
546 preds_is.iter().any(|pred| pred(&tok.kind))
547 && !ex.iter().any(|&word| tok.get_ch(src).eq_str(word))
548 })
549 }
550
551 pub fn then_kind_any_or_words<F>(
554 self,
555 preds: &'static [F],
556 words: &'static [&'static str],
557 ) -> Self
558 where
559 F: Fn(&TokenKind) -> bool + Send + Sync + 'static,
560 {
561 self.then(move |tok: &Token, src: &[char]| {
562 preds.iter().any(|pred| pred(&tok.kind))
563 || words.iter().any(|&word| tok.get_ch(src).eq_str(word))
564 })
565 }
566
567 pub fn then_kind_any_but_not<F1, F2>(self, preds_is: &'static [F1], pred_not: F2) -> Self
570 where
571 F1: Fn(&TokenKind) -> bool + Send + Sync + 'static,
572 F2: Fn(&TokenKind) -> bool + Send + Sync + 'static,
573 {
574 self.then(move |tok: &Token, _src: &[char]| {
575 preds_is.iter().any(|pred| pred(&tok.kind)) && !pred_not(&tok.kind)
576 })
577 }
578
579 pub fn then_kind_any_but_not_except<F1, F2>(
582 self,
583 preds_is: &'static [F1],
584 pred_not: F2,
585 ex: &'static [&'static str],
586 ) -> Self
587 where
588 F1: Fn(&TokenKind) -> bool + Send + Sync + 'static,
589 F2: Fn(&TokenKind) -> bool + Send + Sync + 'static,
590 {
591 self.then(move |tok: &Token, src: &[char]| {
592 preds_is.iter().any(|pred| pred(&tok.kind))
593 && !pred_not(&tok.kind)
594 && !ex.iter().any(|&word| tok.get_ch(src).eq_str(word))
595 })
596 }
597
598 gen_then_from_is!(oov);
602 gen_then_from_is!(swear);
603
604 gen_then_from_is!(nominal);
609 gen_then_from_is!(plural_nominal);
610 gen_then_from_is!(non_plural_nominal);
611 gen_then_from_is!(possessive_nominal);
612
613 gen_then_from_is!(noun);
616 gen_then_from_is!(proper_noun);
617 gen_then_from_is!(singular_noun);
618 gen_then_from_is!(plural_noun);
619 gen_then_from_is!(singular_noun_only);
620 gen_then_from_is!(plural_noun_only);
621 gen_then_from_is!(mass_noun_only);
622
623 gen_then_from_is!(pronoun);
626 gen_then_from_is!(personal_pronoun);
627 gen_then_from_is!(first_person_singular_pronoun);
628 gen_then_from_is!(first_person_plural_pronoun);
629 gen_then_from_is!(second_person_pronoun);
630 gen_then_from_is!(third_person_pronoun);
631 gen_then_from_is!(third_person_singular_pronoun);
632 gen_then_from_is!(third_person_plural_pronoun);
633 gen_then_from_is!(subject_pronoun);
634 gen_then_from_is!(object_pronoun);
635
636 pub fn then_relative_pronoun(self) -> Self {
637 self.then(RelativePronoun::default())
638 }
639
640 gen_then_from_is!(verb);
643 gen_then_from_is!(auxiliary_verb);
644 gen_then_from_is!(linking_verb);
645 gen_then_from_is!(verb_lemma);
646 gen_then_from_is!(verb_simple_past_form);
647 gen_then_from_is!(verb_past_participle_form);
648 gen_then_from_is!(verb_progressive_form);
649 gen_then_from_is!(verb_third_person_singular_present_form);
650
651 gen_then_from_is!(adjective);
654 gen_then_from_is!(positive_adjective);
655 gen_then_from_is!(comparative_adjective);
656 gen_then_from_is!(superlative_adjective);
657
658 gen_then_from_is!(adverb);
661 gen_then_from_is!(frequency_adverb);
662 gen_then_from_is!(degree_adverb);
663
664 gen_then_from_is!(determiner);
667 gen_then_from_is!(demonstrative_determiner);
668 gen_then_from_is!(possessive_determiner);
669 gen_then_from_is!(quantifier);
670 gen_then_from_is!(non_quantifier_determiner);
671 gen_then_from_is!(non_demonstrative_determiner);
672
673 pub fn then_indefinite_article(self) -> Self {
675 self.then(IndefiniteArticle::default())
676 }
677
678 gen_then_from_is!(conjunction);
681 gen_then_from_is!(preposition);
682
683 gen_then_from_is!(number);
686 gen_then_from_is!(cardinal_number);
687 gen_then_from_is!(ordinal_number);
688
689 gen_then_from_is!(punctuation);
692 gen_then_from_is!(apostrophe);
693 gen_then_from_is!(comma);
694 gen_then_from_is!(hyphen);
695 gen_then_from_is!(period);
696 gen_then_from_is!(semicolon);
697 gen_then_from_is!(acute);
698 gen_then_from_is!(quote);
699 gen_then_from_is!(backslash);
700 gen_then_from_is!(slash);
701 gen_then_from_is!(percent);
702 gen_then_from_is!(degree);
703 gen_then_from_is!(open_single);
704 gen_then_from_is!(single_prime);
705 gen_then_from_is!(double_prime);
706 gen_then_from_is!(backtick);
707 gen_then_from_is!(plus);
708
709 gen_then_from_is!(case_separator);
712 gen_then_from_is!(likely_homograph);
713 gen_then_from_is!(sentence_terminator);
714}
715
716impl SequenceExpr {
718 pub fn start_capture(mut self) -> Self {
723 self.capture_range.start = self.exprs.len();
724 self
725 }
726
727 pub fn end_capture(mut self) -> Self {
732 self.capture_range.end = self.exprs.len();
733 self
734 }
735}
736
737impl<S> From<S> for SequenceExpr
738where
739 S: Step + 'static,
740{
741 fn from(step: S) -> Self {
742 Self {
743 exprs: vec![Box::new(step)],
744 ..Default::default()
745 }
746 }
747}
748
749#[cfg(test)]
750mod tests {
751 use crate::{
752 Document, TokenKind,
753 expr::{AnchorEnd, Expr, ExprExt, Repeating, SequenceExpr},
754 linting::tests::SpanVecExt,
755 };
756
757 #[test]
758 fn test_kind_both() {
759 let noun_and_verb =
760 SequenceExpr::default().then_kind_both(TokenKind::is_noun, TokenKind::is_verb);
761 let doc = Document::new_plain_english_curated("Use a good example.");
762 let matches = noun_and_verb.iter_matches_in_doc(&doc).collect::<Vec<_>>();
763 assert_eq!(matches.to_strings(&doc), vec!["Use", "good", "example"]);
764 }
765
766 #[test]
767 fn test_adjective_or_determiner() {
768 let expr = SequenceExpr::default()
769 .then_kind_either(TokenKind::is_adjective, TokenKind::is_determiner);
770 let doc = Document::new_plain_english_curated("Use a good example.");
771 let matches = expr.iter_matches_in_doc(&doc).collect::<Vec<_>>();
772 assert_eq!(matches.to_strings(&doc), vec!["a", "good"]);
773 }
774
775 #[test]
776 fn test_noun_but_not_adjective() {
777 let expr = SequenceExpr::default()
778 .then_kind_is_but_is_not(TokenKind::is_noun, TokenKind::is_adjective);
779 let doc = Document::new_plain_english_curated("Use a good example.");
780 let matches = expr.iter_matches_in_doc(&doc).collect::<Vec<_>>();
781 assert_eq!(matches.to_strings(&doc), vec!["Use", "example"]);
782 }
783
784 #[track_caller]
785 fn assert_no_matches_for(s: &str, expr: &SequenceExpr) {
786 let doc = Document::new_plain_english_curated(s);
787 let mut matches = expr.iter_matches_in_doc(&doc);
788 assert!(matches.next().is_none());
789 }
790
791 #[test]
792 fn test_start_capture() {
793 let expr = SequenceExpr::default()
794 .then_exact_word("hello")
795 .t_ws()
796 .start_capture()
797 .then_exact_word("world");
798
799 let doc = Document::new_plain_english_curated("hello world");
800 let matches = expr.iter_matches_in_doc(&doc).collect::<Vec<_>>();
801 assert_eq!(matches.to_strings(&doc), vec!["world"]);
802
803 assert_no_matches_for("world", &expr);
804 assert_no_matches_for(" world", &expr);
805 }
806
807 #[test]
808 fn test_end_capture() {
809 let expr = SequenceExpr::default()
810 .then_exact_word("hello")
811 .end_capture()
812 .t_ws()
813 .then_exact_word("world");
814
815 let doc = Document::new_plain_english_curated("hello world");
816 let matches = expr.iter_matches_in_doc(&doc).collect::<Vec<_>>();
817 assert_eq!(matches.to_strings(&doc), vec!["hello"]);
818
819 assert_no_matches_for("hello", &expr);
820 assert_no_matches_for("hello ", &expr);
821 }
822
823 #[test]
824 fn test_start_end_capture() {
825 let expr = SequenceExpr::default()
826 .then_exact_word("hello")
827 .t_ws()
828 .start_capture()
829 .then_exact_word("world")
830 .end_capture()
831 .t_ws()
832 .then_exact_word("testing");
833
834 let doc = Document::new_plain_english_curated("hello world testing");
835 let matches = expr.iter_matches_in_doc(&doc).collect::<Vec<_>>();
836 assert_eq!(matches.to_strings(&doc), vec!["world"]);
837
838 assert_no_matches_for("hello world", &expr);
839 assert_no_matches_for("world testing", &expr);
840 assert_no_matches_for("world", &expr);
841 }
842
843 #[test]
844 fn test_start_end_capture_multi_token() {
845 let expr = SequenceExpr::default()
846 .then_exact_word("hello")
847 .t_ws()
848 .start_capture()
849 .then_exact_word("world")
850 .t_ws()
851 .then_exact_word("testing")
852 .end_capture()
853 .t_ws()
854 .then_any_word();
855
856 let doc = Document::new_plain_english_curated("hello world testing something else");
857 let matches = expr.iter_matches_in_doc(&doc).collect::<Vec<_>>();
858 assert_eq!(matches.to_strings(&doc), vec!["world testing"]);
859 }
860
861 #[test]
862 fn test_start_end_capture_multi_match() {
863 let expr = SequenceExpr::default()
864 .then_exact_word("hello")
865 .t_ws()
866 .start_capture()
867 .then_any_word()
868 .t_ws()
869 .then_exact_word("world")
870 .end_capture()
871 .t_ws()
872 .then_exact_word("testing");
873
874 let doc = Document::new_plain_english_curated(
875 "hello one world testing hello two world testing hello three world something else",
876 );
877 let matches = expr.iter_matches_in_doc(&doc).collect::<Vec<_>>();
878 assert_eq!(matches.to_strings(&doc), vec!["one world", "two world"]);
879 }
880
881 #[test]
882 fn test_start_end_capture_multi_token_expr() {
883 let expr = SequenceExpr::default()
884 .then_exact_word("hello")
885 .t_ws()
886 .start_capture()
887 .then(Repeating::new(
888 Box::new(SequenceExpr::default().then_exact_word("world").t_ows()),
889 3,
890 ))
891 .end_capture()
892 .then_exact_word("testing");
893
894 let doc = Document::new_plain_english_curated("hello world world world testing");
895 let matches = expr.iter_matches_in_doc(&doc).collect::<Vec<_>>();
896 assert_eq!(matches.to_strings(&doc), vec!["world world world "]);
897
898 assert_no_matches_for("hello world world world", &expr);
899 assert_no_matches_for("world world world testing", &expr);
900 assert_no_matches_for("world world world", &expr);
901 }
902
903 #[test]
904 fn flag_foo_followed_by_bar_or_at_end_1() {
905 let expr = SequenceExpr::aco("foo").then_any_of([
906 Box::new(SequenceExpr::whitespace().t_aco("bar").then(AnchorEnd)) as Box<dyn Expr>,
907 Box::new(AnchorEnd),
908 ]);
909
910 let doc_with_bar = Document::new_plain_english_curated("foo bar");
911
912 let matches_with_bar = expr.iter_matches_in_doc(&doc_with_bar).collect::<Vec<_>>();
913
914 eprintln!("matches_with_bar: {:#?}", matches_with_bar);
915
916 assert_eq!(matches_with_bar.len(), 1);
918 assert_eq!(matches_with_bar[0].start, 0);
919 assert_eq!(matches_with_bar[0].end, 3);
920 assert_eq!(matches_with_bar.to_strings(&doc_with_bar), vec!["foo bar"]);
921 }
922
923 #[test]
924 fn flag_foo_followed_by_bar_or_at_end_2() {
925 let expr = SequenceExpr::aco("foo").then_any_of([
926 Box::new(SequenceExpr::whitespace().t_aco("bar").then(AnchorEnd)) as Box<dyn Expr>,
927 Box::new(AnchorEnd),
928 ]);
929
930 let doc_with_end = Document::new_plain_english_curated("foo");
931
932 let matches_with_end = expr.iter_matches_in_doc(&doc_with_end).collect::<Vec<_>>();
933
934 eprintln!("matches_with_end: {:#?}", matches_with_end);
935
936 assert_eq!(matches_with_end.len(), 1);
938 assert_eq!(matches_with_end[0].start, 0);
939 assert_eq!(matches_with_end[0].end, 1);
940 assert_eq!(matches_with_end.to_strings(&doc_with_end), vec!["foo"]);
941 }
942
943 #[test]
944 fn flag_foo_followed_by_bar_or_at_end_3() {
945 let expr = SequenceExpr::aco("foo").then_any_of([
946 Box::new(SequenceExpr::whitespace().t_aco("bar").then(AnchorEnd)) as Box<dyn Expr>,
947 Box::new(AnchorEnd),
948 ]);
949
950 let doc_with_foo_bar_baz = Document::new_plain_english_curated("foo bar baz");
951
952 let matches_with_foo_bar_baz = expr
953 .iter_matches_in_doc(&doc_with_foo_bar_baz)
954 .collect::<Vec<_>>();
955
956 eprintln!("matches_with_foo_bar_baz: {:#?}", matches_with_foo_bar_baz);
957
958 assert_eq!(matches_with_foo_bar_baz.len(), 0);
960 assert_eq!(
961 matches_with_foo_bar_baz.to_strings(&doc_with_foo_bar_baz),
962 Vec::<String>::new()
963 );
964 }
965}