mathexpr 0.1.1

A fast, safe mathematical expression parser and evaluator with bytecode compilation
Documentation
//! Bytecode virtual machine for mathexpr.
//!
//! This module provides the VM that executes compiled bytecode.

#[cfg(not(feature = "std"))]
use alloc::vec::Vec;

use crate::compiler::{CompiledExpr, Instruction};
use crate::error::EvalError;

impl CompiledExpr {
    /// Evaluate the compiled expression with the given variable values.
    ///
    /// # Arguments
    ///
    /// * `vars` - Variable values in the same order as `var_names` passed to `compile`
    ///
    /// # Returns
    ///
    /// * `Ok(f64)` - The result of the evaluation
    /// * `Err(EvalError)` - If evaluation fails (e.g., division by zero)
    ///
    /// # Panics
    ///
    /// Panics if the number of variables doesn't match what was compiled.
    ///
    /// # Example
    ///
    /// ```
    /// use mathexpr::{parse, CompiledExpr};
    ///
    /// let ast = parse("x + y").unwrap();
    /// let compiled = CompiledExpr::compile(&ast, &["x", "y"]).unwrap();
    /// let result = compiled.eval(&[3.0, 4.0]).unwrap();
    /// assert_eq!(result, 7.0);
    /// ```
    #[inline]
    pub fn eval(&self, vars: &[f64]) -> Result<f64, EvalError> {
        self.eval_with_current(0.0, vars)
    }

    /// Evaluate the compiled expression with a current value and variable values.
    ///
    /// # Arguments
    ///
    /// * `current` - The current/input value (accessible as `_` in the expression)
    /// * `vars` - Variable values in the same order as `var_names` passed to `compile`
    ///
    /// # Returns
    ///
    /// * `Ok(f64)` - The result of the evaluation
    /// * `Err(EvalError)` - If evaluation fails (e.g., division by zero)
    ///
    /// # Example
    ///
    /// ```
    /// use mathexpr::{parse, CompiledExpr};
    ///
    /// let ast = parse("_ * scale").unwrap();
    /// let compiled = CompiledExpr::compile(&ast, &["scale"]).unwrap();
    /// let result = compiled.eval_with_current(5.0, &[2.0]).unwrap();
    /// assert_eq!(result, 10.0);
    /// ```
    #[inline]
    pub fn eval_with_current(&self, current: f64, vars: &[f64]) -> Result<f64, EvalError> {
        debug_assert_eq!(
            vars.len(),
            self.variable_indices.len(),
            "Variable count mismatch: expected {}, got {}",
            self.variable_indices.len(),
            vars.len()
        );

        let mut stack = Vec::with_capacity(16);

        for inst in &self.instructions {
            match inst {
                Instruction::LoadConst(n) => stack.push(*n),
                Instruction::LoadCurrent => stack.push(current),
                Instruction::LoadVar(idx) => {
                    stack.push(vars[*idx]);
                }
                Instruction::Negate => {
                    let v = stack.pop().unwrap();
                    stack.push(-v);
                }
                Instruction::BinOp(op) => {
                    let r = stack.pop().unwrap();
                    let l = stack.pop().unwrap();
                    stack.push(op.eval(l, r)?);
                }
                Instruction::Call(func) => {
                    let arity = func.arity();
                    let result = match arity {
                        0 => func.eval(&[])?,
                        1 => {
                            let a = stack.pop().unwrap();
                            func.eval(&[a])?
                        }
                        2 => {
                            let b = stack.pop().unwrap();
                            let a = stack.pop().unwrap();
                            func.eval(&[a, b])?
                        }
                        3 => {
                            let c = stack.pop().unwrap();
                            let b = stack.pop().unwrap();
                            let a = stack.pop().unwrap();
                            func.eval(&[a, b, c])?
                        }
                        _ => {
                            // General case for functions with more arguments
                            let mut args = Vec::with_capacity(arity);
                            for _ in 0..arity {
                                args.push(stack.pop().unwrap());
                            }
                            args.reverse();
                            func.eval(&args)?
                        }
                    };
                    stack.push(result);
                }
            }
        }

        Ok(stack.pop().unwrap())
    }
}