keymap_context.rs

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