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 开发工具
7,408 每月下载次数
在 7 个包中使用 (2 个直接使用)
15KB
120 行
Rust Sitter
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) + 3
或 1 + (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
或空值。类似于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.5MB
~36K SLoC