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}