Expand description
A mathematical expression evaluator library with bytecode compilation.
This library provides a complete compiler pipeline for mathematical expressions, from parsing to bytecode execution on a stack-based virtual machine.
§Features
- Flexible numeric types - Choose between f64 (default) or 128-bit Decimal precision
- Rich error messages - Parse errors with syntax highlighting
- Bytecode compilation - Compile once, execute many times
- Custom symbols - Add your own constants and functions
- Local constants -
let…thensyntax for declaring scoped constants - Control flow - Conditional expressions with
if(condition, then, else) - Serialization - Save/load compiled programs (requires
serializationfeature)
§Numeric Type Selection
The library supports two numeric backends via feature flags:
f64-floats(default) - Standard f64 floating-point arithmetic. Faster and simpler, suitable for most use cases. Allows Inf and NaN results.decimal-precision- 128-bit Decimal arithmetic for high precision. No floating-point errors, checked arithmetic with overflow detection. Use for financial calculations or when exact decimal representation is required.
Note: Only one numeric backend can be enabled at a time.
§Quick Start
use expr_solver::{eval, num};
let result = eval("2 + 3 * 4").unwrap();
assert_eq!(result, num!(14));§Working with Numbers
Use the num! macro to create numeric values that work with both f64 and Decimal backends:
use expr_solver::num;
let x = num!(42); // Works with any backend
let y = num!(3.14); // Supports decimals
let z = num!(-10); // And negative numbersThe macro ensures your code works regardless of which numeric backend is enabled.
§Custom Symbols
use expr_solver::{num, eval_with_table, SymTable};
let mut table = SymTable::stdlib();
table.add_const("x", num!(10), false).unwrap();
let result = eval_with_table("x * 2", table).unwrap();
assert_eq!(result, num!(20));§Advanced: Type-State Pattern
The Program type uses the type-state pattern to enforce correct usage:
use expr_solver::{num, SymTable, Program};
// Compile expression to bytecode
let program = Program::new_from_source("x + y").unwrap();
// Link with symbol table (validated at link time)
let mut table = SymTable::new();
table.add_const("x", num!(10), false).unwrap();
table.add_const("y", num!(5), false).unwrap();
let mut linked = program.link(table).unwrap();
// Execute
let result = linked.execute().unwrap();
assert_eq!(result, num!(15));§Local Constants with LET THEN
Declare local constants using let … then syntax:
use expr_solver::{eval, num};
// Single declaration
let result = eval("let x = 10 then x * 2").unwrap();
assert_eq!(result, num!(20));
// Multiple declarations
let result = eval("let x = 5, y = 3 then x + y").unwrap();
assert_eq!(result, num!(8));
// Reference previous declarations
let result = eval("let x = 2, y = x * 3, z = y + 1 then z").unwrap();
assert_eq!(result, num!(7));
// Use with globals and functions
let result = eval("let r = 5, area = pi * r ^ 2 then area").unwrap();
assert!((result - num!(78.53981633974483)).abs() < num!(0.0001));Notes:
- Local constants are evaluated left-to-right
- Can reference previously declared locals and globals
- Cannot reference forward (e.g.,
let x = y, y = 1 then xis an error) - Cannot shadow global constants (e.g.,
let pi = 3 then piis an error) - Duplicate names in the same
letblock are errors
§Supported Operators
- Arithmetic:
+,-,*,/,^(power),!(factorial), unary- - Comparison:
==,!=,<,<=,>,>=(return 1 or 0) - Grouping:
() - Control flow:
if(condition, then_value, else_value)
§Built-in Functions
See SymTable::stdlib() for the complete list of built-in functions and constants.
§Architecture
The library uses a multi-stage compilation pipeline:
- Parsing (
Parser) - Source code → Abstract Syntax Tree (AST) - IR Generation ([
IrBuilder]) - AST → Bytecode + Symbol metadata - Linking ([
Linker]) - Bytecode + SymTable → Linked bytecode - Execution (
Vm) - Linked bytecode → Result
Each stage has its own error type (ParseError, IrError, LinkerError, VmError)
which automatically converts to ProgramError for convenience.
Macros§
- num
- Macro for creating numeric literals in a type-neutral way.
Structs§
- Compiled
- Compiled state - bytecode ready for linking.
- Expr
- Expression node in the AST with source location.
- Linked
- Linked state - ready to execute.
- Parser
- Recursive descent parser for mathematical expressions.
- Program
- Type-state program using Rust’s type system to enforce correct usage.
- SymTable
- Symbol table containing constants and functions.
- Symbol
Metadata - Metadata about a symbol required by compiled bytecode.
- Vm
- Stack-based virtual machine for executing bytecode programs.
Enums§
- BinOp
- Binary operators: arithmetic and comparison.
- Expr
Kind - Expression kind representing different types of expressions.
- IrError
- Errors that can occur during IR (bytecode) generation.
- Linker
Error - Errors that can occur during the linking process.
- Parse
Error - Errors that can occur during parsing.
- Program
Error - Errors that can occur during program operations.
- Program
Origin - Origin of a compiled program.
- Symbol
- A symbol representing either a constant or function.
- Symbol
Error - Errors that can occur during symbol table operations.
- Symbol
Kind - The kind of symbol (constant or function) with its requirements.
- UnOp
- Unary operators: negation and factorial.
- VmError
- Virtual machine runtime errors.
Traits§
- Parse
Number - Helper trait for parsing numbers from strings.
Functions§
- eval
- Evaluates an expression string with the standard library.
- eval_
with_ table - Evaluates an expression string with a custom symbol table.