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