Skip to main content

harper_core/expr/
sequence_expr.rs

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
25/// Generate a `then_*` method from an available `is_*` function on [`TokenKind`].
26macro_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    /// Run the expression starting at an index, returning the total matched window.
62    ///
63    /// If any step returns `None`, the entire expression does as well.
64    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            // Zero-width assertions (like AnchorEnd) validate position without consuming tokens
71            // They should not expand the window or advance the cursor
72            if out.is_empty() {
73                // If zero-width, don't expand window or advance cursor - just validate position
74                continue;
75            }
76
77            // Only advance cursor if we actually matched something
78            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                // This is the first expression being captured, set the window to start at the
86                // current cursor position.
87                // Even though it was set at the beginning of this function, it's necessary to set
88                // it again since the cursor may have advanced since then.
89                window = Span::empty(cursor);
90            } else if !self.capture_range.contains(i) {
91                // Current expression is outside of capture range and shouldn't be captured.
92                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    // Constructor methods
105
106    // Match an [expression](Expr).
107    pub fn with(expr: impl Expr + 'static) -> Self {
108        Self::default().then(expr)
109    }
110
111    // Single token methods
112
113    /// Construct a new sequence with an [`AnyPattern`] at the beginning of the operation list.
114    pub fn anything() -> Self {
115        Self::default().then_anything()
116    }
117
118    // Single word token methods
119
120    /// Construct a new sequence with a [`Word`] at the beginning of the operation list.
121    pub fn any_capitalization_of(word: &'static str) -> Self {
122        Self::default().then_any_capitalization_of(word)
123    }
124
125    /// Shorthand for [`Self::any_capitalization_of`].
126    pub fn aco(word: &'static str) -> Self {
127        Self::any_capitalization_of(word)
128    }
129
130    /// Match any word from the given set of words, case-insensitive.
131    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    /// Match any word.
140    pub fn any_word() -> Self {
141        Self::default().then_any_word()
142    }
143
144    /// Match any number.
145    pub fn number() -> Self {
146        Self::default().then_number()
147    }
148
149    // Expressions of more than one token
150
151    /// Optionally match an expression.
152    pub fn optional(expr: impl Expr + 'static) -> Self {
153        Self::default().then_optional(expr)
154    }
155
156    /// Match a series of words separated by whitespace.
157    pub fn word_seq(words: &'static [&'static str]) -> Self {
158        Self::default().then_word_seq(words)
159    }
160
161    /// Match a fixed phrase.
162    pub fn fixed_phrase(phrase: &'static str) -> Self {
163        Self::default().then_fixed_phrase(phrase)
164    }
165
166    // Multiple expressions
167
168    /// Match the first of multiple expressions.
169    pub fn any_of(exprs: impl IntoIterator<Item = impl AsBoxedExpr>) -> Self {
170        Self::default().then_any_of(exprs)
171    }
172
173    /// Match the longest of multiple expressions.
174    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    /// Will be accepted unless the condition matches.
183    pub fn unless(condition: impl Expr + 'static) -> Self {
184        Self::default().then_unless(condition)
185    }
186
187    // Builder methods
188
189    /// Push an [expression](Expr) to the operation list.
190    pub fn then(mut self, expr: impl Expr + 'static) -> Self {
191        self.exprs.push(Box::new(expr));
192        self
193    }
194
195    /// Push an already-boxed [expression](Expr) to the operation list.
196    pub fn then_boxed(mut self, expr: Box<dyn Expr>) -> Self {
197        self.exprs.push(expr);
198        self
199    }
200
201    /// Pushes an expression that could move the cursor to the sequence, but does not require it.
202    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    /// Pushes an expression that will match any of the provided expressions.
208    ///
209    /// If more than one of the provided expressions match, this function provides no guarantee
210    /// as to which match will end up being used. If you need to get the longest of multiple
211    /// matches, use [`Self::then_longest_of()`] instead.
212    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    /// Pushes an expression that will match the longest of the provided expressions.
218    ///
219    /// If you don't need the longest match, prefer using the short-circuiting
220    /// [`Self::then_any_of()`] instead.
221    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    /// Appends the steps in `other` onto the end of `self`.
227    /// This is more efficient than [`Self::then`] because it avoids pointer redirection.
228    pub fn then_seq(mut self, mut other: Self) -> Self {
229        self.exprs.append(&mut other.exprs);
230        self
231    }
232
233    /// Pushes an expression that will match any word from the given set of words, case-insensitive.
234    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    /// Shorthand for [`Self::then_word_set`].
243    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    /// Match against one or more whitespace tokens.
252    pub fn then_whitespace(self) -> Self {
253        self.then(WhitespacePattern)
254    }
255
256    /// Shorthand for [`Self::then_whitespace`].
257    pub fn t_ws(self) -> Self {
258        self.then_whitespace()
259    }
260
261    /// Match against whitespace tokens or a hyphen.
262    pub fn then_whitespace_or_hyphen(self) -> Self {
263        self.then(WhitespacePattern.or(|tok: &Token, _: &[char]| tok.kind.is_hyphen()))
264    }
265
266    /// Shorthand for [`Self::then_whitespace_or_hyphen`].
267    pub fn t_ws_h(self) -> Self {
268        self.then_whitespace_or_hyphen()
269    }
270
271    /// Match against zero or more whitespace tokens.
272    pub fn then_optional_whitespace(self) -> Self {
273        self.then_optional(WhitespacePattern)
274    }
275
276    /// Shorthand for [`Self::then_optional_whitespace`].
277    pub fn t_ows(self) -> Self {
278        self.then_optional_whitespace()
279    }
280
281    /// Match against zero or more occurrences of the given expression. Like `*` in regex.
282    pub fn then_zero_or_more(self, expr: impl Expr + 'static) -> Self {
283        self.then(Repeating::new(Box::new(expr), 0))
284    }
285
286    /// Match against one or more occurrences of the given expression. Like `+` in regex.
287    pub fn then_one_or_more(self, expr: impl Expr + 'static) -> Self {
288        self.then(Repeating::new(Box::new(expr), 1))
289    }
290
291    /// Match against zero or more whitespace-separated occurrences of the given expression.
292    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    /// Create a new condition that will step one token forward if met.
301    /// If the condition is _not_ met, the whole expression returns `None`.
302    ///
303    /// This can be used to build out exceptions to other rules.
304    ///
305    /// See [`UnlessStep`] for more info.
306    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    /// Match any single token.
313    ///
314    /// See [`AnyPattern`] for more info.
315    pub fn then_anything(self) -> Self {
316        self.then(AnyPattern)
317    }
318
319    /// Match any single token.
320    ///
321    /// Shorthand for [`Self::then_anything`].
322    pub fn t_any(self) -> Self {
323        self.then_anything()
324    }
325
326    // Word matching methods
327
328    /// Matches any word.
329    pub fn then_any_word(self) -> Self {
330        self.then_kind_where(|kind| kind.is_word())
331    }
332
333    /// Match examples of `word` that have any capitalization.
334    pub fn then_any_capitalization_of(self, word: &'static str) -> Self {
335        self.then(Word::new(word))
336    }
337
338    /// Shorthand for [`Self::then_any_capitalization_of`].
339    pub fn t_aco(self, word: &'static str) -> Self {
340        self.then_any_capitalization_of(word)
341    }
342
343    /// Match examples of `word` case-sensitively.
344    pub fn then_exact_word(self, word: &'static str) -> Self {
345        self.then(Word::new_exact(word))
346    }
347
348    /// Match a series of words separated by whitespace.
349    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    /// Match a fixed phrase.
362    pub fn then_fixed_phrase(self, phrase: &'static str) -> Self {
363        self.then(FixedPhrase::from_phrase(phrase))
364    }
365
366    /// Match any word except the ones in `words`.
367    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    // Token kind/predicate matching methods
374
375    // One kind
376
377    /// Matches any token whose `Kind` exactly matches.
378    pub fn then_kind(self, kind: TokenKind) -> Self {
379        self.then_kind_where(move |k| kind == *k)
380    }
381
382    /// Matches a token where the provided closure returns true for the token's kind.
383    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    /// Match a token of a given kind which is not in the list of words.
395    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    // Two kinds
405
406    /// Match a token where both token kind predicates return true.
407    /// For instance, a word that can be both noun and verb.
408    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    /// Match a token where either of the two token kind predicates returns true.
417    /// For instance, an adjective or an adverb.
418    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    /// Match a token where neither of the two token kind predicates returns true.
427    /// For instance, a word that can't be a verb or a noun.
428    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    /// Match a token where the first token kind predicate returns true and the second returns false.
437    /// For instance, a word that can be a noun but cannot be a verb.
438    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    /// Match a token where the first token kind predicate returns true and the second returns false,
447    /// and the token is not in the list of exceptions.
448    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    /// Match a token where the first token kind predicate returns true and all of the second return false.
466    /// For instance, a word that can be a verb but not a noun or an adjective.
467    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    /// Match a token where the first token kind predicate returns true and all of the second return false,
480    /// and the token is not in the list of exceptions.
481    /// For instance, an adjective that isn't also a verb or adverb or the word "likely".
482    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    // More than two kinds
500
501    /// Match a token where both of the first two token kind predicates return true,
502    /// and the third returns false.
503    /// For instance, a word that must be both noun and verb, but not adjective.
504    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    /// Match a token where any of the token kind predicates returns true.
518    /// Like `then_kind_either` but for more than two predicates.
519    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    /// Match a token where none of the token kind predicates returns true.
527    /// Like `then_kind_neither` but for more than two predicates.
528    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    /// Match a token where any of the token kind predicates returns true,
536    /// and the word is not in the list of exceptions.
537    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    /// Match a token where any of the token kind predicates returns true,
552    /// or the token is in the list of words.
553    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    /// Match a token where any of the first token kind predicates returns true
568    /// and the second returns false.
569    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    /// Match a token where any of the first token kind predicates returns true,
580    /// the second returns false, and the token is not in the list of exceptions.    
581    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    // Word property matching methods
599
600    // Out-of-vocabulary word. (Words not in the dictionary)
601    gen_then_from_is!(oov);
602    gen_then_from_is!(swear);
603
604    // Part-of-speech matching methods
605
606    // Nominals (nouns and pronouns)
607
608    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    // Nouns
614
615    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    // Pronouns
624
625    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    // Verbs
641
642    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    // Adjectives
652
653    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    // Adverbs
659
660    gen_then_from_is!(adverb);
661    gen_then_from_is!(frequency_adverb);
662    gen_then_from_is!(degree_adverb);
663
664    // Determiners
665
666    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    /// Push an [`IndefiniteArticle`] to the end of the operation list.
674    pub fn then_indefinite_article(self) -> Self {
675        self.then(IndefiniteArticle::default())
676    }
677
678    // Other parts of speech
679
680    gen_then_from_is!(conjunction);
681    gen_then_from_is!(preposition);
682
683    // Numbers
684
685    gen_then_from_is!(number);
686    gen_then_from_is!(cardinal_number);
687    gen_then_from_is!(ordinal_number);
688
689    // Punctuation
690
691    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    // Other
710
711    gen_then_from_is!(case_separator);
712    gen_then_from_is!(likely_homograph);
713    gen_then_from_is!(sentence_terminator);
714}
715
716// Capture-range methods.
717impl SequenceExpr {
718    /// Start the capture at this point.
719    ///
720    /// [`Expr`] set before this point will be required for a match, but will not be captured in
721    /// the result.
722    pub fn start_capture(mut self) -> Self {
723        self.capture_range.start = self.exprs.len();
724        self
725    }
726
727    /// Start the capture at this point.
728    ///
729    /// [`Expr`] set after this point will be required for a match, but will not be captured in the
730    /// result.
731    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        // "foo bar" matches with span covering both tokens
917        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        // "foo" at end matches with span covering just "foo"
937        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        // "foo bar baz" should NOT match because "bar" is not at the end
959        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}