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>;
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> {
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> {
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> {
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> + 'a>;
116
117    fn iter_matches_in_doc<'a>(&'a self, doc: &'a Document) -> Box<dyn Iterator<Item = Span> + 'a>;
118}
119
120impl<E: ?Sized> ExprExt for E
121where
122    E: Expr,
123{
124    fn iter_matches<'a>(
125        &'a self,
126        tokens: &'a [Token],
127        source: &'a [char],
128    ) -> Box<(dyn Iterator<Item = Span> + 'a)> {
129        let mut last_end = 0usize;
130
131        Box::new((0..tokens.len()).filter_map(move |i| {
132            let span = self.run(i, tokens, source)?;
133            if span.start >= last_end {
134                last_end = span.end;
135                Some(span)
136            } else {
137                None
138            }
139        }))
140    }
141
142    fn iter_matches_in_doc<'a>(
143        &'a self,
144        doc: &'a Document,
145    ) -> Box<(dyn Iterator<Item = Span> + 'a)> {
146        Box::new(self.iter_matches(doc.get_tokens(), doc.get_source()))
147    }
148}
149
150pub trait OwnedExprExt {
151    fn or(self, other: impl Expr + 'static) -> LongestMatchOf;
152}
153
154impl<E> OwnedExprExt for E
155where
156    E: Expr + 'static,
157{
158    fn or(self, other: impl Expr + 'static) -> LongestMatchOf {
159        LongestMatchOf::new(vec![Box::new(self), Box::new(other)])
160    }
161}