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日

#1710 in 开发工具

Download history 1461/week @ 2024-04-14 915/week @ 2024-04-21 997/week @ 2024-04-28 965/week @ 2024-05-05 1421/week @ 2024-05-12 1436/week @ 2024-05-19 1342/week @ 2024-05-26 2358/week @ 2024-06-02 1630/week @ 2024-06-09 1769/week @ 2024-06-16 2659/week @ 2024-06-23 1265/week @ 2024-06-30 1553/week @ 2024-07-07 2249/week @ 2024-07-14 1691/week @ 2024-07-21 1824/week @ 2024-07-28

7,408 每月下载次数
7 个包中使用 (2 个直接使用)

MIT 许可证

15KB
120

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 使用一个带有纯 Rust 运行时的 Tree Sitter 分支来支持 wasm32-unknown-unknown。要使用标准 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::language]

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

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

#[rust_sitter::extra]

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

#[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或空值。类似于VecOption<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.5MB
~36K SLoC