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_eq
s 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.