keymap_context.rs

  1use anyhow::{anyhow, Result};
  2use collections::{HashMap, HashSet};
  3
  4#[derive(Clone, Debug, Default, Eq, PartialEq)]
  5pub struct KeymapContext {
  6    pub set: HashSet<String>,
  7    pub map: HashMap<String, String>,
  8}
  9
 10impl KeymapContext {
 11    pub fn extend(&mut self, other: &Self) {
 12        for v in &other.set {
 13            self.set.insert(v.clone());
 14        }
 15        for (k, v) in &other.map {
 16            self.map.insert(k.clone(), v.clone());
 17        }
 18    }
 19}
 20
 21#[derive(Debug, Eq, PartialEq)]
 22pub enum KeymapContextPredicate {
 23    Identifier(String),
 24    Equal(String, String),
 25    NotEqual(String, String),
 26    Child(Box<KeymapContextPredicate>, Box<KeymapContextPredicate>),
 27    Not(Box<KeymapContextPredicate>),
 28    And(Box<KeymapContextPredicate>, Box<KeymapContextPredicate>),
 29    Or(Box<KeymapContextPredicate>, Box<KeymapContextPredicate>),
 30}
 31
 32impl KeymapContextPredicate {
 33    pub fn parse(source: &str) -> Result<Self> {
 34        let source = Self::skip_whitespace(source);
 35        let (predicate, rest) = Self::parse_expr(source, 0)?;
 36        if let Some(next) = rest.chars().next() {
 37            Err(anyhow!("unexpected character {next:?}"))
 38        } else {
 39            Ok(predicate)
 40        }
 41    }
 42
 43    pub fn eval(&self, contexts: &[KeymapContext]) -> bool {
 44        let Some(context) = contexts.first() else { return false };
 45        match self {
 46            Self::Identifier(name) => context.set.contains(name.as_str()),
 47            Self::Equal(left, right) => context
 48                .map
 49                .get(left)
 50                .map(|value| value == right)
 51                .unwrap_or(false),
 52            Self::NotEqual(left, right) => context
 53                .map
 54                .get(left)
 55                .map(|value| value != right)
 56                .unwrap_or(true),
 57            Self::Not(pred) => !pred.eval(contexts),
 58            Self::Child(parent, child) => parent.eval(&contexts[1..]) && child.eval(contexts),
 59            Self::And(left, right) => left.eval(contexts) && right.eval(contexts),
 60            Self::Or(left, right) => left.eval(contexts) || right.eval(contexts),
 61        }
 62    }
 63
 64    fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
 65        type Op =
 66            fn(KeymapContextPredicate, KeymapContextPredicate) -> Result<KeymapContextPredicate>;
 67
 68        let (mut predicate, rest) = Self::parse_primary(source)?;
 69        source = rest;
 70
 71        'parse: loop {
 72            for (operator, precedence, constructor) in [
 73                (">", PRECEDENCE_CHILD, Self::new_child as Op),
 74                ("&&", PRECEDENCE_AND, Self::new_and as Op),
 75                ("||", PRECEDENCE_OR, Self::new_or as Op),
 76                ("==", PRECEDENCE_EQ, Self::new_eq as Op),
 77                ("!=", PRECEDENCE_EQ, Self::new_neq as Op),
 78            ] {
 79                if source.starts_with(operator) && precedence >= min_precedence {
 80                    source = Self::skip_whitespace(&source[operator.len()..]);
 81                    let (right, rest) = Self::parse_expr(source, precedence + 1)?;
 82                    predicate = constructor(predicate, right)?;
 83                    source = rest;
 84                    continue 'parse;
 85                }
 86            }
 87            break;
 88        }
 89
 90        Ok((predicate, source))
 91    }
 92
 93    fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> {
 94        let next = source
 95            .chars()
 96            .next()
 97            .ok_or_else(|| anyhow!("unexpected eof"))?;
 98        match next {
 99            '(' => {
100                source = Self::skip_whitespace(&source[1..]);
101                let (predicate, rest) = Self::parse_expr(source, 0)?;
102                if rest.starts_with(')') {
103                    source = Self::skip_whitespace(&rest[1..]);
104                    Ok((predicate, source))
105                } else {
106                    Err(anyhow!("expected a ')'"))
107                }
108            }
109            '!' => {
110                let source = Self::skip_whitespace(&source[1..]);
111                let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
112                Ok((KeymapContextPredicate::Not(Box::new(predicate)), source))
113            }
114            _ if next.is_alphanumeric() || next == '_' => {
115                let len = source
116                    .find(|c: char| !(c.is_alphanumeric() || c == '_'))
117                    .unwrap_or(source.len());
118                let (identifier, rest) = source.split_at(len);
119                source = Self::skip_whitespace(rest);
120                Ok((
121                    KeymapContextPredicate::Identifier(identifier.into()),
122                    source,
123                ))
124            }
125            _ => Err(anyhow!("unexpected character {next:?}")),
126        }
127    }
128
129    fn skip_whitespace(source: &str) -> &str {
130        let len = source
131            .find(|c: char| !c.is_whitespace())
132            .unwrap_or(source.len());
133        &source[len..]
134    }
135
136    fn new_or(self, other: Self) -> Result<Self> {
137        Ok(Self::Or(Box::new(self), Box::new(other)))
138    }
139
140    fn new_and(self, other: Self) -> Result<Self> {
141        Ok(Self::And(Box::new(self), Box::new(other)))
142    }
143
144    fn new_child(self, other: Self) -> Result<Self> {
145        Ok(Self::Child(Box::new(self), Box::new(other)))
146    }
147
148    fn new_eq(self, other: Self) -> Result<Self> {
149        if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
150            Ok(Self::Equal(left, right))
151        } else {
152            Err(anyhow!("operands must be identifiers"))
153        }
154    }
155
156    fn new_neq(self, other: Self) -> Result<Self> {
157        if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
158            Ok(Self::NotEqual(left, right))
159        } else {
160            Err(anyhow!("operands must be identifiers"))
161        }
162    }
163}
164
165const PRECEDENCE_CHILD: u32 = 1;
166const PRECEDENCE_OR: u32 = 2;
167const PRECEDENCE_AND: u32 = 3;
168const PRECEDENCE_EQ: u32 = 4;
169const PRECEDENCE_NOT: u32 = 5;
170
171#[cfg(test)]
172mod tests {
173    use super::KeymapContextPredicate::{self, *};
174
175    #[test]
176    fn test_parse_identifiers() {
177        // Identifiers
178        assert_eq!(
179            KeymapContextPredicate::parse("abc12").unwrap(),
180            Identifier("abc12".into())
181        );
182        assert_eq!(
183            KeymapContextPredicate::parse("_1a").unwrap(),
184            Identifier("_1a".into())
185        );
186    }
187
188    #[test]
189    fn test_parse_negations() {
190        assert_eq!(
191            KeymapContextPredicate::parse("!abc").unwrap(),
192            Not(Box::new(Identifier("abc".into())))
193        );
194        assert_eq!(
195            KeymapContextPredicate::parse(" ! ! abc").unwrap(),
196            Not(Box::new(Not(Box::new(Identifier("abc".into())))))
197        );
198    }
199
200    #[test]
201    fn test_parse_equality_operators() {
202        assert_eq!(
203            KeymapContextPredicate::parse("a == b").unwrap(),
204            Equal("a".into(), "b".into())
205        );
206        assert_eq!(
207            KeymapContextPredicate::parse("c!=d").unwrap(),
208            NotEqual("c".into(), "d".into())
209        );
210        assert_eq!(
211            KeymapContextPredicate::parse("c == !d")
212                .unwrap_err()
213                .to_string(),
214            "operands must be identifiers"
215        );
216    }
217
218    #[test]
219    fn test_parse_boolean_operators() {
220        assert_eq!(
221            KeymapContextPredicate::parse("a || b").unwrap(),
222            Or(
223                Box::new(Identifier("a".into())),
224                Box::new(Identifier("b".into()))
225            )
226        );
227        assert_eq!(
228            KeymapContextPredicate::parse("a || !b && c").unwrap(),
229            Or(
230                Box::new(Identifier("a".into())),
231                Box::new(And(
232                    Box::new(Not(Box::new(Identifier("b".into())))),
233                    Box::new(Identifier("c".into()))
234                ))
235            )
236        );
237        assert_eq!(
238            KeymapContextPredicate::parse("a && b || c&&d").unwrap(),
239            Or(
240                Box::new(And(
241                    Box::new(Identifier("a".into())),
242                    Box::new(Identifier("b".into()))
243                )),
244                Box::new(And(
245                    Box::new(Identifier("c".into())),
246                    Box::new(Identifier("d".into()))
247                ))
248            )
249        );
250        assert_eq!(
251            KeymapContextPredicate::parse("a == b && c || d == e && f").unwrap(),
252            Or(
253                Box::new(And(
254                    Box::new(Equal("a".into(), "b".into())),
255                    Box::new(Identifier("c".into()))
256                )),
257                Box::new(And(
258                    Box::new(Equal("d".into(), "e".into())),
259                    Box::new(Identifier("f".into()))
260                ))
261            )
262        );
263        assert_eq!(
264            KeymapContextPredicate::parse("a && b && c && d").unwrap(),
265            And(
266                Box::new(And(
267                    Box::new(And(
268                        Box::new(Identifier("a".into())),
269                        Box::new(Identifier("b".into()))
270                    )),
271                    Box::new(Identifier("c".into())),
272                )),
273                Box::new(Identifier("d".into()))
274            ),
275        );
276    }
277
278    #[test]
279    fn test_parse_parenthesized_expressions() {
280        assert_eq!(
281            KeymapContextPredicate::parse("a && (b == c || d != e)").unwrap(),
282            And(
283                Box::new(Identifier("a".into())),
284                Box::new(Or(
285                    Box::new(Equal("b".into(), "c".into())),
286                    Box::new(NotEqual("d".into(), "e".into())),
287                )),
288            ),
289        );
290        assert_eq!(
291            KeymapContextPredicate::parse(" ( a || b ) ").unwrap(),
292            Or(
293                Box::new(Identifier("a".into())),
294                Box::new(Identifier("b".into())),
295            )
296        );
297    }
298}