Evalexpr is an expression evaluator and tiny scripting language in Rust. It has a small and easy to use interface and can be easily integrated into any application. It is very lightweight and comes with no further dependencies. Evalexpr is available on crates.io, and its API Documentation is available on docs.rs.
Minimum Supported Rust Version: 1.65.0
Add evalexpr as dependency to your Cargo.toml:
[dependencies]
evalexpr = "<desired version>"
Then you can use evalexpr to evaluate expressions like this:
use evalexpr::*;
assert_eq!(eval("1 + 2 + 3"), Ok(Value::from_int(6)));
// `eval` returns a variant of the `Value` enum,
// while `eval_[type]` returns the respective type directly.
// Both can be used interchangeably.
assert_eq!(eval_int("1 + 2 + 3"), Ok(6));
assert_eq!(eval("1 /* inline comments are supported */ - 2 * 3 // as are end-of-line comments"), Ok(Value::from_int(-5)));
assert_eq!(eval("1.0 + 2 * 3"), Ok(Value::from_float(7.0)));
assert_eq!(eval("true && 4 > 2"), Ok(Value::from(true)));
You can chain expressions and assign to variables like this:
use evalexpr::*;
let mut context = HashMapContext::<DefaultNumericTypes>::new();
// Assign 5 to a like this
assert_eq!(eval_empty_with_context_mut("a = 5", &mut context), Ok(EMPTY_VALUE));
// The HashMapContext is type safe, so this will fail now
assert_eq!(eval_empty_with_context_mut("a = 5.0", &mut context),
Err(EvalexprError::expected_int(Value::from_float(5.0))));
// We can check which value the context stores for a like this
assert_eq!(context.get_value("a"), Some(&Value::from_int(5)));
// And use the value in another expression like this
assert_eq!(eval_int_with_context_mut("a = a + 2; a", &mut context), Ok(7));
// It is also possible to save a bit of typing by using an operator-assignment operator
assert_eq!(eval_int_with_context_mut("a += 2; a", &mut context), Ok(9));
And you can use variables and functions in expressions like this:
use evalexpr::*;
let context: HashMapContext<DefaultNumericTypes> = context_map! {
"five" => int 5,
"twelve" => int 12,
"f" => Function::new(|argument| {
if let Ok(int) = argument.as_int() {
Ok(Value::Int(int / 2))
} else if let Ok(float) = argument.as_float() {
Ok(Value::Float(float / 2.0))
} else {
Err(EvalexprError::expected_number(argument.clone()))
}
}),
"avg" => Function::new(|argument| {
let arguments = argument.as_tuple()?;
if let (Value::Int(a), Value::Int(b)) = (&arguments[0], &arguments[1]) {
Ok(Value::Int((a + b) / 2))
} else {
Ok(Value::Float((arguments[0].as_number()? + arguments[1].as_number()?) / 2.0))
}
})
}.unwrap(); // Do proper error handling here
assert_eq!(eval_with_context("five + 8 > f(twelve)", &context), Ok(Value::from(true)));
// `eval_with_context` returns a variant of the `Value` enum,
// while `eval_[type]_with_context` returns the respective type directly.
// Both can be used interchangeably.
assert_eq!(eval_boolean_with_context("five + 8 > f(twelve)", &context), Ok(true));
assert_eq!(eval_with_context("avg(2, 4) == 3", &context), Ok(Value::from(true)));
You can also precompile expressions like this:
use evalexpr::*;
let precompiled = build_operator_tree::<DefaultNumericTypes>("a * b - c > 5").unwrap(); // Do proper error handling here
let mut context = context_map! {
"a" => int 6,
"b" => int 2,
"c" => int 3,
}.unwrap(); // Do proper error handling here
assert_eq!(precompiled.eval_with_context(&context), Ok(Value::from(true)));
context.set_value("c".into(), Value::from_int(8)).unwrap(); // Do proper error handling here
assert_eq!(precompiled.eval_with_context(&context), Ok(Value::from(false)));
// `Node::eval_with_context` returns a variant of the `Value` enum,
// while `Node::eval_[type]_with_context` returns the respective type directly.
// Both can be used interchangeably.
assert_eq!(precompiled.eval_boolean_with_context(&context), Ok(false));
While primarily meant to be used as a library, evalexpr is also available as a command line tool.
It can be installed and used as follows:
cargo install evalexpr
evalexpr 2 + 3 # outputs `5` to stdout.
This crate offers a set of binary and unary operators for building expressions. Operators have a precedence to determine their order of evaluation, where operators of higher precedence are evaluated first. The precedence should resemble that of most common programming languages, especially Rust. Variables and values have a precedence of 200, and function literals have 190.
Supported binary operators:
| Operator | Precedence | Description |
|---|---|---|
| ^ | 120 | Exponentiation |
| * | 100 | Product |
| / | 100 | Division (integer if both arguments are integers, otherwise float) |
| % | 100 | Modulo (integer if both arguments are integers, otherwise float) |
| + | 95 | Sum or String Concatenation |
| - | 95 | Difference |
| < | 80 | Lower than |
| > | 80 | Greater than |
| <= | 80 | Lower than or equal |
| >= | 80 | Greater than or equal |
| == | 80 | Equal |
| != | 80 | Not equal |
| && | 75 | Logical and |
| || | 70 | Logical or |
| = | 50 | Assignment |
| += | 50 | Sum-Assignment or String-Concatenation-Assignment |
| -= | 50 | Difference-Assignment |
| *= | 50 | Product-Assignment |
| /= | 50 | Division-Assignment |
| %= | 50 | Modulo-Assignment |
| ^= | 50 | Exponentiation-Assignment |
| &&= | 50 | Logical-And-Assignment |
| ||= | 50 | Logical-Or-Assignment |
| , | 40 | Aggregation |
| ; | 0 | Expression Chaining |
Supported unary operators:
| Operator | Precedence | Description |
|---|---|---|
| - | 110 | Negation |
| ! | 110 | Logical not |
Operators that take numbers as arguments can either take integers or floating point numbers. If one of the arguments is a floating point number, all others are converted to floating point numbers as well, and the resulting value is a floating point number as well. Otherwise, the result is an integer. An exception to this is the exponentiation operator that always returns a floating point number. Example:
use evalexpr::*;
assert_eq!(eval("1 / 2"), Ok(Value::from_int(0)));
assert_eq!(eval("1.0 / 2"), Ok(Value::from_float(0.5)));
assert_eq!(eval("2^2"), Ok(Value::from_float(4.0)));
The aggregation operator aggregates a set of values into a tuple. A tuple can contain arbitrary values, it is not restricted to a single type. The operator is n-ary, so it supports creating tuples longer than length two. Example:
use evalexpr::*;
assert_eq!(eval("1, \"b\", 3"),
Ok(Value::from(vec![Value::from_int(1), Value::from("b"), Value::from_int(3)])));
To create nested tuples, use parentheses:
use evalexpr::*;
assert_eq!(eval("1, 2, (true, \"b\")"), Ok(Value::from(vec![
Value::from_int(1),
Value::from_int(2),
Value::from(vec![
Value::from(true),
Value::from("b")
])
])));
This crate features the assignment operator, that allows expressions to store their result in a variable in the expression context. If an expression uses the assignment operator, it must be evaluated with a mutable context.
Note that assignments are type safe when using the HashMapContext.
That means that if an identifier is assigned a value of a type once, it cannot be assigned a value of another type.
use evalexpr::*;
let mut context = HashMapContext::<DefaultNumericTypes>::new();
assert_eq!(eval_with_context("a = 5", &context), Err(EvalexprError::ContextNotMutable));
assert_eq!(eval_empty_with_context_mut("a = 5", &mut context), Ok(EMPTY_VALUE));
assert_eq!(eval_empty_with_context_mut("a = 5.0", &mut context),
Err(EvalexprError::expected_int(Value::from_float(5.0))));
assert_eq!(eval_int_with_context("a", &context), Ok(5));
assert_eq!(context.get_value("a"), Some(Value::from_int(5)).as_ref());
For each binary operator, there exists an equivalent operator-assignment operator. Here are some examples:
use evalexpr::*;
assert_eq!(eval_int("a = 2; a *= 2; a += 2; a"), Ok(6));
assert_eq!(eval_float("a = 2.2; a /= 2.0 / 4 + 1; a"), Ok(2.2 / (2.0 / 4.0 + 1.0)));
assert_eq!(eval_string("a = \"abc\"; a += \"def\"; a"), Ok("abcdef".to_string()));
assert_eq!(eval_boolean("a = true; a &&= false; a"), Ok(false));
The expression chaining operator works as one would expect from programming languages that use the semicolon to end statements, like Rust, C or Java.
It has the special feature that it returns the value of the last expression in the expression chain.
If the last expression is terminated by a semicolon as well, then Value::Empty is returned.
Expression chaining is useful together with assignment to create small scripts.
use evalexpr::*;
let mut context = HashMapContext::<DefaultNumericTypes>::new();
assert_eq!(eval("1;2;3;4;"), Ok(Value::Empty));
assert_eq!(eval("1;2;3;4"), Ok(Value::from_int(4)));
// Initialization of variables via script
assert_eq!(eval_empty_with_context_mut("hp = 1; max_hp = 5; heal_amount = 3;", &mut context),
Ok(EMPTY_VALUE));
// Precompile healing script
let healing_script = build_operator_tree("hp = min(hp + heal_amount, max_hp); hp").unwrap(); // Do proper error handling here
// Execute precompiled healing script
assert_eq!(healing_script.eval_int_with_context_mut(&mut context), Ok(4));
assert_eq!(healing_script.eval_int_with_context_mut(&mut context), Ok(5));
An expression evaluator that just evaluates expressions would be useful already, but this crate can do more. It allows using variables, assignments, statement chaining and user-defined functions within an expression. When assigning to variables, the assignment is stored in a context. When the variable is read later on, it is read from the context. Contexts can be preserved between multiple calls to eval by creating them yourself. Here is a simple example to show the difference between preserving and not preserving context between evaluations:
use evalexpr::*;
assert_eq!(eval("a = 5;"), Ok(Value::from(())));
// The context is not preserved between eval calls
assert_eq!(eval("a"), Err(EvalexprError::VariableIdentifierNotFound("a".to_string())));
let mut context = HashMapContext::<DefaultNumericTypes>::new();
assert_eq!(eval_with_context_mut("a = 5;", &mut context), Ok(Value::from(())));
// Assignments require mutable contexts
assert_eq!(eval_with_context("a = 6", &context), Err(EvalexprError::ContextNotMutable));
// The HashMapContext is type safe
assert_eq!(eval_with_context_mut("a = 5.5", &mut context),
Err(EvalexprError::ExpectedInt { actual: Value::from_float(5.5) }));
// Reading a variable does not require a mutable context
assert_eq!(eval_with_context("a", &context), Ok(Value::from_int(5)));
Note that the assignment is forgotten between the two calls to eval in the first example. In the second part, the assignment is correctly preserved. Note as well that to assign to a variable, the context needs to be passed as a mutable reference. When passed as an immutable reference, an error is returned.
Also, the HashMapContext is type safe.
This means that assigning to a again with a different type yields an error.
Type unsafe contexts may be implemented if requested.
For reading a, it is enough to pass an immutable reference.
Contexts can also be manipulated in code. Take a look at the following example:
use evalexpr::*;
let mut context = HashMapContext::<DefaultNumericTypes>::new();
// We can set variables in code like this...
context.set_value("a".into(), Value::from_int(5));
// ...and read from them in expressions
assert_eq!(eval_int_with_context("a", &context), Ok(5));
// We can write or overwrite variables in expressions...
assert_eq!(eval_with_context_mut("a = 10; b = 1.0;", &mut context), Ok(().into()));
// ...and read the value in code like this
assert_eq!(context.get_value("a"), Some(&Value::from_int(10)));
assert_eq!(context.get_value("b"), Some(&Value::from_float(1.0)));
// ...and remove the value in code like this
assert_eq!(context.remove_value("a"), Ok(Some(Value::from_int(10))));
// ...and if the value does not exist when removing, it returns None.
assert_eq!(context.remove_value("a"), Ok(None));
Contexts are also required for user-defined functions.
Those can be passed one by one with the set_function method, but it might be more convenient to use the context_map! macro instead:
```rust use evalexpr::*;
let context: HashMapContext = context_map!{ "f" => Function::new(|args| Ok(Value::from_int(args.as_in
$ claude mcp add evalexpr \
-- python -m otcore.mcp_server <graph>