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
11#[derive(Default)]
12pub struct SequenceExpr {
13    exprs: Vec<Box<dyn Expr>>,
14}
15
16/// Generate a `then_*` method from an available `is_*` function on [`TokenKind`].
17macro_rules! gen_then_from_is {
18    ($quality:ident) => {
19        paste! {
20            #[doc = concat!("Adds a step matching a token where [`TokenKind::is_", stringify!($quality), "()`] returns true.")]
21            pub fn [< then_$quality >] (self) -> Self{
22                self.then_kind_where(|kind| {
23                    kind.[< is_$quality >]()
24                })
25            }
26
27            #[doc = concat!("Adds an optional step matching a token where [`TokenKind::is_", stringify!($quality), "()`] returns true.")]
28            pub fn [< then_optional_$quality >] (self) -> Self{
29                self.then_optional(|tok: &Token, _source: &[char]| {
30                    tok.kind.[< is_$quality >]()
31                })
32            }
33
34            #[doc = concat!("Adds a step matching one or more consecutive tokens where [`TokenKind::is_", stringify!($quality), "()`] returns true.")]
35            pub fn [< then_one_or_more_$quality s >] (self) -> Self{
36                self.then_one_or_more(Box::new(|tok: &Token, _source: &[char]| {
37                    tok.kind.[< is_$quality >]()
38                }))
39            }
40
41            #[doc = concat!("Adds a step matching a token where [`TokenKind::is_", stringify!($quality), "()`] returns false.")]
42            pub fn [< then_anything_but_$quality >] (self) -> Self{
43                self.then_kind_where(|kind| {
44                    !kind.[< is_$quality >]()
45                })
46            }
47        }
48    };
49}
50
51impl Expr for SequenceExpr {
52    /// Run the expression starting at an index, returning the total matched window.
53    ///
54    /// If any step returns `None`, the entire expression does as well.
55    fn run(&self, mut cursor: usize, tokens: &[Token], source: &[char]) -> Option<Span<Token>> {
56        let mut window = Span::empty(cursor);
57
58        for cur_expr in &self.exprs {
59            let out = cur_expr.run(cursor, tokens, source)?;
60
61            // Zero-width assertions (like AnchorEnd) validate position without consuming tokens
62            // They should not expand the window or advance the cursor
63            let is_zero_width = out.end == out.start;
64
65            if !is_zero_width {
66                // Only expand the window if the match actually covers some tokens
67                if out.end > out.start {
68                    window.expand_to_include(out.start);
69                    window.expand_to_include(out.end.checked_sub(1).unwrap_or(out.start));
70                }
71
72                // Only advance cursor if we actually matched something
73                if out.end > cursor {
74                    cursor = out.end;
75                } else if out.start < cursor {
76                    cursor = out.start;
77                }
78            }
79            // If zero-width, don't expand window or advance cursor - just validate position
80        }
81
82        Some(window)
83    }
84}
85
86impl SequenceExpr {
87    // Constructor methods
88
89    // Match an [expression](Expr).
90    pub fn with(expr: impl Expr + 'static) -> Self {
91        Self::default().then(expr)
92    }
93
94    // Single token methods
95
96    /// Construct a new sequence with an [`AnyPattern`] at the beginning of the operation list.
97    pub fn anything() -> Self {
98        Self::default().then_anything()
99    }
100
101    // Single word token methods
102
103    /// Construct a new sequence with a [`Word`] at the beginning of the operation list.
104    pub fn any_capitalization_of(word: &'static str) -> Self {
105        Self::default().then_any_capitalization_of(word)
106    }
107
108    /// Shorthand for [`Self::any_capitalization_of`].
109    pub fn aco(word: &'static str) -> Self {
110        Self::any_capitalization_of(word)
111    }
112
113    /// Match any word from the given set of words, case-insensitive.
114    pub fn word_set<I, S>(words: I) -> Self
115    where
116        I: IntoIterator<Item = S>,
117        S: AsRef<str>,
118    {
119        Self::default().then_word_set(words)
120    }
121
122    /// Match any word.
123    pub fn any_word() -> Self {
124        Self::default().then_any_word()
125    }
126
127    /// Match any number.
128    pub fn number() -> Self {
129        Self::default().then_number()
130    }
131
132    // Expressions of more than one token
133
134    /// Optionally match an expression.
135    pub fn optional(expr: impl Expr + 'static) -> Self {
136        Self::default().then_optional(expr)
137    }
138
139    /// Match a series of words separated by whitespace.
140    pub fn word_seq(words: &'static [&'static str]) -> Self {
141        Self::default().then_word_seq(words)
142    }
143
144    /// Match a fixed phrase.
145    pub fn fixed_phrase(phrase: &'static str) -> Self {
146        Self::default().then_fixed_phrase(phrase)
147    }
148
149    // Multiple expressions
150
151    /// Match the first of multiple expressions.
152    pub fn any_of(exprs: impl IntoIterator<Item = impl AsBoxedExpr>) -> Self {
153        Self::default().then_any_of(exprs)
154    }
155
156    /// Match the longest of multiple expressions.
157    pub fn longest_of(exprs: impl IntoIterator<Item = impl AsBoxedExpr>) -> Self {
158        Self::default().then_longest_of(exprs)
159    }
160
161    pub fn whitespace() -> Self {
162        Self::default().then_whitespace()
163    }
164
165    /// Will be accepted unless the condition matches.
166    pub fn unless(condition: impl Expr + 'static) -> Self {
167        Self::default().then_unless(condition)
168    }
169
170    // Builder methods
171
172    /// Push an [expression](Expr) to the operation list.
173    pub fn then(mut self, expr: impl Expr + 'static) -> Self {
174        self.exprs.push(Box::new(expr));
175        self
176    }
177
178    /// Push an already-boxed [expression](Expr) to the operation list.
179    pub fn then_boxed(mut self, expr: Box<dyn Expr>) -> Self {
180        self.exprs.push(expr);
181        self
182    }
183
184    /// Pushes an expression that could move the cursor to the sequence, but does not require it.
185    pub fn then_optional(mut self, expr: impl Expr + 'static) -> Self {
186        self.exprs.push(Box::new(Optional::new(expr)));
187        self
188    }
189
190    /// Pushes an expression that will match any of the provided expressions.
191    ///
192    /// If more than one of the provided expressions match, this function provides no guarantee
193    /// as to which match will end up being used. If you need to get the longest of multiple
194    /// matches, use [`Self::then_longest_of()`] instead.
195    pub fn then_any_of(mut self, exprs: impl IntoIterator<Item = impl AsBoxedExpr>) -> Self {
196        self.exprs.push(Box::new(FirstMatchOf::new(exprs)));
197        self
198    }
199
200    /// Pushes an expression that will match the longest of the provided expressions.
201    ///
202    /// If you don't need the longest match, prefer using the short-circuiting
203    /// [`Self::then_any_of()`] instead.
204    pub fn then_longest_of(mut self, exprs: impl IntoIterator<Item = impl AsBoxedExpr>) -> Self {
205        self.exprs.push(Box::new(LongestMatchOf::new(exprs)));
206        self
207    }
208
209    /// Appends the steps in `other` onto the end of `self`.
210    /// This is more efficient than [`Self::then`] because it avoids pointer redirection.
211    pub fn then_seq(mut self, mut other: Self) -> Self {
212        self.exprs.append(&mut other.exprs);
213        self
214    }
215
216    /// Pushes an expression that will match any word from the given set of words, case-insensitive.
217    pub fn then_word_set<I, S>(self, words: I) -> Self
218    where
219        I: IntoIterator<Item = S>,
220        S: AsRef<str>,
221    {
222        self.then(WordSet::new(words))
223    }
224
225    /// Shorthand for [`Self::then_word_set`].
226    pub fn t_set<I, S>(self, words: I) -> Self
227    where
228        I: IntoIterator<Item = S>,
229        S: AsRef<str>,
230    {
231        self.then_word_set(words)
232    }
233
234    /// Match against one or more whitespace tokens.
235    pub fn then_whitespace(self) -> Self {
236        self.then(WhitespacePattern)
237    }
238
239    /// Shorthand for [`Self::then_whitespace`].
240    pub fn t_ws(self) -> Self {
241        self.then_whitespace()
242    }
243
244    /// Match against whitespace tokens or a hyphen.
245    pub fn then_whitespace_or_hyphen(self) -> Self {
246        self.then(WhitespacePattern.or(|tok: &Token, _: &[char]| tok.kind.is_hyphen()))
247    }
248
249    /// Shorthand for [`Self::then_whitespace_or_hyphen`].
250    pub fn t_ws_h(self) -> Self {
251        self.then_whitespace_or_hyphen()
252    }
253
254    /// Match against zero or more whitespace tokens.
255    pub fn then_optional_whitespace(self) -> Self {
256        self.then_optional(WhitespacePattern)
257    }
258
259    /// Shorthand for [`Self::then_optional_whitespace`].
260    pub fn t_ows(self) -> Self {
261        self.then_optional_whitespace()
262    }
263
264    /// Match against zero or more occurrences of the given expression. Like `*` in regex.
265    pub fn then_zero_or_more(self, expr: impl Expr + 'static) -> Self {
266        self.then(Repeating::new(Box::new(expr), 0))
267    }
268
269    /// Match against one or more occurrences of the given expression. Like `+` in regex.
270    pub fn then_one_or_more(self, expr: impl Expr + 'static) -> Self {
271        self.then(Repeating::new(Box::new(expr), 1))
272    }
273
274    /// Match against zero or more whitespace-separated occurrences of the given expression.
275    pub fn then_zero_or_more_spaced(self, expr: impl Expr + 'static) -> Self {
276        let expr = Lrc::new(expr);
277        self.then(SequenceExpr::with(expr.clone()).then(Repeating::new(
278            Box::new(SequenceExpr::default().t_ws().then(expr)),
279            0,
280        )))
281    }
282
283    /// Create a new condition that will step one token forward if met.
284    /// If the condition is _not_ met, the whole expression returns `None`.
285    ///
286    /// This can be used to build out exceptions to other rules.
287    ///
288    /// See [`UnlessStep`] for more info.
289    pub fn then_unless(self, condition: impl Expr + 'static) -> Self {
290        self.then(UnlessStep::new(condition, |_tok: &Token, _src: &[char]| {
291            true
292        }))
293    }
294
295    /// Match any single token.
296    ///
297    /// See [`AnyPattern`] for more info.
298    pub fn then_anything(self) -> Self {
299        self.then(AnyPattern)
300    }
301
302    /// Match any single token.
303    ///
304    /// Shorthand for [`Self::then_anything`].
305    pub fn t_any(self) -> Self {
306        self.then_anything()
307    }
308
309    // Word matching methods
310
311    /// Matches any word.
312    pub fn then_any_word(self) -> Self {
313        self.then_kind_where(|kind| kind.is_word())
314    }
315
316    /// Match examples of `word` that have any capitalization.
317    pub fn then_any_capitalization_of(self, word: &'static str) -> Self {
318        self.then(Word::new(word))
319    }
320
321    /// Shorthand for [`Self::then_any_capitalization_of`].
322    pub fn t_aco(self, word: &'static str) -> Self {
323        self.then_any_capitalization_of(word)
324    }
325
326    /// Match examples of `word` case-sensitively.
327    pub fn then_exact_word(self, word: &'static str) -> Self {
328        self.then(Word::new_exact(word))
329    }
330
331    /// Match a series of words separated by whitespace.
332    pub fn then_word_seq(self, words: &'static [&'static str]) -> Self {
333        if let Some((first, rest)) = words.split_first() {
334            let mut expr = self.t_aco(first);
335            for word in rest {
336                expr = expr.t_ws().t_aco(word);
337            }
338            expr
339        } else {
340            self
341        }
342    }
343
344    /// Match a fixed phrase.
345    pub fn then_fixed_phrase(self, phrase: &'static str) -> Self {
346        self.then(FixedPhrase::from_phrase(phrase))
347    }
348
349    /// Match any word except the ones in `words`.
350    pub fn then_word_except(self, words: &'static [&'static str]) -> Self {
351        self.then(move |tok: &Token, src: &[char]| {
352            !tok.kind.is_word() || !words.iter().any(|&word| tok.get_ch(src).eq_str(word))
353        })
354    }
355
356    // Token kind/predicate matching methods
357
358    // One kind
359
360    /// Matches any token whose `Kind` exactly matches.
361    pub fn then_kind(self, kind: TokenKind) -> Self {
362        self.then_kind_where(move |k| kind == *k)
363    }
364
365    /// Matches a token where the provided closure returns true for the token's kind.
366    pub fn then_kind_where<F>(mut self, predicate: F) -> Self
367    where
368        F: Fn(&TokenKind) -> bool + Send + Sync + 'static,
369    {
370        self.exprs
371            .push(Box::new(move |tok: &Token, _source: &[char]| {
372                predicate(&tok.kind)
373            }));
374        self
375    }
376
377    /// Match a token of a given kind which is not in the list of words.
378    pub fn then_kind_except<F>(self, pred_is: F, ex: &'static [&'static str]) -> Self
379    where
380        F: Fn(&TokenKind) -> bool + Send + Sync + 'static,
381    {
382        self.then(move |tok: &Token, src: &[char]| {
383            pred_is(&tok.kind) && !ex.iter().any(|&word| tok.get_ch(src).eq_str(word))
384        })
385    }
386
387    // Two kinds
388
389    /// Match a token where both token kind predicates return true.
390    /// For instance, a word that can be both noun and verb.
391    pub fn then_kind_both<F1, F2>(self, pred_is_1: F1, pred_is_2: F2) -> Self
392    where
393        F1: Fn(&TokenKind) -> bool + Send + Sync + 'static,
394        F2: Fn(&TokenKind) -> bool + Send + Sync + 'static,
395    {
396        self.then_kind_where(move |k| pred_is_1(k) && pred_is_2(k))
397    }
398
399    /// Match a token where either of the two token kind predicates returns true.
400    /// For instance, an adjective or an adverb.
401    pub fn then_kind_either<F1, F2>(self, pred_is_1: F1, pred_is_2: F2) -> Self
402    where
403        F1: Fn(&TokenKind) -> bool + Send + Sync + 'static,
404        F2: Fn(&TokenKind) -> bool + Send + Sync + 'static,
405    {
406        self.then_kind_where(move |k| pred_is_1(k) || pred_is_2(k))
407    }
408
409    /// Match a token where neither of the two token kind predicates returns true.
410    /// For instance, a word that can't be a verb or a noun.
411    pub fn then_kind_neither<F1, F2>(self, pred_isnt_1: F1, pred_isnt_2: F2) -> Self
412    where
413        F1: Fn(&TokenKind) -> bool + Send + Sync + 'static,
414        F2: Fn(&TokenKind) -> bool + Send + Sync + 'static,
415    {
416        self.then_kind_where(move |k| !pred_isnt_1(k) && !pred_isnt_2(k))
417    }
418
419    /// Match a token where the first token kind predicate returns true and the second returns false.
420    /// For instance, a word that can be a noun but cannot be a verb.
421    pub fn then_kind_is_but_is_not<F1, F2>(self, pred_is: F1, pred_not: F2) -> Self
422    where
423        F1: Fn(&TokenKind) -> bool + Send + Sync + 'static,
424        F2: Fn(&TokenKind) -> bool + Send + Sync + 'static,
425    {
426        self.then_kind_where(move |k| pred_is(k) && !pred_not(k))
427    }
428
429    /// Match a token where the first token kind predicate returns true and the second returns false,
430    /// and the token is not in the list of exceptions.
431    pub fn then_kind_is_but_is_not_except<F1, F2>(
432        self,
433        pred_is: F1,
434        pred_not: F2,
435        ex: &'static [&'static str],
436    ) -> Self
437    where
438        F1: Fn(&TokenKind) -> bool + Send + Sync + 'static,
439        F2: Fn(&TokenKind) -> bool + Send + Sync + 'static,
440    {
441        self.then(move |tok: &Token, src: &[char]| {
442            pred_is(&tok.kind)
443                && !pred_not(&tok.kind)
444                && !ex.iter().any(|&word| tok.get_ch(src).eq_str(word))
445        })
446    }
447
448    /// Match a token where the first token kind predicate returns true and all of the second return false.
449    /// For instance, a word that can be a verb but not a noun or an adjective.
450    pub fn then_kind_is_but_isnt_any_of<F1, F2>(
451        self,
452        pred_is: F1,
453        preds_isnt: &'static [F2],
454    ) -> Self
455    where
456        F1: Fn(&TokenKind) -> bool + Send + Sync + 'static,
457        F2: Fn(&TokenKind) -> bool + Send + Sync + 'static,
458    {
459        self.then_kind_where(move |k| pred_is(k) && !preds_isnt.iter().any(|pred| pred(k)))
460    }
461
462    /// Match a token where the first token kind predicate returns true and all of the second return false,
463    /// and the token is not in the list of exceptions.
464    /// For instance, an adjective that isn't also a verb or adverb or the word "likely".
465    pub fn then_kind_is_but_isnt_any_of_except<F1, F2>(
466        self,
467        pred_is: F1,
468        preds_isnt: &'static [F2],
469        ex: &'static [&'static str],
470    ) -> Self
471    where
472        F1: Fn(&TokenKind) -> bool + Send + Sync + 'static,
473        F2: Fn(&TokenKind) -> bool + Send + Sync + 'static,
474    {
475        self.then(move |tok: &Token, src: &[char]| {
476            pred_is(&tok.kind)
477                && !preds_isnt.iter().any(|pred| pred(&tok.kind))
478                && !ex.iter().any(|&word| tok.get_ch(src).eq_str(word))
479        })
480    }
481
482    // More than two kinds
483
484    /// Match a token where both of the first two token kind predicates return true,
485    /// and the third returns false.
486    /// For instance, a word that must be both noun and verb, but not adjective.
487    pub fn then_kind_both_but_not<F1, F2, F3>(
488        self,
489        (pred_is_1, pred_is_2): (F1, F2),
490        pred_not: F3,
491    ) -> Self
492    where
493        F1: Fn(&TokenKind) -> bool + Send + Sync + 'static,
494        F2: Fn(&TokenKind) -> bool + Send + Sync + 'static,
495        F3: Fn(&TokenKind) -> bool + Send + Sync + 'static,
496    {
497        self.then_kind_where(move |k| pred_is_1(k) && pred_is_2(k) && !pred_not(k))
498    }
499
500    /// Match a token where any of the token kind predicates returns true.
501    /// Like `then_kind_either` but for more than two predicates.
502    pub fn then_kind_any<F>(self, preds_is: &'static [F]) -> Self
503    where
504        F: Fn(&TokenKind) -> bool + Send + Sync + 'static,
505    {
506        self.then_kind_where(move |k| preds_is.iter().any(|pred| pred(k)))
507    }
508
509    /// Match a token where none of the token kind predicates returns true.
510    /// Like `then_kind_neither` but for more than two predicates.
511    pub fn then_kind_none_of<F>(self, preds_isnt: &'static [F]) -> Self
512    where
513        F: Fn(&TokenKind) -> bool + Send + Sync + 'static,
514    {
515        self.then_kind_where(move |k| preds_isnt.iter().all(|pred| !pred(k)))
516    }
517
518    /// Match a token where any of the token kind predicates returns true,
519    /// and the word is not in the list of exceptions.
520    pub fn then_kind_any_except<F>(
521        self,
522        preds_is: &'static [F],
523        ex: &'static [&'static str],
524    ) -> Self
525    where
526        F: Fn(&TokenKind) -> bool + Send + Sync + 'static,
527    {
528        self.then(move |tok: &Token, src: &[char]| {
529            preds_is.iter().any(|pred| pred(&tok.kind))
530                && !ex.iter().any(|&word| tok.get_ch(src).eq_str(word))
531        })
532    }
533
534    /// Match a token where any of the token kind predicates returns true,
535    /// or the token is in the list of words.
536    pub fn then_kind_any_or_words<F>(
537        self,
538        preds: &'static [F],
539        words: &'static [&'static str],
540    ) -> Self
541    where
542        F: Fn(&TokenKind) -> bool + Send + Sync + 'static,
543    {
544        self.then(move |tok: &Token, src: &[char]| {
545            preds.iter().any(|pred| pred(&tok.kind))
546                || words.iter().any(|&word| tok.get_ch(src).eq_str(word))
547        })
548    }
549
550    /// Match a token where any of the first token kind predicates returns true
551    /// and the second returns false.
552    pub fn then_kind_any_but_not<F1, F2>(self, preds_is: &'static [F1], pred_not: F2) -> Self
553    where
554        F1: Fn(&TokenKind) -> bool + Send + Sync + 'static,
555        F2: Fn(&TokenKind) -> bool + Send + Sync + 'static,
556    {
557        self.then(move |tok: &Token, _src: &[char]| {
558            preds_is.iter().any(|pred| pred(&tok.kind)) && !pred_not(&tok.kind)
559        })
560    }
561
562    /// Match a token where any of the first token kind predicates returns true,
563    /// the second returns false, and the token is not in the list of exceptions.    
564    pub fn then_kind_any_but_not_except<F1, F2>(
565        self,
566        preds_is: &'static [F1],
567        pred_not: F2,
568        ex: &'static [&'static str],
569    ) -> 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))
576                && !pred_not(&tok.kind)
577                && !ex.iter().any(|&word| tok.get_ch(src).eq_str(word))
578        })
579    }
580
581    // Word property matching methods
582
583    // Out-of-vocabulary word. (Words not in the dictionary)
584    gen_then_from_is!(oov);
585    gen_then_from_is!(swear);
586
587    // Part-of-speech matching methods
588
589    // Nominals (nouns and pronouns)
590
591    gen_then_from_is!(nominal);
592    gen_then_from_is!(plural_nominal);
593    gen_then_from_is!(non_plural_nominal);
594    gen_then_from_is!(possessive_nominal);
595
596    // Nouns
597
598    gen_then_from_is!(noun);
599    gen_then_from_is!(proper_noun);
600    gen_then_from_is!(singular_noun);
601    gen_then_from_is!(plural_noun);
602    gen_then_from_is!(singular_noun_only);
603    gen_then_from_is!(plural_noun_only);
604    gen_then_from_is!(mass_noun_only);
605
606    // Pronouns
607
608    gen_then_from_is!(pronoun);
609    gen_then_from_is!(personal_pronoun);
610    gen_then_from_is!(first_person_singular_pronoun);
611    gen_then_from_is!(first_person_plural_pronoun);
612    gen_then_from_is!(second_person_pronoun);
613    gen_then_from_is!(third_person_pronoun);
614    gen_then_from_is!(third_person_singular_pronoun);
615    gen_then_from_is!(third_person_plural_pronoun);
616    gen_then_from_is!(subject_pronoun);
617    gen_then_from_is!(object_pronoun);
618
619    pub fn then_relative_pronoun(self) -> Self {
620        self.then(RelativePronoun::default())
621    }
622
623    // Verbs
624
625    gen_then_from_is!(verb);
626    gen_then_from_is!(auxiliary_verb);
627    gen_then_from_is!(linking_verb);
628    gen_then_from_is!(verb_lemma);
629    gen_then_from_is!(verb_simple_past_form);
630    gen_then_from_is!(verb_past_participle_form);
631    gen_then_from_is!(verb_progressive_form);
632    gen_then_from_is!(verb_third_person_singular_present_form);
633
634    // Adjectives
635
636    gen_then_from_is!(adjective);
637    gen_then_from_is!(positive_adjective);
638    gen_then_from_is!(comparative_adjective);
639    gen_then_from_is!(superlative_adjective);
640
641    // Adverbs
642
643    gen_then_from_is!(adverb);
644    gen_then_from_is!(frequency_adverb);
645    gen_then_from_is!(degree_adverb);
646
647    // Determiners
648
649    gen_then_from_is!(determiner);
650    gen_then_from_is!(demonstrative_determiner);
651    gen_then_from_is!(possessive_determiner);
652    gen_then_from_is!(quantifier);
653    gen_then_from_is!(non_quantifier_determiner);
654    gen_then_from_is!(non_demonstrative_determiner);
655
656    /// Push an [`IndefiniteArticle`] to the end of the operation list.
657    pub fn then_indefinite_article(self) -> Self {
658        self.then(IndefiniteArticle::default())
659    }
660
661    // Other parts of speech
662
663    gen_then_from_is!(conjunction);
664    gen_then_from_is!(preposition);
665
666    // Numbers
667
668    gen_then_from_is!(number);
669    gen_then_from_is!(cardinal_number);
670    gen_then_from_is!(ordinal_number);
671
672    // Punctuation
673
674    gen_then_from_is!(punctuation);
675    gen_then_from_is!(apostrophe);
676    gen_then_from_is!(comma);
677    gen_then_from_is!(hyphen);
678    gen_then_from_is!(period);
679    gen_then_from_is!(semicolon);
680    gen_then_from_is!(acute);
681    gen_then_from_is!(quote);
682    gen_then_from_is!(backslash);
683    gen_then_from_is!(slash);
684    gen_then_from_is!(percent);
685    gen_then_from_is!(degree);
686    gen_then_from_is!(open_single);
687    gen_then_from_is!(single_prime);
688    gen_then_from_is!(double_prime);
689    gen_then_from_is!(backtick);
690    gen_then_from_is!(plus);
691
692    // Other
693
694    gen_then_from_is!(case_separator);
695    gen_then_from_is!(likely_homograph);
696    gen_then_from_is!(sentence_terminator);
697}
698
699impl<S> From<S> for SequenceExpr
700where
701    S: Step + 'static,
702{
703    fn from(step: S) -> Self {
704        Self {
705            exprs: vec![Box::new(step)],
706        }
707    }
708}
709
710#[cfg(test)]
711mod tests {
712    use crate::{
713        Document, TokenKind,
714        expr::{AnchorEnd, Expr, ExprExt, SequenceExpr},
715        linting::tests::SpanVecExt,
716    };
717
718    #[test]
719    fn test_kind_both() {
720        let noun_and_verb =
721            SequenceExpr::default().then_kind_both(TokenKind::is_noun, TokenKind::is_verb);
722        let doc = Document::new_plain_english_curated("Use a good example.");
723        let matches = noun_and_verb.iter_matches_in_doc(&doc).collect::<Vec<_>>();
724        assert_eq!(matches.to_strings(&doc), vec!["Use", "good", "example"]);
725    }
726
727    #[test]
728    fn test_adjective_or_determiner() {
729        let expr = SequenceExpr::default()
730            .then_kind_either(TokenKind::is_adjective, TokenKind::is_determiner);
731        let doc = Document::new_plain_english_curated("Use a good example.");
732        let matches = expr.iter_matches_in_doc(&doc).collect::<Vec<_>>();
733        assert_eq!(matches.to_strings(&doc), vec!["a", "good"]);
734    }
735
736    #[test]
737    fn test_noun_but_not_adjective() {
738        let expr = SequenceExpr::default()
739            .then_kind_is_but_is_not(TokenKind::is_noun, TokenKind::is_adjective);
740        let doc = Document::new_plain_english_curated("Use a good example.");
741        let matches = expr.iter_matches_in_doc(&doc).collect::<Vec<_>>();
742        assert_eq!(matches.to_strings(&doc), vec!["Use", "example"]);
743    }
744
745    #[test]
746    fn flag_foo_followed_by_bar_or_at_end_1() {
747        let expr = SequenceExpr::aco("foo").then_any_of([
748            Box::new(SequenceExpr::whitespace().t_aco("bar").then(AnchorEnd)) as Box<dyn Expr>,
749            Box::new(AnchorEnd),
750        ]);
751
752        let doc_with_bar = Document::new_plain_english_curated("foo bar");
753
754        let matches_with_bar = expr.iter_matches_in_doc(&doc_with_bar).collect::<Vec<_>>();
755
756        eprintln!("matches_with_bar: {:#?}", matches_with_bar);
757
758        // "foo bar" matches with span covering both tokens
759        assert_eq!(matches_with_bar.len(), 1);
760        assert_eq!(matches_with_bar[0].start, 0);
761        assert_eq!(matches_with_bar[0].end, 3);
762        assert_eq!(matches_with_bar.to_strings(&doc_with_bar), vec!["foo bar"]);
763    }
764
765    #[test]
766    fn flag_foo_followed_by_bar_or_at_end_2() {
767        let expr = SequenceExpr::aco("foo").then_any_of([
768            Box::new(SequenceExpr::whitespace().t_aco("bar").then(AnchorEnd)) as Box<dyn Expr>,
769            Box::new(AnchorEnd),
770        ]);
771
772        let doc_with_end = Document::new_plain_english_curated("foo");
773
774        let matches_with_end = expr.iter_matches_in_doc(&doc_with_end).collect::<Vec<_>>();
775
776        eprintln!("matches_with_end: {:#?}", matches_with_end);
777
778        // "foo" at end matches with span covering just "foo"
779        assert_eq!(matches_with_end.len(), 1);
780        assert_eq!(matches_with_end[0].start, 0);
781        assert_eq!(matches_with_end[0].end, 1);
782        assert_eq!(matches_with_end.to_strings(&doc_with_end), vec!["foo"]);
783    }
784
785    #[test]
786    fn flag_foo_followed_by_bar_or_at_end_3() {
787        let expr = SequenceExpr::aco("foo").then_any_of([
788            Box::new(SequenceExpr::whitespace().t_aco("bar").then(AnchorEnd)) as Box<dyn Expr>,
789            Box::new(AnchorEnd),
790        ]);
791
792        let doc_with_foo_bar_baz = Document::new_plain_english_curated("foo bar baz");
793
794        let matches_with_foo_bar_baz = expr
795            .iter_matches_in_doc(&doc_with_foo_bar_baz)
796            .collect::<Vec<_>>();
797
798        eprintln!("matches_with_foo_bar_baz: {:#?}", matches_with_foo_bar_baz);
799
800        // "foo bar baz" should NOT match because "bar" is not at the end
801        assert_eq!(matches_with_foo_bar_baz.len(), 0);
802        assert_eq!(
803            matches_with_foo_bar_baz.to_strings(&doc_with_foo_bar_baz),
804            Vec::<String>::new()
805        );
806    }
807}