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}