aimx/expressions/
relational.rs

1//! Relational expression parsing and evaluation for AIMX.
2//!
3//! Parses `<`, `>`, `<=`, `>=` over additive expressions and evaluates them to
4//! `Bool` values.
5
6use crate::{
7    aim::{ContextLike, WriterLike, Writer},
8    expressions::{Additive, ExpressionLike, Primary, evaluate_and_promote, parse_additive},
9    literals::Literal,
10    values::Value,
11};
12use anyhow::{Result, anyhow};
13use nom::{
14    IResult, Parser,
15    branch::alt,
16    bytes::complete::tag,
17    character::complete::{char, multispace0},
18    combinator::{map, opt},
19};
20use std::{
21    fmt,
22    sync::Arc,
23};
24
25/// Relational AST node for `<`, `>`, `<=`, `>=` over additive expressions.
26#[derive(Debug, Clone, PartialEq)]
27pub enum Relational {
28    /// Less than comparison: left < right
29    Less(Additive, Additive),
30    /// Less than or equal comparison: left <= right
31    LessOrEqual(Additive, Additive),
32    /// Greater than comparison: left > right
33    Greater(Additive, Additive),
34    /// Greater than or equal comparison: left >= right
35    GreaterOrEqual(Additive, Additive),
36    /// Primary flattened AST optimization
37    Primary(Box<Primary>),
38}
39
40impl Relational {
41    pub fn print(&self, writer: &mut Writer) {
42        match self {
43            Relational::Less(left, right) => {
44                writer.write_binary_op(left, " < ", right);
45            }
46            Relational::LessOrEqual(left, right) => {
47                writer.write_binary_op(left, " <= ", right);
48            }
49            Relational::Greater(left, right) => {
50                writer.write_binary_op(left, " > ", right);
51            }
52            Relational::GreaterOrEqual(left, right) => {
53                writer.write_binary_op(left, " >= ", right);
54            }
55            Relational::Primary(primary) => primary.print(writer),
56        }
57    }
58}
59
60/// Internal representation of relational operators during parsing.
61#[derive(Debug, Clone, Copy, PartialEq)]
62enum RelationalOp {
63    /// Less than operator: <
64    Less,
65    /// Less than or equal operator: <=
66    LessOrEqual,
67    /// Greater than operator: >
68    Greater,
69    /// Greater than or equal operator: >=
70    GreaterOrEqual,
71}
72
73/// Parse a relational expression.
74///
75/// Grammar: `relational := additive (relational_op additive)?`, where
76/// `relational_op := '<' | '>' | '<=' | '>='`.
77///
78/// Accepts optional whitespace around operators. Returns remaining input and the
79/// parsed [`Relational`] node.
80pub fn parse_relational(input: &str) -> IResult<&str, Relational> {
81    let (input, first) = parse_additive(input)?;
82
83    let (input, maybe_relational) = opt((
84        multispace0,
85        alt((
86            map(tag("<="), |_| RelationalOp::LessOrEqual),
87            map(tag(">="), |_| RelationalOp::GreaterOrEqual),
88            map(char('<'), |_| RelationalOp::Less),
89            map(char('>'), |_| RelationalOp::Greater),
90        )),
91        multispace0,
92        parse_additive,
93    ))
94    .parse(input)?;
95
96    let result = match maybe_relational {
97        Some((_, op, _, second)) => match op {
98            RelationalOp::Less => Relational::Less(first, second),
99            RelationalOp::LessOrEqual => Relational::LessOrEqual(first, second),
100            RelationalOp::Greater => Relational::Greater(first, second),
101            RelationalOp::GreaterOrEqual => Relational::GreaterOrEqual(first, second),
102        },
103        None => match first {
104            Additive::Primary(primary) => Relational::Primary(primary),
105            _ => Relational::Primary(Box::new(Primary::Additive(first))),
106        },
107    };
108
109    Ok((input, result))
110}
111
112impl ExpressionLike for Relational {
113    /// Evaluate `self` as a relational expression.
114    fn evaluate(&self, context: &mut dyn ContextLike) -> Result<Arc<Value>> {
115        match self {
116            Relational::Less(left, right) => {
117                let (left_val, right_val) = evaluate_and_promote(context, left, right)?;
118                if left_val.is_error() {
119                    return Ok(left_val);
120                }
121                if right_val.is_error() {
122                    return Ok(right_val);
123                }
124                match (&*left_val, &*right_val) {
125                    (Value::Literal(Literal::Bool(l)), Value::Literal(Literal::Bool(r))) => {
126                        Ok(Arc::new(Value::Literal(Literal::Bool(l < r))))
127                    }
128                    (Value::Literal(Literal::Date(l)), Value::Literal(Literal::Date(r))) => {
129                        Ok(Arc::new(Value::Literal(Literal::Bool(l < r))))
130                    }
131                    (Value::Literal(Literal::Number(l)), Value::Literal(Literal::Number(r))) => {
132                        Ok(Arc::new(Value::Literal(Literal::Bool(l < r))))
133                    }
134                    (Value::Literal(Literal::Task(_, _)), Value::Literal(Literal::Task(_, _))) => {
135                        let l = left_val.to_number()?;
136                        let r = right_val.to_number()?;
137                        Ok(Arc::new(Value::Literal(Literal::Bool(l < r))))
138                    }
139                    (Value::Literal(Literal::Text(l)), Value::Literal(Literal::Text(r))) => {
140                        Ok(Arc::new(Value::Literal(Literal::Bool(l < r))))
141                    }
142                    _ => Err(anyhow!(
143                        "Expected comparable operands, found {} < {}~{}",
144                        left_val.type_as_string(),
145                        right_val.type_as_string(),
146                        self.to_formula(),
147                    )),
148                }
149            }
150            Relational::LessOrEqual(left, right) => {
151                let (left_val, right_val) = evaluate_and_promote(context, left, right)?;
152                if left_val.is_error() {
153                    return Ok(left_val);
154                }
155                if right_val.is_error() {
156                    return Ok(right_val);
157                }
158                match (&*left_val, &*right_val) {
159                    (Value::Literal(Literal::Bool(l)), Value::Literal(Literal::Bool(r))) => {
160                        Ok(Arc::new(Value::Literal(Literal::Bool(l <= r))))
161                    }
162                    (Value::Literal(Literal::Date(l)), Value::Literal(Literal::Date(r))) => {
163                        Ok(Arc::new(Value::Literal(Literal::Bool(l <= r))))
164                    }
165                    (Value::Literal(Literal::Number(l)), Value::Literal(Literal::Number(r))) => {
166                        Ok(Arc::new(Value::Literal(Literal::Bool(l <= r))))
167                    }
168                    (Value::Literal(Literal::Task(_, _)), Value::Literal(Literal::Task(_, _))) => {
169                        let l = left_val.to_number()?;
170                        let r = right_val.to_number()?;
171                        Ok(Arc::new(Value::Literal(Literal::Bool(l <= r))))
172                    }
173                    (Value::Literal(Literal::Text(l)), Value::Literal(Literal::Text(r))) => {
174                        Ok(Arc::new(Value::Literal(Literal::Bool(l <= r))))
175                    }
176                    _ => Err(anyhow!(
177                        "Expected comparable operands, found {} <= {}~{}",
178                        left_val.type_as_string(),
179                        right_val.type_as_string(),
180                        self.to_formula(),
181                    )),
182                }
183            }
184            Relational::Greater(left, right) => {
185                let (left_val, right_val) = evaluate_and_promote(context, left, right)?;
186                if left_val.is_error() {
187                    return Ok(left_val);
188                }
189                if right_val.is_error() {
190                    return Ok(right_val);
191                }
192                match (&*left_val, &*right_val) {
193                    (Value::Literal(Literal::Bool(l)), Value::Literal(Literal::Bool(r))) => {
194                        Ok(Arc::new(Value::Literal(Literal::Bool(l > r))))
195                    }
196                    (Value::Literal(Literal::Date(l)), Value::Literal(Literal::Date(r))) => {
197                        Ok(Arc::new(Value::Literal(Literal::Bool(l > r))))
198                    }
199                    (Value::Literal(Literal::Number(l)), Value::Literal(Literal::Number(r))) => {
200                        Ok(Arc::new(Value::Literal(Literal::Bool(l > r))))
201                    }
202                    (Value::Literal(Literal::Task(_, _)), Value::Literal(Literal::Task(_, _))) => {
203                        let l = left_val.to_number()?;
204                        let r = right_val.to_number()?;
205                        Ok(Arc::new(Value::Literal(Literal::Bool(l > r))))
206                    }
207                    (Value::Literal(Literal::Text(l)), Value::Literal(Literal::Text(r))) => {
208                        Ok(Arc::new(Value::Literal(Literal::Bool(l > r))))
209                    }
210                    _ => Err(anyhow!(
211                        "Expected comparable operands, found {} > {}~{}",
212                        left_val.type_as_string(),
213                        right_val.type_as_string(),
214                        self.to_formula(),
215                    )),
216                }
217            }
218            Relational::GreaterOrEqual(left, right) => {
219                let (left_val, right_val) = evaluate_and_promote(context, left, right)?;
220                if left_val.is_error() {
221                    return Ok(left_val);
222                }
223                if right_val.is_error() {
224                    return Ok(right_val);
225                }
226                match (&*left_val, &*right_val) {
227                    (Value::Literal(Literal::Bool(l)), Value::Literal(Literal::Bool(r))) => {
228                        Ok(Arc::new(Value::Literal(Literal::Bool(l >= r))))
229                    }
230                    (Value::Literal(Literal::Date(l)), Value::Literal(Literal::Date(r))) => {
231                        Ok(Arc::new(Value::Literal(Literal::Bool(l >= r))))
232                    }
233                    (Value::Literal(Literal::Number(l)), Value::Literal(Literal::Number(r))) => {
234                        Ok(Arc::new(Value::Literal(Literal::Bool(l >= r))))
235                    }
236                    (Value::Literal(Literal::Task(_, _)), Value::Literal(Literal::Task(_, _))) => {
237                        let l = left_val.to_number()?;
238                        let r = right_val.to_number()?;
239                        Ok(Arc::new(Value::Literal(Literal::Bool(l >= r))))
240                    }
241                    (Value::Literal(Literal::Text(l)), Value::Literal(Literal::Text(r))) => {
242                        Ok(Arc::new(Value::Literal(Literal::Bool(l >= r))))
243                    }
244                    _ => Err(anyhow!(
245                        "Expected comparable operands, found {} >= {}~{}",
246                        left_val.type_as_string(),
247                        right_val.type_as_string(),
248                        self.to_formula(),
249                    )),
250                }
251            }
252            Relational::Primary(primary) => primary.evaluate(context),
253        }
254    }
255
256    /// Return the formula-string representation (round-trippable by the parser).
257    fn to_formula(&self) -> String {
258        let mut writer = Writer::formulizer();
259        self.print(&mut writer);
260        writer.finish()
261    }
262}
263
264impl WriterLike for Relational {
265    fn write(&self, writer: &mut Writer) {
266        self.print(writer);
267    }
268}
269
270impl fmt::Display for Relational {
271    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272        write!(f, "{}", self.to_stringized())
273    }
274}