#解析器 #消费 #组合数学 #行为

管理者

一个高性能、低级、轻量级且直观的组合解析库

2 个版本

0.1.1 2020 年 12 月 30 日
0.1.0 2020 年 12 月 30 日

#99解析工具

MIT 许可证

89KB
1.5K SLoC

GitHub last commit GitHub branch checks state GitHub Repo stars GitHub

管理器

一个高性能、低级、轻量级且直观的组合解析库。

管理器允许您将针对 Rust 的原始和标准库类型开发的直觉转化为使用此库的直觉。大多数行为都通过 Consumable 特性定义,这可以通过使用 consume_structconsume_enum 宏轻松实现。

此库适用于确定性的正则语言。它最好与预定义的语法结合使用。例如,如果您有一个预定义的 EBNF,在 crate 中实现语法将非常容易。

入门

要开始在自己的特性上实现 Consumable,我建议查看 consume_structconsume_enum 文档。然后您可以回到这里并查看一些常见模式。

常见模式

解析和消费有很多常用的模式。当然,这些模式在这里也非常容易获得。

连接

我们通常想表达两个模式在 source 字符串中依次出现。例如,您可能想表达每个 Line 都跟着一个 ';'。在管理器中,有两种方式可以这样做。

第一种方式,也是首选方式,是使用 consume_structconsume_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> 类型来表示要么关系。如果我们不关心哪个选项被选中,则此选项是首选。

路线图

查看 公开问题 以获取建议的功能(以及已知问题)列表。

贡献

贡献使开源社区成为一个如此迷人的学习、灵感和创造的地方。您所做出的任何贡献都备受赞赏。

  1. 分支项目
  2. 创建您的功能分支(git checkout -b feature/AmazingFeature
  3. 提交您的更改(git commit -m 'Add some AmazingFeature'
  4. 推送到分支(git push origin feature/AmazingFeature
  5. 打开拉取请求

许可协议

在MIT许可下分发。有关更多信息,请参阅LICENSE

依赖关系

~0.3–0.8MB
~19K SLoC