Skip to main content

expr/ast/
operator.rs

1use crate::ast::node::Node;
2use crate::Value::{Array, Bool, Bytes, DateTime, Duration, Float, Integer, KeyedMap, Map, Month, String, Weekday};
3use crate::{bail, Result, Rule};
4use crate::{ContextProvider, Environment, Value};
5use log::trace;
6use pest::iterators::Pair;
7use std::str::FromStr;
8
9#[derive(Debug, Clone, strum::EnumString, strum::Display)]
10pub enum Operator {
11    #[strum(serialize = "+")]
12    Add,
13    #[strum(serialize = "-")]
14    Subtract,
15    #[strum(serialize = "*")]
16    Multiply,
17    #[strum(serialize = "/")]
18    Divide,
19    #[strum(serialize = "%")]
20    Modulo,
21    #[strum(serialize = "^")]
22    Pow,
23    #[strum(serialize = "==")]
24    Equal,
25    #[strum(serialize = "!=")]
26    NotEqual,
27    #[strum(serialize = ">")]
28    GreaterThan,
29    #[strum(serialize = ">=")]
30    GreaterThanOrEqual,
31    #[strum(serialize = "<")]
32    LessThan,
33    #[strum(serialize = "<=")]
34    LessThanOrEqual,
35    #[strum(serialize = "&&", serialize = "and")]
36    And,
37    #[strum(serialize = "||", serialize = "or")]
38    Or,
39    #[strum(serialize = "in")]
40    In,
41    #[strum(serialize = "not in")]
42    NotIn,
43    #[strum(serialize = "contains")]
44    Contains,
45    #[strum(serialize = "startsWith")]
46    StartsWith,
47    #[strum(serialize = "endsWith")]
48    EndsWith,
49    #[strum(serialize = "matches")]
50    Matches,
51}
52
53impl From<Pair<'_, Rule>> for Operator {
54    fn from(pair: Pair<Rule>) -> Self {
55        trace!("[operator] {pair:?}");
56        if pair.as_rule() == Rule::not_in_op {
57            return Operator::NotIn;
58        }
59        match pair.as_str() {
60            "**" => Operator::Pow,
61            op => Operator::from_str(op).unwrap_or_else(|_| unreachable!("Invalid operator {op}")),
62        }
63    }
64}
65
66pub(crate) fn values_equal(left: &Value, right: &Value) -> bool {
67    match (left, right) {
68        (Integer(left), Float(right)) => *left as f64 == *right,
69        (Float(left), Integer(right)) => *left == *right as f64,
70        (Array(left), Array(right)) => {
71            left.len() == right.len()
72                && left
73                    .iter()
74                    .zip(right)
75                    .all(|(left, right)| values_equal(left, right))
76        }
77        (Map(left), Map(right)) => {
78            left.len() == right.len()
79                && left.iter().all(|(key, left)| {
80                    right
81                        .get(key)
82                        .is_some_and(|right| values_equal(left, right))
83                })
84        }
85        (KeyedMap(left), KeyedMap(right)) => {
86            left.len() == right.len()
87                && left.iter().all(|(left_key, left_value)| {
88                    right.iter().any(|(right_key, right_value)| {
89                        map_keys_equal(left_key, right_key)
90                            && values_equal(left_value, right_value)
91                    })
92                })
93        }
94        _ => left == right,
95    }
96}
97
98pub(crate) fn is_comparable_map_key(value: &Value) -> bool {
99    !matches!(
100        value,
101        Array(_) | Bytes(_) | Map(_) | KeyedMap(_)
102    )
103}
104
105pub(crate) fn map_keys_equal(left: &Value, right: &Value) -> bool {
106    std::mem::discriminant(left) == std::mem::discriminant(right) && left == right
107}
108
109impl Environment<'_> {
110    pub fn eval_operator(
111        &self,
112        ctx: &dyn ContextProvider,
113        operator: &Operator,
114        left: &Node,
115        right: &Node,
116        compiled_regex: Option<&regex::Regex>,
117    ) -> Result<Value> {
118        let left = self.eval_expr(ctx, left)?;
119        match &operator {
120            Operator::And => {
121                return match left {
122                    Bool(false) => Ok(Bool(false)),
123                    Bool(true) => match self.eval_expr(ctx, right)? {
124                        Bool(value) => Ok(Bool(value)),
125                        _ => bail!("Invalid operands for operator {operator}"),
126                    },
127                    _ => bail!("Invalid operands for operator {operator}"),
128                };
129            }
130            Operator::Or => {
131                return match left {
132                    Bool(true) => Ok(Bool(true)),
133                    Bool(false) => match self.eval_expr(ctx, right)? {
134                        Bool(value) => Ok(Bool(value)),
135                        _ => bail!("Invalid operands for operator {operator}"),
136                    },
137                    _ => bail!("Invalid operands for operator {operator}"),
138                };
139            }
140            _ => {}
141        }
142        let right = self.eval_expr(ctx, right)?;
143        let result = match operator {
144            Operator::Add => match (left, right) {
145                (Integer(left), Integer(right)) => left.wrapping_add(right).into(),
146                (Float(left), Float(right)) => (left + right).into(),
147                (Integer(left), Float(right)) => Float(left as f64 + right),
148                (Float(left), Integer(right)) => Float(left + right as f64),
149                (String(left), String(right)) => format!("{left}{right}").into(),
150                (DateTime(left), Duration(right)) => {
151                    DateTime(left.checked_add_signed(chrono::Duration::nanoseconds(right))
152                        .ok_or_else(|| crate::Error::ExprError("date out of range".into()))?)
153                }
154                (Duration(left), DateTime(right)) => {
155                    DateTime(right.checked_add_signed(chrono::Duration::nanoseconds(left))
156                        .ok_or_else(|| crate::Error::ExprError("date out of range".into()))?)
157                }
158                (Duration(left), Duration(right)) => Duration(left.wrapping_add(right)),
159                _ => bail!("Invalid operands for operator +"),
160            },
161            Operator::Subtract => match (left, right) {
162                (Integer(left), Integer(right)) => Integer(left.wrapping_sub(right)),
163                (Float(left), Float(right)) => Float(left - right),
164                (Integer(left), Float(right)) => Float(left as f64 - right),
165                (Float(left), Integer(right)) => Float(left - right as f64),
166                (DateTime(left), DateTime(right)) => Duration(
167                    (left - right)
168                        .num_nanoseconds()
169                        .ok_or_else(|| crate::Error::ExprError("duration out of range".into()))?,
170                ),
171                (DateTime(left), Duration(right)) => {
172                    DateTime(left.checked_sub_signed(chrono::Duration::nanoseconds(right))
173                        .ok_or_else(|| crate::Error::ExprError("date out of range".into()))?)
174                }
175                (Duration(left), Duration(right)) => Duration(left.wrapping_sub(right)),
176                _ => bail!("Invalid operands for operator -"),
177            },
178            Operator::Multiply => match (left, right) {
179                (Integer(left), Integer(right)) => Integer(left.wrapping_mul(right)),
180                (Float(left), Float(right)) => Float(left * right),
181                (Integer(left), Float(right)) => Float(left as f64 * right),
182                (Float(left), Integer(right)) => Float(left * right as f64),
183                (Duration(left), Integer(right)) => Duration(left.wrapping_mul(right)),
184                (Integer(left), Duration(right)) => Duration(left.wrapping_mul(right)),
185                _ => bail!("Invalid operands for operator *"),
186            },
187            Operator::Divide => match (left, right) {
188                (Integer(left), Integer(right)) => Float(left as f64 / right as f64),
189                (Float(left), Float(right)) => Float(left / right),
190                (Integer(left), Float(right)) => Float(left as f64 / right),
191                (Float(left), Integer(right)) => Float(left / right as f64),
192                _ => bail!("Invalid operands for operator /"),
193            },
194            Operator::Modulo => match (left, right) {
195                (Integer(_), Integer(0)) => bail!("integer divide by zero"),
196                (Integer(left), Integer(right)) => Integer(left.wrapping_rem(right)),
197                _ => bail!("Invalid operands for operator %"),
198            },
199            Operator::Pow => match (left, right) {
200                (Integer(left), Integer(right)) => Float((left as f64).powf(right as f64)),
201                (Float(left), Float(right)) => Float(left.powf(right)),
202                (Integer(left), Float(right)) => Float((left as f64).powf(right)),
203                (Float(left), Integer(right)) => Float(left.powf(right as f64)),
204                _ => bail!("Invalid operands for operator {operator}"),
205            },
206            Operator::Equal => match (&left, &right) {
207                (Month(_) | Weekday(_), Integer(_))
208                | (Integer(_), Month(_) | Weekday(_)) => {
209                    bail!("Invalid operands for operator {operator}")
210                }
211                (Map(_), KeyedMap(_)) | (KeyedMap(_), Map(_)) => {
212                    bail!("Invalid operands for operator {operator}")
213                }
214                _ => Bool(values_equal(&left, &right)),
215            },
216            Operator::NotEqual => match (&left, &right) {
217                (Month(_) | Weekday(_), Integer(_))
218                | (Integer(_), Month(_) | Weekday(_)) => {
219                    bail!("Invalid operands for operator {operator}")
220                }
221                (Map(_), KeyedMap(_)) | (KeyedMap(_), Map(_)) => {
222                    bail!("Invalid operands for operator {operator}")
223                }
224                _ => Bool(!values_equal(&left, &right)),
225            },
226            Operator::GreaterThan => match (left, right) {
227                (Integer(left), Integer(right)) => (left > right).into(),
228                (Float(left), Float(right)) => (left > right).into(),
229                (Integer(left), Float(right)) => (left as f64 > right).into(),
230                (Float(left), Integer(right)) => (left > right as f64).into(),
231                (String(left), String(right)) => (left > right).into(),
232                (DateTime(left), DateTime(right)) => (left > right).into(),
233                (Duration(left), Duration(right)) => (left > right).into(),
234                _ => bail!("Invalid operands for operator {operator}"),
235            },
236            Operator::GreaterThanOrEqual => match (left, right) {
237                (Integer(left), Integer(right)) => (left >= right).into(),
238                (Float(left), Float(right)) => (left >= right).into(),
239                (Integer(left), Float(right)) => (left as f64 >= right).into(),
240                (Float(left), Integer(right)) => (left >= right as f64).into(),
241                (String(left), String(right)) => (left >= right).into(),
242                (DateTime(left), DateTime(right)) => (left >= right).into(),
243                (Duration(left), Duration(right)) => (left >= right).into(),
244                _ => bail!("Invalid operands for operator {operator}"),
245            },
246            Operator::LessThan => match (left, right) {
247                (Integer(left), Integer(right)) => (left < right).into(),
248                (Float(left), Float(right)) => (left < right).into(),
249                (Integer(left), Float(right)) => ((left as f64) < right).into(),
250                (Float(left), Integer(right)) => (left < right as f64).into(),
251                (String(left), String(right)) => (left < right).into(),
252                (DateTime(left), DateTime(right)) => (left < right).into(),
253                (Duration(left), Duration(right)) => (left < right).into(),
254                _ => bail!("Invalid operands for operator {operator}"),
255            },
256            Operator::LessThanOrEqual => match (left, right) {
257                (Integer(left), Integer(right)) => (left <= right).into(),
258                (Float(left), Float(right)) => (left <= right).into(),
259                (Integer(left), Float(right)) => (left as f64 <= right).into(),
260                (Float(left), Integer(right)) => (left <= right as f64).into(),
261                (String(left), String(right)) => (left <= right).into(),
262                (DateTime(left), DateTime(right)) => (left <= right).into(),
263                (Duration(left), Duration(right)) => (left <= right).into(),
264                _ => bail!("Invalid operands for operator {operator}"),
265            },
266            Operator::And | Operator::Or => unreachable!("handled before evaluating right operand"),
267            Operator::In => match (left, right) {
268                (String(left), Map(right)) => right.contains_key(&left).into(),
269                (left, KeyedMap(right)) => right
270                    .iter()
271                    .any(|(key, _)| map_keys_equal(&left, key))
272                    .into(),
273                (left, Array(right)) => right
274                    .iter()
275                    .any(|right| values_equal(&left, right))
276                    .into(),
277                _ => bail!("Invalid operands for operator {operator}"),
278            },
279            Operator::NotIn => match (left, right) {
280                (String(left), Map(right)) => (!right.contains_key(&left)).into(),
281                (left, KeyedMap(right)) => (!right
282                    .iter()
283                    .any(|(key, _)| map_keys_equal(&left, key)))
284                    .into(),
285                (left, Array(right)) => (!right.iter().any(|right| values_equal(&left, right))).into(),
286                _ => bail!("Invalid operands for operator {operator}"),
287            },
288            Operator::Contains => match (left, right) {
289                (String(left), String(right)) => left.contains(&right).into(),
290                (Array(left), right) => left
291                    .iter()
292                    .any(|left| values_equal(left, &right))
293                    .into(),
294                (Map(left), String(right)) => left.contains_key(&right).into(),
295                _ => bail!("Invalid operands for operator contains"),
296            },
297            Operator::StartsWith => match (left, right) {
298                (String(left), String(right)) => Bool(left.starts_with(&right)),
299                _ => bail!("Invalid operands for operator startsWith"),
300            },
301            Operator::EndsWith => match (left, right) {
302                (String(left), String(right)) => Bool(left.ends_with(&right)),
303                _ => bail!("Invalid operands for operator endsWith"),
304            },
305            Operator::Matches => match (left, right) {
306                (String(left), String(right)) => {
307                    if let Some(regex) = compiled_regex {
308                        Bool(regex.is_match(&left))
309                    } else {
310                        Bool(regex::Regex::new(&right)?.is_match(&left))
311                    }
312                }
313                _ => bail!("Invalid operands for operator matches"),
314            },
315        };
316
317        Ok(result)
318    }
319}