2 个版本
0.1.1 | 2020 年 12 月 30 日 |
---|---|
0.1.0 | 2020 年 12 月 30 日 |
#99 在 解析工具
89KB
1.5K SLoC
管理器
一个高性能、低级、轻量级且直观的组合解析库。
管理器允许您将针对 Rust 的原始和标准库类型开发的直觉转化为使用此库的直觉。大多数行为都通过 Consumable
特性定义,这可以通过使用 consume_struct
和 consume_enum
宏轻松实现。
此库适用于确定性的正则语言。它最好与预定义的语法结合使用。例如,如果您有一个预定义的 EBNF,在 crate 中实现语法将非常容易。
入门
要开始在自己的特性上实现 Consumable
,我建议查看 consume_struct
或 consume_enum
文档。然后您可以回到这里并查看一些常见模式。
常见模式
解析和消费有很多常用的模式。当然,这些模式在这里也非常容易获得。
连接
我们通常想表达两个模式在 source
字符串中依次出现。例如,您可能想表达每个 Line
都跟着一个 ';'
。在管理器中,有两种方式可以这样做。
宏
第一种方式,也是首选方式,是使用 consume_struct
或 consume_enum
宏,您可以在其中呈现顺序消费指令。以下示例中,您可以看到我们首先消费了一个 '('
,然后是一个 i32
,然后是一个关闭的 ')'
。
use manger::{ Consumable, consume_struct };
struct EncasedInteger(i32);
consume_struct!(
EncasedInteger => [
> '(',
value: i32,
> ')';
(value)
]
);
元组
另一种表示相同概念的方式是使用元组类型语法。这可以由最多 10 个类型完成。在这里,我们再次解析相同的 (i32)
结构。
use manger::chars;
type EncasedInteger = (chars::OpenParenthese, i32, chars::CloseParenthese);
重复
大多数时候,您想表示某种重复。有很多人表示重复的方式。这里有两种简单的方式。
Vec
实现重复的最简单方法是使用 Vec<T>
。这将消耗类型 T
的 0 个或多个实例。当然,类型 T
必须实现 Consumable
。这里可以看到它的样子
由于
Vec<T>
会消耗类型T
的实例直到找到错误,因此它自己永远不会失败。因此,您安全地展开结果。
use manger::{ Consumable, consume_struct };
struct EncasedInteger(i32);
consume_struct!(
EncasedInteger => [
> '[',
value: i32,
> ']';
(value)
]
);
let source = "[3][-4][5]";
let (encased_integers, _) = <Vec<EncasedInteger>>::consume_from(source)?;
let sum: i32 = encased_integers
.iter()
.map(|EncasedInteger(value)| value)
.sum();
assert_eq!(sum, 4);
OneOrMore
另一种实现重复的简单方法是使用 OneOrMore<T>
。这允许消耗类型 T
的 1 个或多个实例。同样,类型 T
必须实现 Consumable
。这里可以看到它的样子
use manger::{ Consumable, consume_struct };
use manger::common::OneOrMore;
struct EncasedInteger(i32);
consume_struct!(
EncasedInteger => [
> '[',
value: i32,
> ']';
(value)
]
);
let source = "[3][-4][5]";
let (encased_integers, _) = <OneOrMore<EncasedInteger>>::consume_from(source)?;
let product: i32 = encased_integers
.into_iter()
.map(|EncasedInteger(value)| value)
.product();
assert_eq!(product, -60);
可选值
要表示可选值,可以使用 Option<T>
标准Rust类型。这将消耗类型 T
的 0 个或 1 个实例。
由于
Option<T>
如果未找到错误则会消耗类型T
的实例,因此它自己永远不会失败。因此,您安全地展开结果。
use manger::consume_struct;
use manger::chars;
struct PossiblyEncasedInteger(i32);
consume_struct!(
PossiblyEncasedInteger => [
: Option<chars::OpenParenthese>,
value: i32,
: Option<chars::CloseParenthese>;
(value)
]
);
递归
组合式解析器中常见的另一种模式是递归。由于Rust类型需要预先定义,我们无法进行直接类型递归,我们需要使用标准库中的 Box<T>
类型进行堆分配。我们可以创建一个以下的前缀数学表达式解析器
use manger::consume_enum;
use manger::common::{OneOrMore, Whitespace};
enum Expression {
Times(Box<Expression>, Box<Expression>),
Plus(Box<Expression>, Box<Expression>),
Constant(u32),
}
consume_enum!(
Expression {
Times => [
> '*',
: OneOrMore<Whitespace>,
left: Box<Expression>,
: OneOrMore<Whitespace>,
right: Box<Expression>;
(left, right)
],
Plus => [
> '+',
: OneOrMore<Whitespace>,
left: Box<Expression>,
: OneOrMore<Whitespace>,
right: Box<Expression>;
(left, right)
],
Constant => [
value: u32;
(value)
]
}
);
空白字符
对于空白字符,我们可以使用 manger::common::Whitespace
结构。这将消耗任何由 char::is_whitespace
函数识别为空白字符的 UTF-8 字符。
Either
如果有两种消耗可能性,有两个选项可以选择。在某些情况下,两者都是有效的。
宏
使用 consume_enum
可以创建一个可以在多个选项中进行消耗的结构,并可以看到哪个选项被选中。如果您需要查看哪个不同的选项被选中,这应该是您的选择。
Either<L, R>
您还可以使用 Either<L, R>
类型来表示要么关系。如果我们不关心哪个选项被选中,则此选项是首选。
路线图
查看 公开问题 以获取建议的功能(以及已知问题)列表。
贡献
贡献使开源社区成为一个如此迷人的学习、灵感和创造的地方。您所做出的任何贡献都备受赞赏。
- 分支项目
- 创建您的功能分支(
git checkout -b feature/AmazingFeature
) - 提交您的更改(
git commit -m 'Add some AmazingFeature'
) - 推送到分支(
git push origin feature/AmazingFeature
) - 打开拉取请求
许可协议
在MIT许可下分发。有关更多信息,请参阅LICENSE
。
依赖关系
~0.3–0.8MB
~19K SLoC