exp_rs/eval/
stack_ops.rs

1//! Stack-based operations for iterative AST evaluation
2//!
3//! This module defines the operation types used by the iterative evaluator
4//! to process expressions without recursion.
5
6use crate::Real;
7use crate::error::ExprError;
8use crate::types::{AstExpr, FunctionName, HString};
9use alloc::format;
10
11/// Operations that can be pushed onto the evaluation stack
12#[derive(Clone)]
13pub enum EvalOp<'arena> {
14    /// Push an expression to evaluate
15    Eval {
16        expr: &'arena AstExpr<'arena>,
17        ctx_id: usize,
18    },
19
20    /// Apply a unary operation
21    ApplyUnary { op: UnaryOp },
22
23    /// Apply a binary operation after both operands are evaluated
24    CompleteBinary { op: BinaryOp },
25
26    /// Short-circuit AND operation
27    ShortCircuitAnd {
28        right_expr: &'arena AstExpr<'arena>,
29        ctx_id: usize,
30    },
31
32    /// Short-circuit OR operation  
33    ShortCircuitOr {
34        right_expr: &'arena AstExpr<'arena>,
35        ctx_id: usize,
36    },
37
38    /// Complete AND operation (when not short-circuited)
39    CompleteAnd,
40
41    /// Complete OR operation (when not short-circuited)
42    CompleteOr,
43
44    /// Apply a function with N arguments from the value stack
45    ApplyFunction {
46        name: FunctionName,
47        arg_count: usize,
48        ctx_id: usize,
49    },
50
51    /// Handle ternary operator - condition already evaluated
52    TernaryCondition {
53        true_branch: &'arena AstExpr<'arena>,
54        false_branch: &'arena AstExpr<'arena>,
55        ctx_id: usize,
56    },
57
58    /// Variable lookup
59    LookupVariable { name: HString, ctx_id: usize },
60
61    /// Array access - index already evaluated
62    AccessArray { array_name: HString, ctx_id: usize },
63
64    /// Attribute access
65    AccessAttribute {
66        object_name: HString,
67        attr_name: HString,
68        ctx_id: usize,
69    },
70
71    /// Restore after expression function completes
72    RestoreFunctionParams {
73        /// Parameters for the current function scope
74        params: Option<&'arena [(crate::types::HString, crate::Real)]>,
75    },
76}
77
78/// Unary operators
79#[derive(Debug, Clone, Copy, PartialEq)]
80pub enum UnaryOp {
81    Negate,
82    Not,
83}
84
85/// Binary operators  
86#[derive(Debug, Clone, Copy, PartialEq)]
87pub enum BinaryOp {
88    Add,
89    Subtract,
90    Multiply,
91    Divide,
92    Modulo,
93    Power,
94    Less,
95    Greater,
96    LessEqual,
97    GreaterEqual,
98    Equal,
99    NotEqual,
100    // Note: AND and OR are handled separately for short-circuiting
101}
102
103impl UnaryOp {
104    /// Apply a unary operation to a value
105    pub fn apply(self, operand: Real) -> Real {
106        match self {
107            UnaryOp::Negate => -operand,
108            UnaryOp::Not => {
109                if operand == 0.0 {
110                    1.0
111                } else {
112                    0.0
113                }
114            }
115        }
116    }
117}
118
119impl BinaryOp {
120    /// Apply a binary operation to two values
121    pub fn apply(self, left: Real, right: Real) -> Real {
122        match self {
123            BinaryOp::Add => left + right,
124            BinaryOp::Subtract => left - right,
125            BinaryOp::Multiply => left * right,
126            BinaryOp::Divide => left / right,
127            BinaryOp::Modulo => left % right,
128            BinaryOp::Power => {
129                #[cfg(feature = "libm")]
130                {
131                    crate::functions::pow(left, right)
132                }
133                #[cfg(not(feature = "libm"))]
134                {
135                    // In no_std without libm, power operations must be handled by registered functions
136                    // This should not be reached as the parser would create a function call instead
137                    panic!("Power operation requires libm feature or registered pow function")
138                }
139            }
140            BinaryOp::Less => {
141                if left < right {
142                    1.0
143                } else {
144                    0.0
145                }
146            }
147            BinaryOp::Greater => {
148                if left > right {
149                    1.0
150                } else {
151                    0.0
152                }
153            }
154            BinaryOp::LessEqual => {
155                if left <= right {
156                    1.0
157                } else {
158                    0.0
159                }
160            }
161            BinaryOp::GreaterEqual => {
162                if left >= right {
163                    1.0
164                } else {
165                    0.0
166                }
167            }
168            BinaryOp::Equal => {
169                if left == right {
170                    1.0
171                } else {
172                    0.0
173                }
174            }
175            BinaryOp::NotEqual => {
176                if left != right {
177                    1.0
178                } else {
179                    0.0
180                }
181            }
182        }
183    }
184}
185
186/// Convert from AST representation to stack operation
187pub fn ast_to_stack_op(op: &str) -> Result<BinaryOp, ExprError> {
188    match op {
189        "+" => Ok(BinaryOp::Add),
190        "-" => Ok(BinaryOp::Subtract),
191        "*" => Ok(BinaryOp::Multiply),
192        "/" => Ok(BinaryOp::Divide),
193        "%" => Ok(BinaryOp::Modulo),
194        "^" | "**" => Ok(BinaryOp::Power),
195        "<" => Ok(BinaryOp::Less),
196        ">" => Ok(BinaryOp::Greater),
197        "<=" => Ok(BinaryOp::LessEqual),
198        ">=" => Ok(BinaryOp::GreaterEqual),
199        "==" => Ok(BinaryOp::Equal),
200        "!=" => Ok(BinaryOp::NotEqual),
201        _ => Err(ExprError::Syntax(format!("Unknown operator: {}", op))),
202    }
203}
204
205/// Check if a string is a binary operator
206pub fn is_binary_operator(op: &str) -> bool {
207    matches!(
208        op,
209        "+" | "-" | "*" | "/" | "%" | "^" | "**" | "<" | ">" | "<=" | ">=" | "==" | "!="
210    )
211}
212
213impl<'arena> core::fmt::Debug for EvalOp<'arena> {
214    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
215        match self {
216            EvalOp::Eval { expr: _, ctx_id } => {
217                write!(f, "Eval {{ expr: <AstExpr>, ctx_id: {} }}", ctx_id)
218            }
219            EvalOp::ApplyUnary { op } => {
220                write!(f, "ApplyUnary {{ op: {:?} }}", op)
221            }
222            EvalOp::CompleteBinary { op } => {
223                write!(f, "CompleteBinary {{ op: {:?} }}", op)
224            }
225            EvalOp::ShortCircuitAnd {
226                right_expr: _,
227                ctx_id,
228            } => {
229                write!(
230                    f,
231                    "ShortCircuitAnd {{ right_expr: <AstExpr>, ctx_id: {} }}",
232                    ctx_id
233                )
234            }
235            EvalOp::ShortCircuitOr {
236                right_expr: _,
237                ctx_id,
238            } => {
239                write!(
240                    f,
241                    "ShortCircuitOr {{ right_expr: <AstExpr>, ctx_id: {} }}",
242                    ctx_id
243                )
244            }
245            EvalOp::CompleteAnd => write!(f, "CompleteAnd"),
246            EvalOp::CompleteOr => write!(f, "CompleteOr"),
247            EvalOp::ApplyFunction {
248                name,
249                arg_count,
250                ctx_id,
251            } => {
252                write!(
253                    f,
254                    "ApplyFunction {{ name: {:?}, arg_count: {}, ctx_id: {} }}",
255                    name, arg_count, ctx_id
256                )
257            }
258            EvalOp::LookupVariable { name, ctx_id } => {
259                write!(
260                    f,
261                    "LookupVariable {{ name: {:?}, ctx_id: {} }}",
262                    name, ctx_id
263                )
264            }
265            EvalOp::TernaryCondition {
266                true_branch: _,
267                false_branch: _,
268                ctx_id,
269            } => {
270                write!(
271                    f,
272                    "TernaryCondition {{ true_branch: <AstExpr>, false_branch: <AstExpr>, ctx_id: {} }}",
273                    ctx_id
274                )
275            }
276            EvalOp::AccessArray { array_name, ctx_id } => {
277                write!(
278                    f,
279                    "AccessArray {{ array_name: {:?}, ctx_id: {} }}",
280                    array_name, ctx_id
281                )
282            }
283            EvalOp::AccessAttribute {
284                object_name,
285                attr_name,
286                ctx_id,
287            } => {
288                write!(
289                    f,
290                    "AccessAttribute {{ object_name: {:?}, attr_name: {:?}, ctx_id: {} }}",
291                    object_name, attr_name, ctx_id
292                )
293            }
294            EvalOp::RestoreFunctionParams { params } => {
295                write!(f, "RestoreFunctionParams {{ params: {} }}", params.is_some())
296            }
297        }
298    }
299}