10 个稳定版本

1.1.3 2022年8月15日
1.1.1 2021年7月19日
1.1.0 2021年6月19日
1.0.6 2020年10月28日
1.0.2 2019年9月19日

#21 in 解析工具

Download history 4525/week @ 2024-03-14 5803/week @ 2024-03-21 7165/week @ 2024-03-28 5743/week @ 2024-04-04 6248/week @ 2024-04-11 4860/week @ 2024-04-18 4398/week @ 2024-04-25 5809/week @ 2024-05-02 5691/week @ 2024-05-09 7820/week @ 2024-05-16 5136/week @ 2024-05-23 6251/week @ 2024-05-30 5584/week @ 2024-06-06 4772/week @ 2024-06-13 4853/week @ 2024-06-20 2872/week @ 2024-06-27

19,434 月下载量
用于 35 个 crate (15 直接)

MIT/Apache

34KB
325

pest_consume

重要:我现在不再积极维护这个项目。我在 https://github.com/Nadrieril/dhall-rust 中使用它,但除此之外没有计划添加功能或其他此类内容。将其视为一个概念验证。

pest_consume 扩展 pest 以简化对 pest 解析树的消费。

动机

当使用 pest 编写解析器时,必须手动遍历生成的无类型解析树以提取其他应用程序将使用的数据。这通常会使代码易于出错、难以阅读,并且当语法更新时经常会出错。

pest_consume 力求使这个解析阶段更容易、更干净、更健壮。

pest_consume 的功能包括

  • 强类型;
  • 使用直观的语法消费解析节点;
  • 易于错误处理;
  • 您将不再需要再次编写 .into_inner().next().unwrap()

实现解析器

让我们从一个用于解析 CSV 文件的 pest 语法开始

field = { (ASCII_DIGIT | "." | "-")+ }
record = { field ~ ("," ~ field)* }
file = { SOI ~ (record ~ ("\r\n" | "\n"))* ~ EOI }

和相应的 pest 解析器

use pest_consume::Parser;
// Construct the first half of the parser using pest as usual.
#[derive(Parser)]
#[grammar = "../examples/csv/csv.pest"]
struct CSVParser;

要完成解析器,定义一个带有 pest_consume::parser 属性的 impl 块,并为语法中的每个(非静默)规则定义一个同名方法。注意我们为每个规则选择了输出类型。

use pest_consume::Error;
type Result<T> = std::result::Result<T, Error<Rule>>;
type Node<'i> = pest_consume::Node<'i, Rule, ()>;

// This is the other half of the parser, using pest_consume.
#[pest_consume::parser]
impl CSVParser {
    fn EOI(_input: Node) -> Result<()> {
        Ok(())
    }
    fn field(input: Node) -> Result<f64> {
        ...
    }
    fn record(input: Node) -> Result<Vec<f64>> {
        ...
    }
    fn file(input: Node) -> Result<Vec<Vec<f64>>> {
        ...
    }
}

这将为您实现 Parser,以便您可以在其上调用 Parser::parse 方法。现在,我们可以定义一个完整的解析器,它返回结构化的结果。

fn parse_csv(input_str: &str) -> Result<Vec<Vec<f64>>> {
    // Parse the input into `Nodes`
    let inputs = CSVParser::parse(Rule::file, input_str)?;
    // There should be a single root node in the parsed tree
    let input = inputs.single()?;
    // Consume the `Node` recursively into the final value
    CSVParser::file(input)
}

剩下的工作是为每个规则实现解析。当规则没有子规则时,情况很简单。在这种情况下,我们通常只关心捕获的字符串,可以使用 Node::as_str 访问。

    fn field(input: Node) -> Result<f64> {
        // Get the string captured by this node
        input.as_str()
            // Convert it into the type we want
            .parse::<f64>()
            // In case of  an error, we use `Node::error` to link the error
            // with the part of the input that caused it
            .map_err(|e| input.error(e))
    }

当规则有子规则时,match_nodes 宏提供了一种类型化的方式来解析子规则。 match_nodes 使用与切片模式类似的语法,并允许有多个分支,就像在 match 表达式中一样。

我们为每个分支指定子规则的预期规则,宏将递归消耗子规则,并将结果提供给分支的主体。特殊的 .. 语法表示可变长度的模式:它将匹配给定规则的零个或多个子规则,并提供一个带有结果的迭代器。

use pest_consume::match_nodes;
...
    fn record(input: Node) -> Result<Vec<f64>> {
        // Checks that the children all match the rule `field`, and captures
        // the parsed children in an iterator. `fds` implements
        // `Iterator<Item=f64>` here.
        Ok(match_nodes!(input.into_children();
            [field(fds)..] => fds.collect(),
        ))
    }

对于 file 规则的情况类似。

示例

一些玩具示例可以在 examples/ 目录中找到。一个真实世界的示例可以在 dhall-rust 中找到。

它是如何工作的

此crate的主要类型(NodeNodesParser)主要是对相应的 pest 类型(分别为 PairPairsParser)的封装。如果需要,可以访问封装的类型,但通常不需要这样做。

pest_consume::parser 宏为您实现 Parser 特性,并启用一些高级功能,如优先级提升和规则别名。实际上,大部分魔法都在 match_nodes 中发生;有关详细信息,请参阅那里。

高级功能

有关优先级提升、通过解析器传递自定义数据等内容,请参阅 此处

兼容性

与 rust >= 1.45 兼容(因为它在表达式位置导出 proc-macro)。

许可证

根据您的选择,许可协议为以下之一:

贡献

除非您明确说明,否则任何有意提交以包含在您的工作中的贡献(根据 Apache-2.0 许可证定义),都应如上所述双重许可,而无需任何额外的条款或条件。

许可证:MIT OR Apache-2.0

依赖关系

~2.2–3MB
~59K SLoC