aimx/expressions/conditional.rs
1//! Conditional expression parsing and evaluation.
2//!
3//! This module provides the parsing and evaluation infrastructure for conditional
4//! expressions in the AIMX (Agentic Inference Markup Expressions) language. It
5//! specifically handles the ternary conditional operator (`? :`), which has
6//! **right associativity** and a specific precedence in the expression grammar.
7//!
8//! # Operator Precedence
9//!
10//! The conditional operator has higher precedence than closure expressions:
11//!
12//! 1. **Primary** - Literals, references, parentheses
13//! 2. **Postfix** - Method calls, indexing, function calls
14//! 3. **Unary** - Prefix operators (`!`, `+`, `-`, casts)
15//! 4. **Multiplicative** - Multiplication, division, modulo (`*`, `/`, `%`)
16//! 5. **Additive** - Addition, subtraction (`+`, `-`)
17//! 6. **Relational** - Comparison operators (`<`, `<=`, `>`, `>=`)
18//! 7. **Equality** - Equality and inequality (`=`, `!=`)
19//! 8. **Logical** - Logical AND and OR (`&`, `|`)
20//! 9. **Conditional** - Ternary operator (`?`, `:`)
21//! 10. **Closure** - Anonymous functions (`=>`)
22//! 11. **Procedure** - Sequence expressions (`;`)
23//!
24//! # Grammar
25//!
26//! The conditional operator has the following grammar:
27//!
28//! ```text
29//! conditional := or_expression (S? '?' S? expression S? ':' S? conditional)?
30//! ```
31//!
32//! Where `S` represents optional whitespace. The optional conditional part makes
33//! the operator **right associative**, meaning `a ? b : c ? d : e` is parsed as
34//! `a ? b : (c ? d : e)`.
35//!
36//! # Truthiness Evaluation
37//!
38//! The condition expression is evaluated for truthiness using the following rules:
39//!
40//! - **Boolean values**: `true` evaluates to truthy, `false` to falsy
41//! - **Arrays**: Non-empty arrays are truthy, empty arrays are falsy
42//! - **Other types**: Converted to boolean using Rust's standard truthiness rules
43//! - Numbers: Non-zero values are truthy, zero is falsy
44//! - Text: Non-empty strings are truthy, empty strings are falsy
45//! - Empty values: Always falsy
46//!
47//! # AST Representation
48//!
49//! The module uses AST flattening optimization where [`Conditional`] includes
50//! variants for all lower-precedence expression types. This allows efficient
51//! evaluation without deep recursion.
52//!
53//! # Examples
54//!
55//! ## Basic Usage
56//!
57//! ```text
58//! 5 > 3 ? "yes" : "no" // → "yes"
59//! true ? 1 : 0 // → 1
60//! x > 0 ? "positive" : "non-positive" // conditional assignment
61//! ```
62//!
63//! ## Complex Conditions
64//!
65//! ```text
66//! is_valid & (x > threshold) ? "pass" : "fail"
67//! status == "completed" ? final_score : pending_score
68//! ```
69//!
70//! ## Right Associativity
71//!
72//! ```text
73//! a ? b : c ? d : e // Parsed as: a ? b : (c ? d : e)
74//! x > 0 ? 1 : y > 0 ? 2 : 3 // Parsed as: x > 0 ? 1 : (y > 0 ? 2 : 3)
75//! ```
76//!
77//! # Runtime Behavior
78//!
79//! - **Short-circuit evaluation**: Only the selected branch (true or false) is evaluated
80//! - **Error handling**: Errors in the condition propagate immediately, errors in
81//! unselected branches are not evaluated
82//! - **Type safety**: The true and false expressions can have different types
83//!
84//! # Related Modules
85//!
86//! - [`logical`](crate::expressions::logical) - Higher precedence logical operations (`&`, `|`)
87//! - [`expression`](crate::expression) - Top-level expression parsing
88//! - [`evaluate`](crate::evaluate) - Core evaluation traits
89//! - [`primary`](crate::expressions::primary) - Lower precedence expressions
90
91use nom::{
92 IResult,
93 Parser,
94 character::complete::{char, multispace0},
95};
96use crate::{
97 ContextLike,
98 Expression,
99 parse_expression,
100 expressions::{LogicalOr, parse_or},
101 Literal,
102 Primary,
103 ExpressionLike,
104 Value,
105 Writer,
106};
107use std::fmt;
108use anyhow::{anyhow, Result};
109
110/// A conditional expression node in the abstract syntax tree.
111///
112/// Represents a ternary conditional expression (`condition ? true_expr : false_expr`)
113/// or a lower-precedence expression that has been flattened in the AST.
114///
115/// The enum uses AST flattening optimization where each expression type includes
116/// variants for all lower-precedence expression types. This allows efficient
117/// evaluation without deep recursion.
118///
119/// # Variants
120///
121/// ## `Ternary(LogicalOr, Box<Expression>, Box<Conditional>)`
122///
123/// Represents a ternary conditional operation with:
124/// - `condition`: The condition expression (must evaluate to a boolean or truthy value)
125/// - `true_expr`: The expression to evaluate if condition is truthy
126/// - `false_expr`: The expression to evaluate if condition is falsy
127///
128/// ## `Primary(Box<Primary>)`
129///
130/// Represents a flattened AST node containing a lower-precedence expression.
131/// This variant is used when no ternary operator is present in the expression.
132///
133#[derive(Debug, Clone, PartialEq)]
134pub enum Conditional {
135 Ternary(LogicalOr, Box<Expression>, Box<Conditional>),
136 /// Primary flattened AST optimization
137 Primary(Box<Primary>),
138}
139
140/// Parse the ternary operator components (the `? expr : conditional` part).
141///
142/// This helper function parses the right side of a ternary conditional expression.
143///
144/// # Arguments
145///
146/// * `input` - The input string slice to parse
147///
148/// # Returns
149///
150/// A `IResult` containing the remaining input and a tuple of the true expression
151/// and false conditional expression.
152fn ternary_operator(input: &str) -> IResult<&str, (Expression, Conditional)> {
153 let (input, _) = multispace0.parse(input)?;
154 let (input, _) = char('?').parse(input)?;
155 let (input, _) = multispace0.parse(input)?;
156 let (input, expression) = parse_expression(input)?;
157 let (input, _) = multispace0.parse(input)?;
158 let (input, _) = char(':').parse(input)?;
159 let (input, _) = multispace0.parse(input)?;
160 let (input, conditional) = parse_conditional(input)?;
161 Ok((input, (expression, conditional)))
162}
163
164/// Parse a conditional expression.
165///
166/// Parses ternary conditional expressions (`condition ? true_expr : false_expr`)
167/// with right associativity, or falls back to parsing logical OR expressions.
168///
169/// # Arguments
170///
171/// * `input` - The input string slice to parse
172///
173/// # Returns
174///
175/// A `IResult` containing the remaining input and parsed `Conditional` expression.
176///
177/// # Grammar
178///
179/// ```text
180/// conditional := or (S? '?' S? expression S? ':' S? conditional)?
181/// ```
182///
183/// Where `S` represents optional whitespace. The optional part makes the
184/// conditional operator right associative.
185///
186/// # Parsing Algorithm
187///
188/// 1. First parse a logical OR expression (`parse_or`)
189/// 2. If followed by `?`, parse the ternary operator components
190/// 3. Otherwise, flatten the logical OR expression into a `Conditional::Primary`
191///
192/// # Examples
193///
194/// ## Basic Conditional
195///
196/// ```rust
197/// use aimx::expressions::{parse_conditional, Conditional};
198///
199/// let (remaining, conditional) = parse_conditional("true ? 1 : 0").unwrap();
200/// assert_eq!(remaining, "");
201/// assert!(matches!(conditional, Conditional::Ternary(_, _, _)));
202/// ```
203///
204/// ## Right Associativity
205///
206/// ```rust
207/// use aimx::expressions::{parse_conditional, Conditional};
208///
209/// // Parses as: true ? 1 : (false ? 2 : 3)
210/// let (remaining, conditional) = parse_conditional("true ? 1 : false ? 2 : 3").unwrap();
211/// assert_eq!(remaining, "");
212/// assert!(matches!(conditional, Conditional::Ternary(_, _, _)));
213/// ```
214///
215/// ## Complex Conditions
216///
217/// ```rust
218/// use aimx::expressions::{parse_conditional, Conditional};
219///
220/// let (remaining, conditional) = parse_conditional("1 < 2 & 3 > 4 ? 5 : 6").unwrap();
221/// assert_eq!(remaining, "");
222/// assert!(matches!(conditional, Conditional::Ternary(_, _, _)));
223/// ```
224///
225/// ## Fallback to Logical OR
226///
227/// ```rust
228/// use aimx::expressions::{parse_conditional, Conditional};
229///
230/// // When no ternary operator is present, parses as logical OR
231/// let (remaining, conditional) = parse_conditional("true | false").unwrap();
232/// assert_eq!(remaining, "");
233/// // This gets flattened to a Primary variant containing a LogicalOr
234/// assert!(matches!(conditional, Conditional::Primary(_)));
235/// ```
236///
237/// ## Whitespace Handling
238///
239/// ```rust
240/// use aimx::expressions::{parse_conditional, Conditional};
241///
242/// // Handles various whitespace patterns
243/// let (remaining, conditional) = parse_conditional("true ? 1 : 0").unwrap();
244/// assert_eq!(remaining, "");
245/// assert!(matches!(conditional, Conditional::Ternary(_, _, _)));
246/// ```
247///
248/// ## Parenthesized Conditions
249///
250/// ```rust
251/// use aimx::expressions::{parse_conditional, Conditional};
252///
253/// let (remaining, conditional) = parse_conditional("(true | false) ? 1 : 0").unwrap();
254/// assert_eq!(remaining, "");
255/// assert!(matches!(conditional, Conditional::Ternary(_, _, _)));
256/// ```
257///
258/// # Error Cases
259///
260/// The function returns `Err` for:
261/// - Invalid syntax (missing `?` or `:`)
262/// - Malformed expressions
263/// - Unbalanced parentheses
264///
265/// ```rust
266/// use aimx::expressions::{parse_conditional, Conditional};
267///
268/// // Missing colon - parses as logical OR instead
269/// let (remaining, conditional) = parse_conditional("true ? 1").unwrap();
270/// assert_eq!(remaining, "? 1");
271/// // This gets flattened to a Primary variant containing a LogicalOr
272/// assert!(matches!(conditional, Conditional::Primary(_) ));
273///
274/// // Missing question mark - parses as logical OR instead
275/// let (remaining, conditional) = parse_conditional("true 1 : 0").unwrap();
276/// assert_eq!(remaining, "1 : 0");
277/// // This gets flattened to a Primary variant containing a LogicalOr
278/// assert!(matches!(conditional, Conditional::Primary(_) ));
279/// ```
280pub fn parse_conditional(input: &str) -> IResult<&str, Conditional> {
281 let (input, first) = parse_or(input)?;
282 if let Ok((input, (expression, conditional))) = ternary_operator(input) {
283 let ternary = Conditional::Ternary(first, Box::new(expression), Box::new(conditional));
284 Ok((input, ternary))
285 } else {
286 let conditional = match first {
287 LogicalOr::Primary(primary) => Conditional::Primary(primary),
288 _ => Conditional::Primary(Box::new(Primary::LogicalOr(first))),
289 };
290 Ok((input, conditional))
291 }
292}
293
294impl ExpressionLike for Conditional {
295 fn evaluate(&self, context: &mut dyn ContextLike) -> Result<Value> {
296 match self {
297 Conditional::Ternary(condition, true_expr, false_expr) => {
298 let val = condition.evaluate(context)?;
299 let found = val.type_as_string();
300 match val.as_bool() {
301 Value::Literal(Literal::Bool(true)) => true_expr.evaluate(context),
302 Value::Empty | Value::Literal(Literal::Bool(false)) => false_expr.evaluate(context),
303 Value::Array(array) => {
304 if array.len() > 0 {
305 let value = array.last().unwrap().as_ref();
306 match value {
307 Value::Literal(Literal::Bool(true)) => true_expr.evaluate(context),
308 _ => false_expr.evaluate(context),
309 }
310 } else {
311 false_expr.evaluate(context)
312 }
313 }
314 _ => Err(anyhow!(
315 "Expected Bool, found {}~{}",
316 found,
317 self.to_formula()
318 )),
319 }
320 },
321 Conditional::Primary(primary) => primary.evaluate(context),
322 }
323 }
324
325 fn write(&self, writer: &mut Writer) {
326 match self {
327 Conditional::Ternary(condition, true_expr, false_expr) => {
328 condition.write(writer);
329 writer.write_str(" ? ");
330 true_expr.write(writer);
331 writer.write_str(" : ");
332 false_expr.write(writer);
333 },
334 Conditional::Primary(primary) => primary.write(writer),
335 }
336 }
337 fn to_sanitized(&self) -> String {
338 let mut writer = Writer::sanitizer();
339 self.write(&mut writer);
340 writer.finish()
341 }
342 fn to_formula(&self) -> String {
343 let mut writer = Writer::formulizer();
344 self.write(&mut writer);
345 writer.finish()
346 }
347}
348
349impl fmt::Display for Conditional {
350 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
351 let mut writer = Writer::stringizer();
352 self.write(&mut writer);
353 write!(f, "{}", writer.finish())
354 }
355}