Skip to content
/ mexe Public

Simple and fast arithmetic expression parser

License

Notifications You must be signed in to change notification settings

yds12/mexe

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

45 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

loc tests

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

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.

Similar Projects

Links