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}