harper_core/expr/
expr_map.rs

1use crate::LSend;
2use crate::Span;
3use crate::Token;
4
5use super::Expr;
6
7/// A map from an [`Expr`] to arbitrary data.
8///
9/// It has been a common pattern for rule authors to build a list of expressions that match a
10/// grammatical error.
11/// Then, depending on which expression was matched, a suggestion is chosen from another list.
12///
13/// The [`ExprMap`] unifies these two lists into one.
14///
15/// A great example of this is the [`PronounInfectionBe`](crate::linting::PronounInflectionBe)
16/// rule.
17/// It builds a list of incorrect `PRONOUN + BE` combinations, alongside their corrections.
18///
19/// When used as a [`Expr`] in and of itself, it simply iterates through
20/// all contained expressions, returning the first match found.
21/// You should not assume this search is deterministic.
22pub struct ExprMap<T>
23where
24    T: LSend,
25{
26    rows: Vec<Row<T>>,
27}
28
29struct Row<T>
30where
31    T: LSend,
32{
33    pub key: Box<dyn Expr>,
34    pub element: T,
35}
36
37impl<T> Default for ExprMap<T>
38where
39    T: LSend,
40{
41    fn default() -> Self {
42        Self {
43            rows: Default::default(),
44        }
45    }
46}
47
48impl<T> ExprMap<T>
49where
50    T: LSend,
51{
52    pub fn insert(&mut self, expr: impl Expr + 'static, value: T) {
53        self.rows.push(Row {
54            key: Box::new(expr),
55            element: value,
56        });
57    }
58
59    /// Look up the corresponding value for the given map.
60    pub fn lookup(&self, cursor: usize, tokens: &[Token], source: &[char]) -> Option<&T> {
61        for row in &self.rows {
62            let len = row.key.run(cursor, tokens, source);
63
64            if len.is_some() {
65                return Some(&row.element);
66            }
67        }
68
69        None
70    }
71}
72
73impl<T> Expr for ExprMap<T>
74where
75    T: LSend,
76{
77    fn run(&self, cursor: usize, tokens: &[Token], source: &[char]) -> Option<Span> {
78        self.rows
79            .iter()
80            .filter_map(|row| row.key.run(cursor, tokens, source))
81            .next()
82    }
83}