1use crate::SharedString;
2use anyhow::{anyhow, Result};
3use collections::{HashMap, HashSet};
4use std::any::Any;
5
6pub trait Action: Any + Send + Sync {
7 fn eq(&self, action: &dyn Action) -> bool;
8 fn boxed_clone(&self) -> Box<dyn Action>;
9 fn as_any(&self) -> &dyn Any;
10}
11
12#[derive(Clone, Debug, Default, Eq, PartialEq)]
13pub struct DispatchContext {
14 set: HashSet<SharedString>,
15 map: HashMap<SharedString, SharedString>,
16}
17
18impl<'a> TryFrom<&'a str> for DispatchContext {
19 type Error = anyhow::Error;
20
21 fn try_from(value: &'a str) -> Result<Self> {
22 Self::parse(value)
23 }
24}
25
26impl DispatchContext {
27 pub fn parse(source: &str) -> Result<Self> {
28 let mut context = Self::default();
29 let source = skip_whitespace(source);
30 Self::parse_expr(&source, &mut context)?;
31 Ok(context)
32 }
33
34 fn parse_expr(mut source: &str, context: &mut Self) -> Result<()> {
35 if source.is_empty() {
36 return Ok(());
37 }
38
39 let key = source
40 .chars()
41 .take_while(|c| is_identifier_char(*c))
42 .collect::<String>();
43 source = skip_whitespace(&source[key.len()..]);
44 if let Some(suffix) = source.strip_prefix('=') {
45 source = skip_whitespace(suffix);
46 let value = source
47 .chars()
48 .take_while(|c| is_identifier_char(*c))
49 .collect::<String>();
50 source = skip_whitespace(&source[value.len()..]);
51 context.set(key, value);
52 } else {
53 context.insert(key);
54 }
55
56 Self::parse_expr(source, context)
57 }
58
59 pub fn is_empty(&self) -> bool {
60 self.set.is_empty() && self.map.is_empty()
61 }
62
63 pub fn clear(&mut self) {
64 self.set.clear();
65 self.map.clear();
66 }
67
68 pub fn extend(&mut self, other: &Self) {
69 for v in &other.set {
70 self.set.insert(v.clone());
71 }
72 for (k, v) in &other.map {
73 self.map.insert(k.clone(), v.clone());
74 }
75 }
76
77 pub fn insert<I: Into<SharedString>>(&mut self, identifier: I) {
78 self.set.insert(identifier.into());
79 }
80
81 pub fn set<S1: Into<SharedString>, S2: Into<SharedString>>(&mut self, key: S1, value: S2) {
82 self.map.insert(key.into(), value.into());
83 }
84}
85
86#[derive(Clone, Debug, Eq, PartialEq, Hash)]
87pub enum DispatchContextPredicate {
88 Identifier(SharedString),
89 Equal(SharedString, SharedString),
90 NotEqual(SharedString, SharedString),
91 Child(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
92 Not(Box<DispatchContextPredicate>),
93 And(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
94 Or(Box<DispatchContextPredicate>, Box<DispatchContextPredicate>),
95}
96
97impl DispatchContextPredicate {
98 pub fn parse(source: &str) -> Result<Self> {
99 let source = skip_whitespace(source);
100 let (predicate, rest) = Self::parse_expr(source, 0)?;
101 if let Some(next) = rest.chars().next() {
102 Err(anyhow!("unexpected character {next:?}"))
103 } else {
104 Ok(predicate)
105 }
106 }
107
108 pub fn eval(&self, contexts: &[&DispatchContext]) -> bool {
109 let Some(context) = contexts.last() else {
110 return false;
111 };
112 match self {
113 Self::Identifier(name) => context.set.contains(&name),
114 Self::Equal(left, right) => context
115 .map
116 .get(&left)
117 .map(|value| value == right)
118 .unwrap_or(false),
119 Self::NotEqual(left, right) => context
120 .map
121 .get(&left)
122 .map(|value| value != right)
123 .unwrap_or(true),
124 Self::Not(pred) => !pred.eval(contexts),
125 Self::Child(parent, child) => {
126 parent.eval(&contexts[..contexts.len() - 1]) && child.eval(contexts)
127 }
128 Self::And(left, right) => left.eval(contexts) && right.eval(contexts),
129 Self::Or(left, right) => left.eval(contexts) || right.eval(contexts),
130 }
131 }
132
133 fn parse_expr(mut source: &str, min_precedence: u32) -> anyhow::Result<(Self, &str)> {
134 type Op = fn(
135 DispatchContextPredicate,
136 DispatchContextPredicate,
137 ) -> Result<DispatchContextPredicate>;
138
139 let (mut predicate, rest) = Self::parse_primary(source)?;
140 source = rest;
141
142 'parse: loop {
143 for (operator, precedence, constructor) in [
144 (">", PRECEDENCE_CHILD, Self::new_child as Op),
145 ("&&", PRECEDENCE_AND, Self::new_and as Op),
146 ("||", PRECEDENCE_OR, Self::new_or as Op),
147 ("==", PRECEDENCE_EQ, Self::new_eq as Op),
148 ("!=", PRECEDENCE_EQ, Self::new_neq as Op),
149 ] {
150 if source.starts_with(operator) && precedence >= min_precedence {
151 source = skip_whitespace(&source[operator.len()..]);
152 let (right, rest) = Self::parse_expr(source, precedence + 1)?;
153 predicate = constructor(predicate, right)?;
154 source = rest;
155 continue 'parse;
156 }
157 }
158 break;
159 }
160
161 Ok((predicate, source))
162 }
163
164 fn parse_primary(mut source: &str) -> anyhow::Result<(Self, &str)> {
165 let next = source
166 .chars()
167 .next()
168 .ok_or_else(|| anyhow!("unexpected eof"))?;
169 match next {
170 '(' => {
171 source = skip_whitespace(&source[1..]);
172 let (predicate, rest) = Self::parse_expr(source, 0)?;
173 if rest.starts_with(')') {
174 source = skip_whitespace(&rest[1..]);
175 Ok((predicate, source))
176 } else {
177 Err(anyhow!("expected a ')'"))
178 }
179 }
180 '!' => {
181 let source = skip_whitespace(&source[1..]);
182 let (predicate, source) = Self::parse_expr(&source, PRECEDENCE_NOT)?;
183 Ok((DispatchContextPredicate::Not(Box::new(predicate)), source))
184 }
185 _ if is_identifier_char(next) => {
186 let len = source
187 .find(|c: char| !is_identifier_char(c))
188 .unwrap_or(source.len());
189 let (identifier, rest) = source.split_at(len);
190 source = skip_whitespace(rest);
191 Ok((
192 DispatchContextPredicate::Identifier(identifier.to_string().into()),
193 source,
194 ))
195 }
196 _ => Err(anyhow!("unexpected character {next:?}")),
197 }
198 }
199
200 fn new_or(self, other: Self) -> Result<Self> {
201 Ok(Self::Or(Box::new(self), Box::new(other)))
202 }
203
204 fn new_and(self, other: Self) -> Result<Self> {
205 Ok(Self::And(Box::new(self), Box::new(other)))
206 }
207
208 fn new_child(self, other: Self) -> Result<Self> {
209 Ok(Self::Child(Box::new(self), Box::new(other)))
210 }
211
212 fn new_eq(self, other: Self) -> Result<Self> {
213 if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
214 Ok(Self::Equal(left, right))
215 } else {
216 Err(anyhow!("operands must be identifiers"))
217 }
218 }
219
220 fn new_neq(self, other: Self) -> Result<Self> {
221 if let (Self::Identifier(left), Self::Identifier(right)) = (self, other) {
222 Ok(Self::NotEqual(left, right))
223 } else {
224 Err(anyhow!("operands must be identifiers"))
225 }
226 }
227}
228
229const PRECEDENCE_CHILD: u32 = 1;
230const PRECEDENCE_OR: u32 = 2;
231const PRECEDENCE_AND: u32 = 3;
232const PRECEDENCE_EQ: u32 = 4;
233const PRECEDENCE_NOT: u32 = 5;
234
235fn is_identifier_char(c: char) -> bool {
236 c.is_alphanumeric() || c == '_' || c == '-'
237}
238
239fn skip_whitespace(source: &str) -> &str {
240 let len = source
241 .find(|c: char| !c.is_whitespace())
242 .unwrap_or(source.len());
243 &source[len..]
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249 use DispatchContextPredicate::*;
250
251 #[test]
252 fn test_parse_context() {
253 let mut expected = DispatchContext::default();
254 expected.set("foo", "bar");
255 expected.insert("baz");
256 assert_eq!(DispatchContext::parse("baz foo=bar").unwrap(), expected);
257 assert_eq!(DispatchContext::parse("foo = bar baz").unwrap(), expected);
258 assert_eq!(
259 DispatchContext::parse(" baz foo = bar baz").unwrap(),
260 expected
261 );
262 assert_eq!(DispatchContext::parse(" foo = bar baz").unwrap(), expected);
263 }
264
265 #[test]
266 fn test_parse_identifiers() {
267 // Identifiers
268 assert_eq!(
269 DispatchContextPredicate::parse("abc12").unwrap(),
270 Identifier("abc12".into())
271 );
272 assert_eq!(
273 DispatchContextPredicate::parse("_1a").unwrap(),
274 Identifier("_1a".into())
275 );
276 }
277
278 #[test]
279 fn test_parse_negations() {
280 assert_eq!(
281 DispatchContextPredicate::parse("!abc").unwrap(),
282 Not(Box::new(Identifier("abc".into())))
283 );
284 assert_eq!(
285 DispatchContextPredicate::parse(" ! ! abc").unwrap(),
286 Not(Box::new(Not(Box::new(Identifier("abc".into())))))
287 );
288 }
289
290 #[test]
291 fn test_parse_equality_operators() {
292 assert_eq!(
293 DispatchContextPredicate::parse("a == b").unwrap(),
294 Equal("a".into(), "b".into())
295 );
296 assert_eq!(
297 DispatchContextPredicate::parse("c!=d").unwrap(),
298 NotEqual("c".into(), "d".into())
299 );
300 assert_eq!(
301 DispatchContextPredicate::parse("c == !d")
302 .unwrap_err()
303 .to_string(),
304 "operands must be identifiers"
305 );
306 }
307
308 #[test]
309 fn test_parse_boolean_operators() {
310 assert_eq!(
311 DispatchContextPredicate::parse("a || b").unwrap(),
312 Or(
313 Box::new(Identifier("a".into())),
314 Box::new(Identifier("b".into()))
315 )
316 );
317 assert_eq!(
318 DispatchContextPredicate::parse("a || !b && c").unwrap(),
319 Or(
320 Box::new(Identifier("a".into())),
321 Box::new(And(
322 Box::new(Not(Box::new(Identifier("b".into())))),
323 Box::new(Identifier("c".into()))
324 ))
325 )
326 );
327 assert_eq!(
328 DispatchContextPredicate::parse("a && b || c&&d").unwrap(),
329 Or(
330 Box::new(And(
331 Box::new(Identifier("a".into())),
332 Box::new(Identifier("b".into()))
333 )),
334 Box::new(And(
335 Box::new(Identifier("c".into())),
336 Box::new(Identifier("d".into()))
337 ))
338 )
339 );
340 assert_eq!(
341 DispatchContextPredicate::parse("a == b && c || d == e && f").unwrap(),
342 Or(
343 Box::new(And(
344 Box::new(Equal("a".into(), "b".into())),
345 Box::new(Identifier("c".into()))
346 )),
347 Box::new(And(
348 Box::new(Equal("d".into(), "e".into())),
349 Box::new(Identifier("f".into()))
350 ))
351 )
352 );
353 assert_eq!(
354 DispatchContextPredicate::parse("a && b && c && d").unwrap(),
355 And(
356 Box::new(And(
357 Box::new(And(
358 Box::new(Identifier("a".into())),
359 Box::new(Identifier("b".into()))
360 )),
361 Box::new(Identifier("c".into())),
362 )),
363 Box::new(Identifier("d".into()))
364 ),
365 );
366 }
367
368 #[test]
369 fn test_parse_parenthesized_expressions() {
370 assert_eq!(
371 DispatchContextPredicate::parse("a && (b == c || d != e)").unwrap(),
372 And(
373 Box::new(Identifier("a".into())),
374 Box::new(Or(
375 Box::new(Equal("b".into(), "c".into())),
376 Box::new(NotEqual("d".into(), "e".into())),
377 )),
378 ),
379 );
380 assert_eq!(
381 DispatchContextPredicate::parse(" ( a || b ) ").unwrap(),
382 Or(
383 Box::new(Identifier("a".into())),
384 Box::new(Identifier("b".into())),
385 )
386 );
387 }
388}