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 expr_map;
20mod first_match_of;
21mod fixed_phrase;
22mod longest_match_of;
23mod mergeable_words;
24mod optional;
25mod reflexive_pronoun;
26mod repeating;
27mod sequence_expr;
28mod similar_to_phrase;
29mod space_or_hyphen;
30mod spelled_number_expr;
31mod step;
32mod time_unit_expr;
33mod unless_step;
34mod word_expr_group;
35
36#[cfg(not(feature = "concurrent"))]
37use std::rc::Rc;
38use std::sync::Arc;
39
40pub use all::All;
41pub use anchor_end::AnchorEnd;
42pub use anchor_start::AnchorStart;
43pub use expr_map::ExprMap;
44pub use first_match_of::FirstMatchOf;
45pub use fixed_phrase::FixedPhrase;
46pub use longest_match_of::LongestMatchOf;
47pub use mergeable_words::MergeableWords;
48pub use optional::Optional;
49pub use reflexive_pronoun::ReflexivePronoun;
50pub use repeating::Repeating;
51pub use sequence_expr::SequenceExpr;
52pub use similar_to_phrase::SimilarToPhrase;
53pub use space_or_hyphen::SpaceOrHyphen;
54pub use spelled_number_expr::SpelledNumberExpr;
55pub use step::Step;
56pub use time_unit_expr::TimeUnitExpr;
57pub use unless_step::UnlessStep;
58pub use word_expr_group::WordExprGroup;
59
60use crate::{Document, LSend, Span, Token};
61
62pub trait Expr: LSend {
63    fn run(&self, cursor: usize, tokens: &[Token], source: &[char]) -> Option<Span<Token>>;
64}
65
66impl<S> Expr for S
67where
68    S: Step + ?Sized,
69{
70    fn run(&self, cursor: usize, tokens: &[Token], source: &[char]) -> Option<Span<Token>> {
71        self.step(tokens, cursor, source).map(|s| {
72            if s >= 0 {
73                Span::new_with_len(cursor, s as usize)
74            } else {
75                Span::new(add(cursor, s).unwrap(), cursor)
76            }
77        })
78    }
79}
80
81impl<E> Expr for Arc<E>
82where
83    E: Expr,
84{
85    fn run(&self, cursor: usize, tokens: &[Token], source: &[char]) -> Option<Span<Token>> {
86        self.as_ref().run(cursor, tokens, source)
87    }
88}
89
90#[cfg(not(feature = "concurrent"))]
91impl<E> Expr for Rc<E>
92where
93    E: Expr,
94{
95    fn run(&self, cursor: usize, tokens: &[Token], source: &[char]) -> Option<Span<Token>> {
96        self.as_ref().run(cursor, tokens, source)
97    }
98}
99
100fn add(u: usize, i: isize) -> Option<usize> {
101    if i.is_negative() {
102        u.checked_sub(i.wrapping_abs() as u32 as usize)
103    } else {
104        u.checked_add(i as usize)
105    }
106}
107
108pub trait ExprExt {
109    /// Iterate over all matches of this expression in the document, automatically filtering out
110    /// overlapping matches, preferring the first.
111    fn iter_matches<'a>(
112        &'a self,
113        tokens: &'a [Token],
114        source: &'a [char],
115    ) -> Box<dyn Iterator<Item = Span<Token>> + 'a>;
116
117    fn iter_matches_in_doc<'a>(
118        &'a self,
119        doc: &'a Document,
120    ) -> Box<dyn Iterator<Item = Span<Token>> + 'a>;
121}
122
123impl<E: ?Sized> ExprExt for E
124where
125    E: Expr,
126{
127    fn iter_matches<'a>(
128        &'a self,
129        tokens: &'a [Token],
130        source: &'a [char],
131    ) -> Box<(dyn Iterator<Item = Span<Token>> + 'a)> {
132        let mut last_end = 0usize;
133
134        Box::new((0..tokens.len()).filter_map(move |i| {
135            let span = self.run(i, tokens, source)?;
136            if span.start >= last_end {
137                last_end = span.end;
138                Some(span)
139            } else {
140                None
141            }
142        }))
143    }
144
145    fn iter_matches_in_doc<'a>(
146        &'a self,
147        doc: &'a Document,
148    ) -> Box<(dyn Iterator<Item = Span<Token>> + 'a)> {
149        Box::new(self.iter_matches(doc.get_tokens(), doc.get_source()))
150    }
151}
152
153pub trait OwnedExprExt {
154    fn or(self, other: impl Expr + 'static) -> FirstMatchOf;
155    fn or_longest(self, other: impl Expr + 'static) -> LongestMatchOf;
156}
157
158impl<E> OwnedExprExt for E
159where
160    E: Expr + 'static,
161{
162    /// Returns an expression that matches either the current one or the expression contained in `other`.
163    fn or(self, other: impl Expr + 'static) -> FirstMatchOf {
164        FirstMatchOf::new(vec![Box::new(self), Box::new(other)])
165    }
166
167    /// Returns an expression that matches the longest of the current one or the expression contained in `other`.
168    ///
169    /// If you don't need the longest match, prefer using the short-circuiting [`Self::or()`] instead.
170    fn or_longest(self, other: impl Expr + 'static) -> LongestMatchOf {
171        LongestMatchOf::new(vec![Box::new(self), Box::new(other)])
172    }
173}