## mexe

Simple arithmetic expression evaluator

### 10 releases

 0.2.0 Sep 7, 2022 Sep 2, 2022 Aug 28, 2022 Aug 26, 2022 Jan 23, 2022

#125 in Parser tooling

23KB
362 lines

mathematical expression evaluator.

## How to Use

``````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`.

## Why?

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.

## Includes

• 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.

## Goals

• Minimal
• Fast: O(n)
• No dependencies
• Minimal allocations
• Thoroughly tested

## Run Tests and Benchmarks

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
``````

### Running the fuzzer

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
``````

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
``````

## Grammar

``````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.