A high-performance mathematical expression evaluator implementing a Pratt parser with bytecode compilation and a stack-based virtual machine.
Traditional expression evaluators rely on the Shunting-yard algorithm and tree-walking interpretation. This implementation adopts a compiler-VM architecture for superior performance:
- Pratt Parsing: Top-down operator precedence parsing with O(n) complexity
- Bytecode Compilation: AST flattening into compact, cache-friendly instruction sequences
- Stack-based VM: Zero-overhead execution with tight instruction dispatch loop
| Component | Optimization | Complexity |
|---|---|---|
| Variable Resolution | Index-based interning | O(1) |
| Constant Pool | HashMap deduplication | O(1) |
| Function Dispatch | Direct pointer table | O(1) |
| Batch Evaluation | Shared VM instance | O(n·m) |
Key Optimizations:
- Constant Folding: Compile-time evaluation of constant expressions
- Algebraic Simplification: Identity elimination (x·1 → x, x+0 → x)
- Zero-copy Execution: Direct stack manipulation without heap allocation
- Function Validation: Compile-time arity checking with metadata
┌─────────────┐ │ Source │ └──────┬──────┘ │ Lexical Analysis ▼ ┌─────────────┐ │ Tokens │ └──────┬──────┘ │ Pratt Parsing ▼ ┌─────────────┐ │ AST │ └──────┬──────┘ │ Optimization & Compilation ▼ ┌─────────────┐ ┌──────────────┐ │ Bytecode │────▶│ Symbol Table │ └──────┬──────┘ └──────────────┘ │ │ VM Execution ▼ ┌─────────────┐ │ Result │ └─────────────┘ use matheval_core::Compiler; let compiler = Compiler::new(); let program = compiler.compile("x^2 + 2*x + 1").unwrap(); let mut context = program.create_context(); context.set_by_index(0, 3.0); // x = 3 let result = program.eval(&context).unwrap(); assert_eq!(result, 16.0); // 3² + 2·3 + 1 = 16import matheval compiler = matheval.Compiler() program = compiler.compile("x^2 + 2*x + 1") context = matheval.Context() context.set("x", 3.0) result = program.eval(context) assert result == 16.0Optimized for Monte Carlo simulations and parameter sweeps:
let program = compiler.compile("S * exp(r*T) - K").unwrap(); // Vectorized evaluation: reuses VM instance let var_sets = vec![ &[100.0, 0.05, 1.0, 105.0][..], // S, r, T, K &[110.0, 0.05, 1.0, 105.0][..], &[120.0, 0.05, 1.0, 105.0][..], ]; let results = program.eval_batch(&var_sets).unwrap();Performance: 6-7× faster than loop-based evaluation for large datasets.
Comprehensive error reporting with position tracking:
use matheval_core::{Error, ErrorKind, Position}; let err = Error::wrong_arg_count("sin", 1, 2) .with_position(Position::new(1, 5, 4)) .with_source("x + sin(1, 2)".to_string()); // Output: // Error: Function 'sin' expects 1 argument, but got 2 at line 1, column 5 // // 1 | x + sin(1, 2) // ^ // // Hint: Check the function documentation for the correct number of argumentsArithmetic: +, -, *, /, ^ (right-associative)
Functions: sin, cos, tan, sqrt, abs, floor, ceil, round, exp, ln, log10, max, min
Precedence: Standard mathematical order with parentheses support
- Lexical Analysis: Tokenization with position tracking
- Syntax Analysis: Pratt parsing with binding power resolution
- Optimization: Constant folding and algebraic simplification
- Code Generation: Bytecode emission with symbol table construction
- Instruction Set: 9 opcodes (LoadConst, LoadVar, Add, Sub, Mul, Div, Pow, Neg, Call)
- Stack Model: f64 operand stack with bounds checking
- Function Table: Direct function pointer dispatch
- Metadata: Compile-time arity validation
Program { instructions: Vec<u8>, // Compact bytecode constants: Vec<f64>, // Deduplicated constant pool var_names: Vec<String>, // Variable name table func_table: Vec<BuiltinFn>, // Function pointer table func_metadata: Vec<Metadata>, // Arity information } Expression: x * 2 + sin(y) (10,000 iterations)
| Method | Time | Throughput |
|---|---|---|
| Loop eval() | 34.5 ms | 290k ops/s |
| Batch eval_batch() | 5.0 ms | 2M ops/s |
Speedup: 6.9× for batch evaluation
- 100% Safe Rust: No
unsafeblocks - Comprehensive Testing: 97 unit tests, 9 Python integration tests
- Validation: Compile-time function arity checking
- Error Recovery: Graceful handling of division by zero, stack underflow