5.5 Building ASTs

Of course, most of the time, when you're parsing you don't want to compute a value, you want to build up some kind of data structure. Here's a quick example to show how that is done in LALRPOP. First, we need to define the data structure we will build. We're going to use a very simple enum:

MST --- 当然,大多数时候,当你解析时,你不想计算一个值,你想要构建某种数据结构。这是一个简单的示例,展示了如何在 LALRPOP 中完成此操作。首先,我们需要定义我们将构建的数据结构。我们将使用一个非常简单的枚举:

GPT --- 当然,大多数情况下,当你进行解析时,你并不想计算一个值,而是希望构建某种数据结构。这里是一个快速示例,展示如何在 LALRPOP 中做到这一点。首先,我们需要定义我们将要构建的数据结构。我们将使用一个非常简单的枚举:

pub enum Expr {
    Number(i32),
    Op(Box<Expr>, Opcode, Box<Expr>),
}

pub enum Opcode {
    Mul,
    Div,
    Add,
    Sub,
}

We put this code into an ast.rs module in our project, along with some Debug impls so that things pretty-print nicely. Now we will create the calculator4 example, which will build up this tree. To start, let's just look at the Expr nonterminal, which will show you most everything of how it is done (the most interesting lines have been flagged with comments):

MST --- 我们将这段代码放入项目的 ast.rs 模块中,以及一些 Debug impls,以便打印得漂亮。现在我们将创建 calculator4 示例,它将构建此树。首先,让我们看看 Expr 非终端,它将向您展示它的大部分完成方式(最有趣的行已用注释标记):

GPT --- 我们将这段代码放入项目中的 ast.rs 模块,并添加了一些 Debug 实现,这样可以使数据结构在打印时更加友好。接下来,我们将创建 calculator4 示例,该示例将构建这个抽象语法树(AST)。首先,让我们来看一下 Expr 非终结符,它展示了大部分操作的实现方式(最有趣的部分已经用注释标记出来):

use std::str::FromStr;
use ast::{Expr, Opcode}; // (0)

grammar;

pub Expr: Box<Expr> = { // (1)
    Expr ExprOp Factor => Box::new(Expr::Op(<>)), // (2)
    Factor,
};

ExprOp: Opcode = { // (3)
    "+" => Opcode::Add,
    "-" => Opcode::Sub,
};

First off, we have to import these new names into our file by adding a use statement (0). Next, we want to produce Box values, so we change the type of Expr (and Factor and Term) to Box (1). The action code changes accordingly in (2); here we've used the <> expansion to supply three arguments to Expr::Op. Finally, just for concision, we introduced an ExprOp nonterminal (3) to cover the two opcodes, which now trigger the same action code (before they triggered different action code, so we could do an addition vs a subtraction).

MST --- 首先,我们必须通过添加 use 语句 (0) 将这些新名称导入到我们的文件中。接下来,我们想要生成 Box 值,因此我们将 Expr 的类型(以及 Factor 和 Term)更改为 Box (1)。操作代码在 (2) 中相应地更改;在这里,我们使用 <> 扩展为 Expr::Op 提供三个参数。最后,为了简洁起见,我们引入了一个 ExprOp 非终端 (3) 来覆盖两个操作码,这两个操作码现在触发相同的操作代码(在它们触发不同的操作代码之前,因此我们可以进行加法与减法)。

GPT --- 首先,我们需要通过添加一个 use 声明(0)将这些新名称导入到文件中。接下来,我们希望生成 Box<Expr> 类型的值,因此我们将 Expr(以及 FactorTerm)的类型更改为 Box<Expr>(1)。然后,动作代码在(2)中相应地做了更改;在这里,我们使用了 <> 扩展来为 Expr::Op 提供三个参数。最后,为了简洁起见,我们引入了一个 ExprOp 非终结符(3),它覆盖了两个操作符,现在它们触发相同的动作代码(之前它们触发不同的动作代码,以便区分加法和减法)。

The definition of Factor is transformed in a similar way:

MST --- Factor 的定义以类似的方式转换:

GPT --- Factor 的定义也进行了类似的转换:

Factor: Box<Expr> = {
    Factor FactorOp Term => Box::new(Expr::Op(<>)),
    Term,
};

FactorOp: Opcode = {
    "*" => Opcode::Mul,
    "/" => Opcode::Div,
};

And finally we adjust the definitions of Term and Num. Here, we convert from a raw i32 into a Box when we transition from Num to Term (4):

MST --- 最后,我们调整 Term 和 Num 的定义。在这里,当我们从 Num 过渡到 Term (4) 时,我们从原始 i32 转换为 Box:

GPT --- 最后,我们调整了 TermNum 的定义。在这里,当从 Num 过渡到 Term 时,我们将原始的 i32 转换为 Box<Expr>(4):

Term: Box<Expr> = {
    Num => Box::new(Expr::Number(<>)), // (4)
    "(" <Expr> ")"
};

Num: i32 = {
    r"[0-9]+" => i32::from_str(<>).unwrap()
};

And that's it! Now we can test it by adding some code to our main.rs file that parses an expression and formats it using the Debug impl:

MST --- 就是这样!现在,我们可以通过在 main.rs 文件中添加一些代码来测试它,该代码解析表达式并使用 Debug impl 对其进行格式化:

GPT --- 就是这样!现在我们可以通过向 main.rs 文件中添加一些代码,来测试这个功能,解析一个表达式并使用 Debug 实现来格式化输出:

lalrpop_mod!(pub calculator4);
pub mod ast;

#[test]
fn calculator4() {
    let expr = calculator4::ExprParser::new()
        .parse("22 * 44 + 66")
        .unwrap();
    assert_eq!(&format!("{:?}", expr), "((22 * 44) + 66)");
}

posted on 2025-01-05 11:31  及途又八  阅读(18)  评论(0)    收藏  举报

导航