harper_core/expr/
mod.rs

1mod all;
2mod anchor_end;
3mod anchor_start;
4mod expr_map;
5mod first_match_of;
6mod fixed_phrase;
7mod longest_match_of;
8mod mergeable_words;
9mod optional;
10mod reflexive_pronoun;
11mod repeating;
12mod sequence_expr;
13mod similar_to_phrase;
14mod space_or_hyphen;
15mod spelled_number_expr;
16mod step;
17mod time_unit_expr;
18mod unless_step;
19mod word_expr_group;
20
21#[cfg(not(feature = "concurrent"))]
22use std::rc::Rc;
23use std::sync::Arc;
24
25pub use all::All;
26pub use anchor_end::AnchorEnd;
27pub use anchor_start::AnchorStart;
28pub use expr_map::ExprMap;
29pub use first_match_of::FirstMatchOf;
30pub use fixed_phrase::FixedPhrase;
31pub use longest_match_of::LongestMatchOf;
32pub use mergeable_words::MergeableWords;
33pub use optional::Optional;
34pub use reflexive_pronoun::ReflexivePronoun;
35pub use repeating::Repeating;
36pub use sequence_expr::SequenceExpr;
37pub use similar_to_phrase::SimilarToPhrase;
38pub use space_or_hyphen::SpaceOrHyphen;
39pub use spelled_number_expr::SpelledNumberExpr;
40pub use step::Step;
41pub use time_unit_expr::TimeUnitExpr;
42pub use unless_step::UnlessStep;
43pub use word_expr_group::WordExprGroup;
44
45use crate::{Document, LSend, Span, Token};
46
47/// A common problem in Harper is that we need to identify tokens that fulfil certain criterion.
48/// An `Expr` is a way to express whether a certain set of tokens fulfil that criteria.
49/// When supplied a specific position in a token stream, the job of an `Expr` is to determine the window of tokens (including the cursor itself) that fulfils whatever criteria the author desires.
50/// It is then the job of another system to identify portions of documents that fulfil this criteria.
51pub trait Expr: LSend {
52    fn run(&self, cursor: usize, tokens: &[Token], source: &[char]) -> Option<Span>;
53}
54
55impl<S> Expr for S
56where
57    S: Step + ?Sized,
58{
59    fn run(&self, cursor: usize, tokens: &[Token], source: &[char]) -> Option<Span> {
60        self.step(tokens, cursor, source).map(|s| {
61            if s >= 0 {
62                Span::new_with_len(cursor, s as usize)
63            } else {
64                Span::new(add(cursor, s).unwrap(), cursor)
65            }
66        })
67    }
68}
69
70impl<E> Expr for Arc<E>
71where
72    E: Expr,
73{
74    fn run(&self, cursor: usize, tokens: &[Token], source: &[char]) -> Option<Span> {
75        self.as_ref().run(cursor, tokens, source)
76    }
77}
78
79#[cfg(not(feature = "concurrent"))]
80impl<E> Expr for Rc<E>
81where
82    E: Expr,
83{
84    fn run(&self, cursor: usize, tokens: &[Token], source: &[char]) -> Option<Span> {
85        self.as_ref().run(cursor, tokens, source)
86    }
87}
88
89fn add(u: usize, i: isize) -> Option<usize> {
90    if i.is_negative() {
91        u.checked_sub(i.wrapping_abs() as u32 as usize)
92    } else {
93        u.checked_add(i as usize)
94    }
95}
96
97pub trait ExprExt {
98    /// Iterate over all matches of this expression in the document, automatically filtering out
99    /// overlapping matches, preferring the first.
100    fn iter_matches<'a>(
101        &'a self,
102        tokens: &'a [Token],
103        source: &'a [char],
104    ) -> Box<dyn Iterator<Item = Span> + 'a>;
105
106    fn iter_matches_in_doc<'a>(&'a self, doc: &'a Document) -> Box<dyn Iterator<Item = Span> + 'a>;
107}
108
109impl<E: ?Sized> ExprExt for E
110where
111    E: Expr,
112{
113    fn iter_matches<'a>(
114        &'a self,
115        tokens: &'a [Token],
116        source: &'a [char],
117    ) -> Box<(dyn Iterator<Item = Span> + 'a)> {
118        let mut last_end = 0usize;
119
120        Box::new((0..tokens.len()).filter_map(move |i| {
121            let span = self.run(i, tokens, source)?;
122            if span.start >= last_end {
123                last_end = span.end;
124                Some(span)
125            } else {
126                None
127            }
128        }))
129    }
130
131    fn iter_matches_in_doc<'a>(
132        &'a self,
133        doc: &'a Document,
134    ) -> Box<(dyn Iterator<Item = Span> + 'a)> {
135        Box::new(self.iter_matches(doc.get_tokens(), doc.get_source()))
136    }
137}
138
139pub trait OwnedExprExt {
140    fn or(self, other: impl Expr + 'static) -> LongestMatchOf;
141}
142
143impl<E> OwnedExprExt for E
144where
145    E: Expr + 'static,
146{
147    fn or(self, other: impl Expr + 'static) -> LongestMatchOf {
148        LongestMatchOf::new(vec![Box::new(self), Box::new(other)])
149    }
150}