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