aimx/expressions/
unary.rs

1//! Unary expression parsing and evaluation for the AIM expression grammar.
2//!
3//! This module provides parsers and evaluation logic for unary expressions
4//! in the AIM expression language. Unary operations include logical NOT,
5//! unary plus and minus, and type casting operations. These operators have
6//! right-to-left associativity and form an important part of the expression
7//! hierarchy.
8//!
9//! # Overview
10//!
11//! Unary expressions operate on a single operand and include:
12//! - **Logical operations**: `!` (NOT)
13//! - **Arithmetic operations**: `+` (unary plus), `-` (unary minus)  
14//! - **Type casting**: `(Type)` syntax for explicit type conversion
15//!
16//! # Operator Precedence
17//!
18//! Unary operators have the fourth-highest precedence in the AIMX grammar,
19//! coming after parentheses and postfix operations but before multiplicative
20//! operations. They are **right-to-left associative**, meaning operators group
21//! from right to left when chained.
22//!
23//! # Unary Operators
24//!
25//! | Operator | Description | Example | Result Type |
26//! |----------|-------------|---------|-------------|
27//! | `!` | Logical NOT | `!true` | `Bool` |
28//! | `+` | Unary plus | `+5` | `Number` |
29//! | `-` | Unary minus | `-3.14` | `Number` |
30//! | `(Bool)` | Cast to boolean | `(Bool)0` | `Bool` |
31//! | `(Date)` | Cast to date | `(Date)"2023-01-01"` | `Date` |
32//! | `(Number)` | Cast to number | `(Number)"123"` | `Number` |
33//! | `(Task)` | Cast to task | `(Task)"complete"` | `Task` |
34//! | `(Text)` | Cast to text | `(Text)42` | `Text` |
35//!
36//! # Examples
37//!
38//! ```text
39//! !true             // logical NOT → false
40//! +5                // unary plus → 5
41//! -3.14             // unary minus → -3.14
42//! (Number)"123"     // cast string to number → 123
43//! !(Bool)0          // cast 0 to bool then negate → true
44//! +-+-5             // multiple unary operators → -5
45//! ```
46//!
47//! # Type Casting Behavior
48//!
49//! Type casting operations convert values between AIMX types:
50//!
51//! - **`(Bool)`**: Converts any value to boolean using truthiness rules
52//! - **`(Date)`**: Parses strings as ISO 8601 dates
53//! - **`(Number)`**: Converts strings and booleans to numbers
54//! - **`(Task)`**: Creates task primitives from values
55//! - **`(Text)`**: Converts any value to string representation
56//!
57//! # Related Modules
58//!
59//! - [`postfix`](crate::expressions::postfix) - Higher precedence operations
60//! - [`multiplicative`](crate::expressions::multiplicative) - Lower precedence operations
61//! - [`literal`](crate::literal) - Literal value parsing
62//! - [`value`](crate::value) - Runtime value representation
63
64use nom::{
65    IResult,
66    Parser,
67    branch::alt,
68    character::complete::{char, multispace0},
69    combinator::map,
70    multi::many1,
71};
72use crate::{
73    ContextLike,
74    ExpressionLike,
75    expressions::{parse_postfix, Postfix},
76    Literal,
77    Primary,
78    Typedef,
79    parse_literal_type,
80    Value,
81    Writer
82};
83use std::fmt;
84use anyhow::{anyhow, Result};
85
86/// Represents a unary expression in the AIM grammar.
87/// 
88/// Unary expressions perform operations on a single operand, including
89/// logical NOT, unary plus/minus, and type casting. These operations have
90/// right-to-left associativity, meaning they group from right to left.
91/// 
92/// # AST Flattening Optimization
93/// 
94/// The `Unary` enum uses AST flattening where it includes variants for all
95/// lower-precedence expression types. This allows for efficient evaluation
96/// without deep recursion and simplifies the implementation of the
97/// [`ExpressionLike`] trait.
98/// 
99/// # Variants
100/// 
101/// ## Basic Unary Operations
102/// 
103/// These variants represent single-operand operations:
104/// 
105/// - `Not` - Logical NOT operation: `!operand`
106/// - `Positive` - Unary plus operation: `+operand`  
107/// - `Negative` - Unary minus operation: `-operand`
108/// 
109/// ## Type Casting Operations
110/// 
111/// These variants provide explicit type conversion using the `(Type)` syntax:
112/// 
113/// - `CastBool` - Cast to boolean type: `(Bool)operand`
114/// - `CastDate` - Cast to date type: `(Date)operand`
115/// - `CastNumber` - Cast to number type: `(Number)operand`
116/// - `CastTask` - Cast to task type: `(Task)operand`
117/// - `CastText` - Cast to text type: `(Text)operand`
118/// 
119/// ## Flattened Expressions (AST Optimization)
120/// 
121/// These variants flatten the AST structure for efficient evaluation:
122/// 
123/// - `Primary` - Flattened primary expression (literals, references, parentheses)
124/// 
125/// # Examples
126/// 
127/// ```rust
128/// use aimx::expressions::unary::{Unary, parse_unary};
129/// 
130/// // Parse and match different Unary variants
131/// let (_, not_expr) = parse_unary("!true").unwrap();
132/// assert!(matches!(not_expr, Unary::Not(_)));
133/// 
134/// let (_, positive_expr) = parse_unary("+5").unwrap();
135/// assert!(matches!(positive_expr, Unary::Positive(_)));
136/// 
137/// let (_, cast_expr) = parse_unary("(Number)\"123\"").unwrap();
138/// assert!(matches!(cast_expr, Unary::CastNumber(_)));
139/// 
140/// // Chained operations demonstrate right-to-left associativity
141/// let (_, chained) = parse_unary("!(Bool)0").unwrap();
142/// // This parses as !((Bool)0) - NOT applied to the result of the cast
143/// ```
144/// 
145/// # Evaluation Behavior
146/// 
147/// Each variant has specific evaluation behavior:
148/// 
149/// - **`Not`**: Evaluates operand to boolean, then applies logical NOT
150/// - **`Positive`/`Negative`**: Evaluates operand to number, then applies sign
151/// - **Cast variants**: Evaluates operand, then converts to target type
152/// - **`Primary`**: Delegates evaluation to the contained primary expression
153#[derive(Debug, Clone, PartialEq)]
154pub enum Unary {
155    /// Logical NOT operation: !operand
156    Not(Box<Unary>),
157    /// Unary plus operation: +operand
158    Positive(Box<Unary>),
159    /// Unary minus operation: -operand
160    Negative(Box<Unary>),
161    /// Cast to boolean type: (Bool)operand
162    CastBool(Box<Unary>),
163    /// Cast to date type: (Date)operand
164    CastDate(Box<Unary>),
165    /// Cast to number type: (Number)operand
166    CastNumber(Box<Unary>),
167    /// Cast to task type: (Task)operand
168    CastTask(Box<Unary>),
169    /// Cast to text type: (Text)operand
170    CastText(Box<Unary>),
171    /// Primary flattened AST optimization
172    Primary(Box<Primary>),
173}
174
175/// Parse a cast operator expression (type).
176/// 
177/// This function parses type casting syntax in the form `(Type)` where Type
178/// is a valid type name from the AIM type system. The parser handles optional
179/// whitespace around the parentheses and type name.
180/// 
181/// # Arguments
182/// 
183/// * `input` - A string slice containing the cast operator to parse
184/// 
185/// # Returns
186/// 
187/// * `IResult<&str, Typedef>` - A nom result with remaining input and parsed type definition
188/// 
189/// # Examples
190/// 
191/// ```rust
192/// use aimx::expressions::unary::{Unary, parse_unary};
193/// 
194/// // Parse cast expressions correctly
195/// let result = parse_unary("(Bool) true");
196/// assert!(result.is_ok());
197/// ```
198fn cast_operator(input: &str) -> IResult<&str, Typedef> {
199    let (input, _) = char('(').parse(input)?;
200    let (input, _) = multispace0.parse(input)?;
201    let (input, typedef) = parse_literal_type(input)?;
202    let (input, _) = multispace0.parse(input)?;
203    let (input, _) = char(')').parse(input)?;
204    Ok((input, typedef))
205}
206
207/// Internal representation of unary operators during parsing.
208/// 
209/// This enum is used internally by the parser to represent different types
210/// of unary operators before they are converted to the final `Unary` AST.
211enum UnaryOp {
212    /// Logical NOT operator: !
213    Not,
214    /// Unary plus operator: +
215    Positive,
216    /// Unary minus operator: -
217    Negative,
218    /// Type cast operator: (type)
219    Cast(Typedef),
220}
221
222/// Parse a unary operator.
223/// 
224/// This function parses unary operators including logical NOT, unary plus/minus,
225/// and type casting operators. It handles optional whitespace around operators
226/// and uses the `alt` combinator to try each operator type in sequence.
227/// 
228/// # Arguments
229/// 
230/// * `input` - A string slice containing the unary operator to parse
231/// 
232/// # Returns
233/// 
234/// * `IResult<&str, UnaryOp>` - A nom result with remaining input and parsed unary operator
235/// 
236/// # Operator Precedence
237/// 
238/// The parser tries operators in this order:
239/// 1. `!` - Logical NOT
240/// 2. `+` - Unary plus
241/// 3. `-` - Unary minus
242/// 4. `(type)` - Type casting
243/// 
244/// # Examples
245/// 
246/// ```rust
247/// use aimx::expressions::unary::{Unary, parse_unary};
248/// 
249/// // Test parsing different unary operators with operands
250/// let result = parse_unary("!true");
251/// assert!(result.is_ok());
252/// 
253/// let result = parse_unary("+5");
254/// assert!(result.is_ok());
255/// 
256/// let result = parse_unary("(Bool) 0");
257/// assert!(result.is_ok());
258/// ```
259fn unary_operator(input: &str) -> IResult<&str, UnaryOp> {
260    let (input, _) = multispace0.parse(input)?;
261    let (input, unary) =  alt((
262            map(char('!'), |_| UnaryOp::Not),
263            map(char('+'), |_| UnaryOp::Positive),
264            map(char('-'), |_| UnaryOp::Negative),
265            map(cast_operator, |typedef| UnaryOp::Cast(typedef)),
266        )).parse(input)?;
267    let (input, _) = multispace0.parse(input)?;
268    Ok((input, unary))
269}
270
271impl Unary {
272    /// Create a new unary expression from a unary operator and operand.
273    /// 
274    /// This function constructs the appropriate Unary variant based on the
275    /// provided operator and operand. It boxes the operand to enable
276    /// recursive expression structures.
277    /// 
278    /// # Arguments
279    /// 
280    /// * `op` - The unary operator to apply
281    /// * `unary` - The operand expression
282    /// 
283    /// # Returns
284    /// 
285    /// * `Unary` - The constructed unary expression
286    /// 
287    /// # Examples
288    /// 
289    /// ```rust
290    /// use aimx::expressions::unary::{Unary, parse_unary};
291    /// 
292    /// // Create operand for demonstration
293    /// let operand = Unary::Primary(Box::new(aimx::Primary::Literal(aimx::Literal::Bool(true))));
294    /// // The Unary::new function is private, so use parse_unary to create expressions
295    /// let parsed_expr = parse_unary("!true").unwrap().1;
296    /// assert!(matches!(parsed_expr, Unary::Not(_)));
297    /// ```
298    fn new(op: UnaryOp, unary: Unary) -> Self {
299        let boxed = Box::new(unary);
300        match op {
301            UnaryOp::Not => Unary::Not(boxed),
302            UnaryOp::Positive => Unary::Positive(boxed),
303            UnaryOp::Negative => Unary::Negative(boxed),
304            UnaryOp::Cast(typedef) => match typedef {
305                Typedef::Bool => Unary::CastBool(boxed),
306                Typedef::Date => Unary::CastDate(boxed),
307                Typedef::Number => Unary::CastNumber(boxed),
308                Typedef::Task => Unary::CastTask(boxed),
309                Typedef::Text => Unary::CastText(boxed),
310                _ => unreachable!(),
311            }
312        }
313    }
314}
315
316/// Parse a unary expression.
317/// 
318/// This function parses unary expressions including logical NOT, unary plus/minus,
319/// and type casting operations. It handles the right-to-left associativity of
320/// unary operators and chaining of multiple unary operations.
321/// 
322/// # Arguments
323/// 
324/// * `input` - A string slice containing the unary expression to parse
325/// 
326/// # Returns
327/// 
328/// * `IResult<&str, Unary>` - A nom result with remaining input and parsed unary expression
329/// 
330/// # Parsing Algorithm
331/// 
332/// The parser follows this algorithm:
333/// 1. Attempt to parse one or more unary operators using `many1(unary_operator)`
334/// 2. If successful, parse the postfix expression (operand)
335/// 3. Convert the postfix to a unary expression
336/// 4. Apply unary operators in right-to-left order (reverse of parsing order)
337/// 5. If no unary operators are found, parse the expression as a postfix expression
338/// 
339/// # Right-to-Left Associativity
340/// 
341/// Unary operators are right-to-left associative. This means `!+5` parses as `!(+5)`
342/// rather than `(!+)5`. The parser maintains a stack of operators and applies them
343/// in reverse order to achieve this associativity.
344/// 
345/// # Examples
346/// 
347/// ```rust
348/// use aimx::expressions::unary::parse_unary;
349/// 
350/// // Basic unary operations
351/// let (remaining, expr) = parse_unary("!true").unwrap();
352/// assert_eq!(remaining, "");
353/// 
354/// // Chained operations demonstrate right-to-left associativity
355/// let (remaining, expr) = parse_unary("!(Bool)0").unwrap();
356/// assert_eq!(remaining, "");
357/// 
358/// // Multiple unary operators
359/// let (remaining, expr) = parse_unary("+-+5").unwrap();
360/// assert_eq!(remaining, "");
361/// 
362/// // Type casting
363/// let (remaining, expr) = parse_unary("(Number)\"123\"").unwrap();
364/// assert_eq!(remaining, "");
365/// ```
366/// 
367/// # Error Handling
368/// 
369/// Returns `IResult::Err` if the input cannot be parsed as a valid unary expression.
370/// Common errors include:
371/// - Invalid unary operator syntax
372/// - Missing operand after unary operator
373/// - Invalid type name in cast operator
374pub fn parse_unary(input: &str) -> IResult<&str, Unary> {
375    // Chain any unary operators
376    if let Ok((input, mut unary_chain)) = many1(unary_operator).parse(input) {
377        // Get the primary operand
378        let (input, postfix) = parse_postfix(input)?;
379        // Convert to unary
380        let mut unary = match postfix {
381            Postfix::Primary(primary) => Unary::Primary(primary),
382            _ => Unary::Primary(Box::new(Primary::Postfix(postfix))),
383        };
384        // Iterate unary chain right to left
385        while let Some(op) = unary_chain.pop() {
386            unary = Unary::new(op, unary);
387        }
388        Ok((input, unary))
389    } else {
390        // Parse the primary
391        let (input, postfix) = parse_postfix(input)?;
392        let unary = match postfix {
393            Postfix::Primary(primary) => Unary::Primary(primary),
394            _ => Unary::Primary(Box::new(Primary::Postfix(postfix))),
395        };
396        Ok((input, unary))
397    }
398
399}
400
401impl ExpressionLike for Unary {
402    /// Evaluate the unary expression within the given context.
403    /// 
404    /// This method evaluates the expression recursively, handling each variant
405    /// according to its specific semantics. Evaluation follows the operator
406    /// precedence rules and handles type conversions as needed.
407    /// 
408    /// # Arguments
409    /// 
410    /// * `context` - The evaluation context providing variable values and function implementations
411    /// 
412    /// # Returns
413    /// 
414    /// * `Result<Value>` - The evaluated result or an error if evaluation fails
415    /// 
416    /// # Evaluation Behavior by Variant
417    /// 
418    /// - **`Not`**: Evaluates operand to boolean using `as_bool()`, then applies logical NOT
419    /// - **`Positive`**: Evaluates operand to number using `as_number()`, preserves sign
420    /// - **`Negative`**: Evaluates operand to number using `as_number()`, negates value
421    /// - **Cast variants**: Evaluates operand, then converts to target type using corresponding `as_*()` method
422    /// - **`Primary`**: Delegates evaluation to the contained primary expression
423    /// 
424    /// # Error Handling
425    /// 
426    /// Returns `Err` if:
427    /// - Operand evaluation fails
428    /// - Type conversion fails (e.g., non-numeric operand for `+`/`-`)
429    /// - Invalid type for cast operation
430    /// 
431    /// # Examples
432    /// 
433    /// ```rust
434    /// use aimx::{expressions::unary::Unary, ExpressionLike, Context, Literal};
435    /// 
436    /// let mut context = Context::new();
437    /// 
438    /// // Create a simple unary expression for testing
439    /// let expr = Unary::CastText(Box::new(Unary::Primary(Box::new(aimx::Primary::Literal(Literal::from_number(42.0))))));
440    /// let result = expr.evaluate(&mut context).unwrap();
441    /// assert_eq!(result.to_string(), "42");
442    /// ```
443    fn evaluate(&self, context: &mut dyn ContextLike) -> Result<Value> {
444        match self {
445            Unary::Not(operand) => {
446                let val = operand.evaluate(context)?;
447                let found = val.type_as_string();
448                match val.as_bool() {
449                    Value::Literal(Literal::Bool(b)) => Ok(Value::Literal(Literal::Bool(!b))),
450                    _ => Err(anyhow!(
451                        "Expected Bool, found {}~{}",
452                        found,
453                        self.to_formula(),
454                    )),
455                }
456            },
457            Unary::Positive(operand) => {
458                let val = operand.evaluate(context)?;
459                let found = val.type_as_string();
460                match val.as_number() {
461                    Ok(Value::Literal(Literal::Number(n))) => Ok(Value::Literal(Literal::Number(n))),
462                    _ => Err(anyhow!(
463                        "Expected numeric operand, found {}~{}",
464                        found,
465                        self.to_formula(),
466                    )),
467                }
468            },
469            Unary::Negative(operand) => {
470                let val = operand.evaluate(context)?;
471                let found = val.type_as_string();
472                match val.as_number() {
473                    Ok(Value::Literal(Literal::Number(n))) => Ok(Value::Literal(Literal::Number(-n))),
474                    _ => Err(anyhow!(
475                        "Expected numeric operand, found {}~{}",
476                        found,
477                        self.to_formula(),
478                    )),
479                }
480            },
481            Unary::CastBool(operand) => {
482                let val = operand.evaluate(context)?;
483                Ok(val.as_bool())
484            },
485            Unary::CastDate(operand) => {
486                let val = operand.evaluate(context)?;
487                val.as_date()
488            },
489            Unary::CastNumber(operand) => {
490                let val = operand.evaluate(context)?;
491                val.as_number()
492            },
493            Unary::CastTask(operand) => {
494                let val = operand.evaluate(context)?;
495                val.as_task()
496            },
497            Unary::CastText(operand) => {
498                let val = operand.evaluate(context)?;
499                val.as_text()
500            },
501            Unary::Primary(primary) => primary.evaluate(context),
502        }
503    }
504
505    fn write(&self, writer: &mut Writer) {
506        match self {
507            Unary::Not(operand) => {
508                writer.write_unary_op("!", operand.as_ref());
509            },
510            Unary::Positive(operand) => {
511                writer.write_unary_op("+", operand.as_ref());
512            },
513            Unary::Negative(operand) => {
514                writer.write_unary_op("-", operand.as_ref());
515            },
516            Unary::CastBool(operand) => {
517                writer.write_str("(Bool)");
518                operand.write(writer);
519            },
520            Unary::CastDate(operand) => {
521                writer.write_str("(Date)");
522                operand.write(writer);
523            },
524            Unary::CastNumber(operand) => {
525                writer.write_str("(Number)");
526                operand.write(writer);
527            },
528            Unary::CastTask(operand) => {
529                writer.write_str("(Task)");
530                operand.write(writer);
531            },
532            Unary::CastText(operand) => {
533                writer.write_str("(Text)");
534                operand.write(writer);
535            },
536            Unary::Primary(primary) => primary.write(writer),
537        }
538    }
539
540    /// Convert the expression to a sanitized string representation.
541    /// 
542    /// Sanitized strings are suitable for display to users and may omit
543    /// sensitive information or simplify complex expressions.
544    /// 
545    /// # Returns
546    /// 
547    /// * `String` - A sanitized string representation of the expression
548    fn to_sanitized(&self) -> String {
549        let mut writer = Writer::sanitizer();
550        self.write(&mut writer);
551        writer.finish()
552    }
553
554    /// Convert the expression to a formula string representation.
555    /// 
556    /// Formula strings preserve the exact syntax of the original expression
557    /// and are suitable for serialization or debugging.
558    /// 
559    /// # Returns
560    /// 
561    /// * `String` - A formula string representation of the expression
562    fn to_formula(&self) -> String {
563        let mut writer = Writer::formulizer();
564        self.write(&mut writer);
565        writer.finish()
566    }
567}
568
569impl fmt::Display for Unary {
570    /// Format the unary expression as a string.
571    /// 
572    /// This implementation uses the `Writer::stringizer()` to produce
573    /// a human-readable string representation of the expression.
574    /// 
575    /// # Arguments
576    /// 
577    /// * `f` - The formatter to write to
578    /// 
579    /// # Returns
580    /// 
581    /// * `fmt::Result` - The result of the formatting operation
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}