#expression-parser #expression-evaluator #math #library #parser #higher-order

bin+lib xprs

Xprs 是一个为 Rust 设计的灵活且可扩展的数学表达式解析器和评估器,旨在简单易用

6 个版本

0.1.0 2024 年 3 月 9 日
0.0.2 2024 年 2 月 14 日
0.0.1 2024 年 1 月 7 日
0.0.1-beta22023 年 12 月 8 日

解析器实现 中排名 734

每月下载量 35

WTFPL 许可证

155KB
3K SLoC

Xprs

github crates.io build status docs.rs downloads

Xprs 是一个为 Rust 设计的灵活且可扩展的数学表达式解析器和评估器,旨在简单易用(以及理想情况下,速度快)。

安装

将以下内容添加到您的 Cargo.toml

[dependencies]
xprs = "0.1.0"

或者在您的终端运行以下命令

cargo add xprs

请确保检查 Crates.io 页面以获取最新版本。

MSRV(最低支持 Rust 版本)

目前,最低支持的 Rust 版本是 1.70.0

软件包功能

  • compile-time-optimizations (默认启用)

    在解析时启用优化和评估。此功能将在解析期间自动将表达式如 1 + 2 * 3 转换为 7,从而允许更快地评估。它也适用于函数(例如 sin(0) 将被转换为 0)和“逻辑”结果如 (x - x) * (....) 将被转换为 0,因为无论 x 是什么,x - x 都是 0

    注意:nightly 频道通过 box_patterns 功能门启用更多优化。


  • pemdas (默认启用)

    pejmdas 功能冲突。使用 PEMDAS 运算符优先级。这意味着隐式乘法与显式乘法的优先级相同。例如

    • 6/2(2+1) 被解释为 6/2*(2+1),结果为 9
    • 1/2x 被解释为 (1/2)*x,其中 x2,结果为 1

    注意:在 DisplayDebug 中显示额外的括号,以便更清楚地显示运算顺序。


  • pejmdas:

    pemdas 功能冲突。使用 PEJMDAS 运算顺序。这意味着隐式乘法比显式乘法的优先级更高。例如

    • 6/2(2+1) 被解释为 6/(2*(2+1)),结果为 1
    • 1/2x 被解释为 1/(2*x),其中 x2,结果为 0.25

    注意:在 DisplayDebug 中显示额外的括号,以便更清楚地显示运算顺序。

用法

简单示例

如果您想评估一个不包含任何变量的简单微积分,您可以使用 eval_no_vars 方法(或者如果您确定没有变量存在,可以使用 eval_no_vars_unchecked

use xprs::Xprs;

fn main() {
    let xprs = Xprs::try_from("1 + sin(2) * 3").unwrap();
    println!("1 + sin(2) * 3 = {}", xprs.eval_no_vars().unwrap());
}

注意:数字被解析为 [f64],因此您可以使用科学记数法(例如 1e-3)和下划线(例如 1_000_000e2)。

如果您想评估包含变量的微积分,您可以使用 eval 方法(或者如果您确定您没有缺少任何变量,可以使用 eval_unchecked

use xprs::Xprs;

fn main() {
    let xprs = Xprs::try_from("1 + sin(2) * x").unwrap();
    println!(
        "1 + sin(2) * x = {}",
        xprs.eval(&[("x", 3.0)].into()).unwrap()
    );
}

您还可以将微积分转换为函数并稍后使用

use xprs::Xprs;

fn main() {
    let xprs = Xprs::try_from("1 + sin(2) * x").unwrap();
    let fn_xprs = xprs.bind("x").unwrap();
    println!("1 + sin(2) * 3 = {}", fn_xprs(3.0));
}

您可以使用函数 bindbind2 等,最多到 bind9 来将变量绑定到微积分中。如果您需要更多,可以使用 bind_nbind_n_runtime 方法,这些方法分别接受大小为 N 的数组或切片。

说明:所有 bind 函数(除了 bind_n_runtime)返回一个保证返回 [f64] 的函数的 Resultbind_n_runtime 返回一个函数的 Result,该函数也返回一个 Result 的 [f64],因为没有保证数组/切片的大小是正确的。

上下文和解析器

如果您想定义自己的函数和/或常量并重复使用它们,也可以创建一个 Context 和一个 Parser 实例。

常量和函数可以有任何以字母开头(大写或小写)并且只包含 [A-Za-z0-9_'] 组成的名称。

函数需要具有 fn(&[f64]) -> f64 的签名,因此它们都具有相同的签名并且可以以相同的方式进行调用。我们还需要一个名称以及函数所接受的参数数量,这是一个 Option<usize>,如果 None 则函数可以接受任意数量的参数。您可以定义函数如下

use xprs::{Function, xprs_fn};

fn double(x: f64) -> f64 {
    x * 2.0
}

const DOUBLE: Function = Function::new_static("double", move |args| double(args[0]), Some(1));
// or with the macro (will do an automatic wrapping)
const DOUBLE_MACRO: Function = xprs_fn!("double", double, 1);

fn variadic_sum(args: &[f64]) -> f64 {
    args.iter().sum()
}

const SUM: Function = Function::new_static("sum", variadic_sum, None);
// or with the macro (no wrapping is done for variadic functions)
const SUM_MACRO: Function = xprs_fn!("sum", variadic_sum);

// if a functions captures a variable (cannot be coerced to a static function)
const X: f64 = 42.0;
fn show_capture() {
    let captures = |arg: f64| { X + arg };

    let CAPTURES: Function = Function::new_dyn("captures", move |args| captures(args[0]), Some(1));
    // or with the macro (will do an automatic wrapping)
    let CAPTURES_MACRO: Function = xprs_fn!("captures", dyn captures, 1);
}

要使用 ContextParser,您可以这样做

use xprs::{xprs_fn, Context, Parser};

fn main() {
    let mut context = Context::default()
        .with_fn(xprs_fn!("double", |x| 2. * x, 1))
        .with_var("foo", 1.0);
    context.set_var("bar", 2.0);

    let xprs = Parser::new_with_ctx(context)
        .parse("double(foo) + bar")
        .unwrap();
    println!("double(foo) + bar = {}", xprs.eval_no_vars().unwrap());
}

注意: Context 只是一个围绕 HashMap 的包装,因此您不能有具有相同名称的函数和常量(最后一个将覆盖第一个)。

您还可以使用 Context 来限制在演算中允许的变量。

use xprs::{Context, Parser};

fn main() {
    let context = Context::default()
        .with_expected_vars(["x", "y"].into());

    let parser = Parser::new_with_ctx(context);

    let result = parser.parse("x + y"); // OK
    let fail = parser.parse("x + z"); // Error

    println!("{result:#?} {fail:#?}");
}

错误处理

所有错误都使用 thiserror 实现。解析错误使用 miette crate 实现。 错误信息

支持的运算,内置常量和函数

运算

Xprs 支持以下运算

  • 二元运算:+-*/^%
  • 一元运算:+-!

注意:!(阶乘)仅在正整数上受支持。在负整数或浮点数上调用它将返回 f64::NAN。此外,-4! 被解释为 -(4!),而不是 (-4)!

内置常量

常量 近似值
PI π 3.141592653589793
E e 2.718281828459045

内置函数

Xprs 支持多种函数

  • 三角函数:sincostanasinacosatanatan2sinhcoshtanhasinhacoshatanh
  • 对数函数:ln(底数 2)、log(底数 10)、logn(底数 n,用作 logn(num, base))。
  • 幂函数:sqrtcbrtexp
  • 舍入函数:floorceilroundtrunc
  • 其他函数:absminmaxhypotfractrecipinvert 别名)、summeanfactorialgamma

注意:minmax 可以接受任意数量的参数(如果没有参数,分别返回 f64::INFINITY-f64::INFINITY)。注意2:summean 可以接受任意数量的参数(如果没有参数,分别返回 0f64::NAN)。

高级示例

Xprs 简化

您可以使用 simplify_forsimplify_for_multiple 方法,就地或非就地,对给定的变量(或一组变量)简化 Xprs

use xprs::Xprs;

fn main() {
    let mut xprs = Xprs::try_from("w + sin(x + 2y) * (3 * z)").unwrap();

    println!("{xprs}"); // (w + (sin((x + (2 * y))) * (3 * z)))

    xprs.simplify_for_in_place(("z", 4.0));

    println!("{xprs}"); // (w + (sin((x + (2 * y))) * 12))

    let xprs = xprs.simplify_for_multiple(&[("x", 1.0), ("y", 2.0)]);

    println!("{xprs}"); // (w + -11.507091295957661)
}

高阶函数

您可以在基于之前解析的表达式的上下文中定义函数。

use xprs::{xprs_fn, Context, Parser, Xprs};

fn main() {
    let xprs_hof = Xprs::try_from("2x + y").unwrap();
    let fn_hof = xprs_hof.bind2("x", "y").unwrap();
    let hof = xprs_fn!("hof", dyn fn_hof, 2);
    let ctx = Context::default().with_fn(hof);
    let parser = Parser::new_with_ctx(ctx);

    let xprs = parser.parse("hof(2, 3)").unwrap();

    println!("hof(2, 3) = {}", xprs.eval_no_vars().unwrap());
}

这些示例和其他示例可以在 示例 目录中找到。

文档

完整文档可以在 docs.rs 上找到。

许可

版权所有 © 2023 Victor LEFEBVRE 此作品是免费的。您可以在 Do What The Fuck You Want To Public License,版本 2 的条款下重新分发或修改它,由 Sam Hocevar 发布。有关更多详细信息,请参阅 LICENSE 文件。

待办事项

以下是我希望在将来完成/添加的一些非详尽列表

  • 更好的 CI/CD。
  • 通过将 &str 替换为类似 byteyarn 的内容来删除生命周期。
  • 支持复数。
  • 定义类似 Context(如 evalexpr 中所示)的宏。
  • 支持动态的 Function 名称。
  • 本地可变参数(当 Rust 在稳定版本中支持时)。
  • 如果可能,让 Xprs 成为泛型,使其返回类型为浮点数(考虑到对 Context 的依赖)。

如果您对其中之一感兴趣,请随时提交一个 PR!

依赖关系

~2–11MB
~125K SLoC