exp_rs/
types.rs

1//! Type definitions for the expression parser and evaluator.
2//!
3//! This module contains the core data structures used throughout the expression parser
4//! and evaluator, including the Abstract Syntax Tree (AST) representation, token types,
5//! function definitions, and other auxiliary types.
6
7extern crate alloc;
8
9#[cfg(test)]
10use crate::Real;
11#[cfg(not(test))]
12use crate::{Box, Real, String, Vec};
13#[cfg(not(test))]
14use alloc::rc::Rc;
15#[cfg(test)]
16use std::rc::Rc;
17#[cfg(test)]
18use std::boxed::Box;
19#[cfg(test)]
20use std::string::String;
21#[cfg(test)]
22use std::vec::Vec;
23
24/// Abstract Syntax Tree (AST) node representing an expression.
25/// 
26/// The AST is the core data structure used for representing parsed expressions.
27/// Each variant of this enum represents a different type of expression node,
28/// forming a tree structure that can be evaluated to produce a result.
29#[derive(Clone, Debug, PartialEq)]
30pub enum AstExpr {
31    /// A literal numerical value.
32    /// 
33    /// Examples: `3.14`, `42`, `-1.5`
34    Constant(Real),
35    
36    /// A named variable reference.
37    /// 
38    /// Examples: `x`, `temperature`, `result`
39    Variable(String),
40    
41    /// A function call with a name and list of argument expressions.
42    /// 
43    /// Examples: `sin(x)`, `max(a, b)`, `sqrt(x*x + y*y)`
44    Function { 
45        /// The name of the function being called
46        name: String, 
47        /// The arguments passed to the function
48        args: Vec<AstExpr> 
49    },
50    
51    /// An array element access.
52    /// 
53    /// Examples: `array[0]`, `values[i+1]`
54    Array { 
55        /// The name of the array
56        name: String, 
57        /// The expression for the index
58        index: Box<AstExpr> 
59    },
60    
61    /// An attribute access on an object.
62    /// 
63    /// Examples: `point.x`, `settings.value`
64    Attribute { 
65        /// The base object name
66        base: String, 
67        /// The attribute name
68        attr: String 
69    },
70}
71
72impl AstExpr {
73    /// Helper method that raises a constant expression to a power.
74    /// 
75    /// This is primarily used in testing to evaluate power operations on constants.
76    /// For non-constant expressions, it returns 0.0 as a default value.
77    /// 
78    /// # Parameters
79    /// 
80    /// * `exp` - The exponent to raise the constant to
81    /// 
82    /// # Returns
83    /// 
84    /// The constant raised to the given power, or 0.0 for non-constant expressions
85    pub fn pow(self, exp: Real) -> Real {
86        match self {
87            #[cfg(feature = "f32")]
88            AstExpr::Constant(val) => libm::powf(val, exp),
89            #[cfg(not(feature = "f32"))]
90            AstExpr::Constant(val) => libm::pow(val, exp),
91            _ => 0.0, // Default for non-constant expressions
92        }
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99    use crate::error::ExprError;
100    use crate::eval::eval_ast;
101
102    #[test]
103    fn test_eval_ast_array_and_attribute_errors() {
104        // Array not found
105        let ast = AstExpr::Array {
106            name: "arr".to_string(),
107            index: Box::new(AstExpr::Constant(0.0)),
108        };
109        let err = eval_ast(&ast, None).unwrap_err();
110        match err {
111            ExprError::UnknownVariable { name } => assert_eq!(name, "arr"),
112            _ => panic!("Expected UnknownVariable error"),
113        }
114        // Attribute not found
115        let ast2 = AstExpr::Attribute {
116            base: "foo".to_string(),
117            attr: "bar".to_string(),
118        };
119        let err2 = eval_ast(&ast2, None).unwrap_err();
120        match err2 {
121            ExprError::AttributeNotFound { base, attr } => {
122                assert_eq!(base, "foo");
123                assert_eq!(attr, "bar");
124            }
125            _ => panic!("Expected AttributeNotFound error"),
126        }
127    }
128
129    #[test]
130    fn test_eval_ast_function_wrong_arity() {
131        // sin with 2 args (should be 1)
132        let ast = AstExpr::Function {
133            name: "sin".to_string(),
134            args: vec![AstExpr::Constant(1.0), AstExpr::Constant(2.0)],
135        };
136        let err = eval_ast(&ast, None).unwrap_err();
137        match err {
138            ExprError::InvalidFunctionCall {
139                name,
140                expected,
141                found,
142            } => {
143                assert_eq!(name, "sin");
144                assert_eq!(expected, 1);
145                assert_eq!(found, 2);
146            }
147            _ => panic!("Expected InvalidFunctionCall error"),
148        }
149    }
150
151    #[test]
152    fn test_eval_ast_unknown_function_and_variable() {
153        // Unknown function
154        let ast = AstExpr::Function {
155            name: "notafunc".to_string(),
156            args: vec![AstExpr::Constant(1.0)],
157        };
158        let err = eval_ast(&ast, None).unwrap_err();
159        match err {
160            ExprError::UnknownFunction { name } => assert_eq!(name, "notafunc"),
161            _ => panic!("Expected UnknownFunction error"),
162        }
163        // Unknown variable
164        let ast2 = AstExpr::Variable("notavar".to_string());
165        let err2 = eval_ast(&ast2, None).unwrap_err();
166        match err2 {
167            ExprError::UnknownVariable { name } => assert_eq!(name, "notavar"),
168            _ => panic!("Expected UnknownVariable error"),
169        }
170    }
171}
172
173/// Classifies the kind of expression node in the AST.
174/// 
175/// This enum is used to categorize expression nodes at a higher level than the specific
176/// AST node variants, making it easier to determine the general type of an expression
177/// without matching on all variants.
178#[derive(Copy, Clone, PartialEq, Eq, Debug)]
179pub enum ExprKind {
180    /// A constant numerical value.
181    Constant,
182    
183    /// A variable reference.
184    Variable,
185    
186    /// A function call with a specific arity (number of arguments).
187    Function { 
188        /// Number of arguments the function takes
189        arity: usize 
190    },
191    
192    /// An array element access.
193    Array,
194    
195    /// An object attribute access.
196    Attribute,
197}
198
199/// Classifies the kind of token produced during lexical analysis.
200/// 
201/// These token types are used by the lexer to categorize different elements
202/// in the expression string during the parsing phase.
203#[derive(Copy, Clone, PartialEq, Eq, Debug)]
204pub enum TokenKind {
205    /// A numerical literal.
206    Number,
207    
208    /// A variable identifier.
209    Variable,
210    
211    /// An operator such as +, -, *, /, ^, etc.
212    Operator,
213    
214    /// An opening delimiter like '(' or '['.
215    Open,
216    
217    /// A closing delimiter like ')' or ']'.
218    Close,
219    
220    /// A separator between items, typically a comma.
221    Separator,
222    
223    /// End of the expression.
224    End,
225    
226    /// An error token representing invalid input.
227    Error,
228    
229    /// A null or placeholder token.
230    Null,
231}
232
233/*
234    All legacy bitmasking, ExprType, and OperatorKind have been removed.
235    All parser and evaluator logic now uses AstExpr and enums only.
236    The old Expr struct and related types are no longer present.
237    Next: Update and simplify the test suite to use the new AST parser and evaluator.
238*/
239
240
241/// Represents a native Rust function that can be registered with the evaluation context.
242/// 
243/// Native functions allow users to extend the expression evaluator with custom
244/// functionality written in Rust. These functions can be called from within expressions
245/// like any built-in function.
246///
247/// # Example
248///
249/// ```
250/// # use exp_rs::{EvalContext, Real};
251/// # use exp_rs::engine::interp;
252/// # use std::rc::Rc;
253/// let mut ctx = EvalContext::new();
254///
255/// // Register a custom function that calculates the hypotenuse
256/// ctx.register_native_function(
257///     "hypotenuse",     // Function name
258///     2,                // Takes 2 arguments
259///     |args: &[Real]| { // Implementation
260///         (args[0] * args[0] + args[1] * args[1]).sqrt()
261///     }
262/// );
263///
264/// // Use the function in an expression
265/// let result = interp("hypotenuse(3, 4)", Some(Rc::new(ctx))).unwrap();
266/// assert_eq!(result, 5.0);
267/// ```
268#[derive(Clone)]
269pub struct NativeFunction<'a> {
270    /// Number of arguments the function takes.
271    pub arity: usize,
272    
273    /// The actual implementation of the function as a Rust closure.
274    pub implementation: Rc<dyn Fn(&[Real]) -> Real>,
275    
276    /// The name of the function as it will be used in expressions.
277    pub name: Cow<'a, str>,
278    
279    /// Optional description of what the function does.
280    pub description: Option<String>,
281}
282
283/* We can't derive Clone for NativeFunction because Box<dyn Fn> doesn't implement Clone.
284   Instead, we provide a shallow clone in context.rs for EvalContext, which is safe for read-only use.
285   Do NOT call .clone() on NativeFunction directly. */
286
287use alloc::borrow::Cow;
288
289/// Represents a function defined by an expression string rather than Rust code.
290/// 
291/// Expression functions allow users to define custom functions using the expression
292/// language itself. These functions are compiled once when registered and can be called
293/// from other expressions. They support parameters and can access variables from the
294/// evaluation context.
295///
296/// # Example
297///
298/// ```
299/// # use exp_rs::{EvalContext, Real};
300/// # use exp_rs::engine::interp;
301/// # use std::rc::Rc;
302/// let mut ctx = EvalContext::new();
303///
304/// // Register a function to calculate the area of a circle
305/// ctx.register_expression_function(
306///     "circle_area",            // Function name
307///     &["radius"],              // Parameter names
308///     "pi * radius * radius"    // Function body as an expression
309/// ).unwrap();
310///
311/// // Use the function in another expression
312/// let result = interp("circle_area(2)", Some(Rc::new(ctx))).unwrap();
313/// assert!(result > 12.56 && result < 12.57); // π * 4 ≈ 12.566
314/// ```
315#[derive(Clone)]
316pub struct ExpressionFunction {
317    /// The name of the function as it will be used in expressions.
318    pub name: String,
319    
320    /// The parameter names that the function accepts.
321    pub params: Vec<String>,
322    
323    /// The original expression string defining the function body.
324    pub expression: String,
325    
326    /// The pre-compiled AST of the expression for faster evaluation.
327    pub compiled_ast: AstExpr,
328    
329    /// Optional description of what the function does.
330    pub description: Option<String>,
331}
332
333
334/// Internal representation of a variable in the evaluation system.
335/// 
336/// This is an implementation detail and should not be used directly by library users.
337/// Variables are normally managed through the `EvalContext` interface.
338#[doc(hidden)]
339#[derive(Debug, Clone)]
340pub struct Variable<'a> {
341    /// The name of the variable.
342    pub name: Cow<'a, str>,
343    
344    /// Internal address/identifier for the variable.
345    pub address: i8,
346    
347    /// Function associated with the variable (if any).
348    pub function: fn(Real, Real) -> Real,
349    
350    /// Context or associated AST nodes.
351    pub context: Vec<AstExpr>,
352}
353
354impl<'a> Variable<'a> {
355    /// Creates a new variable with the given name and default values.
356    pub fn new(name: &'a str) -> Variable<'a> {
357        Variable {
358            name: Cow::Borrowed(name),
359            address: 0,
360            function: crate::functions::dummy,
361            context: Vec::<AstExpr>::new(),
362        }
363    }
364}