mathexpr 0.1.1

A fast, safe mathematical expression parser and evaluator with bytecode compilation
Documentation
//! Builder pattern API for mathexpr.
//!
//! This module provides a fluent, ergonomic API for parsing, compiling,
//! and evaluating mathematical expressions.

#[cfg(not(feature = "std"))]
use alloc::{string::String, string::ToString};

use crate::ast::Expr;
use crate::compiler::CompiledExpr;
use crate::error::{CompileError, EvalError, ParseError};
use crate::parser;

/// A parsed mathematical expression.
///
/// This is the entry point for the builder API. Create an `Expression` by
/// parsing a string, then compile it with variable bindings for evaluation.
///
/// # Example
///
/// ```
/// use mathexpr::Expression;
///
/// let result = Expression::parse("sqrt(x^2 + y^2)")?
///     .compile(&["x", "y"])?
///     .eval(&[3.0, 4.0])?;
///
/// assert_eq!(result, 5.0);
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[derive(Debug, Clone)]
pub struct Expression {
    ast: Expr,
    source: Option<String>,
}

impl Expression {
    /// Parse a mathematical expression string.
    ///
    /// # Arguments
    ///
    /// * `input` - The expression string to parse
    ///
    /// # Returns
    ///
    /// * `Ok(Expression)` - A parsed expression ready for compilation
    /// * `Err(ParseError)` - If parsing fails
    ///
    /// # Supported Syntax
    ///
    /// - **Operators**: `+`, `-`, `*`, `/`, `%` (modulo), `^` (power)
    /// - **Parentheses**: `(` and `)`
    /// - **Variables**: Any identifier (e.g., `x`, `score`, `my_var`)
    /// - **Current value**: `_` represents the input value
    /// - **Numbers**: Integer and floating point (e.g., `42`, `3.14`, `-1.5e10`)
    /// - **Functions**: See [`Expression::compile`] for the full list
    ///
    /// # Example
    ///
    /// ```
    /// use mathexpr::Expression;
    ///
    /// let expr = Expression::parse("2 * pi * radius")?;
    /// # Ok::<(), mathexpr::ParseError>(())
    /// ```
    pub fn parse(input: &str) -> Result<Self, ParseError> {
        let ast = parser::parse(input)?;
        Ok(Expression {
            ast,
            source: Some(String::from(input)),
        })
    }

    /// Create an Expression from a pre-built AST.
    ///
    /// This is useful if you're building expressions programmatically.
    pub fn from_ast(ast: Expr) -> Self {
        Expression { ast, source: None }
    }

    /// Get a reference to the underlying AST.
    pub fn ast(&self) -> &Expr {
        &self.ast
    }

    /// Get the original source string, if available.
    pub fn source(&self) -> Option<&str> {
        self.source.as_deref()
    }

    /// Compile the expression with the given variable names.
    ///
    /// The order of variable names determines the order values must be
    /// provided during evaluation.
    ///
    /// # Arguments
    ///
    /// * `var_names` - Variable names in evaluation order
    ///
    /// # Returns
    ///
    /// * `Ok(Executable)` - A compiled expression ready for evaluation
    /// * `Err(CompileError)` - If compilation fails
    ///
    /// # Supported Functions
    ///
    /// ## Core Math (Tier 1)
    /// - `abs(x)` - Absolute value
    /// - `sqrt(x)` - Square root
    /// - `log(x)`, `ln(x)` - Natural logarithm
    /// - `log10(x)` - Base-10 logarithm
    /// - `exp(x)` - Exponential (e^x)
    /// - `min(a, b)` - Minimum
    /// - `max(a, b)` - Maximum
    /// - `pow(base, exp)` - Power (same as `^`)
    /// - `mod(a, b)` - Modulo (same as `%`)
    ///
    /// ## Trigonometric (Tier 2)
    /// - `sin(x)`, `cos(x)`, `tan(x)` - Basic trig
    /// - `asin(x)`, `acos(x)`, `atan(x)` - Inverse trig
    /// - `sinh(x)`, `cosh(x)`, `tanh(x)` - Hyperbolic
    ///
    /// ## Utility (Tier 3)
    /// - `floor(x)`, `ceil(x)`, `round(x)`, `trunc(x)` - Rounding
    /// - `signum(x)` - Sign (-1, 0, or 1)
    /// - `cbrt(x)` - Cube root
    /// - `log2(x)` - Base-2 logarithm
    /// - `clamp(x, min, max)` - Clamp to range
    ///
    /// ## Constants
    /// - `pi` or `pi()` - π ≈ 3.14159...
    /// - `e` or `e()` - Euler's number ≈ 2.71828...
    ///
    /// # Example
    ///
    /// ```
    /// use mathexpr::Expression;
    ///
    /// let executable = Expression::parse("x + y")?
    ///     .compile(&["x", "y"])?;
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    pub fn compile(self, var_names: &[&str]) -> Result<Executable, CompileError> {
        let compiled = CompiledExpr::compile(&self.ast, var_names)?;
        Ok(Executable {
            compiled,
            source: self.source,
        })
    }

    /// Compile the expression without any variables.
    ///
    /// This is a convenience method for expressions that only use
    /// constants or the current value (`_`).
    ///
    /// # Example
    ///
    /// ```
    /// use mathexpr::Expression;
    ///
    /// let result = Expression::parse("2 * pi")?
    ///     .compile_no_vars()?
    ///     .eval(&[])?;
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    pub fn compile_no_vars(self) -> Result<Executable, CompileError> {
        self.compile(&[])
    }
}

/// A compiled expression ready for evaluation.
///
/// This is the result of compiling an [`Expression`]. It can be evaluated
/// multiple times with different variable values efficiently.
///
/// # Example
///
/// ```
/// use mathexpr::Expression;
///
/// let executable = Expression::parse("x^2 + y^2")?
///     .compile(&["x", "y"])?;
///
/// // Evaluate multiple times with different values
/// assert_eq!(executable.eval(&[3.0, 4.0])?, 25.0);
/// assert_eq!(executable.eval(&[5.0, 12.0])?, 169.0);
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
#[derive(Debug, Clone)]
pub struct Executable {
    compiled: CompiledExpr,
    source: Option<String>,
}

impl Executable {
    /// Evaluate the expression with the given variable values.
    ///
    /// # Arguments
    ///
    /// * `vars` - Variable values in the same order as provided to `compile`
    ///
    /// # Returns
    ///
    /// * `Ok(f64)` - The result
    /// * `Err(EvalError)` - If evaluation fails (e.g., division by zero)
    ///
    /// # Example
    ///
    /// ```
    /// use mathexpr::Expression;
    ///
    /// let f = Expression::parse("a + b")?
    ///     .compile(&["a", "b"])?;
    ///
    /// assert_eq!(f.eval(&[1.0, 2.0])?, 3.0);
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn eval(&self, vars: &[f64]) -> Result<f64, EvalError> {
        self.compiled.eval(vars)
    }

    /// Evaluate the expression with a current value and variable values.
    ///
    /// The current value is accessible as `_` in the expression.
    ///
    /// # Arguments
    ///
    /// * `current` - The current/input value
    /// * `vars` - Variable values in the same order as provided to `compile`
    ///
    /// # Example
    ///
    /// ```
    /// use mathexpr::Expression;
    ///
    /// let f = Expression::parse("_ * scale + offset")?
    ///     .compile(&["scale", "offset"])?;
    ///
    /// assert_eq!(f.eval_with_current(10.0, &[2.0, 5.0])?, 25.0);
    /// # Ok::<(), Box<dyn std::error::Error>>(())
    /// ```
    #[inline]
    pub fn eval_with_current(&self, current: f64, vars: &[f64]) -> Result<f64, EvalError> {
        self.compiled.eval_with_current(current, vars)
    }

    /// Get the number of variables expected during evaluation.
    pub fn num_variables(&self) -> usize {
        self.compiled.num_variables()
    }

    /// Check if this expression uses the current value (`_`).
    pub fn uses_current_value(&self) -> bool {
        self.compiled.uses_current_value()
    }

    /// Get the original source string, if available.
    pub fn source(&self) -> Option<&str> {
        self.source.as_deref()
    }

    /// Get a reference to the underlying compiled expression.
    pub fn compiled(&self) -> &CompiledExpr {
        &self.compiled
    }
}

/// Convenience function to parse, compile, and evaluate in one call.
///
/// This is useful for one-off evaluations where you don't need to
/// reuse the compiled expression.
///
/// # Arguments
///
/// * `expr` - The expression string
/// * `var_names` - Variable names
/// * `vars` - Variable values
///
/// # Example
///
/// ```
/// use mathexpr::eval;
///
/// let result = eval("x + y", &["x", "y"], &[3.0, 4.0])?;
/// assert_eq!(result, 7.0);
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn eval(expr: &str, var_names: &[&str], vars: &[f64]) -> Result<f64, EvalError> {
    let parsed = Expression::parse(expr).map_err(|e| EvalError::MathError(e.to_string()))?;
    let compiled = parsed
        .compile(var_names)
        .map_err(|e| EvalError::MathError(e.to_string()))?;
    compiled.eval(vars)
}

/// Convenience function to parse, compile, and evaluate with a current value.
///
/// # Arguments
///
/// * `expr` - The expression string
/// * `current` - The current/input value
/// * `var_names` - Variable names
/// * `vars` - Variable values
///
/// # Example
///
/// ```
/// use mathexpr::eval_with_current;
///
/// let result = eval_with_current("_ * 2 + offset", 5.0, &["offset"], &[10.0])?;
/// assert_eq!(result, 20.0);
/// # Ok::<(), Box<dyn std::error::Error>>(())
/// ```
pub fn eval_with_current(
    expr: &str,
    current: f64,
    var_names: &[&str],
    vars: &[f64],
) -> Result<f64, EvalError> {
    let parsed = Expression::parse(expr).map_err(|e| EvalError::MathError(e.to_string()))?;
    let compiled = parsed
        .compile(var_names)
        .map_err(|e| EvalError::MathError(e.to_string()))?;
    compiled.eval_with_current(current, vars)
}