Skip to main content

expr/ast/
operator.rs

1use crate::ast::node::Node;
2use crate::Value::{Array, Bool, Bytes, Float, Integer, KeyedMap, Map, String};
3#[cfg(feature = "temporal")]
4use crate::Value::{DateTime, Duration, Month, Weekday};
5use crate::{bail, Result, Rule};
6use crate::{ContextProvider, Environment, Value};
7use log::trace;
8use pest::iterators::Pair;
9use std::fmt;
10
11#[derive(Debug, Clone)]
12pub enum Operator {
13    Add,
14    Subtract,
15    Multiply,
16    Divide,
17    Modulo,
18    Pow,
19    Equal,
20    NotEqual,
21    GreaterThan,
22    GreaterThanOrEqual,
23    LessThan,
24    LessThanOrEqual,
25    And,
26    Or,
27    In,
28    NotIn,
29    Contains,
30    StartsWith,
31    EndsWith,
32    Matches,
33}
34
35impl Operator {
36    /// How the operator is written in an error message.
37    ///
38    /// The word spelling for `and`/`or`, which is what an expression that reached an error
39    /// most likely used: `&&` and `||` are the same operator by another name.
40    fn as_str(&self) -> &'static str {
41        match self {
42            Operator::Add => "+",
43            Operator::Subtract => "-",
44            Operator::Multiply => "*",
45            Operator::Divide => "/",
46            Operator::Modulo => "%",
47            Operator::Pow => "^",
48            Operator::Equal => "==",
49            Operator::NotEqual => "!=",
50            Operator::GreaterThan => ">",
51            Operator::GreaterThanOrEqual => ">=",
52            Operator::LessThan => "<",
53            Operator::LessThanOrEqual => "<=",
54            Operator::And => "and",
55            Operator::Or => "or",
56            Operator::In => "in",
57            Operator::NotIn => "not in",
58            Operator::Contains => "contains",
59            Operator::StartsWith => "startsWith",
60            Operator::EndsWith => "endsWith",
61            Operator::Matches => "matches",
62        }
63    }
64
65    /// The operator a token spells, or `None` if it spells none.
66    ///
67    /// Both spellings of the logical operators are accepted, as the grammar allows both. `**`
68    /// is handled by the caller, which is where the grammar's alias for `^` already lived.
69    fn from_token(token: &str) -> Option<Self> {
70        Some(match token {
71            "+" => Operator::Add,
72            "-" => Operator::Subtract,
73            "*" => Operator::Multiply,
74            "/" => Operator::Divide,
75            "%" => Operator::Modulo,
76            "^" => Operator::Pow,
77            "==" => Operator::Equal,
78            "!=" => Operator::NotEqual,
79            ">" => Operator::GreaterThan,
80            ">=" => Operator::GreaterThanOrEqual,
81            "<" => Operator::LessThan,
82            "<=" => Operator::LessThanOrEqual,
83            "&&" | "and" => Operator::And,
84            "||" | "or" => Operator::Or,
85            "in" => Operator::In,
86            "not in" => Operator::NotIn,
87            "contains" => Operator::Contains,
88            "startsWith" => Operator::StartsWith,
89            "endsWith" => Operator::EndsWith,
90            "matches" => Operator::Matches,
91            _ => return None,
92        })
93    }
94}
95
96impl fmt::Display for Operator {
97    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98        f.write_str(self.as_str())
99    }
100}
101
102impl From<Pair<'_, Rule>> for Operator {
103    fn from(pair: Pair<Rule>) -> Self {
104        trace!("[operator] {pair:?}");
105        if pair.as_rule() == Rule::not_in_op {
106            return Operator::NotIn;
107        }
108        match pair.as_str() {
109            "**" => Operator::Pow,
110            op => Operator::from_token(op)
111                .unwrap_or_else(|| unreachable!("Invalid operator {op}")),
112        }
113    }
114}
115
116pub(crate) fn values_equal(left: &Value, right: &Value) -> bool {
117    match (left, right) {
118        (Integer(left), Float(right)) => *left as f64 == *right,
119        (Float(left), Integer(right)) => *left == *right as f64,
120        (Array(left), Array(right)) => {
121            left.len() == right.len()
122                && left
123                    .iter()
124                    .zip(right)
125                    .all(|(left, right)| values_equal(left, right))
126        }
127        (Map(left), Map(right)) => {
128            left.len() == right.len()
129                && left.iter().all(|(key, left)| {
130                    right
131                        .get(key)
132                        .is_some_and(|right| values_equal(left, right))
133                })
134        }
135        (KeyedMap(left), KeyedMap(right)) => {
136            left.len() == right.len()
137                && left.iter().all(|(left_key, left_value)| {
138                    right.iter().any(|(right_key, right_value)| {
139                        map_keys_equal(left_key, right_key)
140                            && values_equal(left_value, right_value)
141                    })
142                })
143        }
144        _ => left == right,
145    }
146}
147
148pub(crate) fn is_comparable_map_key(value: &Value) -> bool {
149    !matches!(
150        value,
151        Array(_) | Bytes(_) | Map(_) | KeyedMap(_)
152    )
153}
154
155pub(crate) fn map_keys_equal(left: &Value, right: &Value) -> bool {
156    std::mem::discriminant(left) == std::mem::discriminant(right) && left == right
157}
158
159impl Environment<'_> {
160    pub fn eval_operator(
161        &self,
162        ctx: &dyn ContextProvider,
163        operator: &Operator,
164        left: &Node,
165        right: &Node,
166        #[cfg(feature = "regex")] compiled_regex: Option<&regex::Regex>,
167    ) -> Result<Value> {
168        let left = self.eval_expr(ctx, left)?;
169        match &operator {
170            Operator::And => {
171                return match left {
172                    Bool(false) => Ok(Bool(false)),
173                    Bool(true) => match self.eval_expr(ctx, right)? {
174                        Bool(value) => Ok(Bool(value)),
175                        _ => bail!("Invalid operands for operator {operator}"),
176                    },
177                    _ => bail!("Invalid operands for operator {operator}"),
178                };
179            }
180            Operator::Or => {
181                return match left {
182                    Bool(true) => Ok(Bool(true)),
183                    Bool(false) => match self.eval_expr(ctx, right)? {
184                        Bool(value) => Ok(Bool(value)),
185                        _ => bail!("Invalid operands for operator {operator}"),
186                    },
187                    _ => bail!("Invalid operands for operator {operator}"),
188                };
189            }
190            _ => {}
191        }
192        let right = self.eval_expr(ctx, right)?;
193        let result = match operator {
194            Operator::Add => match (left, right) {
195                (Integer(left), Integer(right)) => left.wrapping_add(right).into(),
196                (Float(left), Float(right)) => (left + right).into(),
197                (Integer(left), Float(right)) => Float(left as f64 + right),
198                (Float(left), Integer(right)) => Float(left + right as f64),
199                (String(left), String(right)) => format!("{left}{right}").into(),
200                #[cfg(feature = "temporal")]
201                (DateTime(left), Duration(right)) => {
202                    DateTime(left.checked_add_signed(chrono::Duration::nanoseconds(right))
203                        .ok_or_else(|| crate::Error::ExprError("date out of range".into()))?)
204                }
205                #[cfg(feature = "temporal")]
206                (Duration(left), DateTime(right)) => {
207                    DateTime(right.checked_add_signed(chrono::Duration::nanoseconds(left))
208                        .ok_or_else(|| crate::Error::ExprError("date out of range".into()))?)
209                }
210                #[cfg(feature = "temporal")]
211                (Duration(left), Duration(right)) => Duration(left.wrapping_add(right)),
212                _ => bail!("Invalid operands for operator +"),
213            },
214            Operator::Subtract => match (left, right) {
215                (Integer(left), Integer(right)) => Integer(left.wrapping_sub(right)),
216                (Float(left), Float(right)) => Float(left - right),
217                (Integer(left), Float(right)) => Float(left as f64 - right),
218                (Float(left), Integer(right)) => Float(left - right as f64),
219                #[cfg(feature = "temporal")]
220                (DateTime(left), DateTime(right)) => Duration(
221                    (left - right)
222                        .num_nanoseconds()
223                        .ok_or_else(|| crate::Error::ExprError("duration out of range".into()))?,
224                ),
225                #[cfg(feature = "temporal")]
226                (DateTime(left), Duration(right)) => {
227                    DateTime(left.checked_sub_signed(chrono::Duration::nanoseconds(right))
228                        .ok_or_else(|| crate::Error::ExprError("date out of range".into()))?)
229                }
230                #[cfg(feature = "temporal")]
231                (Duration(left), Duration(right)) => Duration(left.wrapping_sub(right)),
232                _ => bail!("Invalid operands for operator -"),
233            },
234            Operator::Multiply => match (left, right) {
235                (Integer(left), Integer(right)) => Integer(left.wrapping_mul(right)),
236                (Float(left), Float(right)) => Float(left * right),
237                (Integer(left), Float(right)) => Float(left as f64 * right),
238                (Float(left), Integer(right)) => Float(left * right as f64),
239                #[cfg(feature = "temporal")]
240                (Duration(left), Integer(right)) => Duration(left.wrapping_mul(right)),
241                #[cfg(feature = "temporal")]
242                (Integer(left), Duration(right)) => Duration(left.wrapping_mul(right)),
243                _ => bail!("Invalid operands for operator *"),
244            },
245            Operator::Divide => match (left, right) {
246                (Integer(left), Integer(right)) => Float(left as f64 / right as f64),
247                (Float(left), Float(right)) => Float(left / right),
248                (Integer(left), Float(right)) => Float(left as f64 / right),
249                (Float(left), Integer(right)) => Float(left / right as f64),
250                _ => bail!("Invalid operands for operator /"),
251            },
252            Operator::Modulo => match (left, right) {
253                (Integer(_), Integer(0)) => bail!("integer divide by zero"),
254                (Integer(left), Integer(right)) => Integer(left.wrapping_rem(right)),
255                _ => bail!("Invalid operands for operator %"),
256            },
257            Operator::Pow => match (left, right) {
258                (Integer(left), Integer(right)) => Float((left as f64).powf(right as f64)),
259                (Float(left), Float(right)) => Float(left.powf(right)),
260                (Integer(left), Float(right)) => Float((left as f64).powf(right)),
261                (Float(left), Integer(right)) => Float(left.powf(right as f64)),
262                _ => bail!("Invalid operands for operator {operator}"),
263            },
264            Operator::Equal => match (&left, &right) {
265                #[cfg(feature = "temporal")]
266                (Month(_) | Weekday(_), Integer(_))
267                | (Integer(_), Month(_) | Weekday(_)) => {
268                    bail!("Invalid operands for operator {operator}")
269                }
270                (Map(_), KeyedMap(_)) | (KeyedMap(_), Map(_)) => {
271                    bail!("Invalid operands for operator {operator}")
272                }
273                _ => Bool(values_equal(&left, &right)),
274            },
275            Operator::NotEqual => match (&left, &right) {
276                #[cfg(feature = "temporal")]
277                (Month(_) | Weekday(_), Integer(_))
278                | (Integer(_), Month(_) | Weekday(_)) => {
279                    bail!("Invalid operands for operator {operator}")
280                }
281                (Map(_), KeyedMap(_)) | (KeyedMap(_), Map(_)) => {
282                    bail!("Invalid operands for operator {operator}")
283                }
284                _ => Bool(!values_equal(&left, &right)),
285            },
286            Operator::GreaterThan => match (left, right) {
287                (Integer(left), Integer(right)) => (left > right).into(),
288                (Float(left), Float(right)) => (left > right).into(),
289                (Integer(left), Float(right)) => (left as f64 > right).into(),
290                (Float(left), Integer(right)) => (left > right as f64).into(),
291                (String(left), String(right)) => (left > right).into(),
292                #[cfg(feature = "temporal")]
293                (DateTime(left), DateTime(right)) => (left > right).into(),
294                #[cfg(feature = "temporal")]
295                (Duration(left), Duration(right)) => (left > right).into(),
296                _ => bail!("Invalid operands for operator {operator}"),
297            },
298            Operator::GreaterThanOrEqual => match (left, right) {
299                (Integer(left), Integer(right)) => (left >= right).into(),
300                (Float(left), Float(right)) => (left >= right).into(),
301                (Integer(left), Float(right)) => (left as f64 >= right).into(),
302                (Float(left), Integer(right)) => (left >= right as f64).into(),
303                (String(left), String(right)) => (left >= right).into(),
304                #[cfg(feature = "temporal")]
305                (DateTime(left), DateTime(right)) => (left >= right).into(),
306                #[cfg(feature = "temporal")]
307                (Duration(left), Duration(right)) => (left >= right).into(),
308                _ => bail!("Invalid operands for operator {operator}"),
309            },
310            Operator::LessThan => match (left, right) {
311                (Integer(left), Integer(right)) => (left < right).into(),
312                (Float(left), Float(right)) => (left < right).into(),
313                (Integer(left), Float(right)) => ((left as f64) < right).into(),
314                (Float(left), Integer(right)) => (left < right as f64).into(),
315                (String(left), String(right)) => (left < right).into(),
316                #[cfg(feature = "temporal")]
317                (DateTime(left), DateTime(right)) => (left < right).into(),
318                #[cfg(feature = "temporal")]
319                (Duration(left), Duration(right)) => (left < right).into(),
320                _ => bail!("Invalid operands for operator {operator}"),
321            },
322            Operator::LessThanOrEqual => match (left, right) {
323                (Integer(left), Integer(right)) => (left <= right).into(),
324                (Float(left), Float(right)) => (left <= right).into(),
325                (Integer(left), Float(right)) => (left as f64 <= right).into(),
326                (Float(left), Integer(right)) => (left <= right as f64).into(),
327                (String(left), String(right)) => (left <= right).into(),
328                #[cfg(feature = "temporal")]
329                (DateTime(left), DateTime(right)) => (left <= right).into(),
330                #[cfg(feature = "temporal")]
331                (Duration(left), Duration(right)) => (left <= right).into(),
332                _ => bail!("Invalid operands for operator {operator}"),
333            },
334            Operator::And | Operator::Or => unreachable!("handled before evaluating right operand"),
335            Operator::In => match (left, right) {
336                (String(left), Map(right)) => right.contains_key(&left).into(),
337                (left, KeyedMap(right)) => right
338                    .iter()
339                    .any(|(key, _)| map_keys_equal(&left, key))
340                    .into(),
341                (left, Array(right)) => right
342                    .iter()
343                    .any(|right| values_equal(&left, right))
344                    .into(),
345                _ => bail!("Invalid operands for operator {operator}"),
346            },
347            Operator::NotIn => match (left, right) {
348                (String(left), Map(right)) => (!right.contains_key(&left)).into(),
349                (left, KeyedMap(right)) => (!right
350                    .iter()
351                    .any(|(key, _)| map_keys_equal(&left, key)))
352                    .into(),
353                (left, Array(right)) => (!right.iter().any(|right| values_equal(&left, right))).into(),
354                _ => bail!("Invalid operands for operator {operator}"),
355            },
356            Operator::Contains => match (left, right) {
357                (String(left), String(right)) => left.contains(&right).into(),
358                (Array(left), right) => left
359                    .iter()
360                    .any(|left| values_equal(left, &right))
361                    .into(),
362                (Map(left), String(right)) => left.contains_key(&right).into(),
363                _ => bail!("Invalid operands for operator contains"),
364            },
365            Operator::StartsWith => match (left, right) {
366                (String(left), String(right)) => Bool(left.starts_with(&right)),
367                _ => bail!("Invalid operands for operator startsWith"),
368            },
369            Operator::EndsWith => match (left, right) {
370                (String(left), String(right)) => Bool(left.ends_with(&right)),
371                _ => bail!("Invalid operands for operator endsWith"),
372            },
373            #[cfg(feature = "regex")]
374            Operator::Matches => match (left, right) {
375                (String(left), String(right)) => {
376                    if let Some(regex) = compiled_regex {
377                        Bool(regex.is_match(&left))
378                    } else {
379                        Bool(regex::Regex::new(&right)?.is_match(&left))
380                    }
381                }
382                _ => bail!("Invalid operands for operator matches"),
383            },
384            // Reachable only because the grammar has no features: the operator parses, and
385            // says what is missing rather than pretending the pattern did not match.
386            #[cfg(not(feature = "regex"))]
387            Operator::Matches => bail!("the `matches` operator requires the `regex` feature"),
388        };
389
390        Ok(result)
391    }
392}