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