Expand description
§exp-rs
A minimal, extensible, no_std-friendly math expression parser and evaluator for Rust.
§Overview
exp-rs is a math expression parser and evaluator library designed to be simple, extensible, and compatible with no_std environments. It was inspired by TinyExpr and TinyExpr++, but with additional features and Rust-native design.
Key features:
- Configurable floating-point precision (f32/f64)
- Support for user-defined variables, constants, arrays, attributes, and functions
- Built-in math functions (sin, cos, pow, etc.) that can be enabled/disabled
- Ability to override any built-in function at runtime
- Array access with
array[index]
syntax - Object attributes with
object.attribute
syntax - Function application by juxtaposition (
sin x
is equivalent tosin(x)
) - Comprehensive error handling
- No_std compatibility for embedded systems
- Integration with CMSIS-DSP for ARM Cortex-M
§Quick Start
Here’s a basic example of evaluating a math expression:
use exp_rs::engine::interp;
fn main() {
// Simple expression evaluation
let result = interp("2 + 3 * 4", None).unwrap();
assert_eq!(result, 14.0); // 2 + (3 * 4) = 14
// Using built-in functions and constants
let result = interp("sin(pi/4) + cos(pi/4)", None).unwrap();
assert!(result - 1.414 < 0.001); // Approximately √2
}
§Using Variables and Constants
// Example of using variables and constants:
// Create an evaluation context
let mut ctx = EvalContext::new();
// Add variables
ctx.set_parameter("x", 5.0);
ctx.set_parameter("y", 10.0);
// Add constants - these won't change once set
ctx.constants.insert("FACTOR".to_string(), 2.5);
// Evaluate expression with variables and constants
let result = interp("x + y * FACTOR", Some(Rc::new(ctx))).unwrap();
// Result: 30.0 (5 + (10 * 2.5) = 30)
§Arrays and Object Attributes
// Example for arrays and object attributes:
// Add an array
ctx.arrays.insert("data".to_string(), vec![10.0, 20.0, 30.0, 40.0, 50.0]);
// Add an object with attributes
let mut point = HashMap::new();
point.insert("x".to_string(), 3.0);
point.insert("y".to_string(), 4.0);
ctx.attributes.insert("point".to_string(), point);
// Access array elements in expressions
interp("data[2]", Some(Rc::new(ctx))).unwrap(); // Returns 30.0
// Access attributes in expressions
interp("point.x + point.y", Some(Rc::new(ctx))).unwrap(); // Returns 7.0
// Combine array and attribute access in expressions
interp("sqrt(point.x^2 + point.y^2) + data[0]", Some(Rc::new(ctx))).unwrap();
// Result: sqrt(3^2 + 4^2) + 10 = 5 + 10 = 15
§Custom Functions
§Native Functions
extern crate alloc;
use exp_rs::context::EvalContext;
use exp_rs::engine::interp;
use alloc::rc::Rc;
fn main() {
let mut ctx = EvalContext::new();
// Register a native function that sums all arguments
ctx.register_native_function("sum", 3, |args| {
args.iter().sum()
});
// Use the custom function
let result = interp("sum(1, 2, 3)", Some(Rc::new(ctx))).unwrap();
assert_eq!(result, 6.0);
}
§Expression Functions
extern crate alloc;
use exp_rs::context::EvalContext;
use exp_rs::engine::interp;
use alloc::rc::Rc;
fn main() {
let mut ctx = EvalContext::new();
// Register an expression function
ctx.register_expression_function(
"hypotenuse",
&["a", "b"],
"sqrt(a^2 + b^2)"
).unwrap();
// Use the custom function
let result = interp("hypotenuse(3, 4)", Some(Rc::new(ctx))).unwrap();
assert_eq!(result, 5.0);
}
§Performance Optimization with AST Caching
For repeated evaluations of the same expression with different variables:
extern crate alloc;
use exp_rs::context::EvalContext;
use exp_rs::engine::interp;
use alloc::rc::Rc;
fn main() {
let mut ctx = EvalContext::new();
ctx.enable_ast_cache(); // Enable AST caching
// First evaluation will parse and cache the AST
ctx.set_parameter("x", 1.0);
let result1 = interp("x^2 + 2*x + 1", Some(Rc::new(ctx.clone()))).unwrap();
assert_eq!(result1, 4.0); // 1^2 + 2*1 + 1 = 4
// Subsequent evaluations with the same expression will reuse the cached AST
ctx.set_parameter("x", 2.0);
let result2 = interp("x^2 + 2*x + 1", Some(Rc::new(ctx.clone()))).unwrap();
assert_eq!(result2, 9.0); // 2^2 + 2*2 + 1 = 9
// This is much faster for repeated evaluations
ctx.set_parameter("x", 3.0);
let result3 = interp("x^2 + 2*x + 1", Some(Rc::new(ctx))).unwrap();
assert_eq!(result3, 16.0); // 3^2 + 2*3 + 1 = 16
}
§Using on Embedded Systems (no_std)
exp-rs is designed to work in no_std environments with the alloc crate:
extern crate alloc;
use exp_rs::interp;
use exp_rs::EvalContext;
use alloc::rc::Rc;
// When using in a no_std environment with alloc:
// This defines an FFI function that can be called from C code
pub extern "C" fn evaluate_expression(x: f32, y: f32) -> f32 {
// Create an evaluation context
let mut ctx = EvalContext::new();
// Set parameters
ctx.set_parameter("x", x as f64);
ctx.set_parameter("y", y as f64);
// Evaluate the expression
let result = interp("sqrt(x^2 + y^2)", Some(Rc::new(ctx))).unwrap();
// Convert back to f32 for C compatibility
result as f32
}
§Disabling Built-in Math Functions
For embedded systems where you want to provide your own math implementations:
// In Cargo.toml:
// exp-rs = { version = "0.1", default-features = false, features = ["f32", "no-builtin-math"] }
extern crate alloc;
use exp_rs::context::EvalContext;
use exp_rs::engine::interp;
use alloc::rc::Rc;
use libm::{sin, cos};
fn main() {
let mut ctx = EvalContext::new();
// Register custom math functions using standard Rust methods
ctx.register_native_function("sin", 1, |args| args[0].sin());
ctx.register_native_function("cos", 1, |args| args[0].cos());
ctx.register_native_function("sqrt", 1, |args| args[0].sqrt());
// Now you can use these functions
let result = interp("sin(0.5) + cos(0.5)", Some(Rc::new(ctx))).unwrap();
assert_eq!(result, sin(0.5) + cos(0.5)); // sin(0.5) + cos(0.5)
println!("Result: {}", result);
}
§Error Handling
Comprehensive error handling is provided:
extern crate alloc;
use exp_rs::context::EvalContext;
use exp_rs::engine::interp;
use exp_rs::error::ExprError;
use alloc::rc::Rc;
fn main() {
let ctx = EvalContext::new();
// Handle syntax errors
match interp("2 + * 3", Some(Rc::new(ctx.clone()))) {
Ok(_) => println!("Unexpected success"),
Err(ExprError::Syntax(msg)) => println!("Syntax error: {}", msg),
Err(e) => println!("Unexpected error: {:?}", e),
}
// Handle unknown variables
match interp("x + 5", Some(Rc::new(ctx.clone()))) {
Ok(_) => println!("Unexpected success"),
Err(ExprError::UnknownVariable { name }) => println!("Unknown variable: {}", name),
Err(e) => println!("Unexpected error: {:?}", e),
}
// Handle division by zero
match interp("1 / 0", Some(Rc::new(ctx))) {
Ok(result) => {
if result.is_infinite() {
println!("Division by zero correctly returned infinity")
} else {
println!("Unexpected result: {}", result)
}
},
Err(e) => println!("Unexpected error: {:?}", e),
}
}
§Supported Grammar
exp-rs supports a superset of the original TinyExpr grammar, closely matching the tinyexpr++ grammar, including:
- Multi-character operators:
&&
,||
,==
,!=
,<=
,>=
,<<
,>>
,<<<
,>>>
,**
,<>
- Logical, comparison, bitwise, and exponentiation operators with correct precedence and associativity
- List expressions and both comma and semicolon as separators
- Function call syntax supporting both parentheses and juxtaposition
- Array and attribute access
- Right-associative exponentiation
§Operator Precedence and Associativity
From lowest to highest precedence:
Precedence | Operators | Associativity |
---|---|---|
1 | , ; | Left |
2 | ` | |
3 | && | Left |
4 | ` | ` |
6 | & | Left (bitwise AND) |
7 | == != < > <= >= <> | Left (comparison) |
8 | << >> <<< >>> | Left (bit shifts) |
9 | + - | Left |
10 | * / % | Left |
14 | unary + - ~ | Right (unary) |
15 | ^ | Right |
16 | ** | Right |
§Built-in Functions
The following functions are available by default (unless no-builtin-math
is enabled):
- Trigonometric:
sin
,cos
,tan
,asin
,acos
,atan
,atan2
- Hyperbolic:
sinh
,cosh
,tanh
- Exponential/Logarithmic:
exp
,log
,log10
,ln
- Power/Root:
sqrt
,pow
- Rounding:
ceil
,floor
- Comparison:
max
,min
- Misc:
abs
,sign
§Built-in Constants
pi
: 3.14159… (π)e
: 2.71828… (Euler’s number)
§Feature Flags
no-builtin-math
: Disables all built-in math functions. You must register your own.f32
: Use 32-bit floating point (single precision) for calculationsf64
: Use 64-bit floating point (double precision) for calculations (default)
Only one of f32
or f64
can be enabled at a time.
§Embedded Systems Support
exp-rs provides extensive support for embedded systems:
no_std
compatible with thealloc
crate- Configurable precision with
f32
/f64
options - Option to disable built-in math functions and provide custom implementations
- Integration with CMSIS-DSP for ARM Cortex-M processors
- Meson build system integration for cross-compilation
- QEMU test harness for validating on ARM hardware
- Optional C FFI for calling from non-Rust code
§Attribution
exp-rs began as a fork of tinyexpr-rs by Krzysztof Kondrak, which itself was a port of the TinyExpr C library by Lewis Van Winkle (codeplea). As the functionality expanded beyond the scope of the original TinyExpr, it evolved into a new project with additional features inspired by tinyexpr-plusplus.
Re-exports§
Modules§
- constants
- context
- engine
- error
- Error types and handling for the exp-rs crate.
- eval
- expression_
functions - Expression functions implementation for the exp-rs library.
- ffi
- C FFI interface for exp-rs.
- functions
- Built-in mathematical functions for expression evaluation.
- lexer
- types
- Type definitions for the expression parser and evaluator.
Macros§
- assert_
approx_ eq - Utility macro to check if two floating point values are approximately equal within a specified epsilon. Supports optional format arguments like assert_eq!.
Structs§
- Box
- A pointer type that uniquely owns a heap allocation of type
T
. - String
- A UTF-8–encoded, growable string.
- Vec
- A contiguous growable array type, written as
Vec<T>
, short for ‘vector’.
Traits§
- ToString
- A trait for converting a value to a
String
.