aimx/expressions/procedure.rs
1//! # Procedure expression parsing and evaluation.
2//!
3//! This module provides parsing and evaluation support for procedure expressions in the AIMX language.
4//! Procedures represent sequences of conditional expressions separated by semicolons (`;`)
5//! and typically form the body of closure expressions.
6//!
7//! ## Syntax
8//!
9//! Procedures support two forms:
10//! - Single expression: `expression`
11//! - Multiple expressions: `expression; expression; ...`
12//!
13//! When multiple expressions are present, they are evaluated sequentially and the result
14//! of the last expression is returned as the procedure's result.
15//!
16//! ## Examples
17//!
18//! ```text
19//! // Simple procedure - single expression
20//! x + 1
21//!
22//! // Multi-expression procedure - evaluates each expression but returns the last one
23//! x * 2; x + 3; x - 1
24//!
25//! // Used in closures with the map function
26//! (5).map(x => x * 2; x + 1) // Returns 11 (5*2=10, then 10+1=11)
27//! ```
28
29use nom::{
30 IResult,
31 Parser,
32 character::complete::{char, multispace0},
33 multi::many1,
34};
35use crate::{
36 ContextLike,
37 ExpressionLike,
38 expressions::{parse_conditional, Conditional},
39 Primary,
40 Value,
41 Writer
42};
43use std::fmt;
44use anyhow::Result;
45
46/// Represents a procedure (sequence of expressions) in the AIMX language.
47///
48/// Procedures are sequences of conditional expressions that can be evaluated sequentially.
49/// They are typically used as the body of closure expressions.
50///
51/// # Variants
52///
53/// - `Array(Vec<Box<Conditional>>)`: A sequence of conditional expressions separated by semicolons
54/// - `Primary(Box<Primary>)`: An optimized representation when the procedure is a simple primary expression
55///
56/// # Evaluation Behavior
57///
58/// When a procedure contains multiple expressions, they are evaluated in order and the result
59/// of the last expression is returned. This allows for sequencing operations while maintaining
60/// a functional programming style.
61///
62/// # Examples
63///
64/// ```text
65/// // Parses to Procedure::Primary(expression)
66/// x + 1
67///
68/// // Parses to Procedure::Array([expression1, expression2, ...])
69/// x * 2; x + 3; x - 1
70/// ```
71#[derive(Debug, Clone, PartialEq)]
72pub enum Procedure {
73 Array(Vec<Box<Conditional>>),
74 /// Primary flattened AST optimization - used when the procedure is a simple primary expression
75 Primary(Box<Primary>),
76}
77
78/// Helper function to parse conditional expressions in a procedure array.
79fn conditional_array(input: &str) -> IResult<&str, Conditional> {
80 let (input, _) = multispace0.parse(input)?;
81 let (input, _) = char(';').parse(input)?;
82 let (input, _) = multispace0.parse(input)?;
83 let (input, conditional) = parse_conditional(input)?;
84 Ok((input, conditional))
85}
86
87/// Parses a procedure expression from the input string.
88///
89/// This is the main entry point for parsing procedures. It attempts to parse
90/// a sequence of conditional expressions separated by semicolons, falling back
91/// to a single primary expression if no semicolons are found.
92///
93/// # Arguments
94///
95/// * `input` - The input string to parse
96///
97/// # Returns
98///
99/// Returns `IResult<&str, Procedure>` containing the remaining unparsed input
100/// and the parsed procedure.
101///
102/// # Examples
103///
104/// ```rust
105/// # use aimx::expressions::procedure::parse_procedure;
106/// let result = parse_procedure("x + 1");
107/// assert!(result.is_ok()); // Returns Procedure::Primary
108///
109/// let result = parse_procedure("x * 2; x + 3; x - 1");
110/// assert!(result.is_ok()); // Returns Procedure::Array with 3 expressions
111/// ```
112pub fn parse_procedure(input: &str) -> IResult<&str, Procedure> {
113 let (input, conditional) = parse_conditional(input)?;
114 if let Ok((input, conditional_list)) = many1(conditional_array).parse(input) {
115 let mut array = vec![Box::new(conditional)];
116 for conditional in conditional_list {
117 array.push(Box::new(conditional));
118 }
119 let (input, _) = multispace0(input)?;
120 Ok((input, Procedure::Array(array)))
121 } else {
122 let procedure = match conditional {
123 Conditional::Primary(primary) => Procedure::Primary(primary),
124 _ => Procedure::Primary(Box::new(Primary::Conditional(conditional))),
125 };
126 let (input, _) = multispace0(input)?;
127 Ok((input, procedure))
128 }
129}
130
131impl ExpressionLike for Procedure {
132 /// Evaluates the procedure expression.
133 ///
134 /// For procedure arrays, this evaluates each conditional expression in sequence
135 /// and returns the result of the last expression. For primary procedures,
136 /// this evaluates the single expression directly.
137 ///
138 /// # Arguments
139 ///
140 /// * `context` - The evaluation context
141 ///
142 /// # Returns
143 ///
144 /// Returns `Result<Value>` containing the result of evaluating the procedure.
145 ///
146 /// # Examples
147 ///
148 /// ```rust
149 /// # use aimx::{Context, ExpressionLike};
150 /// # use aimx::expressions::{conditional::Conditional, procedure::Procedure, primary::Primary};
151 /// # use anyhow::Result;
152 /// # fn example() -> Result<()> {
153 /// let mut context = Context::new();
154 /// let procedure = Procedure::Primary(Box::new(Primary::Literal(aimx::Literal::Number(42.0))));
155 /// let result = procedure.evaluate(&mut context)?;
156 /// // result should be Value::Literal(Literal::Number(42.0))
157 /// # Ok(())
158 /// # }
159 /// ```
160 fn evaluate(&self, context: &mut dyn ContextLike) -> Result<Value> {
161 match self {
162 Procedure::Array(array) => {
163 let mut last = Value::Empty;
164 for conditional in array {
165 last = conditional.evaluate(context)?;
166 }
167 Ok(last)
168 },
169 Procedure::Primary(primary) => primary.evaluate(context),
170 }
171 }
172
173 /// Writes the procedure expression to a writer.
174 ///
175 /// This method formats the procedure according to its variant and writes it
176 /// to the provided writer. The output matches the AIMX syntax for procedures.
177 ///
178 /// # Arguments
179 ///
180 /// * `writer` - The writer to write the formatted procedure to
181 fn write(&self, writer: &mut Writer) {
182 match self {
183 Procedure::Array(conditionals) => {
184 if let Some((first, rest)) = conditionals.split_first() {
185 first.write(writer);
186 for conditional in rest {
187 writer.write_str("; ");
188 conditional.write(writer);
189 }
190 }
191 },
192 Procedure::Primary(primary) => primary.write(writer),
193 }
194 }
195
196 /// Returns a sanitized string representation of the procedure.
197 ///
198 /// Sanitized output is suitable for display to users and may include
199 /// formatting that makes the expression easier to read.
200 ///
201 /// # Returns
202 ///
203 /// Returns a `String` containing the sanitized procedure representation.
204 fn to_sanitized(&self) -> String {
205 let mut writer = Writer::sanitizer();
206 self.write(&mut writer);
207 writer.finish()
208 }
209
210 /// Returns a formula string representation of the procedure.
211 ///
212 /// Formula output is suitable for use as input to the parser and matches
213 /// the exact AIMX syntax.
214 ///
215 /// # Returns
216 ///
217 /// Returns a `String` containing the formula representation.
218 fn to_formula(&self) -> String {
219 let mut writer = Writer::formulizer();
220 self.write(&mut writer);
221 writer.finish()
222 }
223}
224
225impl fmt::Display for Procedure {
226 /// Formats the procedure for display.
227 ///
228 /// This implementation uses the `Writable` trait to format the procedure
229 /// as a string suitable for user display.
230 ///
231 /// # Arguments
232 ///
233 /// * `f` - The formatter to write to
234 ///
235 /// # Returns
236 ///
237 /// Returns `fmt::Result` indicating success or failure.
238 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239 let mut writer = Writer::stringizer();
240 self.write(&mut writer);
241 write!(f, "{}", writer.finish())
242 }
243}