aimx/expressions/postfix.rs
1//! Postfix expression parsing and evaluation for the AIMX language.
2//!
3//! This module provides the parsing and evaluation logic for postfix expressions,
4//! which are operations that occur after their operands. Postfix expressions have
5//! left-to-right associativity and include function calls, array indexing, method
6//! calls, and inference calls.
7//!
8//! Postfix expressions are the second-highest precedence level in the AIMX grammar
9//! hierarchy, coming immediately after primary expressions. They form a critical
10//! part of the operator precedence resolution system, enabling complex chained
11//! operations like `data.filter().map()` or `matrix[0][1]`.
12//!
13//! # Postfix Operators (Left-to-Right Associativity)
14//!
15//! | Operator | Description | Example |
16//! |----------|-------------|---------|
17//! | `_` | Empty placeholder | `_` |
18//! | `()` | Function call | `sum(1, 2, 3)` |
19//! | `[]` | Array indexing | `array[0]` |
20//! | `.` | Method access | `data.filter()` |
21//! | `$` | Inference call | `$std.extract(document, prompt)` |
22//!
23//! # Grammar Rules
24//!
25//! The postfix grammar is more complex than a simple rule due to special handling
26//! for inference calls and the empty placeholder:
27//!
28//! ```text
29//! // Simplified grammar - actual implementation includes special cases
30//! postfix = function_call | primary (("." method_call) | indexing)*
31//! function_call = identifier "(" argument_list ")" // Special handling for "_"
32//! method_call = identifier "(" argument_list ")"
33//! indexing = "[" expression "]"
34//! inference_call = "$" reference "(" argument_list ")" // Special handling in parser
35//! ```
36//!
37//! The parser has special logic for:
38//! - The empty placeholder `_` which is treated as a special function name
39//! - Inference calls that start with `$` and are detected during primary parsing
40//!
41//! # Examples
42//!
43//! ```rust
44//! use aimx::expressions::postfix::{parse_postfix, Postfix};
45//! use aimx::Context;
46//!
47//! // Parse empty placeholder
48//! let (_, postfix) = parse_postfix("_").unwrap();
49//! assert!(matches!(postfix, Postfix::Empty));
50//!
51//! // Parse function call
52//! let (_, postfix) = parse_postfix("sum(1, 2, 3)").unwrap();
53//! assert!(matches!(postfix, Postfix::Function(_, _)));
54//!
55//! // Parse array indexing
56//! let (_, postfix) = parse_postfix("array[0]").unwrap();
57//! assert!(matches!(postfix, Postfix::Index(_, _)));
58//!
59//! // Parse method chaining
60//! let (_, postfix) = parse_postfix("data.filter().map()").unwrap();
61//! assert!(matches!(postfix, Postfix::Method(_, _, _)));
62//!
63//! // Parse nested indexing
64//! let (_, postfix) = parse_postfix("matrix[0][1]").unwrap();
65//! assert!(matches!(postfix, Postfix::Index(_, _)));
66//! ```
67//!
68//! # See Also
69//!
70//! - [`crate::expression`] - Top-level expression parsing and evaluation
71//! - [`crate::expressions::primary`] - Primary expressions (literals, references, parentheses)
72//! - [`crate::expressions::unary`] - Unary expressions (next precedence level)
73//! - [`crate::Context`] - Evaluation context for function and method calls
74
75use nom::{
76 error::Error,
77 Err as NomErr,
78 IResult, Parser,
79 branch::alt,
80 bytes::complete::tag,
81 character::complete::{char, multispace0},
82 combinator::{map, opt},
83 multi::many0,
84 sequence::delimited,
85};
86use crate::{
87 ContextLike,
88 ExpressionLike,
89 Expression,
90 parse_argument_list,
91 parse_expression,
92 expressions::{parse_primary, parse_identifier},
93 Literal,
94 Primary,
95 Reference,
96 Value,
97 Writer
98};
99use std::fmt;
100use anyhow::{anyhow, Result};
101
102/// Represents a postfix expression in the AIMX grammar.
103///
104/// Postfix expressions are operations that occur after their operands, following
105/// the left-to-right associativity rule. This enum captures all possible postfix
106/// operations including function calls, method calls, array indexing, and inference
107/// operations, as well as flattened primary expressions for optimization.
108///
109/// # Variants
110///
111/// - [`Empty`](Postfix::Empty) - The empty placeholder `_` representing no operation
112/// - [`Function`](Postfix::Function) - Function call with identifier and arguments
113/// - [`Index`](Postfix::Index) - Array indexing operation with base and index
114/// - [`Method`](Postfix::Method) - Method call on an object with method name and arguments
115/// - [`Inference`](Postfix::Inference) - Inference call on a reference with arguments
116/// - [`Primary`](Postfix::Primary) - Flattened primary expression (optimization)
117///
118/// # Examples
119///
120/// ```rust
121/// use aimx::expressions::postfix::Postfix;
122/// use aimx::expressions::primary::Primary;
123/// use aimx::Literal;
124///
125/// // Empty placeholder
126/// let empty = Postfix::Empty;
127/// assert!(empty.is_empty());
128///
129/// // Function call
130/// let args = aimx::Expression::Empty;
131/// let function = Postfix::Function("sum".to_string(), Box::new(args));
132///
133/// // Array indexing
134/// let base = Box::new(Postfix::Primary(Box::new(Primary::Literal(Literal::Number(42.0)))));
135/// let index = Box::new(aimx::Expression::Empty);
136/// let index_op = Postfix::Index(base, index);
137///
138/// // Method call
139/// let obj = Box::new(Postfix::Primary(Box::new(Primary::Literal(Literal::Text("data".to_string())))));
140/// let method = Postfix::Method(obj, "to_upper".to_string(), Box::new(aimx::Expression::Empty));
141/// ```
142///
143/// # AST Flattening
144///
145/// The `Primary` variant represents an optimization where expressions that consist
146/// solely of primary expressions (literals, references, parentheses) are flattened
147/// to reduce AST depth and improve evaluation performance.
148///
149/// # See Also
150///
151/// - [`parse_postfix`] - Function to parse postfix expressions from text
152/// - [`ExpressionLike`] - Trait for expression evaluation
153/// - [`Primary`] - Type of flattened primary expressions
154#[derive(Debug, Clone, PartialEq)]
155pub enum Postfix {
156 /// The empty placeholder `_`
157 Empty,
158 /// Function call with function name and argument expression
159 Function(String, Box<Expression>),
160 /// Array indexing operation with base and index expressions
161 Index(Box<Postfix>, Box<Expression>),
162 /// Method call on an object with method name and arguments
163 Method(Box<Postfix>, String, Box<Expression>),
164 /// Inference call on a reference and arguments
165 Inference(Reference, Box<Expression>),
166 /// Primary flattened AST optimization
167 Primary(Box<Primary>),
168}
169
170impl Postfix {
171 /// Check if this postfix expression represents an empty placeholder.
172 ///
173 /// # Returns
174 ///
175 /// * `bool` - True if this is an Empty postfix expression, false otherwise
176 ///
177 /// # Examples
178 ///
179 /// ```rust
180 /// use aimx::expressions::{
181 /// postfix::Postfix,
182 /// primary::Primary,
183 /// };
184 /// use aimx::Literal;
185 ///
186 /// let empty = Postfix::Empty;
187 /// assert!(empty.is_empty());
188 ///
189 /// let literal = Postfix::Primary(Box::new(Primary::Literal(Literal::Number(42.0))));
190 /// assert!(!literal.is_empty());
191 /// ```
192 pub fn is_empty(&self) -> bool {
193 match self {
194 Postfix::Empty => true,
195 _ => false
196 }
197 }
198}
199
200/// Internal representation of postfix operations during parsing.
201enum PostfixOp {
202 /// Array indexing operation
203 Index(Expression),
204 /// Method call with method name and arguments
205 Method(String, Expression),
206}
207
208/// Parse an accessor (dot notation) with optional whitespace.
209///
210/// This function parses the dot (`.`) operator used for field access and method calls,
211/// handling optional whitespace before and after the dot.
212///
213/// # Arguments
214///
215/// * `input` - A string slice containing the accessor to parse
216///
217/// # Returns
218///
219/// * `IResult<&str, &str>` - A nom result with remaining input and parsed accessor
220pub fn parse_accessor(input: &str) -> IResult<&str, &str> {
221 delimited(
222 opt(multispace0),
223 tag("."),
224 opt(multispace0)
225 ).parse(input)
226}
227
228/// Parse an array index expression [expression].
229///
230/// This function parses array indexing syntax with square brackets, handling
231/// optional whitespace around the expression.
232///
233/// # Arguments
234///
235/// * `input` - A string slice containing the index expression to parse
236///
237/// # Returns
238///
239/// * `IResult<&str, PostfixOp>` - A nom result with remaining input and parsed index operation
240fn index_postfix(input: &str) -> IResult<&str, PostfixOp> {
241 let (input, expression) = delimited(
242 char('['),
243 parse_expression,
244 char(']'),
245 ).parse(input)?;
246 Ok((input, PostfixOp::Index(expression)))
247}
248
249/// Parse a function or method argument list.
250///
251/// This function parses argument lists enclosed in parentheses, handling
252/// optional whitespace around the expression and empty argument lists.
253///
254/// # Arguments
255///
256/// * `input` - A string slice containing the argument list to parse
257///
258/// # Returns
259///
260/// * `IResult<&str, Expression>` - A nom result with remaining input and parsed expression
261fn argument_postfix(input: &str) -> IResult<&str, Expression> {
262 delimited(
263 char('('),
264 alt((
265 map(parse_argument_list,|expr| expr),
266 map(multispace0, |_| Expression::Empty),
267 )),
268 char(')'),
269 ).parse(input)
270}
271
272/// Parse a function call or the empty placeholder _.
273///
274/// This function parses function calls with their argument lists, as well as
275/// the special empty placeholder `_` which is treated as a function with
276/// the name "_".
277///
278/// # Arguments
279///
280/// * `input` - A string slice containing the function to parse
281///
282/// # Returns
283///
284/// * `IResult<&str, Postfix>` - A nom result with remaining input and parsed postfix expression
285fn parse_function(input: &str) -> IResult<&str, Postfix> {
286 let (input, name) = parse_identifier(input)?;
287 let (input, _) = multispace0(input)?;
288 if name == "_" {
289 return Ok((input, Postfix::Empty))
290 }
291 let (input, args) = argument_postfix(input)?;
292 let function = Postfix::Function(name, Box::new(args));
293 Ok((input, function))
294}
295
296/// Parse a method call with dot notation.
297///
298/// This function parses method calls using dot notation, handling the
299/// accessor, method name, and argument list.
300///
301/// # Arguments
302///
303/// * `input` - A string slice containing the method call to parse
304///
305/// # Returns
306///
307/// * `IResult<&str, PostfixOp>` - A nom result with remaining input and parsed method operation
308fn parse_method(input: &str) -> IResult<&str, PostfixOp> {
309 let (input,_) = parse_accessor(input)?;
310 let (input, name) = parse_identifier(input)?;
311 let (input, _) = multispace0(input)?;
312 let (input, args) = argument_postfix(input)?;
313 Ok((input, PostfixOp::Method(name, args)))
314}
315
316/// Parse a postfix expression from a string.
317///
318/// This is the main entry point for parsing postfix expressions, which include
319/// identifiers, function calls, array indexing, method calls, and inference calls.
320/// The parser handles left-to-right associativity and chaining of postfix operations,
321/// allowing for complex expressions like `data.filter().map().reduce()`.
322///
323/// The parser works by first attempting to parse a function call (which includes
324/// the special empty placeholder `_`). If that fails, it parses a primary expression
325/// and then checks for special cases like inference calls (references followed by
326/// parentheses). Finally, it processes any chained postfix operations like indexing
327/// or method calls.
328///
329/// # Arguments
330///
331/// * `input` - A string slice containing the postfix expression to parse
332///
333/// # Returns
334///
335/// Returns an [`IResult`] containing:
336/// - The remaining unparsed input (should be empty for successful parsing)
337/// - A [`Postfix`] enum representing the parsed abstract syntax tree
338///
339/// # Complex Parsing Logic
340///
341/// The actual parsing logic is more complex than a simple grammar rule:
342///
343/// 1. Try parsing as a function call (including special `_` case)
344/// 2. If that fails, parse as a primary expression
345/// 3. Check if the next token is `(` for potential inference calls
346/// 4. Parse chained postfix operations (indexing `[]` or method calls `.`)
347///
348/// # Examples
349///
350/// ```rust
351/// use aimx::expressions::postfix::{parse_postfix, Postfix};
352///
353/// // Parse empty placeholder
354/// let (remaining, postfix) = parse_postfix("_").unwrap();
355/// assert_eq!(remaining, "");
356/// assert!(matches!(postfix, Postfix::Empty));
357///
358/// // Parse function call
359/// let (remaining, postfix) = parse_postfix("sum(1, 2, 3)").unwrap();
360/// assert_eq!(remaining, "");
361/// assert!(matches!(postfix, Postfix::Function(_, _)));
362///
363/// // Parse array indexing
364/// let (remaining, postfix) = parse_postfix("array[0]").unwrap();
365/// assert_eq!(remaining, "");
366/// assert!(matches!(postfix, Postfix::Index(_, _)));
367///
368/// // Parse method call
369/// let (remaining, postfix) = parse_postfix("data.filter()").unwrap();
370/// assert_eq!(remaining, "");
371/// assert!(matches!(postfix, Postfix::Method(_, _, _)));
372///
373/// // Parse chained operations
374/// let (remaining, postfix) = parse_postfix("matrix[0][1]").unwrap();
375/// assert_eq!(remaining, "");
376/// assert!(matches!(postfix, Postfix::Index(_, _)));
377///
378/// // Parse complex chaining
379/// let (remaining, postfix) = parse_postfix("data.filter().map().reduce()").unwrap();
380/// assert_eq!(remaining, "");
381/// assert!(matches!(postfix, Postfix::Method(_, _, _)));
382/// ```
383///
384/// # Error Handling
385///
386/// This function returns [`IResult`] which uses Rust's Result type for error handling.
387/// Parsing errors are captured as [`nom`] error variants. The parser handles
388/// whitespace gracefully and provides detailed error information when parsing fails.
389///
390/// # See Also
391///
392/// - [`Postfix`] - The abstract syntax tree returned by this function
393/// - [`parse_accessor`] - Function for parsing dot notation accessors
394/// - [`ExpressionLike`] - Trait for evaluating parsed expressions
395/// - [`crate::aimx_parse`] - Public API function for parsing complete expressions
396pub fn parse_postfix(input: &str) -> IResult<&str, Postfix> {
397 let (input, first) =
398 if let Ok((input, function)) = parse_function(input) {
399 (input, function)
400 } else {
401 let (input, primary) = parse_primary(input)?;
402 if input.starts_with('(') {
403 let (input, args) = argument_postfix(input)?;
404 match primary {
405 Primary::Reference(reference) =>
406 return Ok((input, Postfix::Inference(reference, Box::new(args)))),
407 _ => return Err(NomErr::Failure(Error::new(input, nom::error::ErrorKind::Fail))),
408 }
409 }
410 (input, Postfix::Primary(Box::new(primary)))
411 };
412
413 let (input, rest) = many0(
414 delimited(
415 multispace0,
416 alt((index_postfix, parse_method)),
417 multispace0,
418 )
419 ).parse(input)?;
420
421 // Build the result by folding from left to right
422 let result = rest.into_iter().fold(
423 first,
424 |acc, op| match op {
425 PostfixOp::Index(expression) => Postfix::Index(Box::new(acc), Box::new(expression)),
426 PostfixOp::Method(name, args) => Postfix::Method(Box::new(acc), name, Box::new(args)),
427 },
428 );
429
430 Ok((input, result))
431}
432
433impl ExpressionLike for Postfix {
434 /// Evaluate this postfix expression within the given context.
435 ///
436 /// This method recursively evaluates the postfix expression tree, delegating
437 /// to the appropriate evaluation methods for each postfix variant. It handles
438 /// function calls, method calls, array indexing, and inference operations.
439 ///
440 /// # Arguments
441 ///
442 /// * `context` - The evaluation context providing variable values, function implementations,
443 /// and method implementations
444 ///
445 /// # Returns
446 ///
447 /// Returns a [`Result`] containing the evaluated [`Value`] if successful,
448 /// or an error if evaluation fails.
449 ///
450 /// # Evaluation Strategy
451 ///
452 /// - [`Empty`](Postfix::Empty) expressions return [`Value::Empty`]
453 /// - [`Function`](Postfix::Function) expressions delegate to [`ContextLike::function_call`]
454 /// - [`Index`](Postfix::Index) expressions perform array indexing with bounds checking
455 /// - [`Method`](Postfix::Method) expressions delegate to [`ContextLike::method_call`]
456 /// - [`Inference`](Postfix::Inference) expressions delegate to [`ContextLike::inference_call`]
457 /// - [`Primary`](Postfix::Primary) expressions delegate to [`Primary::evaluate`]
458 ///
459 /// # Examples
460 ///
461 /// ```rust
462 /// use aimx::expressions::postfix::Postfix;
463 /// use aimx::expressions::primary::Primary;
464 /// use aimx::{Context, Literal, ExpressionLike};
465 ///
466 /// // Evaluate empty placeholder
467 /// let mut context = Context::new();
468 /// let postfix = Postfix::Empty;
469 /// let result = postfix.evaluate(&mut context).unwrap();
470 /// assert_eq!(result, aimx::Value::Empty);
471 ///
472 /// // Evaluate literal expression
473 /// let primary = Primary::Literal(Literal::Number(42.0));
474 /// let postfix = Postfix::Primary(Box::new(primary));
475 /// let result = postfix.evaluate(&mut context).unwrap();
476 /// assert_eq!(result.to_string(), "42");
477 /// ```
478 ///
479 /// # Error Handling
480 ///
481 /// This method returns detailed error messages including:
482 /// - Array index out of bounds errors
483 /// - Type mismatch errors for array indexing
484 /// - Function and method call failures
485 /// - Inference call failures
486 ///
487 /// # See Also
488 ///
489 /// - [`ContextLike`] - Trait for evaluation contexts
490 /// - [`Value`] - Result type returned by evaluation
491 /// - [`Primary::evaluate`] - Evaluation of primary expressions
492 fn evaluate(&self, context: &mut dyn ContextLike) -> Result<Value> {
493 match self {
494 Postfix::Empty => {
495 Ok(Value::Empty)
496 }
497 Postfix::Function(name, expression) => {
498 // This is a function call like sum(1, 2, 3)
499 let arg = expression.evaluate(context)?;
500 context.function_call(name, arg)
501 }
502 Postfix::Index(primary, expression) => {
503 let primary_val = primary.evaluate(context)?;
504 let index_val = expression.evaluate(context)?;
505 let found = index_val.type_as_string();
506 // Handle array indexing
507 match (primary_val, index_val) {
508 (Value::Array(array), Value::Literal(Literal::Number(index))) => {
509 let index = index as usize;
510 if index < array.len() {
511 Ok(array[index].as_ref().clone())
512 } else {
513 Err(anyhow!("Index out of bounds: {} >= {}~{}",
514 index,
515 array.len(),
516 self.to_formula()))
517 }
518 }
519 (Value::Array(_), _) => {
520 Err(anyhow!("Array index must be a number ~{}", self.to_formula()))
521 }
522 _ => {
523 Err(anyhow!("Expected Array for index, found {}~{}", found, self.to_formula()))
524 }
525 }
526 }
527 Postfix::Method(primary, name, expression) => {
528 // This is a method call like array.sum()
529 let value = primary.evaluate(context)?;
530 let arg = expression.evaluate(context)?;
531 context.method_call(name, value, arg)
532 }
533 Postfix::Inference(reference, expression) => {
534 // This is an inference call like $std.extract(document, prompt)
535 let arg = expression.evaluate(context)?;
536 context.inference_call(reference, arg)
537 }
538 Postfix::Primary(primary) => primary.evaluate(context),
539 }
540 }
541
542 fn write(&self, writer: &mut Writer) {
543 match self {
544 // Check for Empty '_'
545 Postfix::Empty => {
546 writer.write_char('_');
547 }
548 Postfix::Function(identifier, expression) => {
549 writer.write_str(&identifier);
550 writer.write_char('(');
551 expression.write(writer);
552 writer.write_char(')');
553 }
554 Postfix::Index(postfix, expression) => {
555 postfix.write(writer);
556 writer.write_char('[');
557 expression.write(writer);
558 writer.write_char(']');
559 }
560 Postfix::Method(postfix, identifier, expression) => {
561 postfix.write(writer);
562 writer.write_char('.');
563 writer.write_str(&identifier);
564 writer.write_char('(');
565 expression.write(writer);
566 writer.write_char(')');
567 }
568 Postfix::Inference(reference, expression) => {
569 writer.write_char('$');
570 reference.write(writer);
571 writer.write_char('(');
572 expression.write(writer);
573 writer.write_char(')');
574 }
575 Postfix::Primary(primary) => primary.write(writer),
576 }
577 }
578 fn to_sanitized(&self) -> String {
579 let mut writer = Writer::sanitizer();
580 self.write(&mut writer);
581 writer.finish()
582 }
583 fn to_formula(&self) -> String {
584 let mut writer = Writer::formulizer();
585 self.write(&mut writer);
586 writer.finish()
587 }
588}
589
590impl fmt::Display for Postfix {
591 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
592 let mut writer = Writer::stringizer();
593 self.write(&mut writer);
594 write!(f, "{}", writer.finish())
595 }
596}