aimx/expressions/
conditional.rs

1//! Conditional (`? :`) expression parsing and evaluation.
2//!
3//! Parses ternary conditionals with right associativity on top of [`Coalesce`]
4//! expressions and flattens into [`Conditional`] for efficient evaluation.
5//! The condition is evaluated first; on error the error is propagated without
6//! evaluating either branch. Otherwise the selected branch is evaluated based on
7//! [`Value::to_bool`].
8use crate::{
9    aim::{ContextLike, WriterLike, Writer},
10    expressions::{
11        Coalesce, Expression, ExpressionLike, Primary, parse_coalesce, parse_expression,
12    },
13    literals::Literal,
14    values::Value,
15};
16use anyhow::Result;
17use nom::{
18    IResult, Parser,
19    character::complete::{char, multispace0}, sequence::{delimited, preceded},
20};
21use std::{
22    fmt,
23    sync::Arc,
24};
25
26/// Conditional expression node.
27///
28/// Represents `condition ? true_expr : false_expr` or a flattened primary
29/// expression. The condition is a [`Coalesce`] expression; branches are stored as
30/// boxed [`Expression`] / [`Conditional`] nodes for right-associative nesting.
31/// `Primary` holds the underlying [`Primary`] when no ternary is present.
32#[derive(Debug, Clone, PartialEq)]
33pub enum Conditional {
34    Ternary(Coalesce, Box<Expression>, Box<Conditional>),
35    /// Primary flattened AST optimization
36    Primary(Box<Primary>),
37}
38
39impl Conditional {
40    pub fn print(&self, writer: &mut Writer) {
41        match self {
42            Conditional::Ternary(condition, true_expr, false_expr) => {
43                condition.print(writer);
44                writer.write_str(" ? ");
45                true_expr.print(writer);
46                writer.write_str(" : ");
47                false_expr.print(writer);
48            }
49            Conditional::Primary(primary) => primary.print(writer),
50        }
51    }
52}
53
54/// Parse the `? expr : conditional` suffix after an already parsed condition.
55///
56/// Returns the chosen branch expression and nested [`Conditional`] for the
57/// false branch.
58fn ternary_operator(input: &str) -> IResult<&str, (Expression, Conditional)> {
59    let (input, _) = preceded(
60        multispace0,
61        char('?')
62    ).parse(input)?;
63    
64    let (input, expression) = delimited(
65        multispace0,
66        parse_expression,
67        multispace0
68    ).parse(input)?;
69    
70    let (input, conditional) = preceded(
71        char(':'),
72        preceded(multispace0, parse_conditional)
73    ).parse(input)?;
74    Ok((input, (expression, conditional)))
75}
76
77/// Parse a conditional expression.
78///
79/// Entry point for the `? :` layer in the precedence chain.
80/// Parses `Coalesce` first; if followed by `?` it builds
81/// `Conditional::Ternary`, otherwise it wraps into `Conditional::Primary`.
82/// Right-associative by recursive call on the false branch.
83pub fn parse_conditional(input: &str) -> IResult<&str, Conditional> {
84    let (input, first) = parse_coalesce(input)?;
85    if let Ok((input, (expression, conditional))) = ternary_operator(input) {
86        let ternary = Conditional::Ternary(first, Box::new(expression), Box::new(conditional));
87        Ok((input, ternary))
88    } else {
89        let conditional = match first {
90            Coalesce::Primary(primary) => Conditional::Primary(primary),
91            _ => Conditional::Primary(Box::new(Primary::Coalesce(first))),
92        };
93        Ok((input, conditional))
94    }
95}
96
97impl ExpressionLike for Conditional {
98    fn evaluate(&self, context: &mut dyn ContextLike) -> Result<Arc<Value>> {
99        match self {
100            Conditional::Ternary(condition, true_expr, false_expr) => {
101                let value = condition.evaluate(context)?;
102                if value.is_error() {
103                    return Ok(value);
104                }
105                match &*value.to_bool() {
106                    Value::Literal(Literal::Bool(true)) => true_expr.evaluate(context),
107                    _ => false_expr.evaluate(context),
108                }
109            }
110            Conditional::Primary(primary) => primary.evaluate(context),
111        }
112    }
113
114    /// Return the formula-string representation (round-trippable by the parser).
115    fn to_formula(&self) -> String {
116        let mut writer = Writer::formulizer();
117        self.print(&mut writer);
118        writer.finish()
119    }
120}
121
122impl WriterLike for Conditional {
123    fn write(&self, writer: &mut Writer) {
124        self.print(writer);
125    }
126}
127
128impl fmt::Display for Conditional {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        write!(f, "{}", self.to_stringized())
131    }
132}