aimx/expressions/
additive.rs

1//! Additive expression parsing and evaluation for the AIMX language.
2//!
3//! This module handles parsing and evaluating additive expressions using the `+` (addition)
4//! and `-` (subtraction) operators. Additive operators have left associativity and
5//! higher precedence than relational operators but lower precedence than multiplicative
6//! operators (`*`, `/`, `%`).
7//!
8//! Additive expressions are a fundamental part of the AIMX grammar hierarchy and provide
9//! the building blocks for arithmetic expressions, string operations, and boolean logic.
10//! The module implements the core parsing logic and evaluation behavior for these
11//! operations.
12//!
13//! # Grammar
14//!
15//! Additive expressions follow the grammar:
16//!
17//! ```text
18//! additive := multiplicative (S? ('+' | '-') S? multiplicative)*
19//! ```
20//!
21//! Where `S` represents optional whitespace. The parser builds an abstract syntax tree
22//! (AST) with left associativity, ensuring operations are grouped from left to right.
23//!
24//! # Operator Precedence
25//!
26//! Additive operators have the following precedence characteristics:
27//!
28//! - **Higher precedence than**: relational (`<`, `<=`, `>`, `>=`), equality (`=`, `!=`),
29//!   logical (`&`, `|`), conditional (`?`, `:`), closure (`=>`), procedure (`;`)
30//! - **Lower precedence than**: multiplicative (`*`, `/`, `%`), unary (`!`, `+`, `-`), 
31//!   postfix (`.`, `()`, `[]`), primary (literals, references, parentheses)
32//!
33//! # Examples
34//!
35//! ```text
36//! 2 + 3 + 4          // Parsed as (2 + 3) + 4 = 9 (left associative)
37//! 10 - 5 - 2         // Parsed as (10 - 5) - 2 = 3 (left associative)
38//! 2 * 3 + 4          // Parsed as (2 * 3) + 4 = 10 (multiplicative precedence)
39//! 2 + 3 * 4          // Parsed as 2 + (3 * 4) = 14 (multiplicative precedence)
40//! "Hello" + "World"  // Parsed as string concatenation = "HelloWorld"
41//! "abc" - "b"        // Parsed as string removal = "ac"
42//! true + false       // Parsed as boolean XOR = true
43//! true - false       // Parsed as boolean XNOR = false
44//! ```
45//!
46//! # Type Behavior
47//!
48//! Additive operations support operations between compatible types with specific behaviors:
49//!
50//! ## Addition (`+`)
51//!
52//! - **Numbers**: Mathematical addition (`2 + 3 = 5`)
53//! - **Text**: String concatenation (`"Hello" + "World" = "HelloWorld"`)
54//! - **Booleans**: XOR operation (`true + false = true`, `true + true = false`)
55//! - **Other types**: Attempted conversion to compatible types where possible
56//!
57//! ## Subtraction (`-`)
58//!
59//! - **Numbers**: Mathematical subtraction (`5 - 2 = 3`)
60//! - **Text**: String removal (`"HelloWorld" - "World" = "Hello"`)
61//! - **Booleans**: XNOR operation (`true - false = false`, `true - true = true`)
62//! - **Other types**: Attempted conversion to compatible types where possible
63//!
64//! # Boolean Operations Note
65//!
66//! The boolean behavior for additive operators is not standard in most programming languages:
67//! - `+` performs XOR operation on booleans (`true + false = true`, `true + true = false`)
68//! - `-` performs XNOR operation on booleans (`true - false = false`, `true - true = true`)
69//!
70//! Users should be aware that this is a special feature of the AIMX language and might be 
71//! unintuitive for developers familiar with other languages.
72//!
73//! # Error Handling
74//!
75//! During evaluation, additive operations:
76//! - Return errors for incompatible type combinations
77//! - Provide detailed error messages with type information
78//!
79//! # Examples
80//!
81//! ```rust
82//! use aimx::expressions::additive::{parse_additive, Additive};
83//!
84//! // Parse a simple addition
85//! let result = parse_additive("123 + 456");
86//! assert!(result.is_ok());
87//! assert!(matches!(result.unwrap().1, Additive::Add(_, _)));
88//!
89//! // Parse chained operations (left associative)
90//! let result = parse_additive("10 - 5 - 2");
91//! assert!(result.is_ok());
92//! // Parsed as (10 - 5) - 2 = 3
93//! ```
94//!
95//! # Related Modules
96//!
97//! - [`multiplicative`](super::multiplicative) - Higher precedence multiplicative operations
98//! - [`relational`](super::relational) - Lower precedence relational operations
99//! - [`expression`](crate::expression) - Top-level expression parsing
100//! - [`primary`](super::primary) - Flattened primary expression optimization
101
102use crate::{
103    ContextLike,
104    evaluate_and_promote,
105    ExpressionLike,
106    expressions::{parse_multiplicative, Multiplicative},
107    Literal,
108    Primary,
109    Value,
110    Writer,
111};
112use nom::{
113    IResult, Parser,
114    branch::alt,
115    character::complete::{char, multispace0},
116    multi::many0,
117};
118use std::fmt;
119use anyhow::{anyhow, Result};
120
121/// An additive expression node in the abstract syntax tree.
122///
123/// Represents an expression involving additive operations (`+`, `-`) or a
124/// lower-precedence expression that has been flattened in the AST.
125///
126/// # Variants
127///
128/// - `Add(Box<Additive>, Multiplicative)` - Addition operation
129/// - `Subtract(Box<Additive>, Multiplicative)` - Subtraction operation
130/// - `Primary(Primary)` - Flattened variants from lower precedence levels
131///
132/// # Examples
133///
134/// ```rust
135/// use aimx::expressions::additive::Additive;
136/// use aimx::Literal;
137/// use aimx::expressions::primary::Primary;
138/// use aimx::expressions::multiplicative::Multiplicative;
139///
140/// // Represents the expression: 5 + 3
141/// let add_expr = Additive::Add(
142///     Box::new(Additive::Primary(Box::new(Primary::Literal(Literal::Number(5.0))))),
143///     Multiplicative::Primary(Box::new(Primary::Literal(Literal::Number(3.0))))
144/// );
145/// ```
146#[derive(Debug, Clone, PartialEq)]
147pub enum Additive {
148    Add(Box<Additive>, Multiplicative),
149    Subtract(Box<Additive>, Multiplicative),
150    /// Primary flattened AST optimization
151    Primary(Box<Primary>),
152}
153
154/// Parse an additive expression.
155///
156/// Parses expressions with additive operators (`+`, `-`) according to left associativity.
157/// The parser consumes multiplicative expressions as operands and builds an AST node
158/// representing the additive operation.
159///
160/// # Arguments
161///
162/// * `input` - The input string slice to parse
163///
164/// # Returns
165///
166/// A `IResult` containing the remaining input and parsed `Additive` expression.
167///
168/// # Grammar
169///
170/// ```text
171/// additive := multiplicative (S? ('+' | '-') S? multiplicative)*
172/// ```
173///
174/// Where `S` represents optional whitespace.
175///
176/// # Examples
177///
178/// ```rust
179/// use aimx::expressions::additive::parse_additive;
180///
181/// // Simple addition
182/// let result = parse_additive("123 + 456");
183/// assert!(result.is_ok());
184///
185/// // Chained operations (left associative)
186/// let result = parse_additive("10 - 5 - 2");
187/// assert!(result.is_ok());
188/// // Parsed as (10 - 5) - 2
189///
190/// // With whitespace
191/// let result = parse_additive("123   +   456");
192/// assert!(result.is_ok());
193/// ```
194pub fn parse_additive(input: &str) -> IResult<&str, Additive> {
195    let (input, first) = parse_multiplicative(input)?;
196    let (input, rest) = many0((
197        multispace0,
198        alt((char('+'), char('-'))),
199        multispace0,
200        parse_multiplicative,
201    ))
202    .parse(input)?;
203
204    // Build the result by folding from left to right
205    let result = rest.into_iter().fold(
206        match first {
207            Multiplicative::Primary(primary) => Additive::Primary(primary),
208            _ => Additive::Primary(Box::new(Primary::Multiplicative(first))),
209        },
210        |acc, (_, op, _, next)| match op {
211            '+' => Additive::Add(Box::new(acc), next),
212            '-' => Additive::Subtract(Box::new(acc), next),
213            _ => unreachable!(),
214        },
215    );
216
217    Ok((input, result))
218}
219
220impl ExpressionLike for Additive {
221    fn evaluate(&self, context: &mut dyn ContextLike) -> Result<Value> {
222        match self {
223            Additive::Add(left, right) => {
224                let (left_val, right_val) = evaluate_and_promote(context, left.as_ref(), right)?;
225                // For addition, we can add numbers or concatenate strings
226                match (&left_val, &right_val) {
227                    (Value::Literal(Literal::Bool(l)), Value::Literal(Literal::Bool(r))) => {
228                        Ok(Value::Literal(Literal::Bool(l ^ r)))
229                    }
230                    (Value::Literal(Literal::Number(l)), Value::Literal(Literal::Number(r))) => {
231                        Ok(Value::Literal(Literal::Number(l + r)))
232                    }
233                    (Value::Literal(Literal::Text(l)), Value::Literal(Literal::Text(r))) => {
234                        Ok(Value::Literal(Literal::Text(format!("{}{}", l, r))))
235                    }
236                    _ => Err(anyhow!(
237                        "Expected Bool, Number, or Text, found {} + {}~{}",
238                        left_val.type_as_string(),
239                        right_val.type_as_string(),
240                        self.to_formula(),
241                    )),
242                }
243            }
244            Additive::Subtract(left, right) => {
245                let (left_val, right_val) = evaluate_and_promote(context, left.as_ref(), right)?;
246                // For subtraction, we need numeric operands
247                match (&left_val, &right_val) {
248                    (Value::Literal(Literal::Bool(l)), Value::Literal(Literal::Bool(r))) => {
249                        Ok(Value::Literal(Literal::Bool(!(l ^ r))))
250                    }
251                    (Value::Literal(Literal::Number(l)), Value::Literal(Literal::Number(r))) => {
252                        Ok(Value::Literal(Literal::Number(l - r)))
253                    }
254                    (Value::Literal(Literal::Text(l)), Value::Literal(Literal::Text(r))) => {
255                        let removed = l.replace(r, "");
256                        Ok(Value::Literal(Literal::Text(removed)))
257                    }
258                    _ => Err(anyhow!(
259                        "Expected Bool, Number, or Text, found {} - {}~{}",
260                        left_val.type_as_string(),
261                        right_val.type_as_string(),
262                        self.to_formula(),
263                    )),
264                }
265            }
266            Additive::Primary(primary) => primary.evaluate(context),
267        }
268    }
269
270    fn write(&self, writer: &mut Writer) {
271        match self {
272            Additive::Add(left, right) => {
273                writer.write_binary_op(left.as_ref(), " + ", right);
274            }
275            Additive::Subtract(left, right) => {
276                writer.write_binary_op(left.as_ref(), " - ", right);
277            }
278            Additive::Primary(primary) => primary.write(writer),
279        }
280    }
281    fn to_sanitized(&self) -> String {
282        let mut writer = Writer::sanitizer();
283        self.write(&mut writer);
284        writer.finish()
285    }
286    fn to_formula(&self) -> String {
287        let mut writer = Writer::formulizer();
288        self.write(&mut writer);
289        writer.finish()
290    }
291}
292
293impl fmt::Display for Additive {
294    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295        let mut writer = Writer::stringizer();
296        self.write(&mut writer);
297        write!(f, "{}", writer.finish())
298    }
299}