mathematical expression evaluator.
use mexe::eval;
fn main() {
let forty_six = eval("(5 * 8) + 6").unwrap();
let two = eval("1 + 1").unwrap();
println!("{} & {}", forty_six, two);
assert_eq!(forty_six, 46.0);
assert_eq!(two, 2.0);
}Note: the above assert_eqs work, but for float comparison in general use a
crate such as float-cmp.
If you need to evaluate simple arithmetic expressions, this crate offers a fast and lightweight solution.
In our current benchmarks,
it's about 4-10x faster than meval, about 2x faster than fasteval, and
the fully-featured evalexpr is generally the slowest. Note that those crates
do much more than mexe -- especially evalexpr. Our focus on a very small
problem makes it easier for us to ship a fast and lean library.
- sum
- subtraction
- multiplication
- division
- integers
- floats
- parentheses
- arbitrary whitespace
Floats are represented as X.Y where X and Y are non-empty sequences of
digits. The notation with exponents for floats or omitting either side of the
point is not accepted.
- Minimal
- Fast: O(n)
- No dependencies
- Minimal allocations
- Thoroughly tested
Unit tests and integration tests:
cargo test
We leverage the glc crate to generate valid
random inputs for mexe. The command below will run an ignored integration test
that runs indefinitely and shows the output in the terminal until you stop it
with CTRL+C:
cargo test --test integration without_bounds -- --nocapture --ignored
Benchmarks:
cargo bench -- bench_cmp # comparison with other crates
cargo bench -- bench_mexe # only mexe
Fuzz tests have been ran with cargo-fuzz.
To run it yourself, you need to install the nightly toolchain
(rustup toolchain install nightly) and the tool itself:
cargo install cargo-fuzz (check for more detailed instructions and
dependencies in the project's readme).
After that run:
cargo fuzz init
cargo fuzz add fn_eval
Go to fuzz/fuzz_targets/fn_eval.rs and paste this code:
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
// fuzzed code goes here
if let Ok(text) = std::str::from_utf8(data) {
let _ = mexe::eval(text);
}
});Now finally run:
cargo +nightly fuzz run fn_eval
E -> T E'
E' -> + T E'
E' -> - T E'
E' -> ε
T -> F T'
T' -> * F T'
T' -> / F T'
T' -> ε
F -> ( E )
F -> n
F -> - ( E )
F -> - n
where ε is the empty string and n is a terminal number token. Grammar idea
adapted from this post.
Our first implementation uses an LL(1) parser.