aimx/expressions/
relational.rs

1//! Relational expression parsing and evaluation for the AIMX expression grammar.
2//!
3//! This module provides parsers and evaluation logic for relational expressions
4//! in the AIMX (Agentic Inference Markup Expressions) language. Relational operations
5//! include comparison operators such as less than, greater than, less than or equal,
6//! and greater than or equal. These operators have left-to-right associativity and
7//! form an important part of the expression hierarchy, handling comparisons between
8//! values of compatible types.
9//!
10//! # Relational Operators (Left-to-Right Associativity)
11//!
12//! | Operator | Description | Associativity |
13//! |----------|-------------|---------------|
14//! | `<`      | Less than | Left to right |
15//! | `>`      | Greater than | Left to right |
16//! | `<=`     | Less than or equal | Left to right |
17//! | `>=`     | Greater than or equal | Left to right |
18//!
19//! # Supported Type Comparisons
20//!
21//! Relational operators support comparisons between comparable types:
22//!
23//! - **Numbers**: Standard numeric comparisons
24//! - **Booleans**: `false` < `true` (false = 0, true = 1)
25//! - **Dates**: Chronological order comparisons
26//! - **Text**: Lexicographical order
27//! - **Tasks**: Status-based comparison (pending < completed < failed)
28//!
29//! # Operator Precedence
30//!
31//! Relational operators have higher precedence than equality operators (`=`, `!=`)
32//! but lower precedence than additive operators (`+`, `-`).
33//!
34//! # Examples
35//!
36//! ```rust
37//! use aimx::expressions::relational::{parse_relational, Relational};
38//! use aimx::{ExpressionLike, Context, Value, Literal};
39//!
40//! // Basic numeric comparisons
41//! let (_, expr) = parse_relational("5 < 10").unwrap();
42//! let mut context = Context::new();
43//! let result = expr.evaluate(&mut context).unwrap();
44//! assert_eq!(result.to_string(), "true");
45//!
46//! // Floating point comparisons
47//! let (_, expr) = parse_relational("3.14 > 2.71").unwrap();
48//! let result = expr.evaluate(&mut context).unwrap();
49//! assert_eq!(result.to_string(), "true");
50//!
51//! // Variable comparisons
52//! let mut context = Context::new()
53//!     .with_value("threshold", Value::Literal(Literal::Number(50.0)))
54//!     .with_value("value", Value::Literal(Literal::Number(42.0)));
55//! let (_, expr) = parse_relational("value <= threshold").unwrap();
56//! let result = expr.evaluate(&mut context).unwrap();
57//! assert_eq!(result.to_string(), "true");
58//!
59//! // String comparisons (lexicographical order)
60//! let (_, expr) = parse_relational("\"apple\" < \"banana\"").unwrap();
61//! let result = expr.evaluate(&mut context).unwrap();
62//! assert_eq!(result.to_string(), "true");
63//! ```
64//!
65//! # Usage in Workflow Rules
66//!
67//! Relational expressions are commonly used in workflow rules for conditional logic:
68//!
69//! ```aim
70//! // Example workflow rules using relational expressions
71//! IS_BUDGET_EXCEEDED: Bool = actual_cost > budget_limit
72//! IS_DEADLINE_MISSED: Bool = current_date > deadline
73//! IS_PRIORITY_HIGH: Bool = priority_level >= 8
74//! DISCOUNT_ELIGIBLE: Bool = order_total >= 100.0
75//! ```
76
77use crate::{
78    ContextLike,
79    evaluate_and_promote,
80    ExpressionLike,
81    expressions::{Additive, parse_additive},
82    Literal,
83    Primary,
84    Value,
85    Writer,
86};
87use nom::{
88    IResult, Parser,
89    branch::alt,
90    bytes::complete::tag,
91    character::complete::{char, multispace0},
92    combinator::{map, opt},
93};
94use std::fmt;
95use anyhow::{anyhow, Result};
96
97/// Represents a relational expression in the AIMX grammar.
98/// 
99/// Relational expressions perform comparisons between values using operators
100/// like less than, greater than, less than or equal, and greater than or equal.
101/// These expressions form part of the expression hierarchy and can be used in
102/// conditional statements and boolean logic.
103/// 
104/// The `Relational` enum uses AST flattening optimization, where it includes
105/// variants for lower-precedence expressions. This allows efficient evaluation
106/// without deep recursion.
107/// 
108/// # Variants
109/// 
110/// ## Comparison Variants
111/// 
112/// * [`Less`](Relational::Less) - Less than comparison: `left < right`
113/// * [`LessOrEqual`](Relational::LessOrEqual) - Less than or equal comparison: `left <= right`
114/// * [`Greater`](Relational::Greater) - Greater than comparison: `left > right`
115/// * [`GreaterOrEqual`](Relational::GreaterOrEqual) - Greater than or equal comparison: `left >= right`
116/// 
117/// ## AST Flattening Variants
118/// 
119/// * [`Primary`](Relational::Primary) - Flattened primary expression (optimization)
120/// 
121/// # Examples
122/// 
123/// ```rust
124/// use aimx::expressions::relational::{Relational, parse_relational};
125/// use aimx::{ExpressionLike, Context, Value};
126/// 
127/// // Parse and evaluate a relational expression
128/// let (_, expr) = parse_relational("5 < 10").unwrap();
129/// let mut context = Context::new();
130/// let result = expr.evaluate(&mut context).unwrap();
131/// assert_eq!(result.to_string(), "true");
132/// ```
133#[derive(Debug, Clone, PartialEq)]
134pub enum Relational {
135    /// Less than comparison: left < right
136    Less(Additive, Additive),
137    /// Less than or equal comparison: left <= right
138    LessOrEqual(Additive, Additive),
139    /// Greater than comparison: left > right
140    Greater(Additive, Additive),
141    /// Greater than or equal comparison: left >= right
142    GreaterOrEqual(Additive, Additive),
143    /// Primary flattened AST optimization
144    Primary(Box<Primary>),
145}
146
147/// Internal representation of relational operators during parsing.
148#[derive(Debug, Clone, Copy, PartialEq)]
149enum RelationalOp {
150    /// Less than operator: <
151    Less,
152    /// Less than or equal operator: <=
153    LessOrEqual,
154    /// Greater than operator: >
155    Greater,
156    /// Greater than or equal operator: >=
157    GreaterOrEqual,
158}
159
160/// Parse a relational expression from a string.
161/// 
162/// This function parses relational expressions which compare two additive
163/// expressions using relational operators. It handles operator precedence
164/// and optional whitespace around operators. The parser follows the AIMX
165/// grammar rules for relational expressions.
166/// 
167/// # Grammar Rules
168/// 
169/// ```text
170/// relational := additive (relational_op additive)?
171/// relational_op := '<' | '>' | '<=' | '>='
172/// ```
173/// 
174/// # Arguments
175/// 
176/// * `input` - A string slice containing the relational expression to parse
177/// 
178/// # Returns
179/// 
180/// * `IResult<&str, Relational>` - A nom result with remaining input and parsed relational expression
181/// 
182/// # Examples
183/// 
184/// ```rust
185/// use aimx::expressions::relational::parse_relational;
186/// 
187/// // Basic numeric comparisons
188/// let (remaining, expr) = parse_relational("5 < 10").unwrap();
189/// assert_eq!(remaining, "");
190/// 
191/// // Floating point comparisons
192/// let (remaining, expr) = parse_relational("3.14 >= 2.71").unwrap();
193/// assert_eq!(remaining, "");
194/// 
195/// // Variable comparisons
196/// let (remaining, expr) = parse_relational("x <= y").unwrap();
197/// assert_eq!(remaining, "");
198/// 
199/// // With whitespace
200/// let (remaining, expr) = parse_relational("123     <     456").unwrap();
201/// assert_eq!(remaining, "");
202/// 
203/// // Complex expressions
204/// let (remaining, expr) = parse_relational("1 + 2 < 3 + 4").unwrap();
205/// assert_eq!(remaining, "");
206/// ```
207/// 
208/// # Error Handling
209/// 
210/// The parser returns a nom `IResult` which can be used to handle parsing errors.
211pub fn parse_relational(input: &str) -> IResult<&str, Relational> {
212    let (input, first) = parse_additive(input)?;
213
214    let (input, maybe_relational) = opt((
215        multispace0,
216        alt((
217            map(tag("<="), |_| RelationalOp::LessOrEqual),
218            map(tag(">="), |_| RelationalOp::GreaterOrEqual),
219            map(char('<'), |_| RelationalOp::Less),
220            map(char('>'), |_| RelationalOp::Greater),
221        )),
222        multispace0,
223        parse_additive,
224    ))
225    .parse(input)?;
226
227    let result = match maybe_relational {
228        Some((_, op, _, second)) => match op {
229            RelationalOp::Less => Relational::Less(first, second),
230            RelationalOp::LessOrEqual => Relational::LessOrEqual(first, second),
231            RelationalOp::Greater => Relational::Greater(first, second),
232            RelationalOp::GreaterOrEqual => Relational::GreaterOrEqual(first, second),
233        },
234        None => match first {
235            Additive::Primary(primary) => Relational::Primary(primary),
236            _ => Relational::Primary(Box::new(Primary::Additive(first))),
237        },
238    };
239
240    Ok((input, result))
241}
242
243impl ExpressionLike for Relational {
244    /// Evaluates the relational expression within the given context.
245    /// 
246    /// This method performs type-safe comparisons between the left and right operands
247    /// of the relational expression. It uses type promotion to ensure compatible
248    /// comparisons and returns a boolean result.
249    /// 
250    /// # Type Promotion Rules
251    /// 
252    /// The evaluation uses [`evaluate_and_promote`] to ensure both operands are
253    /// of comparable types. Supported type comparisons include:
254    /// 
255    /// - **Numbers**: Standard numeric comparison
256    /// - **Booleans**: `false` (0) < `true` (1)
257    /// - **Dates**: Chronological order comparison
258    /// - **Text**: Lexicographical comparison
259    /// - **Tasks**: Status-based comparison (converted to numbers)
260    /// 
261    /// # Errors
262    /// 
263    /// Returns an error if:
264    /// - The operands are not of comparable types
265    /// - Type promotion fails
266    /// - Evaluation of sub-expressions fails
267    /// 
268    /// # Examples
269    /// 
270    /// ```rust
271    /// use aimx::expressions::relational::{Relational, parse_relational};
272    /// use aimx::{ExpressionLike, Context};
273    /// 
274    /// let (_, expr) = parse_relational("5 < 10").unwrap();
275    /// let mut context = Context::new();
276    /// let result = expr.evaluate(&mut context).unwrap();
277    /// assert_eq!(result.to_string(), "true");
278    /// ```
279    fn evaluate(&self, context: &mut dyn ContextLike) -> Result<Value> {
280        match self {
281            Relational::Less(left, right) => {
282                let (left_val, right_val) = evaluate_and_promote(context, left, right)?;
283                match (&left_val, &right_val) {
284                    (Value::Literal(Literal::Bool(l)), Value::Literal(Literal::Bool(r))) => {
285                        Ok(Value::Literal(Literal::Bool(l < r)))
286                    }
287                    (Value::Literal(Literal::Date(l)), Value::Literal(Literal::Date(r))) => {
288                        Ok(Value::Literal(Literal::Bool(l < r)))
289                    }
290                    (Value::Literal(Literal::Number(l)), Value::Literal(Literal::Number(r))) => {
291                        Ok(Value::Literal(Literal::Bool(l < r)))
292                    }
293                    (Value::Literal(Literal::Task(_, _)), Value::Literal(Literal::Task(_, _))) => {
294                        let l = left_val.to_number()?;
295                        let r = right_val.to_number()?;
296                        Ok(Value::Literal(Literal::Bool(l < r)))
297                    }
298                    (Value::Literal(Literal::Text(l)), Value::Literal(Literal::Text(r))) => {
299                        Ok(Value::Literal(Literal::Bool(l < r)))
300                    }
301                    _ => Err(anyhow!(
302                        "Expected comparable operands, found {} < {}~{}",
303                        left_val.type_as_string(),
304                        right_val.type_as_string(),
305                        self.to_formula(),
306                    )),
307                }
308            }
309            Relational::LessOrEqual(left, right) => {
310                let (left_val, right_val) = evaluate_and_promote(context, left, right)?;
311                match (&left_val, &right_val) {
312                    (Value::Literal(Literal::Bool(l)), Value::Literal(Literal::Bool(r))) => {
313                        Ok(Value::Literal(Literal::Bool(l <= r)))
314                    }
315                    (Value::Literal(Literal::Date(l)), Value::Literal(Literal::Date(r))) => {
316                        Ok(Value::Literal(Literal::Bool(l <= r)))
317                    }
318                    (Value::Literal(Literal::Number(l)), Value::Literal(Literal::Number(r))) => {
319                        Ok(Value::Literal(Literal::Bool(l <= r)))
320                    }
321                    (Value::Literal(Literal::Task(_, _)), Value::Literal(Literal::Task(_, _))) => {
322                        let l = left_val.to_number()?;
323                        let r = right_val.to_number()?;
324                        Ok(Value::Literal(Literal::Bool(l <= r)))
325                    }
326                    (Value::Literal(Literal::Text(l)), Value::Literal(Literal::Text(r))) => {
327                        Ok(Value::Literal(Literal::Bool(l <= r)))
328                    }
329                    _ => Err(anyhow!(
330                        "Expected comparable operands, found {} <= {}~{}",
331                        left_val.type_as_string(),
332                        right_val.type_as_string(),
333                        self.to_formula(),
334                    )),
335                }
336            }
337            Relational::Greater(left, right) => {
338                let (left_val, right_val) = evaluate_and_promote(context, left, right)?;
339                match (&left_val, &right_val) {
340                    (Value::Literal(Literal::Bool(l)), Value::Literal(Literal::Bool(r))) => {
341                        Ok(Value::Literal(Literal::Bool(l > r)))
342                    }
343                    (Value::Literal(Literal::Date(l)), Value::Literal(Literal::Date(r))) => {
344                        Ok(Value::Literal(Literal::Bool(l > r)))
345                    }
346                    (Value::Literal(Literal::Number(l)), Value::Literal(Literal::Number(r))) => {
347                        Ok(Value::Literal(Literal::Bool(l > r)))
348                    }
349                    (Value::Literal(Literal::Task(_, _)), Value::Literal(Literal::Task(_, _))) => {
350                        let l = left_val.to_number()?;
351                        let r = right_val.to_number()?;
352                        Ok(Value::Literal(Literal::Bool(l > r)))
353                    }
354                    (Value::Literal(Literal::Text(l)), Value::Literal(Literal::Text(r))) => {
355                        Ok(Value::Literal(Literal::Bool(l > r)))
356                    }
357                    _ => Err(anyhow!(
358                        "Expected comparable operands, found {} > {}~{}",
359                        left_val.type_as_string(),
360                        right_val.type_as_string(),
361                        self.to_formula(),
362                    )),
363                }
364            }
365            Relational::GreaterOrEqual(left, right) => {
366                let (left_val, right_val) = evaluate_and_promote(context, left, right)?;
367                match (&left_val, &right_val) {
368                    (Value::Literal(Literal::Bool(l)), Value::Literal(Literal::Bool(r))) => {
369                        Ok(Value::Literal(Literal::Bool(l >= r)))
370                    }
371                    (Value::Literal(Literal::Date(l)), Value::Literal(Literal::Date(r))) => {
372                        Ok(Value::Literal(Literal::Bool(l >= r)))
373                    }
374                    (Value::Literal(Literal::Number(l)), Value::Literal(Literal::Number(r))) => {
375                        Ok(Value::Literal(Literal::Bool(l >= r)))
376                    }
377                    (Value::Literal(Literal::Task(_, _)), Value::Literal(Literal::Task(_, _))) => {
378                        let l = left_val.to_number()?;
379                        let r = right_val.to_number()?;
380                        Ok(Value::Literal(Literal::Bool(l >= r)))
381                    }
382                    (Value::Literal(Literal::Text(l)), Value::Literal(Literal::Text(r))) => {
383                        Ok(Value::Literal(Literal::Bool(l >= r)))
384                    }
385                    _ => Err(anyhow!(
386                        "Expected comparable operands, found {} >= {}~{}",
387                        left_val.type_as_string(),
388                        right_val.type_as_string(),
389                        self.to_formula(),
390                    )),
391                }
392            }
393            Relational::Primary(primary) => primary.evaluate(context),
394        }
395    }
396
397    fn write(&self, writer: &mut Writer) {
398        match self {
399            Relational::Less(left, right) => {
400                writer.write_binary_op(left, " < ", right);
401            }
402            Relational::LessOrEqual(left, right) => {
403                writer.write_binary_op(left, " <= ", right);
404            }
405            Relational::Greater(left, right) => {
406                writer.write_binary_op(left, " > ", right);
407            }
408            Relational::GreaterOrEqual(left, right) => {
409                writer.write_binary_op(left, " >= ", right);
410            }
411            Relational::Primary(primary) => primary.write(writer),
412        }
413    }
414    fn to_sanitized(&self) -> String {
415        let mut writer = Writer::sanitizer();
416        self.write(&mut writer);
417        writer.finish()
418    }
419    fn to_formula(&self) -> String {
420        let mut writer = Writer::formulizer();
421        self.write(&mut writer);
422        writer.finish()
423    }
424}
425
426impl fmt::Display for Relational {
427    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
428        let mut writer = Writer::stringizer();
429        self.write(&mut writer);
430        write!(f, "{}", writer.finish())
431    }
432}