aimx/expressions/
equality.rs

1//! Equality expression parsing and evaluation for the AIMX language.
2//!
3//! This module provides parsers and evaluation logic for equality expressions
4//! using the `=` and `!=` operators. The AIMX grammar accepts both `==` and `=` 
5//! for equality comparison, and both `!=` and `<>` for inequality comparison.
6//!
7//! Equality expressions form an important part of the expression hierarchy,
8//! handling comparisons between values of the same type. They have lower
9//! precedence than relational operators (`<`, `<=`, `>`, `>=`) but higher
10//! precedence than logical operators (`&`, `|`).
11//!
12//! # Grammar
13//!
14//! The equality operators have left associativity and follow this grammar rule:
15//!
16//! ```text
17//! equality := relational (S? ('=' | "!=" | '==') S? relational)?
18//! ```
19//!
20//! Where `S` represents optional whitespace.
21//!
22//! # Operator Support
23//!
24//! | Operator | Meaning | Aliases |
25//! |----------|---------|---------|
26//! | `=` | Equality | `==` |
27//! | `!=` | Inequality | |
28//!
29//! # Examples
30//!
31//! ```text
32//! 5 = 5                    // true (number equality)
33//! "hello" != "world"       // true (text inequality)
34//! 1 + 2 = 3                // true (arithmetic result comparison)
35//! x > 0 = y < 10           // (x > 0) = (y < 10) - relational comparison equality
36//! true = false             // false (boolean equality)
37//! ```
38//!
39//! # Type Behavior
40//!
41//! Equality operations require operands of compatible types. The evaluation
42//! system uses type promotion via [`evaluate_and_promote`]
43//! to ensure both operands have the same type before comparison:
44//!
45//! - **Boolean**: `true = false` → `false`
46//! - **Number**: `5 = 5.0` → `true` (numeric equality)
47//! - **Text**: `"abc" = "abc"` → `true` (string equality)
48//! - **Date**: DateTime equality comparison
49//! - **Task**: Task status equality comparison
50//!
51//! # AST Flattening Optimization
52//!
53//! Like other expression modules, equality expressions use AST flattening
54//! where expressions that don't contain equality operators are flattened
55//! to [`Primary`] expressions for improved evaluation performance.
56//!
57//! # Error Handling
58//!
59//! Equality evaluation provides detailed error messages when type promotion
60//! fails or incompatible types are compared:
61//!
62//! ```text
63//! "Expected Bool, Date, Number, Task or Text, found Type1 = Type2 ~formula"
64//! ```
65//!
66//! # Related Modules
67//!
68//! - [`relational`](super::relational) - Higher precedence relational operations
69//! - [`logical`](super::logical) - Lower precedence logical operations
70//! - [`expression`](crate::expression) - Top-level expression parsing
71//! - [`evaluate`](crate::evaluate) - Core evaluation traits and type promotion
72
73use crate::{
74    ContextLike,
75    evaluate_and_promote,
76    ExpressionLike,
77    expressions::{Relational, parse_relational},
78    Literal,
79    Primary,
80    Value,
81    Writer,
82};
83use nom::{
84    IResult, Parser, branch::alt, bytes::complete::tag, character::complete::multispace0,
85    combinator::map,
86};
87use std::fmt;
88use anyhow::{anyhow, Result};
89
90/// An equality expression node in the abstract syntax tree.
91///
92/// Represents an equality comparison (`=`, `!=`) or a lower-precedence
93/// expression that has been flattened in the AST. This enum is part of
94/// the AIMX expression hierarchy and implements the [`ExpressionLike`] trait.
95///
96/// # AST Flattening
97///
98/// The `Primary` variant represents an optimization where expressions that
99/// don't contain equality operators are flattened to reduce AST depth and
100/// improve evaluation performance. This flattening occurs during parsing
101/// when an expression doesn't contain any equality operators.
102///
103/// # Variants
104///
105/// ## `Equal(Relational, Relational)`
106/// Represents an equality comparison using the `=` or `==` operator.
107/// The operands are [`Relational`] expressions, which have higher precedence.
108///
109/// ## `NotEqual(Relational, Relational)`
110/// Represents an inequality comparison using the `!=` operator.
111/// The operands are [`Relational`] expressions, which have higher precedence.
112///
113/// ## `Primary(Primary)`
114/// A flattened primary expression for optimization. This variant is used
115/// when the expression doesn't contain any equality operators.
116///
117/// # Examples
118///
119/// ```rust
120/// use aimx::expressions::{
121///     equality::Equality,
122///     relational::Relational,
123///     primary::Primary,
124/// };
125/// use aimx::{Literal, ExpressionLike, Context};
126///
127/// // This demonstrates how equality expressions can be constructed
128/// // In practice, these would be parsed from text expressions
129///
130/// // Create an equality expression conceptually like: 5 = 5
131/// // Create an inequality expression conceptually like: 5 != 10
132/// ```
133///
134/// # See Also
135///
136/// - [`parse_equality`] - Function to parse equality expressions
137/// - [`ExpressionLike`] - Trait for expression evaluation
138/// - [`Relational`] - Higher precedence expression type
139/// - [`Primary`] - Flattened primary expression type
140#[derive(Debug, Clone, PartialEq)]
141pub enum Equality {
142    Equal(Relational, Relational),
143    NotEqual(Relational, Relational),
144    /// Primary flattened AST optimization
145    Primary(Box<Primary>),
146}
147
148/// Internal operator type for equality parsing.
149///
150/// This enum represents the equality operators during the parsing phase.
151/// It's used internally by the parser to distinguish between equality
152/// and inequality operations before constructing the final AST.
153#[derive(Debug, Clone, Copy, PartialEq)]
154enum EqualityOp {
155    /// Equality operator (`=`, `==`)
156    Equal,
157    /// Inequality operator (`!=`)
158    NotEqual,
159}
160
161/// Parse an equality operator `=`/`==` or `!=` and the following relational expression.
162///
163/// This internal helper function parses an equality operator followed by
164/// a relational expression. It handles optional whitespace and operator aliases.
165///
166/// # Grammar
167///
168/// ```text
169/// equality_operator := S? ('=' | "!=" | '==') S? relational
170/// ```
171///
172/// Where `S` represents optional whitespace.
173///
174/// # Returns
175///
176/// Returns an `IResult` containing:
177/// - The remaining input
178/// - A tuple of `(EqualityOp, Relational)` representing the operator and right operand
179fn equality_operator(input: &str) -> IResult<&str, (EqualityOp, Relational)> {
180    let (input, _) = multispace0.parse(input)?;
181    let (input, equality_op) = alt((
182        map(tag("!="), |_| EqualityOp::NotEqual),
183        map(tag("=="), |_| EqualityOp::Equal),
184        map(tag("="), |_| EqualityOp::Equal),
185    ))
186    .parse(input)?;
187    let (input, _) = multispace0.parse(input)?;
188    let (input, relational) = parse_relational(input)?;
189    Ok((input, (equality_op, relational)))
190}
191
192/// Parse an equality expression from a string slice.
193///
194/// This function is the main entry point for parsing equality expressions.
195/// It handles the complete equality grammar rule, including optional whitespace
196/// and operator aliases. The parser follows the AIMX precedence rules, where
197/// equality operators have lower precedence than relational operators.
198///
199/// # Grammar
200///
201/// The function implements this grammar rule:
202/// ```text
203/// equality := relational (S? ('=' | "!=" | '==') S? relational)?
204/// ```
205///
206/// Where `S` represents optional whitespace.
207///
208/// # Parsing Strategy
209///
210/// 1. First attempts to parse a relational expression
211/// 2. If an equality operator is found, parses the second relational expression
212/// 3. If no equality operator is found, flattens the expression to [`Primary`]
213/// 4. Returns the parsed [`Equality`] expression and remaining input
214///
215/// # Operator Support
216///
217/// The parser accepts these equality operators:
218/// - `=` (equality) - also accepts `==` as an alias
219/// - `!=` (inequality)
220///
221/// # AST Flattening
222///
223/// When no equality operator is found, the parser flattens the expression
224/// to optimize evaluation performance. This means expressions like `5` or
225/// `x > y` are represented as [`Equality::Primary`] rather than creating
226/// unnecessary AST nodes.
227///
228/// # Arguments
229///
230/// * `input` - The input string slice to parse
231///
232/// # Returns
233///
234/// Returns an [`IResult`] containing:
235/// - The remaining unparsed input (if any)
236/// - The parsed [`Equality`] expression
237///
238/// # Examples
239///
240/// ## Basic Equality
241///
242/// ```rust
243/// use aimx::expressions::equality::{parse_equality, Equality};
244///
245/// let (remaining, expr) = parse_equality("5 = 5").unwrap();
246/// assert_eq!(remaining, "");
247/// assert!(matches!(expr, Equality::Equal(_, _)));
248/// ```
249///
250/// ## Inequality with Whitespace
251///
252/// ```rust
253/// use aimx::expressions::equality::{parse_equality, Equality};
254///
255/// let (remaining, expr) = parse_equality("5 != 10").unwrap();
256/// assert_eq!(remaining, "");
257/// assert!(matches!(expr, Equality::NotEqual(_, _)));
258/// ```
259///
260/// ## Operator Aliases
261///
262/// ```rust
263/// use aimx::expressions::equality::{parse_equality, Equality};
264///
265/// // Both '=' and '==' are accepted
266/// let (_, expr1) = parse_equality("5 = 5").unwrap();
267/// let (_, expr2) = parse_equality("5 == 5").unwrap();
268/// assert!(matches!(expr1, Equality::Equal(_, _)));
269/// assert!(matches!(expr2, Equality::Equal(_, _)));
270/// ```
271///
272/// ## Complex Expressions
273///
274/// ```rust
275/// use aimx::expressions::equality::{parse_equality, Equality};
276///
277/// // Equality with relational expressions
278/// let (remaining, expr) = parse_equality("x > 0 = y < 10").unwrap();
279/// assert_eq!(remaining, "");
280/// assert!(matches!(expr, Equality::Equal(_, _)));
281///
282/// // Equality with arithmetic expressions
283/// let (remaining, expr) = parse_equality("2 + 3 = 5").unwrap();
284/// assert_eq!(remaining, "");
285/// assert!(matches!(expr, Equality::Equal(_, _)));
286/// ```
287///
288/// ## Flattened Expressions
289///
290/// ```rust
291/// use aimx::expressions::equality::{parse_equality, Equality};
292///
293/// // Expressions without equality operators are flattened
294/// let (remaining, expr) = parse_equality("5").unwrap();
295/// assert_eq!(remaining, "");
296/// assert!(matches!(expr, Equality::Primary(_)));
297///
298/// let (remaining, expr) = parse_equality("x > y").unwrap();
299/// assert_eq!(remaining, "");
300/// assert!(matches!(expr, Equality::Primary(_)));
301/// ```
302///
303/// ## Chaining Behavior
304///
305/// ```rust
306/// use aimx::expressions::equality::{parse_equality, Equality};
307///
308/// // Equality operations are not chained
309/// let (remaining, expr) = parse_equality("1 = 1 = 1").unwrap();
310/// assert_eq!(remaining, "= 1"); // Only first equality is parsed
311/// assert!(matches!(expr, Equality::Equal(_, _)));
312/// ```
313///
314/// # Error Handling
315///
316/// The function returns [`IResult`] which can contain parsing errors.
317/// Common errors include:
318/// - Invalid operator syntax
319/// - Malformed relational expressions
320/// - Unexpected end of input
321///
322/// # See Also
323///
324/// - [`Equality`] - The expression type returned by this function
325/// - [`parse_relational`] - Function for parsing relational expressions
326/// - [`ExpressionLike`] - Trait for expression evaluation
327pub fn parse_equality(input: &str) -> IResult<&str, Equality> {
328    let (input, first) = parse_relational(input)?;
329    if let Ok((input, (equality_op, second))) = equality_operator(input) {
330        let equality = match equality_op {
331            EqualityOp::Equal => Equality::Equal(first, second),
332            EqualityOp::NotEqual => Equality::NotEqual(first, second),
333        };
334        Ok((input, equality))
335    } else {
336        let equality = match first {
337            Relational::Primary(primary) => Equality::Primary(primary),
338            _ => Equality::Primary(Box::new(Primary::Relational(first))),
339        };
340        Ok((input, equality))
341    }
342}
343
344impl ExpressionLike for Equality {
345    /// Evaluate this equality expression within the given context.
346    ///
347    /// This method implements the core evaluation logic for equality expressions.
348    /// It handles both equality (`=`) and inequality (`!=`) comparisons, using
349    /// type promotion to ensure both operands have compatible types.
350    ///
351    /// # Evaluation Strategy
352    ///
353    /// 1. **Equality Comparison** (`Equality::Equal`):
354    ///    - Evaluates both operands using [`evaluate_and_promote`]
355    ///    - Promotes the right operand to match the left operand's type
356    ///    - Compares values of the same type using `==` operator
357    ///    - Returns `true` if equal, `false` otherwise
358    ///
359    /// 2. **Inequality Comparison** (`Equality::NotEqual`):
360    ///    - Evaluates both operands using [`evaluate_and_promote`]
361    ///    - Promotes the right operand to match the left operand's type
362    ///    - Compares values of the same type using `!=` operator
363    ///    - Returns `true` if not equal, `false` otherwise
364    ///
365    /// 3. **Flattened Expression** (`Equality::Primary`):
366    ///    - Delegates evaluation to the underlying primary expression
367    ///
368    /// # Type Support
369    ///
370    /// Equality operations support these value types:
371    /// - `Bool` - Boolean equality comparison
372    /// - `Date` - DateTime equality comparison
373    /// - `Number` - Numeric equality comparison
374    /// - `Text` - String equality comparison
375    /// - `Task` - Task status equality comparison
376    ///
377    /// # Error Handling
378    ///
379    /// Returns an error if:
380    /// - Type promotion fails (incompatible types)
381    /// - Either operand evaluation fails
382    /// - Comparison of unsupported types is attempted
383    ///
384    /// Error messages include type information and the original formula:
385    /// ```text
386    /// "Expected Bool, Date, Number, Task or Text, found Type1 = Type2 ~formula"
387    /// ```
388    ///
389    /// # Arguments
390    ///
391    /// * `context` - The evaluation context providing variable values and function implementations
392    ///
393    /// # Returns
394    ///
395    /// Returns a `Result<Value>` containing:
396    /// - `Value::Literal(Literal::Bool(true))` if comparison evaluates to true
397    /// - `Value::Literal(Literal::Bool(false))` if comparison evaluates to false
398    /// - An error if evaluation fails
399    ///
400    /// # Examples
401    ///
402    /// ```rust
403    /// use aimx::expressions::equality::{Equality, parse_equality};
404    /// use aimx::{ExpressionLike, Context, Literal, Value};
405    ///
406    /// let mut context = Context::new();
407    ///
408    /// // Evaluate equality: 5 = 5
409    /// let expr = parse_equality("5 = 5").unwrap().1;
410    /// let result = expr.evaluate(&mut context).unwrap();
411    /// assert_eq!(result, Value::Literal(Literal::Bool(true)));
412    ///
413    /// // Evaluate inequality: 5 != 10
414    /// let expr = parse_equality("5 != 10").unwrap().1;
415    /// let result = expr.evaluate(&mut context).unwrap();
416    /// assert_eq!(result, Value::Literal(Literal::Bool(true)));
417    ///
418    /// // Evaluate with type promotion: 5 = "5"
419    /// let expr = parse_equality("5 = \"5\"").unwrap().1;
420    /// let result = expr.evaluate(&mut context).unwrap();
421    /// assert_eq!(result, Value::Literal(Literal::Bool(true)));
422    /// ```
423    fn evaluate(&self, context: &mut dyn ContextLike) -> Result<Value> {
424        match self {
425            Equality::Equal(left, right) => {
426                let (left_val, right_val) = evaluate_and_promote(context, left, right)?;
427                // For equality, we can compare values of the same type
428                let result = match (&left_val, &right_val) {
429                    (Value::Literal(Literal::Bool(l)), Value::Literal(Literal::Bool(r))) => l == r,
430                    (Value::Literal(Literal::Date(l)), Value::Literal(Literal::Date(r))) => l == r,
431                    (Value::Literal(Literal::Number(l)), Value::Literal(Literal::Number(r))) => l == r,
432                    (Value::Literal(Literal::Text(l)), Value::Literal(Literal::Text(r))) => l == r,
433                    (Value::Literal(Literal::Task(_, l)), Value::Literal(Literal::Task(_, r))) => l == r,
434                    _ => return Err(anyhow!(
435                        "Expected Bool, Date, Number, Task or Text, found {} = {}~{}",
436                        left_val.type_as_string(),
437                        right_val.type_as_string(),
438                        self.to_formula(),
439                    )),
440                };
441                Ok(Value::Literal(Literal::Bool(result)))
442            }
443            Equality::NotEqual(left, right) => {
444                let (left_val, right_val) = evaluate_and_promote(context, left, right)?;
445                // For inequality, we can compare values of the same type
446                let result = match (&left_val, &right_val) {
447                    (Value::Literal(Literal::Bool(l)), Value::Literal(Literal::Bool(r))) => l != r,
448                    (Value::Literal(Literal::Date(l)), Value::Literal(Literal::Date(r))) => l != r,
449                    (Value::Literal(Literal::Number(l)), Value::Literal(Literal::Number(r))) => l != r,
450                    (Value::Literal(Literal::Text(l)), Value::Literal(Literal::Text(r))) => l != r,
451                    (Value::Literal(Literal::Task(_, l)), Value::Literal(Literal::Task(_, r))) => l != r,
452                    _ => return Err(anyhow!(
453                        "Expected Bool, Date, Number, Task or Text, found {} != {}~{}",
454                        left_val.type_as_string(),
455                        right_val.type_as_string(),
456                        self.to_formula(),
457                    )), // Different types are not equal
458                };
459                Ok(Value::Literal(Literal::Bool(result)))
460            }
461            Equality::Primary(primary) => primary.evaluate(context),
462        }
463    }
464
465    /// Write this equality expression to the provided writer.
466    ///
467    /// This method serializes the expression to a text representation using
468    /// the writer's current formatting mode. The writer handles indentation,
469    /// operator spacing, and other formatting concerns.
470    ///
471    /// # Serialization Strategy
472    ///
473    /// - **Equality Comparison** (`Equality::Equal`):
474    ///   - Writes left operand
475    ///   - Writes `" = "` operator with spaces
476    ///   - Writes right operand
477    ///
478    /// - **Inequality Comparison** (`Equality::NotEqual`):
479    ///   - Writes left operand
480    ///   - Writes `" != "` operator with spaces
481    ///   - Writes right operand
482    ///
483    /// - **Flattened Expression** (`Equality::Primary`):
484    ///   - Delegates writing to the underlying primary expression
485    ///
486    /// # Arguments
487    ///
488    /// * `writer` - The writer to serialize the expression to
489    ///
490    /// # Examples
491    ///
492    /// ```rust
493    /// use aimx::expressions::equality::{Equality, parse_equality};
494    /// use aimx::{Writer, PrintMode, Prefix};
495    ///
496    /// // This demonstrates the general pattern for writing equality expressions
497    /// // In practice, these would be used internally by the expression evaluator
498    ///
499    /// // Writing an equality expression like "5 = 5"
500    /// // Writing an inequality expression like "5 != 10"
501    /// ```
502    fn write(&self, writer: &mut Writer) {
503        match self {
504            Equality::Equal(left, right) => {
505                writer.write_binary_op(left, " = ", right);
506            }
507            Equality::NotEqual(left, right) => {
508                writer.write_binary_op(left, " != ", right);
509            }
510            Equality::Primary(primary) => primary.write(writer),
511        }
512    }
513    
514    /// Convert this equality expression to a sanitized string representation.
515    ///
516    /// This method produces a string with special characters escaped to make it
517    /// safe for various contexts like JSON output, HTML embedding, or other
518    /// contexts where escaping is required.
519    ///
520    /// # Returns
521    ///
522    /// A sanitized string representation of this expression.
523    ///
524    fn to_sanitized(&self) -> String {
525        let mut writer = Writer::sanitizer();
526        self.write(&mut writer);
527        writer.finish()
528    }
529    
530    /// Convert this equality expression to a formula string representation.
531    ///
532    /// This method produces a string with proper quoting and escaping for use
533    /// in formulas. The output should be round-trippable, meaning it can be
534    /// parsed back into an equivalent expression.
535    ///
536    /// # Returns
537    ///
538    /// A formula string representation of this expression.
539    ///
540    fn to_formula(&self) -> String {
541        let mut writer = Writer::formulizer();
542        self.write(&mut writer);
543        writer.finish()
544    }
545}
546
547impl fmt::Display for Equality {
548    /// Format this equality expression as a string.
549    ///
550    /// This implementation delegates to the [`write`](ExpressionLike::write) method
551    /// using a string-based writer with default formatting. The output is suitable
552    /// for display purposes and debugging.
553    ///
554    /// # Format
555    ///
556    /// The formatting follows these conventions:
557    /// - Operators are surrounded by spaces: `5 = 5`, `5 != 10`
558    /// - Complex expressions are properly parenthesized
559    /// - Literals are formatted according to their type
560    /// - Variables and references are displayed as-is
561    ///
562    /// # Examples
563    ///
564    /// ```rust
565    /// use aimx::expressions::equality::{Equality, parse_equality};
566    ///
567    /// let expr = parse_equality("5 = 5").unwrap().1;
568    /// assert_eq!(expr.to_string(), "5 = 5");
569    ///
570    /// let expr = parse_equality("x > 0 = y < 10").unwrap().1;
571    /// assert_eq!(expr.to_string(), "x > 0 = y < 10");
572    ///
573    /// let expr = parse_equality("5").unwrap().1;
574    /// assert_eq!(expr.to_string(), "5");
575    /// ```
576    ///
577    /// # See Also
578    ///
579    /// - [`ExpressionLike::write`] - The underlying serialization method
580    /// - [`ExpressionLike::to_formula`] - For round-trippable formula representation
581    /// - [`ExpressionLike::to_sanitized`] - For sanitized output
582    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
583        let mut writer = Writer::stringizer();
584        self.write(&mut writer);
585        write!(f, "{}", writer.finish())
586    }
587}