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