aimx/expressions/
equality.rs

1//! Equality expression parsing and evaluation for `==` and `!=`.
2//!
3//! Provides `Equality` AST nodes, [`parse_equality`], and an
4//! [`ExpressionLike`] implementation over relational expressions. Uses
5//! [`evaluate_and_promote`] to compare `Bool`, `Number`, `Text`, `Date`, and
6//! `Task` values with error propagation. Expressions without equality operators
7//! are flattened into [`Primary`] for efficiency.
8
9use crate::{
10    aim::{ContextLike, WriterLike, Writer},
11    expressions::{ExpressionLike, Primary, Relational, evaluate_and_promote, parse_relational},
12    literals::Literal,
13    values::Value,
14};
15use anyhow::{Result, anyhow};
16use nom::{
17    IResult, Parser, branch::alt, bytes::complete::tag, character::complete::multispace0,
18    combinator::value, sequence::{preceded, separated_pair},
19};
20use std::{
21    fmt,
22    sync::Arc,
23};
24
25/// Equality expression node.
26///
27/// Represents `==` and `!=` over [`Relational`] operands, or a flattened
28/// [`Primary`] expression when no equality operator is present.
29#[derive(Debug, Clone, PartialEq)]
30pub enum Equality {
31    Equal(Relational, Relational),
32    NotEqual(Relational, Relational),
33    /// Flattened primary when no equality is used.
34    Primary(Box<Primary>),
35}
36
37impl Equality {
38    /// Write the expression in formula form using the given writer.
39    pub fn print(&self, writer: &mut Writer) {
40        match self {
41            Equality::Equal(left, right) => {
42                writer.write_binary_op(left, " == ", right);
43            }
44            Equality::NotEqual(left, right) => {
45                writer.write_binary_op(left, " != ", right);
46            }
47            Equality::Primary(primary) => primary.print(writer),
48        }
49    }
50}
51
52/// Internal equality operator used while parsing.
53#[derive(Debug, Clone, Copy, PartialEq)]
54enum EqualityOp {
55    Equal,
56    NotEqual,
57}
58
59/// Parse `==` or `!=` and the following relational expression.
60fn equality_operator(input: &str) -> IResult<&str, (EqualityOp, Relational)> {
61    separated_pair(
62        preceded(
63            multispace0,
64            alt((
65                value(EqualityOp::NotEqual, tag("!=")),
66                value(EqualityOp::Equal, tag("==")),
67            ))
68        ),
69        multispace0,
70        parse_relational
71    ).parse(input)
72}
73
74/// Parse an equality expression: `relational ( ("==" | "!=") relational )?`.
75///
76/// Returns [`Equality::Primary`] when no equality operator is found.
77pub fn parse_equality(input: &str) -> IResult<&str, Equality> {
78    let (input, first) = parse_relational(input)?;
79    if let Ok((input, (equality_op, second))) = equality_operator(input) {
80        let equality = match equality_op {
81            EqualityOp::Equal => Equality::Equal(first, second),
82            EqualityOp::NotEqual => Equality::NotEqual(first, second),
83        };
84        Ok((input, equality))
85    } else {
86        let equality = match first {
87            Relational::Primary(primary) => Equality::Primary(primary),
88            _ => Equality::Primary(Box::new(Primary::Relational(first))),
89        };
90        Ok((input, equality))
91    }
92}
93
94impl ExpressionLike for Equality {
95    /// Evaluate to a boolean literal or propagate errors.
96    fn evaluate(&self, context: &mut dyn ContextLike) -> Result<Arc<Value>> {
97        match self {
98            Equality::Equal(left, right) => {
99                let (left_val, right_val) = evaluate_and_promote(context, left, right)?;
100                if left_val.is_error() {
101                    return Ok(left_val);
102                }
103                if right_val.is_error() {
104                    return Ok(right_val);
105                }
106                let result = match (&*left_val, &*right_val) {
107                    (Value::Literal(Literal::Bool(l)), Value::Literal(Literal::Bool(r))) => l == r,
108                    (Value::Literal(Literal::Date(l)), Value::Literal(Literal::Date(r))) => l == r,
109                    (Value::Literal(Literal::Number(l)), Value::Literal(Literal::Number(r))) => {
110                        l == r
111                    }
112                    (Value::Literal(Literal::Text(l)), Value::Literal(Literal::Text(r))) => l == r,
113                    (Value::Literal(Literal::Task(_, l)), Value::Literal(Literal::Task(_, r))) => {
114                        l == r
115                    }
116                    _ => {
117                        return Err(anyhow!(
118                            "Expected Bool, Date, Number, Task or Text, found {} == {}~{}",
119                            left_val.type_as_string(),
120                            right_val.type_as_string(),
121                            self.to_formula(),
122                        ));
123                    }
124                };
125                Ok(Arc::new(Value::Literal(Literal::Bool(result))))
126            }
127            Equality::NotEqual(left, right) => {
128                let (left_val, right_val) = evaluate_and_promote(context, left, right)?;
129                if left_val.is_error() {
130                    return Ok(left_val);
131                }
132                if right_val.is_error() {
133                    return Ok(right_val);
134                }
135                let result = match (&*left_val, &*right_val) {
136                    (Value::Literal(Literal::Bool(l)), Value::Literal(Literal::Bool(r))) => l != r,
137                    (Value::Literal(Literal::Date(l)), Value::Literal(Literal::Date(r))) => l != r,
138                    (Value::Literal(Literal::Number(l)), Value::Literal(Literal::Number(r))) => {
139                        l != r
140                    }
141                    (Value::Literal(Literal::Text(l)), Value::Literal(Literal::Text(r))) => l != r,
142                    (Value::Literal(Literal::Task(_, l)), Value::Literal(Literal::Task(_, r))) => {
143                        l != r
144                    }
145                    _ => {
146                        return Err(anyhow!(
147                            "Expected Bool, Date, Number, Task or Text, found {} != {}~{}",
148                            left_val.type_as_string(),
149                            right_val.type_as_string(),
150                            self.to_formula(),
151                        ));
152                    }
153                };
154                Ok(Arc::new(Value::Literal(Literal::Bool(result))))
155            }
156            Equality::Primary(primary) => primary.evaluate(context),
157        }
158    }
159
160    /// Return the formula-string representation (round-trippable by the parser).
161    fn to_formula(&self) -> String {
162        let mut writer = Writer::formulizer();
163        self.print(&mut writer);
164        writer.finish()
165    }
166}
167
168impl WriterLike for Equality {
169    fn write(&self, writer: &mut Writer) {
170        self.print(writer);
171    }
172}
173
174impl fmt::Display for Equality {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        write!(f, "{}", self.to_stringized())
177    }
178}