aimx/expressions/
multiplicative.rs

1//! Multiplicative expression parsing and evaluation.
2//!
3//! Parses and evaluates `*`, `/`, and `%` operators for AIMX expressions.
4//! Implements left-associative multiplicative precedence above additive and
5//! below unary/postfix, using [`Unary`] operands and [`Primary`] for
6//! flattened subexpressions.
7
8use crate::{
9    aim::{ContextLike, WriterLike, Writer},
10    expressions::{ExpressionLike, Primary, Unary, evaluate_and_promote, parse_unary},
11    literals::Literal,
12    values::Value,
13};
14use anyhow::{Result, anyhow};
15use nom::{
16    IResult, Parser,
17    branch::alt,
18    character::complete::{char, multispace0},
19    multi::many0,
20};
21use std::{
22    fmt,
23    sync::Arc,
24};
25
26/// Multiplicative expression node.
27///
28/// Represents `*`, `/`, `%` over [`Unary`] operands, or a flattened
29/// [`Primary`] when no multiplicative operator is present.
30#[derive(Debug, Clone, PartialEq)]
31pub enum Multiplicative {
32    Multiply(Box<Multiplicative>, Unary),
33    Divide(Box<Multiplicative>, Unary),
34    Modulus(Box<Multiplicative>, Unary),
35    /// Flattened primary expression when no multiplicative operator applies.
36    Primary(Box<Primary>),
37}
38
39impl Multiplicative {
40    pub fn print(&self, writer: &mut Writer) {
41        match self {
42            Multiplicative::Multiply(left, right) => {
43                writer.write_binary_op(left.as_ref(), " * ", right);
44            }
45            Multiplicative::Divide(left, right) => {
46                writer.write_binary_op(left.as_ref(), " / ", right);
47            }
48            Multiplicative::Modulus(left, right) => {
49                writer.write_binary_op(left.as_ref(), " % ", right);
50            }
51            Multiplicative::Primary(primary) => primary.print(writer),
52        }
53    }
54}
55
56/// Parse a multiplicative expression.
57///
58/// Grammar: `multiplicative := unary (S? ('*' | '/' | '%') S? unary)*`.
59pub fn parse_multiplicative(input: &str) -> IResult<&str, Multiplicative> {
60    let (input, first) = parse_unary(input)?;
61    let (input, rest) = many0((
62        multispace0,
63        alt((char('*'), char('/'), char('%'))),
64        multispace0,
65        parse_unary,
66    ))
67    .parse(input)?;
68
69    // Fold left-to-right to enforce left associativity.
70    let result = rest.into_iter().fold(
71        match first {
72            Unary::Primary(primary) => Multiplicative::Primary(primary),
73            _ => Multiplicative::Primary(Box::new(Primary::Unary(first))),
74        },
75        |acc, (_, op, _, next)| match op {
76            '*' => Multiplicative::Multiply(Box::new(acc), next),
77            '/' => Multiplicative::Divide(Box::new(acc), next),
78            '%' => Multiplicative::Modulus(Box::new(acc), next),
79            _ => unreachable!(),
80        },
81    );
82
83    Ok((input, result))
84}
85
86impl ExpressionLike for Multiplicative {
87    fn evaluate(&self, context: &mut dyn ContextLike) -> Result<Arc<Value>> {
88        match self {
89            Multiplicative::Multiply(left, right) => {
90                let (left_val, right_val) = evaluate_and_promote(context, left.as_ref(), right)?;
91                if left_val.is_error() {
92                    return Ok(left_val);
93                }
94                if right_val.is_error() {
95                    return Ok(right_val);
96                }
97                match (&*left_val, &*right_val) {
98                    (Value::Literal(Literal::Number(l)), Value::Literal(Literal::Number(r))) => {
99                        Ok(Arc::new(Value::Literal(Literal::Number(l * r))))
100                    }
101                    _ => Err(anyhow!(
102                        "Expected Number, found {} * {}~{}",
103                        left_val.type_as_string(),
104                        right_val.type_as_string(),
105                        self.to_formula(),
106                    )),
107                }
108            }
109            Multiplicative::Divide(left, right) => {
110                let (left_val, right_val) = evaluate_and_promote(context, left.as_ref(), right)?;
111                if left_val.is_error() {
112                    return Ok(left_val);
113                }
114                if right_val.is_error() {
115                    return Ok(right_val);
116                }
117                match (&*left_val, &*right_val) {
118                    (Value::Literal(Literal::Number(l)), Value::Literal(Literal::Number(r))) => {
119                        if *r == 0.0 {
120                            Err(anyhow!("Division by zero ~{}", self.to_formula()))
121                        } else {
122                            Ok(Arc::new(Value::Literal(Literal::Number(l / r))))
123                        }
124                    }
125                    _ => Err(anyhow!(
126                        "Expected Number, found {} / {}~{}",
127                        left_val.type_as_string(),
128                        right_val.type_as_string(),
129                        self.to_formula(),
130                    )),
131                }
132            }
133            Multiplicative::Modulus(left, right) => {
134                let (left_val, right_val) = evaluate_and_promote(context, left.as_ref(), right)?;
135                if left_val.is_error() {
136                    return Ok(left_val);
137                }
138                if right_val.is_error() {
139                    return Ok(right_val);
140                }
141                match (&*left_val, &*right_val) {
142                    (Value::Literal(Literal::Number(l)), Value::Literal(Literal::Number(r))) => {
143                        if *r == 0.0 {
144                            Err(anyhow!("Division by zero (modulus) ~{}", self.to_formula()))
145                        } else {
146                            Ok(Arc::new(Value::Literal(Literal::Number(l % r))))
147                        }
148                    }
149                    _ => Err(anyhow!(
150                        "Expected Number, found {} % {}~{}",
151                        left_val.type_as_string(),
152                        right_val.type_as_string(),
153                        self.to_formula(),
154                    )),
155                }
156            }
157            Multiplicative::Primary(primary) => primary.evaluate(context),
158        }
159    }
160
161    /// Return the formula-string representation (round-trippable by the parser).
162    fn to_formula(&self) -> String {
163        let mut writer = Writer::formulizer();
164        self.print(&mut writer);
165        writer.finish()
166    }
167}
168
169impl WriterLike for Multiplicative {
170    fn write(&self, writer: &mut Writer) {
171        self.print(writer);
172    }
173}
174
175impl fmt::Display for Multiplicative {
176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        write!(f, "{}", self.to_stringized())
178    }
179}