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