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