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