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}