Skip to main content

harper_core/patterns/
mod.rs

1//! [`Pattern`]s are one of the more powerful ways to query text inside Harper, especially for beginners. They are a simplified abstraction over [`Expr`](crate::expr::Expr).
2//!
3//! Through the [`ExprLinter`](crate::linting::ExprLinter) trait, they make it much easier to
4//! build Harper [rules](crate::linting::Linter).
5//!
6//! See the page about [`SequenceExpr`](crate::expr::SequenceExpr) for a concrete example of their use.
7
8use crate::{Document, LSend, Span, Token};
9
10mod any_pattern;
11mod derived_from;
12mod implies_quantity;
13mod indefinite_article;
14mod inflection_of_be;
15mod invert;
16mod modal_verb;
17mod nominal_phrase;
18mod prepositional_preceder;
19mod relative_pronoun;
20mod upos_set;
21mod whitespace_pattern;
22mod within_edit_distance;
23mod word;
24mod word_set;
25
26pub use any_pattern::AnyPattern;
27pub use derived_from::DerivedFrom;
28pub use implies_quantity::ImpliesQuantity;
29pub use indefinite_article::IndefiniteArticle;
30pub use inflection_of_be::InflectionOfBe;
31pub use invert::Invert;
32pub use modal_verb::ModalVerb;
33pub use nominal_phrase::NominalPhrase;
34pub use prepositional_preceder::{PrepositionalPrecederPattern, prepositional_preceder};
35pub use relative_pronoun::RelativePronoun;
36pub use upos_set::UPOSSet;
37pub use whitespace_pattern::WhitespacePattern;
38pub use within_edit_distance::WithinEditDistance;
39pub use word::Word;
40pub use word_set::WordSet;
41
42pub trait Pattern: LSend {
43    /// Check if the pattern matches at the start of the given token slice.
44    ///
45    /// Returns the length of the match if successful, or `None` if not.
46    fn matches(&self, tokens: &[Token], source: &[char]) -> Option<usize>;
47}
48
49pub trait PatternExt {
50    fn iter_matches(&self, tokens: &[Token], source: &[char]) -> impl Iterator<Item = Span<Token>>;
51
52    /// Search through all tokens to locate all non-overlapping pattern matches.
53    fn find_all_matches(&self, tokens: &[Token], source: &[char]) -> Vec<Span<Token>> {
54        self.iter_matches(tokens, source).collect()
55    }
56}
57
58impl<P> PatternExt for P
59where
60    P: Pattern + ?Sized,
61{
62    fn iter_matches(&self, tokens: &[Token], source: &[char]) -> impl Iterator<Item = Span<Token>> {
63        MatchIter::new(self, tokens, source)
64    }
65}
66
67struct MatchIter<'a, 'b, 'c, P: ?Sized> {
68    pattern: &'a P,
69    tokens: &'b [Token],
70    source: &'c [char],
71    index: usize,
72}
73impl<'a, 'b, 'c, P> MatchIter<'a, 'b, 'c, P>
74where
75    P: Pattern + ?Sized,
76{
77    fn new(pattern: &'a P, tokens: &'b [Token], source: &'c [char]) -> Self {
78        Self {
79            pattern,
80            tokens,
81            source,
82            index: 0,
83        }
84    }
85}
86impl<P> Iterator for MatchIter<'_, '_, '_, P>
87where
88    P: Pattern + ?Sized,
89{
90    type Item = Span<Token>;
91
92    fn next(&mut self) -> Option<Self::Item> {
93        while self.index < self.tokens.len() {
94            if let Some(len) = self
95                .pattern
96                .matches(&self.tokens[self.index..], self.source)
97            {
98                let span = Span::new_with_len(self.index, len);
99                self.index += len.max(1);
100                return Some(span);
101            } else {
102                self.index += 1;
103            }
104        }
105
106        None
107    }
108}
109
110/// A simpler version of the [`Pattern`] trait that only matches a single
111/// token.
112pub trait SingleTokenPattern: LSend {
113    fn matches_token(&self, token: &Token, source: &[char]) -> bool;
114}
115
116impl<S: SingleTokenPattern> Pattern for S {
117    fn matches(&self, tokens: &[Token], source: &[char]) -> Option<usize> {
118        if self.matches_token(tokens.first()?, source) {
119            Some(1)
120        } else {
121            None
122        }
123    }
124}
125
126impl<F: LSend + Fn(&Token, &[char]) -> bool> SingleTokenPattern for F {
127    fn matches_token(&self, token: &Token, source: &[char]) -> bool {
128        self(token, source)
129    }
130}
131
132pub trait DocPattern {
133    fn find_all_matches_in_doc(&self, document: &Document) -> Vec<Span<Token>>;
134}
135
136impl<P: PatternExt> DocPattern for P {
137    fn find_all_matches_in_doc(&self, document: &Document) -> Vec<Span<Token>> {
138        self.find_all_matches(document.get_tokens(), document.get_source())
139    }
140}