harper_core/expr/
mod.rs

1//! An `Expr` is a declarative way to express whether a certain set of tokens fulfill a criteria.
2//!
3//! For example, if we want to look for the word "that" followed by an adjective, we could build an
4//! expression to do so.
5//!
6//! The actual searching is done by another system (usually a part of the [lint framework](crate::linting::ExprLinter)).
7//! It iterates through a document, checking if each index matches the criteria.
8//!
9//! When supplied a specific position in a token stream, the technical job of an `Expr` is to determine the window of tokens (including the cursor itself) that fulfills whatever criteria the author desires.
10//!
11//! The goal of the `Expr` initiative is to make rules easier to _read_ as well as to write.
12//! Gone are the days of trying to manually parse the logic of another man's Rust code.
13//!
14//! See also: [`SequenceExpr`].
15
16mod all;
17mod anchor_end;
18mod anchor_start;
19mod duration_expr;
20mod expr_map;
21mod filter;
22mod first_match_of;
23mod fixed_phrase;
24mod longest_match_of;
25mod mergeable_words;
26mod optional;
27mod reflexive_pronoun;
28mod repeating;
29mod sequence_expr;
30mod similar_to_phrase;
31mod space_or_hyphen;
32mod spelled_number_expr;
33mod step;
34mod time_unit_expr;
35mod unless_step;
36mod word_expr_group;
37
38#[cfg(not(feature = "concurrent"))]
39use std::rc::Rc;
40use std::sync::Arc;
41
42pub use all::All;
43pub use anchor_end::AnchorEnd;
44pub use anchor_start::AnchorStart;
45pub use duration_expr::DurationExpr;
46pub use expr_map::ExprMap;
47pub use filter::Filter;
48pub use first_match_of::FirstMatchOf;
49pub use fixed_phrase::FixedPhrase;
50pub use longest_match_of::LongestMatchOf;
51pub use mergeable_words::MergeableWords;
52pub use optional::Optional;
53pub use reflexive_pronoun::ReflexivePronoun;
54pub use repeating::Repeating;
55pub use sequence_expr::SequenceExpr;
56pub use similar_to_phrase::SimilarToPhrase;
57pub use space_or_hyphen::SpaceOrHyphen;
58pub use spelled_number_expr::SpelledNumberExpr;
59pub use step::Step;
60pub use time_unit_expr::TimeUnitExpr;
61pub use unless_step::UnlessStep;
62pub use word_expr_group::WordExprGroup;
63
64use crate::{Document, LSend, Span, Token};
65
66pub trait Expr: LSend {
67    fn run(&self, cursor: usize, tokens: &[Token], source: &[char]) -> Option<Span<Token>>;
68}
69
70impl<S> Expr for S
71where
72    S: Step + ?Sized,
73{
74    fn run(&self, cursor: usize, tokens: &[Token], source: &[char]) -> Option<Span<Token>> {
75        self.step(tokens, cursor, source).map(|s| {
76            if s >= 0 {
77                Span::new_with_len(cursor, s as usize)
78            } else {
79                Span::new(add(cursor, s).unwrap(), cursor)
80            }
81        })
82    }
83}
84
85impl<E> Expr for Arc<E>
86where
87    E: Expr,
88{
89    fn run(&self, cursor: usize, tokens: &[Token], source: &[char]) -> Option<Span<Token>> {
90        self.as_ref().run(cursor, tokens, source)
91    }
92}
93
94impl Expr for Box<dyn Expr> {
95    fn run(&self, cursor: usize, tokens: &[Token], source: &[char]) -> Option<Span<Token>> {
96        self.as_ref().run(cursor, tokens, source)
97    }
98}
99
100#[cfg(not(feature = "concurrent"))]
101impl<E> Expr for Rc<E>
102where
103    E: Expr,
104{
105    fn run(&self, cursor: usize, tokens: &[Token], source: &[char]) -> Option<Span<Token>> {
106        self.as_ref().run(cursor, tokens, source)
107    }
108}
109
110fn add(u: usize, i: isize) -> Option<usize> {
111    if i.is_negative() {
112        u.checked_sub(i.wrapping_abs() as u32 as usize)
113    } else {
114        u.checked_add(i as usize)
115    }
116}
117
118pub trait ExprExt {
119    /// Iterate over all matches of this expression in the document, automatically filtering out
120    /// overlapping matches, preferring the first.
121    fn iter_matches<'a>(
122        &'a self,
123        tokens: &'a [Token],
124        source: &'a [char],
125    ) -> Box<dyn Iterator<Item = Span<Token>> + 'a>;
126
127    fn iter_matches_in_doc<'a>(
128        &'a self,
129        doc: &'a Document,
130    ) -> Box<dyn Iterator<Item = Span<Token>> + 'a>;
131}
132
133impl<E: ?Sized> ExprExt for E
134where
135    E: Expr,
136{
137    fn iter_matches<'a>(
138        &'a self,
139        tokens: &'a [Token],
140        source: &'a [char],
141    ) -> Box<dyn Iterator<Item = Span<Token>> + 'a> {
142        let mut last_end = 0usize;
143
144        Box::new((0..tokens.len()).filter_map(move |i| {
145            let span = self.run(i, tokens, source)?;
146            if span.start >= last_end {
147                last_end = span.end;
148                Some(span)
149            } else {
150                None
151            }
152        }))
153    }
154
155    fn iter_matches_in_doc<'a>(
156        &'a self,
157        doc: &'a Document,
158    ) -> Box<dyn Iterator<Item = Span<Token>> + 'a> {
159        Box::new(self.iter_matches(doc.get_tokens(), doc.get_source()))
160    }
161}
162
163pub trait OwnedExprExt {
164    fn or(self, other: impl Expr + 'static) -> FirstMatchOf;
165    fn and(self, other: impl Expr + 'static) -> All;
166    fn and_not(self, other: impl Expr + 'static) -> All;
167    fn or_longest(self, other: impl Expr + 'static) -> LongestMatchOf;
168}
169
170impl<E> OwnedExprExt for E
171where
172    E: Expr + 'static,
173{
174    /// Returns an expression that matches either the current one or the expression contained in `other`.
175    fn or(self, other: impl Expr + 'static) -> FirstMatchOf {
176        FirstMatchOf::new(vec![Box::new(self), Box::new(other)])
177    }
178
179    /// Returns an expression that matches only if both the current one and the expression contained in `other` do.
180    fn and(self, other: impl Expr + 'static) -> All {
181        All::new(vec![Box::new(self), Box::new(other)])
182    }
183
184    /// Returns an expression that matches only if the current one matches and the expression contained in `other` does not.
185    fn and_not(self, other: impl Expr + 'static) -> All {
186        self.and(UnlessStep::new(other, |_tok: &Token, _src: &[char]| true))
187    }
188
189    /// Returns an expression that matches the longest of the current one or the expression contained in `other`.
190    ///
191    /// If you don't need the longest match, prefer using the short-circuiting [`Self::or()`] instead.
192    fn or_longest(self, other: impl Expr + 'static) -> LongestMatchOf {
193        LongestMatchOf::new(vec![Box::new(self), Box::new(other)])
194    }
195}