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}