keymap_context.rs

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