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}