2个版本
0.1.18 | 2020年4月24日 |
---|---|
0.1.0 | 2020年4月14日 |
#20 in #tree-node
1MB
26K SLoC
Rust源代码解析器
基于
syn
改编如果你不清楚自己在做什么,不要在过程宏上下文中使用
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
相关联。这些跨度允许过程宏显示指向用户代码中所有正确位置的详细错误消息。下面是一个示例。 -
功能标志 — 功能性被严格地门控,因此您的过程宏只启用它们需要的功能,并且不需要为所有其他功能支付编译时间。
如果您在Rust中的任何与过程宏相关的问题上遇到困难,我很乐意提供帮助,即使问题与Syn无关。请在本存储库中提交工单。
版本要求:Syn支持rustc 1.31及以上版本。
资源
了解过程宏的最好方法是通过编写一些。考虑通过这个过程宏研讨会熟悉不同类型的过程宏。在完成每个项目时,研讨会包含指向Syn文档的相关链接。
派生宏示例
使用Syn的规范派生宏看起来像这样。我们编写一个带有proc_macro_derive
属性和要派生的特质名称的普通Rust函数。每次该派生在用户代码中出现时,Rust编译器都会将它们的数据结构作为标记传递给我们的宏。我们可以执行任意Rust代码来了解如何处理这些标记,然后将一些标记返回给编译器,以便将其编译到用户的crate中。
[dependencies]
syn = "1.0"
quote = "1.0"
[lib]
proc-macro = true
extern crate proc_macro;
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
示例目录展示了派生宏的完整工作实现。它适用于任何Rust编译器1.31+。该示例派生了一个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上的流行crate lazy_static
,作为过程宏。
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
扩展名)的名称。
Brandon W Maister 的这篇文档详细讨论了调试:调试 Rust 的新自定义派生系统。
可选功能
Syn 将许多功能放在可选功能后面,以优化最常见用例的编译时间。以下功能可用。
derive
(默认启用) — 表示 derive 宏可能的输入的数据结构,包括结构体、枚举和类型。full
— 表示所有有效 Rust 源代码的语法树的 数据结构,包括项和表达式。parsing
(默认启用) — 将输入令牌解析为所选类型的语法树节点的能力。printing
(默认启用) — 将语法树节点打印为 Rust 源代码的令牌。visit
— 用于遍历语法树的 trait。visit-mut
— 用于遍历和原地修改语法树的 trait。fold
— 用于转换拥有语法树的 trait。clone-impls
(默认启用) — 所有语法树类型的 Clone 实现。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 会自动检测并使用编译器的数据结构。
许可证
根据您的选择,在 Apache License, Version 2.0 或 MIT 许可证 下获得许可。除非您明确声明,否则您提交的任何旨在包含在此 crate 中的贡献,根据 Apache-2.0 许可证定义,均应按上述方式双重许可,不附加任何额外条款或条件。