aimx/expressions/
evaluate.rs

1//! Core evaluation traits for AIMX expressions.
2//!
3//! Provides [`ExpressionLike`] for AST nodes and helpers for binary type
4//! promotion and static evaluation suitable for agentic usage.
5
6use crate::{
7    aim::{ContextLike, WorkflowStatus},
8    expressions::Reference,
9    functions::FunctionRegistry,
10    values::Value,
11};
12use anyhow::{Result, anyhow};
13use std::{
14    mem::replace,
15    sync::Arc,
16};
17
18/// Core evaluation interface for expression AST nodes.
19///
20/// Implemented by all AIMX expression types to support serialization and
21/// evaluation against a [`ContextLike`] to produce a [`Value`].
22pub trait ExpressionLike {
23    /// Evaluate this expression with the provided [`ContextLike`], returning a [`Value`] or error.
24    fn evaluate(&self, context: &mut dyn ContextLike) -> Result<Arc<Value>>;
25
26    fn to_formula(&self) -> String;
27}
28
29/// Evaluate two expressions and promote the right [`Value`] to the left's type.
30///
31/// Used by binary operators to apply AIMX type promotion rules.
32/// Returns `(left_value, promoted_right_value)` on success.
33pub fn evaluate_and_promote(
34    context: &mut dyn ContextLike,
35    left: &dyn ExpressionLike,
36    right: &dyn ExpressionLike,
37) -> Result<(Arc<Value>, Arc<Value>)> {
38    // evaluate left operand
39    let left_val = left.evaluate(context)?;
40    // evaluate right operand
41    let unknown_type = right.evaluate(context)?;
42    // promote right result to left operands' type
43    match unknown_type.as_type(left_val.clone()) {
44        Ok(right_val) => Ok((left_val, right_val)),
45        Err(err) => {
46            let message = format!("{}~{}", err, right.to_formula());
47            Err(anyhow!(message))
48        }
49    }
50    // return matching types
51}
52
53/// Evaluate an expression statically without external state.
54///
55/// Uses a shared internal [`ContextLike`] that allows only pure built-ins and
56/// closure parameters. Any access to external variables, inference, or
57/// workflow state returns `Err`.
58/// Intended for constant folding and validation by agentic callers.
59pub fn statically_evaluate(expression: &dyn ExpressionLike) -> Result<Arc<Value>> {
60    // Create a static context that only supports function calls
61    struct StaticContext {
62        stack: [(Arc<str>, Arc<Value>); 2],
63    }
64
65    impl StaticContext {
66        pub fn new() -> Self {
67            Self {
68                stack: [(Value::empty_str(), Value::empty()), (Value::empty_str(), Value::empty())],
69            }
70        }
71    }
72
73    impl ContextLike for StaticContext {
74        fn start_closure(&mut self) -> [(Arc<str>, Arc<Value>); 2] {
75            replace(
76                &mut self.stack,
77                [(Value::empty_str(), Value::empty()), (Value::empty_str(), Value::empty())],
78            )
79        }
80
81        fn set_key(&mut self, index: usize, identifier: Arc<str>) {
82            if self.stack[index].0 != identifier {
83                self.stack[index].0 = identifier.clone();
84            }
85        }
86        fn set_parameter(&mut self, index: usize, value: Arc<Value>) {
87            self.stack[index].1 = value.clone();
88        }
89
90        fn end_closure(&mut self, stack: [(Arc<str>, Arc<Value>); 2]) {
91            self.stack = stack;
92        }
93        fn get_value(&mut self, reference: &Reference) -> Result<Arc<Value>> {
94            if *reference == *self.stack[0].0 {
95                Ok(self.stack[0].1.clone())
96            } else if *reference == *self.stack[1].0 {
97                Ok(self.stack[1].1.clone())
98            } else {
99                Err(anyhow!("Non-static expression"))
100            }
101        }
102        fn set_value(&mut self, identifier: &str, value: Arc<Value>) -> Result<()> {
103            if *identifier == *self.stack[0].0 {
104                self.stack[0].1 = value;
105                Ok(())
106            } else if *identifier == *self.stack[1].0 {
107                self.stack[1].1 = value;
108                Ok(())
109            } else {
110                Err(anyhow!("Non-static expression"))
111            }
112        }
113        fn function_call(&mut self, name: Arc<str>, arg: Arc<Value>) -> Result<Arc<Value>> {
114            let registry = FunctionRegistry::read_lock()?;
115            registry.function_call(self, name, arg)
116        }
117        fn method_call(&mut self, name: Arc<str>, value: Arc<Value>, arg: Arc<Value>) -> Result<Arc<Value>> {
118            let registry = FunctionRegistry::read_lock()?;
119            registry.method_call(self, name, value, arg)
120        }
121        fn inference_call(&mut self, _reference: Arc<Reference>, _arg: Arc<Value>) -> Result<Arc<Value>> {
122            Err(anyhow!("Non-static inference"))
123        }
124        fn run_evaluation(&mut self, _start_state: Option<Arc<str>>) -> WorkflowStatus {
125            WorkflowStatus::Failed {
126                state_id: None,
127                errata: None,
128            }
129        }
130    }
131
132    // Use OnceLock for thread-safe one-time initialization
133    use std::sync::Mutex;
134    use std::sync::OnceLock;
135
136    static INSTANCE: OnceLock<Mutex<StaticContext>> = OnceLock::new();
137
138    let context = INSTANCE.get_or_init(|| Mutex::new(StaticContext::new()));
139
140    let mut context = context.lock().unwrap();
141    expression.evaluate(&mut *context)
142}