action.rs

  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}