57 个版本 (稳定)

11.3.0 2023年12月9日
11.2.0 2023年11月17日
11.1.0 2023年8月19日
11.0.1 2023年7月26日
0.5.0 2019年3月19日

#1271 in 游戏开发

Download history 17699/week @ 2024-04-22 23260/week @ 2024-04-29 24670/week @ 2024-05-06 24601/week @ 2024-05-13 44500/week @ 2024-05-20 35619/week @ 2024-05-27 42897/week @ 2024-06-03 44852/week @ 2024-06-10 41971/week @ 2024-06-17 43039/week @ 2024-06-24 31210/week @ 2024-07-01 25575/week @ 2024-07-08 33905/week @ 2024-07-15 42495/week @ 2024-07-22 40804/week @ 2024-07-29 47587/week @ 2024-08-05

165,768 每月下载量
105 个包中 使用(43 个直接使用

MIT 许可证

220KB
3K SLoC

evalexpr

Version Downloads Project Status: Active – The project has reached a stable, usable state and is being actively developed. Coverage Status

Evalexpr 是一个 Rust 中的表达式评估器和小型脚本语言。它具有小巧且易于使用的接口,可以轻松集成到任何应用程序中。它非常轻量级,不依赖任何其他库。Evalexpr 可在 crates.io 上获取,其 API 文档 可在 docs.rs 上获取。

最低支持的 Rust 版本 1.65.0

快速入门

evalexpr 作为依赖项添加到 Cargo.toml

[dependencies]
evalexpr = "<desired version>"

然后您可以使用 evalexpr评估表达式 如此

use evalexpr::*;

assert_eq!(eval("1 + 2 + 3"), Ok(Value::from(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(-5)));
assert_eq!(eval("1.0 + 2 * 3"), Ok(Value::from(7.0)));
assert_eq!(eval("true && 4 > 2"), Ok(Value::from(true)));

您也可以 链式 表达式并将 赋值 如此

use evalexpr::*;

let mut context = HashMapContext::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(5.0))));
// We can check which value the context stores for a like this
assert_eq!(context.get_value("a"), Some(&Value::from(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));

您还可以在表达式中使用 变量函数 如此

use evalexpr::*;

let context = context_map! {
    "five" => 5,
    "twelve" => 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)));

您还可以 预编译 表达式如此

use evalexpr::*;

let precompiled = build_operator_tree("a * b - c > 5").unwrap(); // Do proper error handling here

let mut context = context_map! {
    "a" => 6,
    "b" => 2,
    "c" => 3
}.unwrap(); // Do proper error handling here
assert_eq!(precompiled.eval_with_context(&context), Ok(Value::from(true)));

context.set_value("c".into(), 8.into()).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));

命令行界面

虽然主要设计为库使用,但 evalexpr 也可用作命令行工具。以下是如何安装和使用它的说明

cargo install evalexpr
evalexpr 2 + 3 # outputs `5` to stdout.

功能

运算符

此包提供了一组二元和一元运算符,用于构建表达式。运算符的优先级决定了它们的评估顺序,优先级较高的运算符先被评估。优先级应类似于大多数编程语言,特别是 Rust。变量和值的优先级为 200,函数字面量的优先级为 190。

支持的二元运算符

运算符 优先级 描述
^ 120 指数运算
* 100 乘积
/ 100 除法(如果两个参数都是整数,则为整数,否则为浮点数)
% 100 取模(如果两个参数都是整数,则为整数,否则为浮点数)
+ 95 求和或字符串连接
- 95 差值
< 80 小于
> 80 大于
<= 80 小于等于
>= 80 大于等于
== 80 等于
!= 80 不等于
&& 75 逻辑与
|| 70 逻辑或
= 50 赋值
+= 50 求和赋值或字符串连接赋值
-= 50 差值赋值
*= 50 乘积赋值
/= 50 除法赋值
%= 50 模数赋值
^= 50 指数赋值
&&= 50 逻辑与赋值
||= 50 逻辑或赋值
, 40 聚合
; 0 表达式链

支持的单一操作符

运算符 优先级 描述
- 110 否定
! 110 逻辑非

接受数字作为参数的操作符可以接受整数或浮点数。如果其中一个参数是浮点数,其他所有参数也会转换为浮点数,并且结果也是浮点数。否则,结果是整数。这里有一个例外是指数操作符,它总是返回一个浮点数。示例

use evalexpr::*;

assert_eq!(eval("1 / 2"), Ok(Value::from(0)));
assert_eq!(eval("1.0 / 2"), Ok(Value::from(0.5)));
assert_eq!(eval("2^2"), Ok(Value::from(4.0)));

聚合操作符

聚合操作符将一组值聚合为一个元组。元组可以包含任意值,它不受单一类型的限制。操作符是n元的,因此它支持创建长度超过两个的元组。示例

use evalexpr::*;

assert_eq!(eval("1, \"b\", 3"),
           Ok(Value::from(vec![Value::from(1), Value::from("b"), Value::from(3)])));

要创建嵌套元组,请使用括号

use evalexpr::*;

assert_eq!(eval("1, 2, (true, \"b\")"), Ok(Value::from(vec![
    Value::from(1),
    Value::from(2),
    Value::from(vec![
        Value::from(true),
        Value::from("b")
    ])
])));

赋值操作符

此crate具有赋值操作符,允许表达式在表达式上下文中将它们的结果存储在变量中。如果一个表达式使用赋值操作符,则必须以可变上下文对其进行评估。

请注意,当使用HashMapContext时,赋值是类型安全的。这意味着一旦一个标识符被分配了一个类型的值,它就不能再分配另一个类型的值。

use evalexpr::*;

let mut context = HashMapContext::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(5.0.into())));
assert_eq!(eval_int_with_context("a", &context), Ok(5));
assert_eq!(context.get_value("a"), Some(5.into()).as_ref());

对于每个二元操作符,都存在一个等效的操作符赋值操作符。以下是一些示例

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));

表达式链操作符

表达式链操作符的作用类似于使用分号结束语句的编程语言,如RustCJava。它有一个特殊的功能,即返回表达式链中最后一个表达式的值。如果最后一个表达式也被分号终止,则返回Value::Empty。表达式链与赋值结合使用时非常有用,可以创建小的脚本。

use evalexpr::*;

let mut context = HashMapContext::new();
assert_eq!(eval("1;2;3;4;"), Ok(Value::Empty));
assert_eq!(eval("1;2;3;4"), Ok(4.into()));

// 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));

上下文

一个仅评估表达式的表达式评估器已经很有用,但此crate可以做得更多。它允许在表达式中使用变量赋值语句链用户定义的函数。在将变量赋值时,赋值会存储在上下文中。稍后读取变量时,它将从中读取上下文。可以通过自己创建上下文来在多个对eval的调用之间保留上下文。以下是一个简单的示例,以展示在评估之间保留和不保留上下文之间的差异

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::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(5.5) }));
// Reading a variable does not require a mutable context
assert_eq!(eval_with_context("a", &context), Ok(Value::from(5)));

请注意,第一个示例中的两次eval调用之间忘记了赋值。在第二部分中,赋值被正确地保留了。请注意,要将变量赋值,上下文需要作为可变引用传递。当以不可变引用传递时,将返回错误。

此外,HashMapContext也是类型安全的。这意味着将a再次分配为不同类型的值会导致错误。如果需要,可以实现类型不安全的上下文。对于读取a,只需要传递一个不可变引用。

也可以在代码中操作上下文。请看以下示例

use evalexpr::*;

let mut context = HashMapContext::new();
// We can set variables in code like this...
context.set_value("a".into(), 5.into());
// ...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(10)));
assert_eq!(context.get_value("b"), Some(&Value::from(1.0)));

上下文对于用户定义的函数也是必需的。这些可以通过set_function方法逐个传递,但使用context_map!宏可能更方便

use evalexpr::*;

let context = context_map!{
    "f" => Function::new(|args| Ok(Value::from(args.as_int()? + 5))),
}.unwrap_or_else(|error| panic!("Error creating context: {}", error));
assert_eq!(eval_int_with_context("f 5", &context), Ok(10));

有关用户定义函数的更多信息,请参阅相应的部分

内建函数

这个包提供了一组内置函数(请参阅下文以获取完整列表)。如果需要,可以通过以下方式禁用它们

use evalexpr::*;
let mut context = HashMapContext::new();
assert_eq!(eval_with_context("max(1,3)",&context),Ok(Value::from(3)));
context.set_builtin_functions_disabled(true).unwrap(); // Do proper error handling here
assert_eq!(eval_with_context("max(1,3)",&context),Err(EvalexprError::FunctionIdentifierNotFound(String::from("max"))));

并非所有上下文都支持启用或禁用内置函数。具体来说,EmptyContext 默认禁用内置函数,并且无法启用。对称地,EmptyContextWithBuiltinFunctions 默认启用内置函数,并且无法禁用。

标识符 参数数量 参数类型 描述
min >= 1 数值 返回参数中的最小值
max >= 1 数值 返回参数中的最大值
len 1 字符串/元组 返回字符串的字符长度,或元组中元素的数量(非递归)
floor 1 数值 返回小于或等于一个数的最大整数
round 1 数值 返回一个数最近的整数。对于0.5的情况,四舍五入到最近的整数
ceil 1 数值 返回大于或等于一个数的最小整数
if 3 布尔型,任何类型,任何类型 如果第一个参数为真,则返回第二个参数,否则返回第三个参数
contains 2 元组,任何非元组 如果第二个参数存在于第一个元组参数中,则返回true。
contains_any 2 元组,任何非元组的元组 如果第二个元组参数中的任何值存在于第一个元组参数中,则返回true。
typeof 1 任何类型 根据参数类型返回 "string","float","int","boolean","tuple" 或 "empty"。
math::is_nan 1 数值 如果参数是浮点值 NaN,则返回 true,如果是其他浮点值,则返回 false,如果不是数字,则抛出错误。
math::is_finite 1 数值 如果参数是有限浮点数,则返回 true,否则返回 false。
math::is_infinite 1 数值 如果参数是无限浮点数,则返回 true,否则返回 false。
math::is_normal 1 数值 如果参数是既不是零、无限、亚正常也不是 NaN 的浮点数,则返回 true,否则返回 false。
math::ln 1 数值 返回数字的自然对数
math::log 2 数值,数值 返回以任意底数的数字的对数
math::log2 1 数值 返回数字的2为底的对数
math::log10 1 数值 返回数字的10为底的对数
math::exp 1 数值 返回 e^(number),即指数函数
math::exp2 1 数值 返回 2^(number)
math::pow 2 数值,数值 将一个数提升到另一个数的幂
math::cos 1 数值 计算一个数的余弦值(以弧度为单位)
math::acos 1 数值 计算一个数的反余弦值。返回值在 [0, π] 范围内,以弧度为单位,如果数值超出 [-1, 1] 范围,则返回 NaN。
math::cosh 1 数值 双曲余弦函数
math::acosh 1 数值 双曲余弦的反函数
math::sin 1 数值 计算一个数的正弦值(以弧度为单位)
math::asin 1 数值 计算一个数的反正弦值。返回值在 [-π/2, π/2] 范围内,以弧度为单位,如果数值超出 [-1, 1] 范围,则返回 NaN。
math::sinh 1 数值 双曲正弦函数
math::asinh 1 数值 双曲正弦的反函数
math::tan 1 数值 计算一个数的正切值(以弧度为单位)
math::atan 1 数值 计算一个数的反正切值。返回值在 [-π/2, π/2] 范围内,以弧度为单位
math::atan2 2 数值,数值 计算四象限反正切值,以弧度为单位
math::tanh 1 数值 双曲正切函数
math::atanh 1 数值 双曲正切函数的逆。
math::sqrt 1 数值 返回一个数的平方根。对于负数返回 NaN。
math::cbrt 1 数值 返回一个数的立方根。
math::hypot 2 数值 根据两个参数给出的长度计算直角三角形的斜边长度。
math::abs 1 数值 返回一个数的绝对值,如果参数是整数则返回整数,否则返回浮点数。
str::regex_matches 2 字符串,字符串 如果第一个参数与第二个参数中的正则表达式匹配,则返回 true(需要 regex_support 功能标志)。
str::regex_replace 3 字符串,字符串,字符串 返回第一个参数,其中所有与第二个参数中的正则表达式匹配的部分都被第三个参数替换(需要 regex_support 功能标志)。
str::to_lowercase 1 字符串 返回字符串的小写版本。
str::to_uppercase 1 字符串 返回字符串的大写版本。
str::trim 1 字符串 移除字符串的首尾空白字符。
str::from >= 0 任何类型 将传入的值作为字符串返回。
str::substring 3 字符串,整数,整数 返回第一个参数的子字符串,从第二个参数开始,到第三个参数结束。如果省略最后一个参数,子字符串将扩展到字符串的末尾。
bitand 2 整数 计算给定整数的按位与。
bitor 2 整数 计算给定整数的按位或。
bitxor 2 整数 计算给定整数的按位异或。
bitnot 1 整数 计算给定整数的按位非。
shl 2 整数 计算给定整数按位左移其他给定整数的结果。
shr 2 整数 计算给定整数按位右移其他给定整数的结果。
random 0 返回一个介于 0 和 1 之间的随机浮点数。需要 rand 功能标志。

minmax 函数可以处理整数和浮点数的混合。如果最大值或最小值是整数,则返回整数。否则,返回浮点数。

正则表达式函数需要 regex_support 功能标志。

运算符接受值作为参数并产生值作为结果。值可以是布尔值、整数或浮点数、字符串、元组或空类型。值表示如下表所示。

值类型 示例
::字符串 "abc", "", "a\"b\\c"
::布尔值 true, false
::整数 3, -9, 0, 135412, 0xfe02, -0x1e
::浮点数 3..351.000.5123.55423e4-2e-33.54e+2
::元组 (3, 55.0, false, ())(1, 2)
:: ()

整数在内部表示为i64,浮点数表示为f64

元组、整数、浮点数、类型别名

整数、浮点数、类型别名

表达式、元组、整数、浮点数、类型别名

类型别名 类型别名
::from(4) ::整数(4)
::from(4.4) ::浮点数(4.4)
::from(类型别名) ::布尔值(类型别名)
::from(类型别名[::from(3)]) ::元组(类型别名[::整数(3)])

类型别名

类型别名 类型别名
::from(4).类型别名() 类型别名(4)
::from(4.4).类型别名() 类型别名(4.4)
::from(类型别名).类型别名() 类型别名(类型别名::类型别名{类型别名: ::布尔值(类型别名)})

类型别名

类型别名

类型别名

类型别名

类型别名

类型别名 类型别名 类型别名
类型别名 类型别名
类型别名 类型别名
类型别名<类型别名 类型别名 类型别名
类型别名 类型别名 类型别名
123 类型别名 表达式被解释为 Value::Int
类型别名 类型别名 表达式被解释为 Value::Bool
.34 类型别名 表达式被解释为 Value::Float

变量具有优先级200。

用户定义函数

此crate允许定义任意函数以在解析表达式中使用。函数被定义为包装一个 fn(&Value) -> EvalexprResult<Value> 实例的 Function。定义需要包含在用于评估的 Context 中。目前,函数不能在表达式中定义,但这可能会在未来改变。

函数传递给它直接后面的任何值,无论是元组还是单个值。如果没有值在函数后面,则将其解释为变量。更具体地说,函数后面必须跟一个开括号 (、另一个字面量或一个值。虽然没有特别支持多值函数,但可以通过要求单个元组参数来实现。

请注意,函数需要验证传递给它们的值的类型。错误模块包含一些用于验证的快捷方式,以及用于传递错误值类型的错误类型。此外,大多数数值函数需要区分整数或浮点数被调用,并相应地操作。

以下是一些关于解释为函数调用的表达式的示例和反例

类型别名 函数? 类型别名
a v 类型别名
x5.5 类型别名
类型别名(3, 类型别名) 类型别名
类型别名4 类型别名 调用 a,该调用与用 4 调用 b 的结果相同
5类型别名 类型别名 错误,值不能后面跟一个字面量
12 3 类型别名 错误,值不能后面跟一个值
类型别名5 6 类型别名 错误,函数调用不能后面跟一个值

函数具有优先级190。

Serde

要使用此crate与serde一起使用,必须在 Cargo.toml 中设置 serde_support 功能标志。可以这样完成

[dependencies]
evalexpr = {version = "<desired version>", features = ["serde_support"]}

此crate为表示解析表达式树的类型 Node 实现了 serde::de::Deserialize。实现期望一个 serde string 作为输入。以下是一个使用 ron格式 的示例解析

注释

Evalexpr支持C风格的内联注释和行尾注释。内联注释以 /* 开头,并以 */ 结尾。行尾注释以 // 开头,并以换行符结尾。例如

use evalexpr::*;

assert_eq!(
    eval(
        "
        // input
        a = 1;  // assignment
        // output
        2 * a /* first double a */ + 2 // then add 2"
    ),
    Ok(Value::Int(4))
);
extern crate ron;
use evalexpr::*;

let mut context = context_map!{
    "five" => 5
}.unwrap(); // Do proper error handling here

// In ron format, strings are surrounded by "
let serialized_free = "\"five * five\"";
match ron::de::from_str::<Node>(serialized_free) {
    Ok(free) => assert_eq!(free.eval_with_context(&context), Ok(Value::from(25))),
    Err(error) => {
        () // Handle error
    }
}

使用 serde,可以将表达式集成到任意复杂的数据中。

此crate还为 HashMapContext 实现了 SerializeDeserialize,但请注意,只有变量被(反)序列化,而不是函数。

许可证

该软件包主要在MIT许可证的条款下分发。有关详细信息,请参阅LICENSE

无恐慌

该软件包广泛使用了Result模式,旨在永不引发恐慌。唯一例外是因失败的分配而引发的恐慌。但是不幸的是,Rust没有提供任何功能来证明这种行为。该软件包的开发者尚未找到任何确保无恐慌行为的好方法。请立即在github上报告此软件包中的恐慌。

即使该软件包本身无恐慌,但它允许用户定义由软件包执行的自定义函数。用户需要确保他们提供给软件包的函数永远不会引发恐慌。

不可信的输入

该软件包没有考虑到不可信的输入,但由于其简单性和无恐慌的自由,它可能是安全的,以下注意事项请牢记:

  • 限制不可信输入的长度。
  • 如果在评估不可信输入之间保持可变上下文,不可信输入可能会逐渐填满它,直到应用程序耗尽内存。
  • 如果没有提供上下文,则隐式提供一个临时可变上下文。这将在评估每个字符串后释放,因此不会发生渐进填充。
  • 如果没有提供上下文或可变上下文,并且激活了regex_support功能,则可以使用regex_replace内置函数构建一个指数级大小的字符串。

贡献

如果您有任何功能想法或看到代码、架构、接口、算法或文档中的任何问题,请在github上创建一个问题。如果有问题描述了您想说的内容,请为该问题添加点赞或其他合适的表情符号,以便我知道哪些应该优先处理。

贡献者注意事项

  • 该软件包使用sync-readme cargo子命令来保持src/lib.rsREADME.md中的文档同步。子命令仅从src/lib.rs中的文档同步到README.md。因此,请修改src/lib.rs中的文档,而不是在README.md中的以下内容之间进行修改:<!-- cargo-sync-readme start --><!-- cargo-sync-readme end -->

依赖关系

~0–750KB
~13K SLoC