使用旧的 Rust 2015
0.3.2 |
|
---|---|
0.3.1 |
|
0.3.0 |
|
0.2.0 |
|
0.1.0 |
|
#166 在 #tokens
47KB
1.5K SLoC
Rust 源代码解析器
Syn 是一个解析库,可以将 Rust 令牌流解析成 Rust 源代码的语法树。
目前这个库主要用于 Rust 过程宏,但也包含一些可能更通用的 API。
-
数据结构 — Syn 提供了一个完整的语法树,可以表示任何有效的 Rust 源代码。语法树以
syn::File
为根,它表示一个完整的源文件,但还有其他可能对过程宏有用的入口点,包括syn::Item
、syn::Expr
和syn::Type
。 -
派生 — 对派生宏特别感兴趣的是
syn::DeriveInput
,它是派生宏的三个合法输入项之一。以下示例展示了在一个可以派生用户定义特质的实现库中使用此类型。 -
解析 — Syn 中的解析围绕具有签名的 解析函数 构建:
fn(ParseStream) -> Result<T>
。Syn 定义的每个语法树节点都是可单独解析的,可以用作自定义语法的构建块,或者你可以梦想出全新的语法,而不涉及我们的任何语法树类型。 -
位置信息 — Syn 解析的每个令牌都与一个
Span
相关联,该 Span 追踪行和列信息回溯到该令牌的源。这些 Span 允许过程宏显示指向用户代码中所有正确位置的详细错误消息。以下是一个示例。 -
功能标志 — 功能被积极地进行门控,因此您的过程宏只启用它们需要的功能,而不会在编译时间上为所有其他功能付费。
版本要求:Syn 支持 rustc 1.61 及以上版本。
资源
了解过程宏的最佳方式是通过编写一些。可以考虑通过这个过程宏工作坊来熟悉不同类型的过程宏。在工作坊中,您将学习每个项目时,Syn 文档中的相关链接。
派生宏的示例
使用 Syn 的规范派生宏看起来像这样。我们编写一个带有 proc_macro_derive
属性和我们要派生的特名称的普通 Rust 函数。每当在用户的代码中遇到该派生时,Rust 编译器就会将用户的数据结构作为标记传递给我们的宏。我们可以执行任意的 Rust 代码来确定如何处理这些标记,然后将一些标记返回给编译器以编译到用户的 crate。
[dependencies]
syn = "2.0"
quote = "1.0"
[lib]
proc-macro = true
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(MyMacro)]
pub fn my_macro(input: TokenStream) -> TokenStream {
// Parse the input tokens into a syntax tree
let input = parse_macro_input!(input as DeriveInput);
// Build the output, possibly using quasi-quotation
let expanded = quote! {
// ...
};
// Hand the output tokens back to the compiler
TokenStream::from(expanded)
}
heapsize
示例目录展示了派生宏的一个完整的实现示例。该示例派生了一个 HeapSize
特性,它计算一个值拥有的堆内存量的估计值。
pub trait HeapSize {
/// Total number of bytes of heap memory owned by `self`.
fn heap_size_of_children(&self) -> usize;
}
派生宏允许用户在程序中的数据结构上编写 #[derive(HeapSize)]
。
#[derive(HeapSize)]
struct Demo<'a, T: ?Sized> {
a: Box<T>,
b: u8,
c: &'a str,
d: String,
}
跨度与错误报告
基于标记的过程宏 API 提供了对编译器错误信息在用户代码中显示位置的良好控制。考虑一下,如果用户的某个字段类型没有实现 HeapSize
,用户会看到的错误。
#[derive(HeapSize)]
struct Broken {
ok: String,
bad: std::thread::Thread,
}
通过跟踪跨度信息,就像在 heapsize
示例中展示的那样,Syn 中的基于标记的宏能够触发直接指向问题源的错误。
error[E0277]: the trait bound `std::thread::Thread: HeapSize` is not satisfied
--> src/main.rs:7:5
|
7 | bad: std::thread::Thread,
| ^^^^^^^^^^^^^^^^^^^^^^^^ the trait `HeapSize` is not implemented for `std::thread::Thread`
解析自定义语法
lazy-static
示例目录展示了如何使用 Syn 的解析 API 解析输入标记的 functionlike!(...)
过程宏的实现。
该示例重新实现了来自 crates.io 的流行 lazy_static
crate,作为过程宏。
lazy_static! {
static ref USERNAME: Regex = Regex::new("^[a-z0-9_-]{3,16}$").unwrap();
}
该实现展示了如何在宏输入上触发自定义警告和错误信息。
warning: come on, pick a more creative name
--> src/main.rs:10:16
|
10 | static ref FOO: String = "lazy_static".to_owned();
| ^^^
测试
在测试宏时,我们不仅关心宏能否成功使用,而且还关心当宏接收到无效输入时,它产生的错误信息是否尽可能有帮助。可以考虑使用 trybuild
crate 为您的宏产生的错误或由 Rust 编译器在宏误用后检测到的错误编写测试。此类测试有助于避免后续重构导致错误不再触发或不如之前那样有帮助的问题。
调试
在开发过程宏时,查看生成的代码可能很有帮助。使用 cargo rustc -- -Zunstable-options --pretty=expanded
或 cargo expand
子命令。
要显示使用您的过程宏的一些crate的展开代码,请在该crate中运行cargo expand
。要显示您自己的测试用例之一的展开代码,请运行cargo expand --test the_test_case
,其中最后一个参数是不带.rs
扩展名的测试文件名。
布兰登·W·迈斯特(Brandon W Maister)在这篇文章中详细讨论了调试:[调试Rust的新自定义派生系统](https://quodlibetor.github.io/posts/debugging-rusts-new-custom-derive-system/)
可选功能
Syn将许多功能放在可选功能后面,以优化最常见的用例的编译时间。以下功能可用。
derive
(默认启用)—— 表示派生宏可能输入的数据结构,包括结构体、枚举和类型。full
—— 表示所有有效Rust源代码语法树的 数据结构,包括项和表达式。parsing
(默认启用)—— 将输入令牌解析为所选类型的语法树节点的能力。printing
(默认启用)—— 将语法树节点打印为Rust源代码令牌的能力。visit
—— 遍历语法树的特质。visit-mut
—— 遍历和就地修改语法树的特质。fold
—— 转换拥有语法树的特质。clone-impls
(默认启用)—— 所有语法树类型的克隆实现。extra-traits
—— 所有语法树类型的Debug、Eq、PartialEq、Hash实现。proc-macro
(默认启用)—— 对rustc工具链中的动态库libproc_macro的运行时依赖。
过程宏适配器
Syn操作的是来自crates.io的proc-macro2 crate提供的令牌表示,而不是直接使用编译器内置的过程宏crate。这使得使用Syn的代码可以在过程宏上下文之外执行,例如在单元测试或build.rs中,我们避免了需要为过程宏和非宏用例不兼容的生态系统。
通常,所有代码都应针对proc-macro2编写,而不是proc-macro。一个例外是过程宏入口点的签名,语言要求使用proc_macro::TokenStream
。
当过程宏活动时,proc-macro2 crate将自动检测并使用编译器的数据结构。
许可
根据您的选择,在Apache License, Version 2.0或MIT license下许可。除非您明确说明,否则您提交的任何旨在包含在此crate中的贡献,如Apache-2.0许可中定义的,均将按照上述方式双重许可,不附加任何额外的条款或条件。
依赖关系
~190KB