nanoexpr 0.2.1

Fast no_std math expression parser and evaluator with zero heap allocation: formulas with variables and functions for microcontrollers, WebAssembly and C, in f32, f64, integers or calculator-exact decimals
Documentation
use nanoexpr::{Error, ErrorKind, Evaluator, Functions, PercentSign, Span};

type Call = fn(&str, &[i64]) -> Option<Result<i64, ErrorKind>>;

fn context() -> ([(&'static str, i64); 3], Functions<Call>) {
    (
        [("x", 3), ("y", 5), ("e", 2)],
        Functions(|name, arguments| match (name, arguments) {
            ("square", [value]) => Some(Ok(value * value)),
            ("x", [value]) => Some(Ok(value + 100)),
            _ => None,
        }),
    )
}

fn implicit(input: &str) -> Result<i64, Error> {
    let context = context();
    Evaluator::<_>::new(&context)
        .implicit_multiplication(true)
        .evaluate(input)
}

fn explicit(input: &str) -> Result<i64, Error> {
    let context = context();
    Evaluator::<_>::new(&context).evaluate(input)
}

#[test]
fn operands_multiply() {
    for (input, expected) in [
        ("2x", 6),
        ("2 x", 6),
        ("x y", 15),
        ("xy", 0),
        ("2(3 + 4)", 14),
        ("(1 + 2)(3 + 4)", 21),
        ("(2)x", 6),
        ("3square(4)", 48),
        ("square(2)square(3)", 36),
        ("2x y", 30),
        ("2e", 4),
    ] {
        let expected = if input == "xy" {
            Err(Error::new(ErrorKind::UnknownVariable, Span::new(0, 2)))
        } else {
            Ok(expected)
        };
        assert_eq!(implicit(input), expected, "{input}");
    }
}

#[test]
fn precedence_is_that_of_explicit_multiplication() {
    for (input, expected) in [
        ("12/2x", 18),
        ("12/2*x", 18),
        ("2x^2", 18),
        ("-2x", -6),
        ("-2^2x", -12),
        ("1 + 2x", 7),
        ("2x mod 4", 2),
        ("x(2)", 102),
        ("2x(2)", 204),
        ("2 x (2)", 204),
        ("x < 2x", 1),
        ("0 ? 1 : 2x", 6),
        ("2-x", -1),
    ] {
        assert_eq!(implicit(input), Ok(expected), "{input}");
    }
}

#[test]
fn numbers_after_operands_stay_errors() {
    assert_eq!(
        implicit("2 3"),
        Err(Error::new(ErrorKind::UnexpectedToken, Span::new(2, 3)))
    );
    assert_eq!(
        implicit("x 2"),
        Err(Error::new(ErrorKind::UnexpectedToken, Span::new(2, 3)))
    );
    assert_eq!(
        implicit("(1)2"),
        Err(Error::new(ErrorKind::UnexpectedToken, Span::new(3, 4)))
    );
}

#[test]
fn errors_cover_the_product() {
    assert_eq!(
        implicit("1 + 2z"),
        Err(Error::new(ErrorKind::UnknownVariable, Span::new(5, 6)))
    );
    assert_eq!(
        implicit("1 + 9223372036854775807x"),
        Err(Error::new(ErrorKind::Overflow, Span::new(4, 24)))
    );
    assert_eq!(
        implicit("2(1"),
        Err(Error::new(ErrorKind::UnclosedParenthesis, Span::new(1, 2)))
    );
    assert_eq!(implicit("0 && 2(1 / 0)"), Ok(0));
}

#[test]
fn is_off_by_default() {
    assert_eq!(
        explicit("2x"),
        Err(Error::new(ErrorKind::UnexpectedToken, Span::new(1, 2)))
    );
    assert_eq!(
        explicit("2(3)"),
        Err(Error::new(ErrorKind::UnexpectedToken, Span::new(1, 2)))
    );
    assert_eq!(explicit("x(2)"), Ok(102));
}

#[test]
fn works_with_percentages() {
    let context = context();
    let calculator = Evaluator::<_>::new(&context)
        .implicit_multiplication(true)
        .percent_sign(PercentSign::Percentage);
    assert_eq!(calculator.evaluate::<i64>("200%x"), Ok(6));
    assert_eq!(
        calculator.evaluate::<i64>("x 7 mod 4"),
        Err(Error::new(ErrorKind::UnexpectedToken, Span::new(2, 3)))
    );
}

#[test]
fn depth_stays_bounded() {
    let context = context();
    let evaluator = Evaluator::<_>::new(&context)
        .implicit_multiplication(true)
        .max_depth(3);
    assert_eq!(evaluator.evaluate::<i64>("x x x x x x"), Ok(729));
    assert_eq!(
        evaluator.evaluate::<i64>("x(((1)))").unwrap_err().kind,
        ErrorKind::TooDeep
    );
}

#[cfg(feature = "libm")]
#[test]
fn calculator_constants() {
    let calculator = Evaluator::<_>::new(&nanoexpr::Math).implicit_multiplication(true);
    assert_eq!(
        calculator.evaluate::<f64>("2pi"),
        Ok(2.0 * core::f64::consts::PI)
    );
    assert_eq!(calculator.evaluate::<f64>("2e3"), Ok(2000.0));
    assert_eq!(
        calculator.evaluate::<f64>("2e"),
        Ok(2.0 * core::f64::consts::E)
    );
    assert_eq!(calculator.evaluate::<f64>("3sqrt(16)"), Ok(12.0));
    assert_eq!(calculator.evaluate::<f64>("-2^2"), Ok(-4.0));
    assert_eq!(
        calculator.evaluate::<f64>("2 pi r",),
        Err(Error::new(ErrorKind::UnknownVariable, Span::new(5, 6)))
    );
}