aimx/expressions/
logical_and.rs

1//! Logical expression parsing for `&` / `&&` (AND).
2
3use crate::{
4    aim::{ContextLike, WriterLike, Writer},
5    expressions::{Equality, ExpressionLike, Primary, parse_equality},
6    literals::Literal,
7    values::Value,
8};
9use anyhow::{Result, anyhow};
10use nom::{
11    IResult, Parser, branch::alt, bytes::complete::tag, character::complete::multispace0,
12    multi::many0, sequence::preceded,
13};
14use std::{
15    fmt,
16    sync::Arc,
17};
18
19/// Logical AND expression over [`Equality`] expressions.
20///
21/// `And` represents left-associative `&` / `&&` chains.
22/// `Primary` wraps lower-precedence expressions via [`Primary`].
23#[derive(Debug, Clone, PartialEq)]
24pub enum LogicalAnd {
25    And(Box<LogicalAnd>, Equality),
26    /// Primary flattened AST optimization
27    Primary(Box<Primary>),
28}
29
30impl LogicalAnd {
31    pub fn print(&self, writer: &mut Writer) {
32        match self {
33            LogicalAnd::And(left, right) => {
34                writer.write_binary_op(left.as_ref(), " & ", right);
35            }
36            LogicalAnd::Primary(primary) => primary.print(writer),
37        }
38    }
39}
40
41/// Parse a logical AND operator (`&` or `&&`) and the following equality expression.
42fn and_operator(input: &str) -> IResult<&str, Equality> {
43    preceded(
44        (
45            multispace0,
46            alt((tag("&&"), tag("&"))),
47            multispace0,
48        ),
49        parse_equality
50    ).parse(input)
51}
52
53/// Parse a left-associative `&` / `&&` chain over [`Equality`] expressions.
54pub fn parse_and(input: &str) -> IResult<&str, LogicalAnd> {
55    let (input, first) = parse_equality(input)?;
56    let (input, logical_chain) = many0(and_operator).parse(input)?;
57
58    let and = logical_chain.into_iter().fold(
59        match first {
60            Equality::Primary(primary) => LogicalAnd::Primary(primary),
61            _ => LogicalAnd::Primary(Box::new(Primary::Equality(first))),
62        },
63        |acc, next| LogicalAnd::And(Box::new(acc), next),
64    );
65
66    Ok((input, and))
67}
68
69impl ExpressionLike for LogicalAnd {
70    fn evaluate(&self, context: &mut dyn ContextLike) -> Result<Arc<Value>> {
71        match self {
72            LogicalAnd::And(left, right) => {
73                // Evaluate left operand first
74                let left_val = left.evaluate(context)?;
75                if left_val.is_error() {
76                    return Ok(left_val);
77                }
78
79                // Coerce left to boolean using truthiness rules
80                let left_bool = left_val.clone().as_bool();
81                if !left_bool.is_bool() {
82                    return Err(anyhow!(
83                        "Expected Bool, found {}~{}",
84                        left_val.type_as_string(),
85                        self.to_formula(),
86                    ));
87                }
88
89                let left_lit = left_bool.to_literal();
90                let Literal::Bool(l) = left_lit else {
91                    return Err(anyhow!(
92                        "Expected Bool, found {}~{}",
93                        left_val.type_as_string(),
94                        self.to_formula(),
95                    ));
96                };
97
98                // Short-circuit: if left is false, we do not evaluate right
99                if !l {
100                    return Ok(Arc::new(Value::Literal(Literal::Bool(false))));
101                }
102
103                // Left is true; evaluate and combine with right
104                let right_val = right.evaluate(context)?;
105                if right_val.is_error() {
106                    return Ok(right_val);
107                }
108
109                let right_bool = right_val.clone().as_bool();
110                if !right_bool.is_bool() {
111                    return Err(anyhow!(
112                        "Expected Bool, found {} & {}~{}",
113                        left_val.type_as_string(),
114                        right_val.type_as_string(),
115                        self.to_formula(),
116                    ));
117                }
118
119                let right_lit = right_bool.to_literal();
120                let Literal::Bool(r) = right_lit else {
121                    return Err(anyhow!(
122                        "Expected Bool, found {} & {}~{}",
123                        left_val.type_as_string(),
124                        right_val.type_as_string(),
125                        self.to_formula(),
126                    ));
127                };
128
129                Ok(Arc::new(Value::Literal(Literal::Bool(l && r))))
130            }
131            LogicalAnd::Primary(primary) => primary.evaluate(context),
132        }
133    }
134
135    /// Return the formula-string representation (round-trippable by the parser).
136    fn to_formula(&self) -> String {
137        let mut writer = Writer::formulizer();
138        self.print(&mut writer);
139        writer.finish()
140    }
141}
142
143impl WriterLike for LogicalAnd {
144    fn write(&self, writer: &mut Writer) {
145        self.print(writer);
146    }
147}
148
149impl fmt::Display for LogicalAnd {
150    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151        write!(f, "{}", self.to_stringized())
152    }
153}