4 releases
0.1.0 | Feb 11, 2021 |
---|---|
0.0.7 |
|
0.0.5 |
|
0.0.4 |
|
#1297 in Math
140 downloads per month
1.5MB
11K
SLoC
exprtk_rs
Rust bindings to ExprTk library.
Requires at least Runst version 1.37.
- Documentation
- Run
cargo bench
to compare execution times (also with native execution) - Fuzzing was used to further validate the API
lib.rs
:
This crate provides bindings for the ExprTk library.
For an overview of the data structures see the ExprTk main page.
While exprtk-sys
maps most functions of the library to Rust, the high level bindings
were considerably simplified. Each Expression owns a
SymbolTable, they cannot be shared between different instances,
and multiple symbol tables per expression are not possible.
Variables are owned by the SymbolTable
instance. The functions for adding variables
(add_variable()), strings
(add_stringvar()), vectors
(add_vector()) all return
usize
, which is a variable ID representing the index in of the value in
an internal data structure. It can be used to later get symbol values and modify them.
Scalars are either modified via mutable references, or via std::cell::Cell
types without
the requirement of mutable access to the SymbolTable
.
Strings are changed using set_string(),
which requires mutable access.
Since access and mutation through variable IDs requires a bounds check, these operations
are slower than direct modification through pointers, as done in C++. The performance impact
is naturally more severe for small expressions with fast running times, but seems not too
problematic in most cases. Run cargo bench
to see the impact (compare with unsafe variant).
For each data type (scalars, strings and vectors), access IDs start at zero
and are incremented on addition of new variables of the given type.
As there is no guarantee that double
is always f64
, the c_double
type is used all
over the library. Other precisions are currently not supported.
ExprTk does not handle non-ASCII encodings, therefore variable names and formulae are checked for non-ASCII characters or null bytes and will fail with an error.
Examples:
This code corresponds to the example 1 in the ExprTk documentation:
use exprtk_rs::*;
let expression_string = "clamp(-1.0,sin(2 * pi * x) + cos(x / 2 * pi),+1.0)";
let mut symbol_table = SymbolTable::new();
symbol_table.add_constants();
let var_id = symbol_table.add_variable("x", 0.).unwrap().unwrap();
let mut expression = Expression::new(expression_string, symbol_table).unwrap();
// this value is a reference to a std::cell::Cell that can be changed
expression.symbols().value_cell(var_id).set(-5.);
while expression.symbols().value(var_id) <= 5. {
let y = expression.value();
println!("{}\t{}", expression.symbols().value(var_id), y);
*expression.symbols_mut().value_mut(var_id) += 0.001;
}
Unknown variables
Unknown variables encountered in an expression can be automatically added to the symbol table.
The function Expression::parse_vars
will return a Vec
containing the newly added variable
names and their variable IDs.
This works only for regular variables, not for strings or vectors.
use exprtk_rs::*;
let expr_string = "a*x^2 + b*x + c";
let (mut expr, unknown_vars) = Expression::parse_vars(expr_string, SymbolTable::new()).unwrap();
assert_eq!(
unknown_vars,
vec![("a".to_string(), 0), ("x".to_string(), 1), ("b".to_string(), 2), ("c".to_string(), 3)]
);
// modify the values
expr.symbols().value_cell(0).set(2.); // a
expr.symbols().value_cell(2).set(3.); // b
expr.symbols().value_cell(3).set(1.); // c
expr.symbols().value_cell(1).set(5.); // x
assert_eq!(expr.value(), 66.);
Example using strings
use exprtk_rs::*;
let mut symbol_table = SymbolTable::new();
let s1_id = symbol_table.add_stringvar("s1", "Hello").unwrap().unwrap();
let s2_id = symbol_table.add_stringvar("s2", "world!").unwrap().unwrap();
// concatenation
let mut expr = Expression::new("s1 + ' ' + s2 == 'Hello world!'", symbol_table).unwrap();
// a boolean `true` is represented by `1`
assert_eq!(expr.value(), 1.);
// Modifying a string
expr.symbols_mut().set_string(s1_id, "");
assert_eq!(expr.value(), 0.);
Functions
There is currently the possibility to add functions/closures with up to ten scalar arguments. Example:
use exprtk_rs::*;
let mut symbol_table = SymbolTable::new();
symbol_table.add_func2("add", |x, y| x + y);
symbol_table.add_variable("x", 1.).unwrap();
let mut expr = Expression::new("add(x, 1)", symbol_table).unwrap();
assert_eq!(expr.value(), 2.);