aimx/expressions/
logical_or.rs

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