6 个版本
0.1.0 | 2024 年 3 月 9 日 |
---|---|
0.0.2 | 2024 年 2 月 14 日 |
0.0.1 | 2024 年 1 月 7 日 |
0.0.1-beta2 | 2023 年 12 月 8 日 |
在 解析器实现 中排名 734
每月下载量 35
155KB
3K SLoC
Xprs
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
,其中x
为2
,结果为1
。
注意:在
Display
和Debug
中显示额外的括号,以便更清楚地显示运算顺序。
-
pejmdas
:与
pemdas
功能冲突。使用 PEJMDAS 运算顺序。这意味着隐式乘法比显式乘法的优先级更高。例如6/2(2+1)
被解释为6/(2*(2+1))
,结果为1
。1/2x
被解释为1/(2*x)
,其中x
为2
,结果为0.25
。
注意:在
Display
和Debug
中显示额外的括号,以便更清楚地显示运算顺序。
用法
简单示例
如果您想评估一个不包含任何变量的简单微积分,您可以使用 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));
}
您可以使用函数 bind
、bind2
等,最多到 bind9
来将变量绑定到微积分中。如果您需要更多,可以使用 bind_n
和 bind_n_runtime
方法,这些方法分别接受大小为 N 的数组或切片。
说明:所有 bind
函数(除了 bind_n_runtime
)返回一个保证返回 [f64
] 的函数的 Result
。 bind_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);
}
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 支持多种函数
- 三角函数:
sin
、cos
、tan
、asin
、acos
、atan
、atan2
、sinh
、cosh
、tanh
、asinh
、acosh
、atanh
。 - 对数函数:
ln
(底数 2)、log
(底数 10)、logn
(底数 n,用作logn(num, base)
)。 - 幂函数:
sqrt
、cbrt
、exp
。 - 舍入函数:
floor
、ceil
、round
、trunc
。 - 其他函数:
abs
、min
、max
、hypot
、fract
、recip
(invert
别名)、sum
、mean
、factorial
和gamma
。
注意:min
和 max
可以接受任意数量的参数(如果没有参数,分别返回 f64::INFINITY
和 -f64::INFINITY
)。注意2:sum
和 mean
可以接受任意数量的参数(如果没有参数,分别返回 0
和 f64::NAN
)。
高级示例
Xprs 简化
您可以使用 simplify_for
或 simplify_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