MEXPRP

Crates.io Docs.rs License

A math expression parsing and evaluation library for Rust with the goal of being simple to use, yet powerful.

API docs here. Also see the examples/ directory.

Motivation

The main reason I wrote MEXPRP was for a 3D equation grapher I've been working on. Now that's not near done, so it's being saved for a future post. Of course, I could've used an existing Rust expression parser, but the only other one was a 0.1.0 library that had what I considered I somewhat strange API. Also, for some reason I've been kind of interested in keeping the dependencies down for the 3D equation grapher I was talking about. Anyway, now there's at least two 0.1.0 Rust math expression libraries with API's that can be considered strange.

Features

Usage

The simplest, but not always the most efficient, way to use MEXPRP is with mexprp::eval(). This function takes a &str as an argument, parses it, and evaluates it. E.g. like this:

rust mexprp::eval("(2 + 7) / 3")? // 3.0

MEXPRP also supports some functions and constants.

rust mexprp::eval("sin(max(2, 3, pi))")? // 0.0

A "better" way to evaluate an expression is to compile it first, with the Expression struct. This has the advantage of retaining a completely parsed and organized instance of an expression for faster evaluation.

rust let expr = Expression::parse("3 ^ (4 - 1)")?; expr.eval()? // 27.0

But what's the point of being able to evaluate the same expression over and over again? Well, you can evaluate expressions with variables set to a specific value by evaluating it with a context.

rust let expr = Expression::parse("3x / 6")?; let mut ctx = Context::new(); ctx.set_var("x", 8.0); expr.eval_ctx(&ctx)?; // 4.0 ctx.set_var("x", 10.0); expr.eval_ctx(&ctx)?; // 5.0

It's also possible to define custom functions. When defining custom functions, you should be aware of a minor drawback. Expressions need to be parsed with the custom context containing the custom function definitions in order for them to recognized as functions instead of variables during parsing. The reason for this is that without a list of functions present at parse time, the parser has no way to know if a name in the expression is a variable or a function. For example, in foo(3 + 5), foo could be a function called with one argument, or a variable multiplied by (3 + 5).

In order to bypass this, simply create a context before parsing the expression, then use Expression::parse_ctx() to parse the expression. The expression still has to be evaluated with a context as well. This context can differ from the one it was parsed with, but it should still contain any definitions necessary for the expression to be evaluated, or attempts to evaluate it will fail.

There are two ways to define a function. A function is anything that implements the func::Func trait. There is a blanket impl of this trait for all Fn(&[Term], &Context) -> Calculation, allowing you to pass in a closure. You can also pass in an empty struct that you implement Func for manually, which is no harder then writing it as a closure, but can be more flexible. The Func trait consists of one method, with the signature fn(args: &[Term], ctx: &Context) -> Calculation. (A Term is just an Expression without the metadata.) In case the arguments given were not properly formatted (e.g. there was an incorrect amount given), you can just return Err(MathError::IncorrectArguments).

```rust let mut ctx = Context::new(); ctx.set_func("funca", |args: &[Term], ctx: &Context| -> Calculation { if args.len() != 2 { return Err(MathError::IncorrectArguments) }

let a = args[0].eval(ctx)?;
let b = args[1].eval(ctx)?;
Ok(a + b)

});

struct FuncB; impl Func for FuncB { fn eval(&self, args: &[Term], ctx: &Context) -> Calculation { if args.is_empty() { return Err(MathError::IncorrectArguments) }

    let mut sum = 0.0;
    for arg in args {
        sum += arg.eval(ctx)?;
    }
    Ok(sum)
}

} ctx.set_func("funcb", FuncB);

let expr = Expression::parsectx("funca(5, funcb(3, 4, 3))", &ctx)?; expr.evalctx(&ctx)?; // 15.0

ctx.set_func("funca", |args: &[Term], ctx: &Context| -> Calculation { if args.len() != 2 { return Err(MathError::IncorrectArguments) }

let a = args[0].eval(ctx)?;
let b = args[1].eval(ctx)?;
Ok(a - b)

});

expr.eval_ctx(&ctx)?; // -5.0 ```

License

MPL-2.0