aimx/expressions/closure.rs
1
2//! # Closure expression parsing and invocation.
3//!
4//! This module provides parsing and evaluation support for closure expressions in the AIMX language.
5//! Closures are anonymous functions that can capture variables from their surrounding scope and are
6//! denoted by the `=>` operator.
7//!
8//! ## Syntax
9//!
10//! AIMX supports two forms of closures:
11//! - Single parameter: `param => expression`
12//! - Double parameter: `(param1, param2) => expression`
13//!
14//! Closures are first-class values that can be stored in variables, passed as arguments to functions,
15//! and returned from functions.
16//!
17//! ## Examples
18//!
19//! ```text
20//! // Simple closure with single parameter
21//! x => x * 2
22//!
23//! // Closure with two parameters
24//! (x, y) => x + y
25//!
26//! // Using closures with array operations
27//! nums.filter(x => x > 5)
28//! nums.map((item, index) => item * index)
29//! ```
30
31use nom::{
32 IResult,
33 Parser,
34 character::complete::{char, multispace0},
35 bytes::tag,
36};
37use crate::{
38 ContextLike,
39 ExpressionLike,
40 expressions::{Conditional, parse_conditional, parse_procedure, parse_identifier, Procedure},
41 Primary,
42 Value,
43 Writer
44};
45use std::fmt;
46use anyhow::Result;
47
48/// Represents a closure (anonymous function) expression in the AIMX language.
49///
50/// Closures capture their environment and can be used as first-class values.
51/// They are typically created using the `=>` operator and can have one or two parameters.
52///
53/// # Variants
54///
55/// - `One(String, Procedure)`: A closure with a single parameter
56/// - `Two(String, String, Procedure)`: A closure with two parameters
57/// - `Primary(Box<Primary>)`: An optimized representation when the closure is a simple primary expression
58///
59/// # Examples
60///
61/// ```text
62/// // Parses to Closure::One("x", procedure)
63/// x => x + 1
64///
65/// // Parses to Closure::Two("x", "y", procedure)
66/// (x, y) => x + y
67/// ```
68#[derive(Debug, Clone, PartialEq)]
69pub enum Closure {
70 One(String, Procedure),
71 Two(String, String, Procedure),
72 /// Primary flattened AST optimization - used when the closure body is a simple primary expression
73 Primary(Box<Primary>),
74}
75
76impl Closure {
77 /// Invokes the closure with the current context.
78 ///
79 /// This method evaluates the closure body after setting up the parameter bindings
80 /// in the context. For closures with parameters, the parameters are bound to
81 /// special context keys (0 for single parameter, 0 and 1 for double parameters).
82 ///
83 /// # Arguments
84 ///
85 /// * `context` - The evaluation context
86 ///
87 /// # Returns
88 ///
89 /// Returns `Result<Value>` containing the result of evaluating the closure body.
90 ///
91 /// # Examples
92 ///
93 /// ```rust
94 /// # use aimx::{expressions::Closure, context::{Context, ContextLike}, expressions::Procedure};
95 /// # use aimx::Primary;
96 /// # use anyhow::Result;
97 /// # fn example() -> Result<()> {
98 /// let mut context = Context::new();
99 /// let closure = Closure::One("x".to_string(), Procedure::Primary(Box::new(Primary::Literal(aimx::Literal::Number(42.0)))));
100 ///
101 /// // Parameter values are set using the context's key mechanism
102 /// let param_name = "x".to_string();
103 /// context.set_key(0, ¶m_name);
104 /// let result = closure.invoke(&mut context)?;
105 /// // result should be Value::Literal(Literal::Number(42.0))
106 /// # Ok(())
107 /// # }
108 /// ```
109 pub fn invoke(&self, context: &mut dyn ContextLike) -> Result<Value> {
110 match self {
111 Closure::Two(one, two, conditional) => {
112 context.set_key(0, one);
113 context.set_key(1, two);
114 conditional.evaluate(context)
115 }
116 Closure::One(one, conditional) => {
117 context.set_key(0, one);
118 conditional.evaluate(context)
119 }
120 Closure::Primary(primary) => primary.evaluate(context),
121 }
122 }
123}
124
125/// Parse a single-parameter closure expression.
126///
127/// Handles the syntax: `identifier => procedure`
128fn single_closure(input: &str) -> IResult<&str, Closure> {
129 let (input, one) = parse_identifier(input)?;
130 let (input, _) = multispace0.parse(input)?;
131 let (input, _) = tag("=>").parse(input)?;
132 let (input, _) = multispace0.parse(input)?;
133 let (input, procedure) = parse_procedure(input)?;
134 Ok((input, Closure::One(one, procedure)))
135}
136
137/// Parse a two-parameter closure expression.
138///
139/// Handles the syntax: `(identifier, identifier) => procedure`
140fn double_closure(input: &str) -> IResult<&str, Closure> {
141 let (input, _) = char('(').parse(input)?;
142 let (input, one) = parse_identifier(input)?;
143 let (input, _) = multispace0.parse(input)?;
144 let (input, _) = char(',').parse(input)?;
145 let (input, _) = multispace0.parse(input)?;
146 let (input, two) = parse_identifier(input)?;
147 let (input, _) = multispace0.parse(input)?;
148 let (input, _) = char(')').parse(input)?;
149 let (input, _) = multispace0.parse(input)?;
150 let (input, _) = tag("=>").parse(input)?;
151 let (input, _) = multispace0.parse(input)?;
152 let (input, procedure) = parse_procedure(input)?;
153 Ok((input, Closure::Two(one, two, procedure)))
154}
155
156/// Parses a closure expression from the input string.
157///
158/// This is the main entry point for parsing closures. It attempts to parse
159/// both single-parameter and two-parameter closures, falling back to
160/// `parse_closure_conditional` if neither form matches.
161///
162/// # Arguments
163///
164/// * `input` - The input string to parse
165///
166/// # Returns
167///
168/// Returns `IResult<&str, Closure>` containing the remaining unparsed input
169/// and the parsed closure.
170///
171/// # Examples
172///
173/// ```rust
174/// # use aimx::expressions::closure::parse_closure;
175/// let result = parse_closure("x => x * 2");
176/// assert!(result.is_ok());
177///
178/// let result = parse_closure("(a, b) => a + b");
179/// assert!(result.is_ok());
180///
181/// let result = parse_closure("simple_expression");
182/// assert!(result.is_ok()); // Falls back to conditional parsing
183/// ```
184pub fn parse_closure(input: &str) -> IResult<&str, Closure> {
185 if input.starts_with('(') {
186 if let Ok((input, closure)) = double_closure(input) {
187 return Ok((input, closure));
188 }
189 } else if let Ok((input, closure)) = single_closure(input) {
190 return Ok((input, closure));
191 }
192 parse_closure_conditional(input)
193}
194
195/// Parses a closure that falls back to conditional expression parsing.
196///
197/// This function is used when the input doesn't match the standard closure
198/// syntax. It parses the input as a conditional expression and wraps it
199/// in a `Closure::Primary` variant.
200///
201/// # Arguments
202///
203/// * `input` - The input string to parse
204///
205/// # Returns
206///
207/// Returns `IResult<&str, Closure>` containing the remaining unparsed input
208/// and the parsed closure.
209pub fn parse_closure_conditional(input: &str) -> IResult<&str, Closure> {
210 let (input, conditional) = parse_conditional(input)?;
211 let closure = match conditional {
212 Conditional::Primary(primary) => Closure::Primary(primary),
213 _ => Closure::Primary(Box::new(Primary::Conditional(conditional))),
214 };
215 Ok((input, closure))
216}
217
218impl ExpressionLike for Closure {
219 /// Evaluates the closure expression.
220 ///
221 /// For primary closures (optimized simple expressions), this evaluates the expression directly.
222 /// For parameterized closures, this returns a `Value::Closure` containing the closure itself,
223 /// since closures with parameters need to be invoked with `invoke()` to evaluate them.
224 ///
225 /// # Arguments
226 ///
227 /// * `context` - The evaluation context
228 ///
229 /// # Returns
230 ///
231 /// Returns `Result<Value>` containing either the evaluated result (for primary closures)
232 /// or the closure itself as a value (for parameterized closures).
233 ///
234 /// # Examples
235 ///
236 /// ```rust
237 /// # use aimx::{Context, ExpressionLike};
238 /// # use aimx::expressions::{closure::Closure, primary::Primary};
239 /// # use anyhow::Result;
240 /// # fn example() -> Result<()> {
241 /// let mut context = Context::new();
242 /// let closure = Closure::Primary(Box::new(Primary::Literal(aimx::Literal::Number(42.0))));
243 /// let result = closure.evaluate(&mut context)?;
244 /// // result should be Value::Literal(Literal::Number(42.0))
245 /// # Ok(())
246 /// # }
247 /// ```
248 fn evaluate(&self, context: &mut dyn ContextLike) -> Result<Value> {
249 match self {
250 Closure::Primary(primary) => primary.evaluate(context),
251 _ => Ok(Value::Closure(self.clone())),
252 }
253 }
254
255 /// Writes the closure expression to a writer.
256 ///
257 /// This method formats the closure according to its variant and writes it
258 /// to the provided writer. The output matches the AIMX syntax for closures.
259 ///
260 /// # Arguments
261 ///
262 /// * `writer` - The writer to write the formatted closure to
263 fn write(&self, writer: &mut Writer) {
264 match self {
265 Closure::One(one, conditional) => {
266 writer.write_str(one);
267 writer.write_str(" => ");
268 conditional.write(writer);
269 },
270 Closure::Two(one, two, conditional) => {
271 writer.write_char('(');
272 writer.write_str(one);
273 writer.write_str(", ");
274 writer.write_str(two);
275 writer.write_str(") => ");
276 conditional.write(writer);
277 },
278 Closure::Primary(primary) => primary.write(writer),
279 }
280 }
281
282 /// Returns a sanitized string representation of the closure.
283 ///
284 /// Sanitized output is suitable for display to users and may include
285 /// formatting that makes the expression easier to read.
286 ///
287 /// # Returns
288 ///
289 /// Returns a `String` containing the sanitized closure representation.
290 fn to_sanitized(&self) -> String {
291 let mut writer = Writer::sanitizer();
292 self.write(&mut writer);
293 writer.finish()
294 }
295
296 /// Returns a formula string representation of the closure.
297 ///
298 /// Formula output is suitable for use as input to the parser and matches
299 /// the exact AIMX syntax.
300 ///
301 /// # Returns
302 ///
303 /// Returns a `String` containing the formula representation.
304 fn to_formula(&self) -> String {
305 let mut writer = Writer::formulizer();
306 self.write(&mut writer);
307 writer.finish()
308 }
309}
310
311impl fmt::Display for Closure {
312 /// Formats the closure for display.
313 ///
314 /// This implementation uses the `Writable` trait to format the closure
315 /// as a string suitable for user display.
316 ///
317 /// # Arguments
318 ///
319 /// * `f` - The formatter to write to
320 ///
321 /// # Returns
322 ///
323 /// Returns `fmt::Result` indicating success or failure.
324 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325 let mut writer = Writer::stringizer();
326 self.write(&mut writer);
327 write!(f, "{}", writer.finish())
328 }
329}