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}