aimx/
evaluate.rs

1//! Core evaluation traits and utilities for AIMX expressions.
2//!
3//! This module defines the core traits and utilities for evaluating expressions
4//! within a context. It provides the foundation for the AIMX evaluation system,
5//! including the main [`ExpressionLike`] trait that all expression types implement,
6//! and utility functions for type promotion and static evaluation.
7//!
8//! The evaluation system follows a recursive descent pattern where each expression
9//! type knows how to evaluate itself within a given context. This design allows for
10//! flexible extension and maintains separation of concerns between parsing and evaluation.
11//!
12//! # Key Components
13//!
14//! - [`ExpressionLike`] - The core trait that all expression types implement
15//! - [`evaluate_and_promote`] - Helper function for binary operator evaluation with type promotion
16//! - [`statically_evaluate`] - Function for evaluating expressions without external context
17//!
18//! # Architecture
19//!
20//! The evaluation system is designed around the [`ExpressionLike`] trait, which provides
21//! a uniform interface for evaluating different types of expressions. Each expression
22//! type (arithmetic, logical, conditional, etc.) implements this trait to provide
23//! its specific evaluation logic.
24//!
25//! ## Type Promotion
26//!
27//! AIMX uses a sophisticated type promotion system where the left operand's type
28//! determines how the right operand is converted. The [`evaluate_and_promote`] function
29//! implements this logic for binary operators.
30//!
31//! ## Static Evaluation
32//!
33//! The [`statically_evaluate`] function allows evaluating expressions that contain
34//! only literals and built-in functions, without requiring external context. This is
35//! useful for constant folding, optimization, and testing.
36//!
37//! # Examples
38//!
39//! ## Basic Expression Evaluation
40//!
41//! ```rust
42//! use aimx::{aimx_parse, ExpressionLike, Context};
43//!
44//! let expression = aimx_parse("2 + 3");
45//! let mut context = Context::new();
46//! let result = expression.evaluate(&mut context).unwrap();
47//! assert_eq!(result.to_string(), "5");
48//! ```
49//!
50//! ## Using Static Evaluation
51//!
52//! ```rust
53//! use aimx::{aimx_parse, evaluate::statically_evaluate};
54//!
55//! let expression = aimx_parse("sqrt(16) + abs(-5)");
56//! let result = statically_evaluate(&expression).unwrap();
57//! assert_eq!(result.to_string(), "9");
58//! ```
59//!
60//! ## Implementing Custom Expression Types
61//!
62//! ```rust
63//! use aimx::{ExpressionLike, ContextLike, Value, Writer};
64//! use anyhow::Result;
65//!
66//! struct CustomExpression {
67//!     value: Value,
68//! }
69//!
70//! impl ExpressionLike for CustomExpression {
71//!     fn evaluate(&self, _context: &mut dyn ContextLike) -> Result<Value> {
72//!         Ok(self.value.clone())
73//!     }
74//!
75//!     fn write(&self, writer: &mut Writer) {
76//!         writer.print_value(&writer.prefix(), &self.value);
77//!     }
78//!
79//!     fn to_sanitized(&self) -> String {
80//!         self.value.to_sanitized()
81//!     }
82//!
83//!     fn to_formula(&self) -> String {
84//!         self.value.to_formula()
85//!     }
86//! }
87//! ```
88
89use crate::{
90    ContextLike,
91    Value,
92    Reference,
93    FunctionRegistry,
94    Writer,
95};
96use anyhow::{anyhow, Result};
97use std::mem::replace;
98
99/// Core trait for evaluating expressions and serializing them to text representations.
100///
101/// This trait defines the interface that all expression types in the AIMX grammar must
102/// implement. It provides methods for evaluation within a context and for converting
103/// expressions to various string representations.
104///
105/// The trait is designed to be implemented by all AST node types, allowing the
106/// evaluation engine to work uniformly across different expression types without
107/// needing to know their specific implementation details.
108///
109/// # Implementation Guidelines
110///
111/// When implementing this trait:
112/// - **Evaluation**: Should handle type checking, type promotion, and error handling
113/// - **Serialization**: Should produce representations that can be parsed back
114/// - **Context Usage**: Should properly interact with the context for variable lookup
115///
116/// # Examples
117///
118/// ## Basic Usage
119///
120/// ```rust
121/// use aimx::{aimx_parse, ExpressionLike, Context};
122///
123/// let expression = aimx_parse("2 + 3 * 4");
124/// let mut context = Context::new();
125/// let result = expression.evaluate(&mut context).unwrap();
126/// assert_eq!(result.to_string(), "14");
127/// ```
128///
129/// ## Custom Implementation
130///
131/// ```rust
132/// use aimx::{ExpressionLike, ContextLike, Value, Writer};
133/// use anyhow::Result;
134///
135/// struct ConstantExpression {
136///     value: Value,
137/// }
138///
139/// impl ExpressionLike for ConstantExpression {
140///     fn evaluate(&self, _context: &mut dyn ContextLike) -> Result<Value> {
141///         Ok(self.value.clone())
142///     }
143///
144///     fn write(&self, writer: &mut Writer) {
145///         writer.print_value(&writer.prefix(), &self.value);
146///     }
147///
148///     fn to_sanitized(&self) -> String {
149///         self.value.to_sanitized()
150///     }
151///
152///     fn to_formula(&self) -> String {
153///         self.value.to_formula()
154///     }
155/// }
156/// ```
157pub trait ExpressionLike {
158    /// Write this expression to the provided writer.
159    ///
160    /// This method serializes the expression to a text representation using the
161    /// writer's current formatting mode. The writer handles indentation, quoting,
162    /// and other formatting concerns.
163    ///
164    /// # Arguments
165    ///
166    /// * `writer` - The writer to serialize the expression to
167    ///
168    /// # Examples
169    ///
170    /// ```rust
171    /// use aimx::{aimx_parse, ExpressionLike, Writer, PrintMode, Prefix};
172    ///
173    /// let expression = aimx_parse("2 + 3");
174    /// let mut writer = Writer::new(PrintMode::None, Prefix::None);
175    /// expression.write(&mut writer);
176    /// let result = writer.finish();
177    /// assert_eq!(result, "2 + 3");
178    /// ```
179    fn write(&self, writer: &mut Writer);
180    
181    /// Convert this expression to a sanitized string representation.
182    ///
183    /// This method produces a string with special characters escaped to make it
184    /// safe for various contexts like JSON output, HTML embedding, or other
185    /// contexts where escaping is required.
186    ///
187    /// # Returns
188    ///
189    /// A sanitized string representation of this expression.
190    ///
191    /// # Examples
192    ///
193    /// ```rust
194    /// use aimx::{aimx_parse, ExpressionLike};
195    ///
196    /// let expression = aimx_parse(r#""hello" + "world""#);
197    /// let sanitized = expression.to_sanitized();
198    /// // Contains properly escaped characters
199    /// ```
200    fn to_sanitized(&self) -> String;
201    
202    /// Convert this expression to a formula string representation.
203    ///
204    /// This method produces a string with proper quoting and escaping for use
205    /// in formulas. The output should be round-trippable, meaning it can be
206    /// parsed back into an equivalent expression.
207    ///
208    /// # Returns
209    ///
210    /// A formula string representation of this expression.
211    ///
212    /// # Examples
213    ///
214    /// ```rust
215    /// use aimx::{aimx_parse, ExpressionLike};
216    ///
217    /// let expression = aimx_parse("2 + 3");
218    /// let formula = expression.to_formula();
219    /// assert_eq!(formula, "2 + 3");
220    /// ```
221    fn to_formula(&self) -> String;
222
223    /// Evaluate the expression within the given context.
224    ///
225    /// This is the core method that performs the actual evaluation of the expression.
226    /// It uses the provided context to resolve variables, call functions, and
227    /// perform other runtime operations.
228    ///
229    /// # Arguments
230    ///
231    /// * `context` - A mutable reference to the evaluation context that provides
232    ///               variable values, function implementations, and other runtime
233    ///               information needed for evaluation.
234    ///
235    /// # Returns
236    ///
237    /// Returns a `Result<Value>` containing the evaluated result or an error
238    /// if evaluation fails.
239    ///
240    /// # Errors
241    ///
242    /// This method can return various errors including:
243    /// - `Undefined variable` errors when referencing undefined variables
244    /// - `Type mismatch` errors when operations are performed on incompatible types
245    /// - `Function not found` errors when calling undefined functions
246    /// - `Circular reference` errors when detecting infinite recursion
247    /// - `Division by zero` errors in arithmetic operations
248    /// - Other evaluation errors as appropriate for the expression type
249    ///
250    /// # Examples
251    ///
252    /// ```rust
253    /// use aimx::{aimx_parse, ExpressionLike, Context};
254    ///
255    /// let expression = aimx_parse("x + 5");
256    /// let mut context = Context::new().with_value("x", aimx::Value::from_number(10.0));
257    /// let result = expression.evaluate(&mut context).unwrap();
258    /// assert_eq!(result.to_string(), "15");
259    /// ```
260    fn evaluate(&self, context: &mut dyn ContextLike) -> Result<Value>;
261}
262
263/// Helper function to evaluate two expressions and promote the right operand
264/// to match the type of the left operand.
265///
266/// This function implements AIMX's type promotion system for binary operators.
267/// It evaluates both operands, then promotes the right operand's type to match
268/// the left operand's type according to AIMX grammar rules.
269///
270/// # Type Promotion Rules
271///
272/// The type promotion follows these rules:
273/// - If the left operand is `Bool`, the right operand is converted to boolean
274/// - If the left operand is `Date`, the right operand is converted to date
275/// - If the left operand is `Number`, the right operand is converted to number
276/// - If the left operand is `Task`, the right operand is converted to task
277/// - If the left operand is `Text`, the right operand is converted to text
278///
279/// # Usage
280///
281/// This function is primarily used internally by binary operator implementations
282/// (arithmetic, logical, relational, etc.) to ensure type compatibility.
283///
284/// # Arguments
285///
286/// * `context` - The evaluation context used for evaluating both operands
287/// * `left` - The left operand expression
288/// * `right` - The right operand expression
289///
290/// # Returns
291///
292/// Returns a `Result` containing a tuple of `(Value, Value)` where:
293/// - The first value is the evaluated left operand
294/// - The second value is the evaluated right operand, promoted to match the left operand's type
295///
296/// # Errors
297///
298/// This function can return errors if:
299/// - Either operand fails to evaluate
300/// - The right operand cannot be promoted to match the left operand's type
301///
302/// # Examples
303///
304/// ```rust
305/// use aimx::{aimx_parse, evaluate::evaluate_and_promote, Context};
306///
307/// let mut context = Context::new();
308/// let left_expr = aimx_parse("5");
309/// let right_expr = aimx_parse("\"3\"");
310/// 
311/// let (left_val, right_val) = evaluate_and_promote(
312///     &mut context,
313///     &left_expr,
314///     &right_expr
315/// ).unwrap();
316/// 
317/// // The string "3" is promoted to number 3.0
318/// assert!(left_val.is_number());
319/// assert!(right_val.is_number());
320/// assert_eq!(right_val.to_number().unwrap(), 3.0);
321/// ```
322pub fn evaluate_and_promote(
323    context: &mut dyn ContextLike,
324    left: &dyn ExpressionLike,
325    right: &dyn ExpressionLike,
326) -> Result<(Value, Value)> {
327    // evaluate left operand
328    let left_val = left.evaluate(context)?;
329    // evaluate right operand
330    let unknown_type = right.evaluate(context)?;
331    // promote right result to left operands' type
332    match unknown_type.as_type(&left_val) {
333        Ok(right_val) => Ok((left_val, right_val)),
334        Err(err) => {
335            let message = format!("{}~{}", err, right.to_formula());
336            Err(anyhow!(message))
337        }
338    }
339    // return matching types
340    
341}
342
343/// Evaluate an expression statically without external context.
344///
345/// This function evaluates expressions that contain only literals, operators,
346/// and function calls. It cannot evaluate expressions that reference variables
347/// or perform assignments. The function uses a thread-safe singleton context
348/// with access to all the built-in functions.
349///
350/// # Purpose
351///
352/// Static evaluation is useful for:
353/// - Constant folding and optimization
354/// - Evaluating pure mathematical expressions
355/// - Testing and debugging expression parsing
356/// - Pre-computing values that don't depend on external state
357///
358/// # Supported Expressions
359///
360/// **Allowed:**
361/// - All literal values: `42`, `true`, `"hello"`, `_`
362/// - Arithmetic operations: `1 + 2 * 3`, `(4 - 1) * 5`
363/// - Logical operations: `true & false | true`
364/// - Relational operations: `5 > 3`, `2 == 1 + 1`
365/// - Conditional expressions: `true ? 1 : 2`
366/// - Type casting: `(Number) "123"`, `(Text) 42`
367/// - Unary operations: `-5`, `!true`, `+10`
368/// - Arrays: `(1, 2, 3)`, `(1, "hello", true)`
369/// - Function calls: `abs(-5)`, `max((1, 5, 3))`, `length("hello")`
370/// - Method calls: `"hello".length()`, `3.14.round()`
371/// - Complex nested expressions: `abs(-5) + max((1, 2)) * length("test")`
372///
373/// **Not Allowed:**
374/// - Variable references: `x + 5`, `my_var`
375/// - Assignment operations: `x = 5`
376/// - Any expression requiring external context
377///
378/// # Thread Safety
379///
380/// This function uses a thread-safe singleton pattern with `OnceLock<Mutex<>>`
381/// to ensure that the function registry is initialized only once and can be
382/// safely accessed from multiple threads simultaneously.
383///
384/// # Examples
385///
386/// ```rust
387/// use aimx::{ExpressionLike, evaluate::statically_evaluate};
388/// use aimx::expression::parse_expression;
389///
390/// // Basic arithmetic
391/// let (_, expression) = parse_expression("1 + 2 * 3").unwrap();
392/// let result = statically_evaluate(&expression).unwrap();
393/// // Result: 7
394///
395/// // Function calls
396/// let (_, expression) = parse_expression("abs(-5) + max((1, 2))").unwrap();
397/// let result = statically_evaluate(&expression).unwrap();
398/// // Result: 7 (5 + 2)
399///
400/// // Complex expressions
401/// let (_, expression) = parse_expression("5 > 3 ? \"yes\" : \"no\"").unwrap();
402/// let result = statically_evaluate(&expression).unwrap();
403/// // Result: "yes"
404///
405/// // Array operations
406/// let (_, expression) = parse_expression("max((1, 5, 3))").unwrap();
407/// let result = statically_evaluate(&expression).unwrap();
408/// // Result: 5
409/// ```
410///
411/// # Errors
412///
413/// This function will return an error for:
414/// - Expressions containing variable references
415/// - Assignment operations
416/// - Invalid syntax or parsing errors
417/// - Runtime errors (division by zero, type mismatches, etc.)
418/// - Function calls to undefined functions
419///
420/// ```rust
421/// use aimx::{ExpressionLike, evaluate::statically_evaluate};
422/// use aimx::expression::parse_expression;
423///
424/// // This will fail because it references a variable
425/// let (_, expression) = parse_expression("x + 5").unwrap();
426/// let result = statically_evaluate(&expression);
427/// assert!(result.is_err()); // Error: "Non-static expression"
428///
429/// // This will fail due to division by zero
430/// let (_, expression) = parse_expression("10 / 0").unwrap();
431/// let result = statically_evaluate(&expression);
432/// assert!(result.is_err()); // Error: Division by zero
433/// ```
434///
435/// # Performance
436///
437/// The singleton pattern ensures that function registration happens only once,
438/// making subsequent calls very efficient. The function registry is shared
439/// across all threads, minimizing memory usage.
440///
441/// # Arguments
442///
443/// * `expression` - The evaluatable expression to statically evaluate
444///
445/// # Returns
446///
447/// Returns a `Result<Value>` containing the evaluated result or an error
448/// if the expression cannot be evaluated statically.
449pub fn statically_evaluate(expression: &dyn ExpressionLike) -> Result<Value> {
450    // Create a static context that only supports function calls
451    struct StaticContext {
452        stack: [(String, Value); 2],
453    }
454
455    impl StaticContext {
456        pub fn new() -> Self {
457            Self {
458                stack: [
459                    (String::new(), Value::Empty),
460                    (String::new(), Value::Empty)
461                ],
462            }
463        }
464    }
465    
466    impl ContextLike for StaticContext {
467        fn start_closure(&mut self) -> [(String, Value); 2] {
468            replace(&mut self.stack, [
469                    (String::new(), Value::Empty),
470                    (String::new(), Value::Empty)
471                ])
472        }
473
474        fn set_key(&mut self, index: usize, identifier: &str) {
475            if self.stack[index].0 != *identifier {
476                self.stack[index].0 = identifier.to_string();
477            }
478        }
479        fn set_value(&mut self, index: usize, value: &Value) {
480            self.stack[index].1 = value.clone();
481        }
482
483        fn end_closure(&mut self, stack: [(String, Value); 2]) {
484            self.stack = stack;
485        }
486        fn get_referenced(&mut self, reference: &Reference) -> Result<Value> {
487            if *reference == self.stack[0].0 {
488                Ok(self.stack[0].1.clone())
489            } else if *reference == self.stack[1].0 {
490                Ok(self.stack[1].1.clone())
491            } else {
492                Err(anyhow!("Non-static expression"))
493            }
494        }
495        fn set_referenced(&mut self, reference: &Reference, value: Value) -> Result<()> {
496            if *reference == self.stack[0].0 {
497                self.stack[0].1 = value;
498                Ok(())
499            } else if *reference == self.stack[1].0 {
500                self.stack[1].1 = value;
501                Ok(())
502            } else {
503                Err(anyhow!("Non-static expression"))
504            }
505        }
506        fn function_call(&mut self, name: &str, arg: Value) -> Result<Value> {
507            let registry = FunctionRegistry::read_lock()?;
508            registry.function_call(self, name, arg)
509        }
510        fn method_call(&mut self, name: &str, value: Value, arg: Value) -> Result<Value> {
511            let registry = FunctionRegistry::read_lock()?;
512            registry.method_call(self, name, value, arg)
513        }
514        fn inference_call(&mut self, _reference: &Reference, _arg: Value) -> Result<Value> {
515            Err(anyhow!("Non-static inference"))
516        }
517    }
518    
519    // Use OnceLock for thread-safe one-time initialization
520    use std::sync::OnceLock;
521    use std::sync::Mutex;
522    
523    static INSTANCE: OnceLock<Mutex<StaticContext>> = OnceLock::new();
524    
525    let context = INSTANCE.get_or_init(|| {
526        Mutex::new(StaticContext::new())
527    });
528    
529    let mut context = context.lock().unwrap();
530    expression.evaluate(&mut *context)
531}