#parser-generator #codegen #parse-tree #parser

rust-sitter

一个用于定义树形结构文法的同时包含 Rust 逻辑的包

12 个版本

0.4.2 2024年4月19日
0.4.1 2023年11月5日
0.3.4 2023年6月11日
0.3.2 2023年3月27日
0.1.2 2022年8月23日

#78开发工具

Download history 823/week @ 2024-05-02 1336/week @ 2024-05-09 1187/week @ 2024-05-16 1563/week @ 2024-05-23 2187/week @ 2024-05-30 1302/week @ 2024-06-06 2244/week @ 2024-06-13 2386/week @ 2024-06-20 1708/week @ 2024-06-27 1341/week @ 2024-07-04 1937/week @ 2024-07-11 1940/week @ 2024-07-18 1659/week @ 2024-07-25 1719/week @ 2024-08-01 2343/week @ 2024-08-08 1036/week @ 2024-08-15

6,945 每月下载量
用于 4 个 Crates(直接使用 2 个)

MIT 许可证

25KB
319 代码行

Rust Sitter

Crates.io

Rust Sitter 通过利用 Tree Sitter 解析器生成器,使得在 Rust 中创建高效的解析器变得简单。使用 Rust Sitter,您可以使用注解的 Rust 代码定义整个文法,并让宏为您生成解析器和类型安全的绑定!

安装

首先,将 Rust/Tree Sitter 添加到您的 Cargo.toml

[dependencies]
rust-sitter = "0.4.2"

[build-dependencies]
rust-sitter-tool = "0.4.2"

注意:默认情况下,Rust Sitter 使用一个支持 wasm32-unknown-unknown 的纯 Rust 运行时的 Tree Sitter 分支。要使用标准 C 运行时,请禁用默认功能并启用 tree-sitter-standard 功能

第一步是配置您的 build.rs 以编译和链接生成的 Tree Sitter 解析器

use std::path::PathBuf;

fn main() {
    println!("cargo:rerun-if-changed=src");
    rust_sitter_tool::build_parsers(&PathBuf::from("src/main.rs"));
}

定义文法

现在我们已经将 Rust Sitter 添加到我们的项目中,我们可以定义我们的文法。Rust Sitter 文法是在注解的 Rust 模块中定义的。首先,我们定义将包含我们的文法的模块

#[rust_sitter::grammar("arithmetic")]
mod grammar {

}

然后,在模块内部,我们可以定义单个 AST 节点。在这个简单的例子中,我们将定义一个可以用在数学表达式中的表达式。请注意,我们注解这个类型为 #[rust_sitter::language] 来表示它是根 AST 类型。

#[rust_sitter::language]
pub enum Expr {
    Number(u32),
    Add(Box<Expr>, Box<Expr>)
}

现在我们已经定义了类型,我们必须注解枚举变体来描述如何在解析的文本中识别它们。首先,我们可以应用 rust_sitter::leaf 来使用正则表达式匹配与数字相对应的数字,并定义一个转换,将生成的字符串解析为 u32

Number(
    #[rust_sitter::leaf(pattern = r"\d+", transform = |v| v.parse().unwrap())]
    u32,
)

对于 Add 变体,情况稍微复杂一些。首先,我们需要添加一个额外的字段,对应于 +,它必须位于两个子表达式之间。这可以通过使用 text 参数实现,该参数属于 rust_sitter::leaf,它指示解析器匹配特定字符串。由于我们将解析到 (),我们不需要提供转换。

Add(
    Box<Expr>,
    #[rust_sitter::leaf(text = "+")] (),
    Box<Expr>,
)

然而,如果我们尝试编译这个语法,我们将会看到一个错误,因为像 1 + 2 + 3 这样的表达式会产生冲突的解析树,这些表达式可以被解析为 (1 + 2) + 31 + (2 + 3)。我们想要前者,因此我们可以添加另一个注释,指定我们希望这个规则具有左结合性。

#[rust_sitter::prec_left(1)]
Add(
    Box<Expr>,
    #[rust_sitter::leaf(text = "+")] (),
    Box<Expr>,
)

综合来看,我们的语法看起来是这样的

#[rust_sitter::grammar("arithmetic")]
mod grammar {
    #[rust_sitter::language]
    pub enum Expr {
        Number(
            #[rust_sitter::leaf(pattern = r"\d+", transform = |v| v.parse().unwrap())]
            u32,
        ),
        #[rust_sitter::prec_left(1)]
        Add(
            Box<Expr>,
            #[rust_sitter::leaf(text = "+")] (),
            Box<Expr>,
        )
    }
}

然后我们可以使用这个语法解析文本

dbg!(grammar::parse("1+2+3"));
/*
grammar::parse("1+2+3") = Ok(Add(
    Add(
        Number(
            1,
        ),
        (),
        Number(
            2,
        ),
    ),
    (),
    Number(
        3,
    ),
))
*/

类型注释

Rust Sitter 支持多种注释,可以应用于您语法中的类型和字段。这些注释可以用来控制解析器的行为以及生成的 AST 的构建方式。

#[rust_sitter::语言]

这个注释标记了解析的入口点,并确定解析后将返回哪种 AST 类型。语法中只能有一个类型被标记为入口点。

#[rust_sitter::language]
struct Code {
    ...
}

#[rust_sitter::额外]

这个注释将一个节点标记为额外,并且在解析时可以安全地跳过。这对于处理空白、换行符和注释很有用。

#[rust_sitter::extra]
struct Whitespace {
    #[rust_sitter::leaf(pattern = r"\s")]
    _whitespace: (),
}

字段注释

#[rust_sitter::leaf(...)]

#[rust_sitter::leaf(...)] 注释可以用来定义 AST 中的叶节点。这个注释接受一些参数,用于控制解析器的行为

  • pattern 参数接受一个正则表达式,用于匹配叶节点的文本。这个参数是必需的。
  • text 参数接受一个字符串,用于匹配叶节点的文本。这个参数与 pattern 参数互斥。
  • transform 参数接受一个函数,用于将匹配到的文本(一个 &str)转换为所需类型。如果目标类型是 (),则此参数是可选的。

leaf 可以应用于结构体 / 枚举变体中的字段(如上所示),或者直接应用于没有字段的类型

#[rust_sitter::leaf(text = "9")]
struct BigDigit;

enum SmallDigit {
    #[rust_sitter::leaf(text = "0")]
    Zero,
    #[rust_sitter::leaf(text = "1")]
    One,
}

#[rust_sitter::prec(...)] / #[rust_sitter::prec_left(...)] / #[rust_sitter::prec_right(...)]

此注释可用于定义一个非左/右结合运算符。此注释接受一个参数,即运算符的优先级(优先级越高,结合越紧密)。

#[rust_sitter::跳过(...)]

此注释可用于定义一个不对应输入字符串中任何内容的字段,例如某些元数据。此注释接受一个参数,该参数是在运行时用于填充该字段的值。

#[rust_sitter::单词]

此注释将字段标记为 Tree Sitter 单词,这对于处理涉及关键字错误的操作非常有用。语法中只能有一个字段被标记为单词。

特殊类型

Rust Sitter 有几种特殊类型,可用于定义更复杂的语法。

Vec<T>

为了解析重复的结构,可以使用 Vec<T> 解析 T 的列表。请注意,Vec<T> 类型 不能 被另一个 Vec 包装(如果需要,请创建额外的结构体)。可以对 Vec 字段应用两个特殊属性来控制解析行为。

#[rust_sitter::delimited(...)] 属性可以用于指定列表元素之间的分隔符,并接受与未命名字段相同格式的参数。例如,我们可以定义一个解析逗号分隔表达式列表的语法

pub struct CommaSeparatedExprs {
    #[rust_sitter::delimited(
        #[rust_sitter::leaf(text = ",")]
        ()
    )]
    numbers: Vec<Expr>,
}

#[rust_sitter::repeat(...)] 属性可以用于指定解析器的额外配置。目前只有一个可用的参数:non_empty,它接受一个布尔值,指定列表是否必须包含至少一个元素。例如,我们可以定义一个解析非空逗号分隔数字列表的语法

pub struct CommaSeparatedExprs {
    #[rust_sitter::repeat(non_empty = true)]
    #[rust_sitter::delimited(
        #[rust_sitter::leaf(text = ",")]
        ()
    )]
    numbers: Vec<Expr>,
}

Option<T>

要解析可选结构,您可以使用一个 Option<T> 来解析单个 T 或无。类似于 Vec,类型 Option<T> 不能 被包装在另一个 Option 中(如果需要,创建额外的结构)。例如,我们可以使前一个示例中的列表元素可选,以便可以解析如 1,,2 的字符串。

pub struct CommaSeparatedExprs {
    #[rust_sitter::repeat(non_empty = true)]
    #[rust_sitter::delimited(
        #[rust_sitter::leaf(text = ",")]
        ()
    )]
    numbers: Vec<Option<Expr>>,
}

rust_sitter::跨越<T>

当使用 Rust Sitter 来驱动诊断工具时,访问标记解析节点对应文本部分的跨度可能会有帮助。为此,您可以使用 Spanned<T> 类型,它捕获底层的解析 T 以及对应子字符串的开始(包含)和结束(不包含)的索引对。Spanned 类型可以在任何地方使用,并且不影响解析逻辑。例如,我们可以捕获我们之前示例中的表达式的跨度。

pub struct CommaSeparatedExprs {
    #[rust_sitter::repeat(non_empty = true)]
    #[rust_sitter::delimited(
        #[rust_sitter::leaf(text = ",")]
        ()
    )]
    numbers: Vec<Option<Spanned<Expr>>>,
}

盒子<T>

解析时会在内部类型周围自动构建盒子,但 Rust Sitter 不会做更多的事情。

依赖项

~1.2–2.5MB
~57K SLoC