exp_rs/
context.rs

1extern crate alloc;
2
3#[cfg(test)]
4use crate::Real;
5#[cfg(not(test))]
6use crate::{Real, String, ToString, Vec};
7#[cfg(not(test))]
8use alloc::rc::Rc;
9// Import heapless types and helper traits
10use crate::types::{TryIntoFunctionName, TryIntoHeaplessString};
11#[cfg(test)]
12use std::rc::Rc;
13#[cfg(test)]
14use std::string::{String, ToString};
15#[cfg(test)]
16use std::vec::Vec;
17
18/// Registry for different types of functions available in an evaluation context.
19///
20/// This struct holds three types of functions:
21/// 1. Native functions: Rust functions that can be called from expressions
22/// 2. Expression functions: Functions defined using expression strings
23/// 3. User functions: Functions defined by the user with custom behavior
24///
25/// This is an internal implementation detail and users typically don't interact with it directly.
26#[allow(dead_code)]
27#[derive(Default, Clone)]
28pub struct FunctionRegistry {
29    /// Native functions implemented in Rust code
30    pub native_functions: crate::types::NativeFunctionMap,
31    /// Functions defined using expression strings
32    pub expression_functions: crate::types::ExpressionFunctionMap,
33}
34
35/// Evaluation context for expressions.
36///
37/// This is the main configuration object that holds variables, constants, arrays, functions,
38/// and other settings for evaluating expressions. You typically create an `EvalContext`,
39/// add your variables and functions, and then pass it to the `interp` function.
40///
41/// # Examples
42///
43/// ```
44/// use exp_rs::context::EvalContext;
45/// use exp_rs::engine::interp;
46/// use exp_rs::Real;
47/// use std::rc::Rc;
48///
49/// let mut ctx = EvalContext::new();
50///
51/// // Add variables
52/// ctx.set_parameter("x", 5.0 as Real);
53/// ctx.set_parameter("y", 10.0 as Real);
54///
55/// // Add a constant
56/// ctx.constants.insert("PI_SQUARED".try_into().unwrap(), 9.8696 as Real).unwrap();
57///
58/// // Register a custom function
59/// ctx.register_native_function("multiply", 2, |args| args[0] * args[1]);
60///
61/// // Evaluate expressions using this context
62/// let result = interp("x + y * PI_SQUARED", Some(Rc::new(ctx.clone()))).unwrap();
63/// let result2 = interp("multiply(x, y)", Some(Rc::new(ctx))).unwrap();
64/// ```
65///
66/// Contexts can be nested to create scopes:
67///
68/// ```
69/// use exp_rs::context::EvalContext;
70/// use exp_rs::Real;
71/// use std::rc::Rc;
72///
73/// let mut parent = EvalContext::new();
74/// parent.set_parameter("x", 1.0 as Real);
75///
76/// let mut child = EvalContext::new();
77/// child.set_parameter("y", 2.0 as Real);
78/// child.parent = Some(Rc::new(parent));
79///
80/// // The child context can access both its own variables and the parent's
81/// ```
82pub struct EvalContext {
83    /// Variables that can be modified during evaluation
84    pub variables: crate::types::VariableMap,
85    /// Constants that cannot be modified during evaluation
86    pub constants: crate::types::ConstantMap,
87    /// Arrays of values that can be accessed using array[index] syntax
88    pub arrays: crate::types::ArrayMap,
89    /// Object attributes that can be accessed using object.attribute syntax
90    pub attributes: crate::types::AttributeMap,
91    /// Multi-dimensional arrays (not yet fully supported)
92    pub nested_arrays: crate::types::NestedArrayMap,
93    /// Registry of functions available in this context
94    pub native_functions: Rc<crate::types::NativeFunctionMap>,
95    /// Optional parent context for variable/function inheritance
96    pub parent: Option<Rc<EvalContext>>,
97}
98
99impl EvalContext {
100    /// Creates a new empty evaluation context.
101    ///
102    /// The context starts with no variables, constants, arrays, or functions.
103    /// You can add these elements using the appropriate methods and fields.
104    ///
105    /// # Examples
106    ///
107    /// ```
108    /// use exp_rs::context::EvalContext;
109    ///
110    /// let ctx = EvalContext::new();
111    /// // Now add variables, constants, functions, etc.
112    /// ```
113    pub fn new() -> Self {
114        let mut ctx = Self {
115            variables: crate::types::VariableMap::new(),
116            constants: crate::types::ConstantMap::new(),
117            arrays: crate::types::ArrayMap::new(),
118            attributes: crate::types::AttributeMap::new(),
119            nested_arrays: crate::types::NestedArrayMap::new(),
120            native_functions: Rc::new(crate::types::NativeFunctionMap::new()),
121            parent: None,
122        };
123
124        // Always register default math functions
125        // The actual functions registered depend on feature flags
126        ctx.register_default_math_functions();
127
128        ctx
129    }
130
131    /// Creates a new context with default math functions registered.
132    ///
133    /// This is a convenience method for creating a context with all standard
134    /// math functions already registered. It's equivalent to calling `new()`
135    /// since default functions are now always registered.
136    ///
137    /// Kept for backward compatibility.
138    pub fn with_default_functions() -> Self {
139        // Simply call new() as it now always registers default functions
140        Self::new()
141    }
142
143    /// Creates an evaluation context without any pre-registered functions.
144    ///
145    /// This creates a context with no built-in functions or constants.
146    /// Note that basic operators (+, -, *, /, %, <, >, <=, >=, ==, !=) are still
147    /// available as they are handled by the parser, not the function registry.
148    ///
149    /// # Examples
150    ///
151    /// ```
152    /// use exp_rs::context::EvalContext;
153    ///
154    /// let mut ctx = EvalContext::empty();
155    /// // Basic operators still work
156    /// // But functions like sin, cos, abs, etc. must be registered manually
157    /// ctx.register_native_function("abs", 1, |args| args[0].abs());
158    /// ctx.register_native_function("sin", 1, |args| args[0].sin());
159    /// ```
160    pub fn empty() -> Self {
161        Self {
162            variables: crate::types::VariableMap::new(),
163            constants: crate::types::ConstantMap::new(),
164            arrays: crate::types::ArrayMap::new(),
165            attributes: crate::types::AttributeMap::new(),
166            nested_arrays: crate::types::NestedArrayMap::new(),
167            native_functions: Rc::new(crate::types::NativeFunctionMap::new()),
168            parent: None,
169        }
170    }
171
172    /// Sets a parameter (variable) in the context.
173    ///
174    /// This method adds or updates a variable in the context. Variables can be used
175    /// in expressions and their values can be changed between evaluations.
176    ///
177    /// # Parameters
178    ///
179    /// * `name`: The name of the variable
180    /// * `value`: The value to assign to the variable
181    ///
182    /// # Returns
183    ///
184    /// The previous value of the variable, if it existed
185    ///
186    /// # Examples
187    ///
188    /// ```
189    /// use exp_rs::context::EvalContext;
190    /// use exp_rs::engine::interp;
191    /// use exp_rs::Real;
192    /// use std::rc::Rc;
193    ///
194    /// let mut ctx = EvalContext::new();
195    /// ctx.set_parameter("x", 42.0 as Real);
196    ///
197    /// let result = interp("x * 2", Some(Rc::new(ctx))).unwrap();
198    /// assert_eq!(result, 84.0);
199    /// ```
200    pub fn set_parameter(
201        &mut self,
202        name: &str,
203        value: Real,
204    ) -> Result<Option<Real>, crate::error::ExprError> {
205        let key = name.try_into_heapless()?;
206        match self.variables.insert(key, value) {
207            Ok(old_value) => Ok(old_value),
208            Err(_) => Err(crate::error::ExprError::CapacityExceeded("variables")),
209        }
210    }
211
212    /// Registers a native function in the context.
213    ///
214    /// Native functions are implemented in Rust and can be called from expressions.
215    /// They take a slice of Real values as arguments and return a Real value.
216    ///
217    /// # Parameters
218    ///
219    /// * `name`: The name of the function as it will be used in expressions
220    /// * `arity`: The number of arguments the function expects
221    /// * `implementation`: A closure or function that implements the function logic
222    ///
223    /// # Examples
224    ///
225    /// ```
226    /// use exp_rs::context::EvalContext;
227    /// use exp_rs::engine::interp;
228    /// use exp_rs::Real;
229    /// use std::rc::Rc;
230    ///
231    /// let mut ctx = EvalContext::new();
232    ///
233    /// // Register a function that adds all its arguments
234    /// ctx.register_native_function("sum", 3, |args| {
235    ///     args.iter().sum::<Real>()
236    /// });
237    ///
238    /// let result = interp("sum(10, 20, 30)", Some(Rc::new(ctx))).unwrap();
239    /// assert_eq!(result, 60.0);
240    /// ```
241    ///
242    /// Functions with variable argument counts:
243    ///
244    /// ```
245    /// use exp_rs::context::EvalContext;
246    /// use exp_rs::engine::interp;
247    /// use exp_rs::Real;
248    /// use std::rc::Rc;
249    ///
250    /// let mut ctx = EvalContext::new();
251    ///
252    /// // Register a function that calculates the mean of its arguments
253    /// ctx.register_native_function("mean", 5, |args| {
254    ///     args.iter().sum::<Real>() / args.len() as Real
255    /// });
256    ///
257    /// let result = interp("mean(1, 2, 3, 4, 5)", Some(Rc::new(ctx))).unwrap();
258    /// assert_eq!(result, 3.0);
259    /// ```
260    pub fn register_native_function<F>(
261        &mut self,
262        name: &str,
263        arity: usize,
264        implementation: F,
265    ) -> Result<(), crate::error::ExprError>
266    where
267        F: Fn(&[Real]) -> Real + 'static,
268    {
269        let key = name.try_into_function_name()?;
270        let function = crate::types::NativeFunction {
271            arity,
272            implementation: Rc::new(implementation),
273            name: key.clone(),
274            description: None,
275        };
276
277        match Rc::make_mut(&mut self.native_functions).insert(key, function) {
278            Ok(_) => Ok(()),
279            Err(_) => Err(crate::error::ExprError::CapacityExceeded(
280                "native_functions",
281            )),
282        }
283    }
284
285    /// Enables AST caching for this context to improve performance.
286    ///
287    /// When enabled, repeated calls to `interp` with the same expression string
288    /// will reuse the parsed AST, greatly improving performance for repeated evaluations
289    /// with different variable values.
290    ///
291    /// This is particularly useful in loops or when evaluating the same expression
292    /// multiple times with different parameter values.
293    ///
294    /// # Note
295    ///
296    /// AST caching has been removed in the arena-based implementation.
297    /// The arena architecture provides better performance characteristics
298    /// without the need for explicit caching.
299    ///
300    /// Disables AST caching and clears the cache.
301    ///
302    /// This is useful if you want to free up memory or if you want to force
303    /// re-parsing of expressions.
304    ///
305    /// # Note
306    ///
307    /// AST caching has been removed in the arena-based implementation.
308    /// This functionality is no longer available.
309    ///
310    /// Clear the AST cache if enabled.
311    ///
312    /// Registers all built-in math functions as native functions in the context.
313    ///
314    /// # Usage
315    ///
316    /// ```
317    /// # use exp_rs::EvalContext;
318    /// let mut ctx = EvalContext::new();
319    /// ctx.register_default_math_functions();
320    /// ```
321    ///
322    /// After calling this, you can override any built-in by registering your own native function
323    /// with the same name using [`register_native_function`](Self::register_native_function).
324    ///
325    /// # Feature: `libm`
326    ///
327    /// If the `libm` feature is enabled, this will use the libm implementations.
328    /// Otherwise, it will use the standard library implementation which is not available
329    /// in `no_std` environments.
330    ///
331    /// Enables default math functions for this context.
332    ///
333    /// Alias for `register_default_math_functions()`.
334    pub fn enable_default_functions(&mut self) {
335        self.register_default_math_functions();
336    }
337
338    /// Registers all built-in math functions as native functions in the context.
339    pub fn register_default_math_functions(&mut self) {
340        // Basic operators as functions (always available)
341        let _ = self.register_native_function("+", 2, |args| args[0] + args[1]);
342        let _ = self.register_native_function("-", 2, |args| args[0] - args[1]);
343        let _ = self.register_native_function("*", 2, |args| args[0] * args[1]);
344        let _ = self.register_native_function("/", 2, |args| args[0] / args[1]);
345        let _ = self.register_native_function("%", 2, |args| args[0] % args[1]);
346
347        // Comparison operators (always available)
348        let _ =
349            self.register_native_function("<", 2, |args| if args[0] < args[1] { 1.0 } else { 0.0 });
350        let _ =
351            self.register_native_function(">", 2, |args| if args[0] > args[1] { 1.0 } else { 0.0 });
352        let _ = self.register_native_function(
353            "<=",
354            2,
355            |args| if args[0] <= args[1] { 1.0 } else { 0.0 },
356        );
357        let _ = self.register_native_function(
358            ">=",
359            2,
360            |args| if args[0] >= args[1] { 1.0 } else { 0.0 },
361        );
362        let _ = self.register_native_function(
363            "==",
364            2,
365            |args| if args[0] == args[1] { 1.0 } else { 0.0 },
366        );
367        let _ = self.register_native_function(
368            "!=",
369            2,
370            |args| if args[0] != args[1] { 1.0 } else { 0.0 },
371        );
372
373        // Logical operators (always available)
374        let _ = self.register_native_function("&&", 2, |args| {
375            if args[0] != 0.0 && args[1] != 0.0 {
376                1.0
377            } else {
378                0.0
379            }
380        });
381        let _ = self.register_native_function("||", 2, |args| {
382            if args[0] != 0.0 || args[1] != 0.0 {
383                1.0
384            } else {
385                0.0
386            }
387        });
388
389        // Function aliases for the operators (always available)
390        let _ = self.register_native_function("add", 2, |args| args[0] + args[1]);
391        let _ = self.register_native_function("sub", 2, |args| args[0] - args[1]);
392        let _ = self.register_native_function("mul", 2, |args| args[0] * args[1]);
393        let _ = self.register_native_function("div", 2, |args| args[0] / args[1]);
394        let _ = self.register_native_function("fmod", 2, |args| args[0] % args[1]);
395        let _ = self.register_native_function("neg", 1, |args| -args[0]);
396
397        // Sequence operators (always available)
398        let _ = self.register_native_function(",", 2, |args| args[1]); // The actual comma operator
399        let _ = self.register_native_function("comma", 2, |args| args[1]); // Function alias for the comma operator
400
401        // Core math functions that don't require libm (always available)
402        let _ = self.register_native_function("abs", 1, |args| args[0].abs());
403        let _ = self.register_native_function("max", 2, |args| args[0].max(args[1]));
404        let _ = self.register_native_function("min", 2, |args| args[0].min(args[1]));
405        let _ = self.register_native_function("sign", 1, |args| {
406            if args[0] > 0.0 {
407                1.0
408            } else if args[0] < 0.0 {
409                -1.0
410            } else {
411                0.0
412            }
413        });
414
415        // Math constants (always available)
416        #[cfg(feature = "f32")]
417        let _ = self.register_native_function("e", 0, |_| core::f32::consts::E);
418        #[cfg(not(feature = "f32"))]
419        let _ = self.register_native_function("e", 0, |_| core::f64::consts::E);
420
421        #[cfg(feature = "f32")]
422        let _ = self.register_native_function("pi", 0, |_| core::f32::consts::PI);
423        #[cfg(not(feature = "f32"))]
424        let _ = self.register_native_function("pi", 0, |_| core::f64::consts::PI);
425
426        // Advanced math functions with libm
427        #[cfg(feature = "libm")]
428        {
429            let _ = self
430                .register_native_function("acos", 1, |args| crate::functions::acos(args[0], 0.0));
431            let _ = self
432                .register_native_function("asin", 1, |args| crate::functions::asin(args[0], 0.0));
433            let _ = self
434                .register_native_function("atan", 1, |args| crate::functions::atan(args[0], 0.0));
435            let _ = self.register_native_function("atan2", 2, |args| {
436                crate::functions::atan2(args[0], args[1])
437            });
438            let _ = self
439                .register_native_function("ceil", 1, |args| crate::functions::ceil(args[0], 0.0));
440            let _ =
441                self.register_native_function("cos", 1, |args| crate::functions::cos(args[0], 0.0));
442            let _ = self
443                .register_native_function("cosh", 1, |args| crate::functions::cosh(args[0], 0.0));
444            let _ =
445                self.register_native_function("exp", 1, |args| crate::functions::exp(args[0], 0.0));
446            let _ = self
447                .register_native_function("floor", 1, |args| crate::functions::floor(args[0], 0.0));
448            let _ = self
449                .register_native_function("round", 1, |args| crate::functions::round(args[0], 0.0));
450            let _ =
451                self.register_native_function("ln", 1, |args| crate::functions::ln(args[0], 0.0));
452            let _ =
453                self.register_native_function("log", 1, |args| crate::functions::log(args[0], 0.0));
454            let _ = self
455                .register_native_function("log10", 1, |args| crate::functions::log10(args[0], 0.0));
456            let _ = self
457                .register_native_function("pow", 2, |args| crate::functions::pow(args[0], args[1]));
458            let _ = self
459                .register_native_function("^", 2, |args| crate::functions::pow(args[0], args[1]));
460            let _ =
461                self.register_native_function("sin", 1, |args| crate::functions::sin(args[0], 0.0));
462            let _ = self
463                .register_native_function("sinh", 1, |args| crate::functions::sinh(args[0], 0.0));
464            let _ = self
465                .register_native_function("sqrt", 1, |args| crate::functions::sqrt(args[0], 0.0));
466            let _ =
467                self.register_native_function("tan", 1, |args| crate::functions::tan(args[0], 0.0));
468            let _ = self
469                .register_native_function("tanh", 1, |args| crate::functions::tanh(args[0], 0.0));
470        }
471
472        // In test mode without libm, provide std library implementations
473        #[cfg(all(not(feature = "libm"), test))]
474        {
475            let _ = self.register_native_function("acos", 1, |args| args[0].acos());
476            let _ = self.register_native_function("asin", 1, |args| args[0].asin());
477            let _ = self.register_native_function("atan", 1, |args| args[0].atan());
478            let _ = self.register_native_function("atan2", 2, |args| args[0].atan2(args[1]));
479            let _ = self.register_native_function("ceil", 1, |args| args[0].ceil());
480            let _ = self.register_native_function("cos", 1, |args| args[0].cos());
481            let _ = self.register_native_function("cosh", 1, |args| args[0].cosh());
482            let _ = self.register_native_function("exp", 1, |args| args[0].exp());
483            let _ = self.register_native_function("floor", 1, |args| args[0].floor());
484            let _ = self.register_native_function("round", 1, |args| args[0].round());
485            let _ = self.register_native_function("ln", 1, |args| args[0].ln());
486            let _ = self.register_native_function("log", 1, |args| args[0].log10());
487            let _ = self.register_native_function("log10", 1, |args| args[0].log10());
488            let _ = self.register_native_function("pow", 2, |args| args[0].powf(args[1]));
489            let _ = self.register_native_function("^", 2, |args| args[0].powf(args[1]));
490            let _ = self.register_native_function("sin", 1, |args| args[0].sin());
491            let _ = self.register_native_function("sinh", 1, |args| args[0].sinh());
492            let _ = self.register_native_function("sqrt", 1, |args| args[0].sqrt());
493            let _ = self.register_native_function("tan", 1, |args| args[0].tan());
494            let _ = self.register_native_function("tanh", 1, |args| args[0].tanh());
495        }
496
497        // In non-test no_std mode without libm, we don't register advanced math functions
498        // Users must register their own implementations if needed
499    }
500
501    /// Register a native function with the context.
502    ///
503    /// # Overriding Built-ins
504    ///
505    /// If a function with the same name as a built-in is registered, the user-defined function
506    /// will take precedence over the built-in. This allows users to override any built-in math
507    /// function at runtime.
508    ///
509    /// # Disabling Built-ins
510    ///
511    /// If the `libm` feature is not enabled, built-in math functions are not available,
512    /// and users must register their own implementations for all required functions.
513    ///
514    /// # Example
515    ///
516    /// ```
517    /// # use exp_rs::EvalContext;
518    /// let mut ctx = EvalContext::new();
519    /// // Override the "sin" function
520    /// ctx.register_native_function("sin", 1, |args| 42.0);
521    /// ```
522
523    pub fn get_variable(&self, name: &str) -> Option<Real> {
524        if let Ok(key) = name.try_into_heapless() {
525            if let Some(val) = self.variables.get(&key) {
526                return Some(*val);
527            }
528        }
529
530        if let Some(parent) = &self.parent {
531            parent.get_variable(name)
532        } else {
533            None
534        }
535    }
536
537    pub fn get_constant(&self, name: &str) -> Option<Real> {
538        if let Ok(key) = name.try_into_heapless() {
539            if let Some(val) = self.constants.get(&key) {
540                return Some(*val);
541            }
542        }
543
544        if let Some(parent) = &self.parent {
545            parent.get_constant(name)
546        } else {
547            None
548        }
549    }
550
551    pub fn get_array(&self, name: &str) -> Option<&alloc::vec::Vec<crate::Real>> {
552        if let Ok(key) = name.try_into_heapless() {
553            if let Some(arr) = self.arrays.get(&key) {
554                return Some(arr);
555            }
556        }
557
558        if let Some(parent) = &self.parent {
559            parent.get_array(name)
560        } else {
561            None
562        }
563    }
564
565    /// Helper method to set an attribute value on an object
566    pub fn set_attribute(
567        &mut self,
568        object_name: &str,
569        attr_name: &str,
570        value: Real,
571    ) -> Result<Option<Real>, crate::error::ExprError> {
572        let obj_key = object_name.try_into_heapless()?;
573        let attr_key = attr_name.try_into_heapless()?;
574
575        // Get or create the object's attribute map
576        if !self.attributes.contains_key(&obj_key) {
577            let attr_map = heapless::FnvIndexMap::<
578                crate::types::HString,
579                Real,
580                { crate::types::EXP_RS_MAX_ATTR_KEYS },
581            >::new();
582            self.attributes
583                .insert(obj_key.clone(), attr_map)
584                .map_err(|_| crate::error::ExprError::CapacityExceeded("attributes"))?;
585        }
586
587        // Get mutable reference to the attribute map and insert the value
588        if let Some(attr_map) = self.attributes.get_mut(&obj_key) {
589            attr_map
590                .insert(attr_key, value)
591                .map_err(|_| crate::error::ExprError::CapacityExceeded("object attributes"))
592        } else {
593            unreachable!("Just inserted the object")
594        }
595    }
596
597    pub fn get_attribute_map(
598        &self,
599        base: &str,
600    ) -> Option<
601        &heapless::FnvIndexMap<crate::types::HString, Real, { crate::types::EXP_RS_MAX_ATTR_KEYS }>,
602    > {
603        if let Ok(key) = base.try_into_heapless() {
604            if let Some(attr_map) = self.attributes.get(&key) {
605                return Some(attr_map);
606            }
607        }
608
609        if let Some(parent) = &self.parent {
610            parent.get_attribute_map(base)
611        } else {
612            None
613        }
614    }
615
616    pub fn get_native_function(&self, name: &str) -> Option<&crate::types::NativeFunction> {
617        if let Ok(key) = name.try_into_function_name() {
618            if let Some(f) = self.native_functions.get(&key) {
619                return Some(f);
620            }
621        }
622
623        if let Some(parent) = &self.parent {
624            parent.get_native_function(name)
625        } else {
626            None
627        }
628    }
629
630    /// Get a list of all native function names in this context (including parent contexts)
631    pub fn list_native_functions(&self) -> Vec<String> {
632        let mut functions = Vec::new();
633        let mut seen = alloc::collections::BTreeSet::new();
634
635        // Collect from current context
636        for (name, _) in self.native_functions.iter() {
637            let name_str = name.to_string();
638            if seen.insert(name_str.clone()) {
639                functions.push(name_str);
640            }
641        }
642
643        // Collect from parent contexts
644        if let Some(parent) = &self.parent {
645            for name in parent.list_native_functions() {
646                if seen.insert(name.clone()) {
647                    functions.push(name);
648                }
649            }
650        }
651
652        functions.sort();
653        functions
654    }
655
656    /// Get a list of all expression function names in this context (including parent contexts)
657    pub fn list_expression_functions(&self) -> Vec<String> {
658        let mut functions = Vec::new();
659        let mut seen = alloc::collections::BTreeSet::new();
660
661        // Collect from current context
662        // Expression functions no longer exist in context
663
664        // Collect from parent contexts
665        if let Some(parent) = &self.parent {
666            for name in parent.list_expression_functions() {
667                if seen.insert(name.clone()) {
668                    functions.push(name);
669                }
670            }
671        }
672
673        functions.sort();
674        functions
675    }
676}
677
678impl Clone for EvalContext {
679    fn clone(&self) -> Self {
680        Self {
681            variables: self.variables.clone(),
682            constants: self.constants.clone(),
683            arrays: self.arrays.clone(),
684            attributes: self.attributes.clone(),
685            nested_arrays: self.nested_arrays.clone(),
686            native_functions: self.native_functions.clone(),
687            parent: self.parent.clone(),
688        }
689    }
690}
691
692impl Default for EvalContext {
693    /// Creates a new EvalContext with default values and math functions registered.
694    /// This ensures that EvalContext::default() behaves the same as
695    fn default() -> Self {
696        EvalContext::new()
697        // let mut ctx = Self {
698        //     variables: HashMap::new(),
699        //     constants: HashMap::new(),
700        //     arrays: HashMap::new(),
701        //     attributes: HashMap::new(),
702        //     nested_arrays: HashMap::new(),
703        //     function_registry: Rc::new(FunctionRegistry::default()),
704        //     parent: None,
705        //     ast_cache: None,
706        // };
707        //
708        // // Register default math functions, same as in new()
709        // ctx.register_default_math_functions();
710        //
711        // ctx
712    }
713}
714
715// Helper trait removed - heapless containers support Clone directly
716
717#[cfg(test)]
718mod tests {
719    use super::*;
720    use crate::engine;
721    use crate::types::AstExpr;
722    use crate::types::TryIntoHeaplessString;
723    use std::rc::Rc;
724
725    #[test]
726    fn test_get_variable_parent_chain() {
727        // Create parent context with some variables
728        let mut parent_ctx = EvalContext::new();
729        let _ = parent_ctx.set_parameter("parent_only", 1.0);
730        let _ = parent_ctx.set_parameter("shadowed", 2.0);
731
732        // Create child context with its own variables
733        let mut child_ctx = EvalContext::new();
734        let _ = child_ctx.set_parameter("child_only", 3.0);
735        let _ = child_ctx.set_parameter("shadowed", 4.0); // Shadows parent's value
736        child_ctx.parent = Some(Rc::new(parent_ctx));
737
738        // Test variable only in parent
739        assert_eq!(child_ctx.get_variable("parent_only"), Some(1.0));
740
741        // Test variable only in child
742        assert_eq!(child_ctx.get_variable("child_only"), Some(3.0));
743
744        // Test shadowed variable (child's value should be returned)
745        assert_eq!(child_ctx.get_variable("shadowed"), Some(4.0));
746
747        // Test non-existent variable
748        assert_eq!(child_ctx.get_variable("nonexistent"), None);
749    }
750
751    #[test]
752    fn test_get_variable_deep_chain() {
753        // Create grandparent context
754        let mut grandparent_ctx = EvalContext::new();
755        let _ = grandparent_ctx.set_parameter("grandparent_var", 1.0);
756        let _ = grandparent_ctx.set_parameter("shadowed", 2.0);
757
758        // Create parent context
759        let mut parent_ctx = EvalContext::new();
760        let _ = parent_ctx.set_parameter("parent_var", 3.0);
761        let _ = parent_ctx.set_parameter("shadowed", 4.0);
762        parent_ctx.parent = Some(Rc::new(grandparent_ctx));
763
764        // Create child context
765        let mut child_ctx = EvalContext::new();
766        let _ = child_ctx.set_parameter("child_var", 5.0);
767        let _ = child_ctx.set_parameter("shadowed", 6.0);
768        child_ctx.parent = Some(Rc::new(parent_ctx));
769
770        // Test lookup at each level
771        assert_eq!(child_ctx.get_variable("child_var"), Some(5.0));
772        assert_eq!(child_ctx.get_variable("parent_var"), Some(3.0));
773        assert_eq!(child_ctx.get_variable("grandparent_var"), Some(1.0));
774
775        // Test shadowing - should get closest value
776        assert_eq!(child_ctx.get_variable("shadowed"), Some(6.0));
777    }
778
779    #[test]
780    fn test_get_variable_null_parent() {
781        let mut ctx = EvalContext::new();
782        let _ = ctx.set_parameter("x", 1.0);
783        ctx.parent = None;
784
785        assert_eq!(ctx.get_variable("x"), Some(1.0));
786        assert_eq!(ctx.get_variable("nonexistent"), None);
787    }
788
789    #[test]
790    fn test_get_variable_cyclic_reference_safety() {
791        // Create two contexts that will reference each other
792        let mut ctx1 = EvalContext::new();
793        let mut ctx2 = EvalContext::new();
794
795        let _ = ctx1.set_parameter("var1", 1.0);
796        let _ = ctx2.set_parameter("var2", 2.0);
797
798        // Create a cyclic reference (this would be unsafe in practice)
799        // We'll test that variable lookup still works without infinite recursion
800        let ctx1_rc = Rc::new(ctx1);
801        ctx2.parent = Some(Rc::clone(&ctx1_rc));
802
803        // Test lookup still works in potential cycle
804        assert_eq!(ctx2.get_variable("var2"), Some(2.0));
805        assert_eq!(ctx2.get_variable("var1"), Some(1.0));
806    }
807
808    #[test]
809    fn test_get_variable_in_function_scope() {
810        let mut ctx = EvalContext::new();
811
812        // Set up parent context with a variable
813        let _ = ctx.set_parameter("x", 100.0);
814
815        // Create a function context that uses 'x' as parameter
816        let mut func_ctx = EvalContext::new();
817        let _ = func_ctx.set_parameter("x", 5.0); // Parameter value
818        func_ctx.parent = Some(Rc::new(ctx.clone()));
819
820        // Test variable lookup in function scope
821        assert_eq!(
822            func_ctx.get_variable("x"),
823            Some(5.0),
824            "Function parameter should shadow parent variable"
825        );
826
827        // Print debug info
828        println!("Parent context x = {:?}", ctx.get_variable("x"));
829        println!("Function context x = {:?}", func_ctx.get_variable("x"));
830        println!("Function context variables: {:?}", func_ctx.variables);
831        println!(
832            "Function context parent variables: {:?}",
833            func_ctx.parent.as_ref().map(|p| &p.variables)
834        );
835    }
836
837    #[test]
838    fn test_get_variable_nested_scopes() {
839        let mut root_ctx = EvalContext::new();
840        let _ = root_ctx.set_parameter("x", 1.0);
841        let _ = root_ctx.set_parameter("y", 1.0);
842
843        let mut mid_ctx = EvalContext::new();
844        let _ = mid_ctx.set_parameter("x", 2.0);
845        mid_ctx.parent = Some(Rc::new(root_ctx));
846
847        let mut leaf_ctx = EvalContext::new();
848        let _ = leaf_ctx.set_parameter("x", 3.0);
849        leaf_ctx.parent = Some(Rc::new(mid_ctx));
850
851        // Test variable lookup at each level
852        assert_eq!(
853            leaf_ctx.get_variable("x"),
854            Some(3.0),
855            "Should get leaf context value"
856        );
857        assert_eq!(
858            leaf_ctx.get_variable("y"),
859            Some(1.0),
860            "Should get root context value when not shadowed"
861        );
862
863        println!("Variable lookup in nested scopes:");
864        println!("leaf x = {:?}", leaf_ctx.get_variable("x"));
865        println!("leaf y = {:?}", leaf_ctx.get_variable("y"));
866        println!("leaf variables: {:?}", leaf_ctx.variables);
867        println!(
868            "mid variables: {:?}",
869            leaf_ctx.parent.as_ref().map(|p| &p.variables)
870        );
871        println!(
872            "root variables: {:?}",
873            leaf_ctx
874                .parent
875                .as_ref()
876                .and_then(|p| p.parent.as_ref())
877                .map(|p| &p.variables)
878        );
879    }
880
881    #[test]
882    fn test_get_variable_function_parameter_precedence() {
883        let mut ctx = EvalContext::new();
884        let arena = bumpalo::Bump::new();
885        let mut batch = crate::expression::Expression::new(&arena);
886
887        // Register a function that uses parameter 'x' in arena batch
888        batch
889            .register_expression_function("f", &["x"], "x * 2")
890            .unwrap();
891
892        // Set a global 'x'
893        let _ = ctx.set_parameter("x", 100.0);
894
895        // Create evaluation context for function
896        let mut func_ctx = EvalContext::new();
897        let _ = func_ctx.set_parameter("x", 5.0); // Parameter value
898        func_ctx.parent = Some(Rc::new(ctx));
899
900        println!("Function parameter context:");
901        println!("func_ctx x = {:?}", func_ctx.get_variable("x"));
902        println!("func_ctx variables: {:?}", func_ctx.variables);
903        println!(
904            "parent variables: {:?}",
905            func_ctx.parent.as_ref().map(|p| &p.variables)
906        );
907
908        assert_eq!(
909            func_ctx.get_variable("x"),
910            Some(5.0),
911            "Function parameter should take precedence over global x"
912        );
913    }
914
915    #[test]
916    fn test_get_variable_temporary_scope() {
917        let mut ctx = EvalContext::new();
918        let _ = ctx.set_parameter("x", 1.0);
919
920        // Create temporary scope
921        let mut temp_ctx = EvalContext::new();
922        temp_ctx.parent = Some(Rc::new(ctx));
923
924        // Variable lookup should find parent value
925        assert_eq!(
926            temp_ctx.get_variable("x"),
927            Some(1.0),
928            "Should find variable in parent scope"
929        );
930
931        // Add variable to temporary scope
932        let _ = temp_ctx.set_parameter("x", 2.0);
933
934        // Should now find local value
935        assert_eq!(
936            temp_ctx.get_variable("x"),
937            Some(2.0),
938            "Should find shadowed variable in local scope"
939        );
940
941        println!("Temporary scope variable lookup:");
942        println!("temp x = {:?}", temp_ctx.get_variable("x"));
943        println!("temp variables: {:?}", temp_ctx.variables);
944        println!(
945            "parent variables: {:?}",
946            temp_ctx.parent.as_ref().map(|p| &p.variables)
947        );
948    }
949
950    #[test]
951    fn test_native_function() {
952        let mut ctx = EvalContext::new();
953
954        ctx.register_native_function("add_all", 3, |args| args.iter().sum())
955            .unwrap();
956
957        let val = engine::interp("add_all(1, 2, 3)", Some(Rc::new(ctx))).unwrap();
958        assert_eq!(val, 6.0);
959    }
960
961    #[test]
962    fn test_array_access() {
963        let mut ctx = EvalContext::new();
964        ctx.arrays
965            .insert(
966                "climb_wave_wait_time".try_into_heapless().unwrap(),
967                vec![10.0, 20.0, 30.0],
968            )
969            .expect("Failed to insert array");
970        let val = engine::interp("climb_wave_wait_time[1]", Some(Rc::new(ctx.clone()))).unwrap();
971        assert_eq!(val, 20.0);
972    }
973
974    #[test]
975    fn test_array_access_ast_structure() {
976        let mut ctx = EvalContext::new();
977        ctx.arrays
978            .insert(
979                "climb_wave_wait_time".try_into_heapless().unwrap(),
980                vec![10.0, 20.0, 30.0],
981            )
982            .expect("Failed to insert array");
983        use bumpalo::Bump;
984        let arena = Bump::new();
985        let ast = engine::parse_expression("climb_wave_wait_time[1]", &arena).unwrap();
986        match ast {
987            AstExpr::Array { name, index } => {
988                assert_eq!(name, "climb_wave_wait_time");
989                match *index {
990                    AstExpr::Constant(val) => assert_eq!(val, 1.0),
991                    _ => panic!("Expected constant index"),
992                }
993            }
994            _ => panic!("Expected array AST node"),
995        }
996    }
997
998    #[test]
999    fn test_attribute_access() {
1000        let mut ctx = EvalContext::new();
1001        let mut foo_map = heapless::FnvIndexMap::<
1002            crate::types::HString,
1003            crate::Real,
1004            { crate::types::EXP_RS_MAX_ATTR_KEYS },
1005        >::new();
1006        foo_map
1007            .insert("bar".try_into_heapless().unwrap(), 42.0)
1008            .unwrap();
1009        ctx.attributes
1010            .insert("foo".try_into_heapless().unwrap(), foo_map)
1011            .unwrap();
1012
1013        use bumpalo::Bump;
1014        let arena = Bump::new();
1015        let ast = engine::parse_expression("foo.bar", &arena).unwrap();
1016        println!("AST for foo.bar: {:?}", ast);
1017
1018        let ctx_copy = ctx.clone();
1019        let eval_result = crate::eval::eval_ast(&ast, Some(Rc::new(ctx_copy)), &arena);
1020        println!("Direct eval_ast result: {:?}", eval_result);
1021
1022        let ctx_copy2 = ctx.clone();
1023        let val = engine::interp("foo.bar", Some(Rc::new(ctx_copy2))).unwrap();
1024        assert_eq!(val, 42.0);
1025
1026        let ctx_copy3 = ctx.clone();
1027        let err = engine::interp("foo.baz", Some(Rc::new(ctx_copy3))).unwrap_err();
1028        println!("Error for foo.baz: {:?}", err);
1029
1030        let ctx_copy4 = ctx.clone();
1031        let err2 = engine::interp("nope.bar", Some(Rc::new(ctx_copy4))).unwrap_err();
1032        println!("Error for nope.bar: {:?}", err2);
1033
1034        let err3 = engine::interp("foo.bar", None).unwrap_err();
1035        println!("Error for foo.bar with None context: {:?}", err3);
1036    }
1037
1038    #[test]
1039    fn test_set_parameter() {
1040        let mut ctx = EvalContext::new();
1041
1042        let prev = ctx.set_parameter("x", 10.0);
1043        assert_eq!(prev.unwrap(), None);
1044
1045        let val = engine::interp("x", Some(Rc::new(ctx.clone()))).unwrap();
1046        assert_eq!(val, 10.0);
1047
1048        let prev = ctx.set_parameter("x", 20.0);
1049        assert_eq!(prev.unwrap(), Some(10.0));
1050
1051        let val = engine::interp("x", Some(Rc::new(ctx.clone()))).unwrap();
1052        assert_eq!(val, 20.0);
1053
1054        let val = engine::interp("x * 2", Some(Rc::new(ctx.clone()))).unwrap();
1055        assert_eq!(val, 40.0);
1056    }
1057}