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