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}