1个不稳定版本

使用旧Rust 2015

0.1.0 2017年7月26日

#2152 in 过程宏


2 个crate中使用(通过 unrest_tmp_syn

MIT/Apache

75KB
1.5K SLoC

Nom解析器,用于Rust源代码

Build Status Latest Version Rust Documentation

无需Syntex依赖项即可解析Rust源代码,旨在与 宏1.1 一起使用。

设计用于快速编译时间。

  • 编译时间 syn(从头开始包括所有依赖项):6秒
  • 编译时间 syntex/quasi/aster 堆栈:60+秒

如果您在宏1.1中遇到困难,即使问题与syn无关,我也很乐意提供帮助。请在此仓库中提交问题。

与宏1.1一起使用

[dependencies]
syn = "0.12"
quote = "0.3"

[lib]
proc-macro = true
extern crate proc_macro;
use proc_macro::TokenStream;

extern crate syn;

#[macro_use]
extern crate quote;

#[proc_macro_derive(MyMacro)]
pub fn my_macro(input: TokenStream) -> TokenStream {
    let source = input.to_string();

    // Parse the string representation into a syntax tree
    let ast = syn::parse_derive_input(&source).unwrap();

    // Build the output, possibly using quasi-quotation
    let expanded = quote! {
        // ...
    };

    // Parse back to a token stream and return it
    expanded.parse().unwrap()
}

完整示例

假设我们有一个以下简单的trait,它返回结构体中的字段数

trait NumFields {
    fn num_fields() -> usize;
}

基于 synquote 的完整宏1.1实现如下

extern crate proc_macro;
use proc_macro::TokenStream;

extern crate syn;

#[macro_use]
extern crate quote;

#[proc_macro_derive(NumFields)]
pub fn num_fields(input: TokenStream) -> TokenStream {
    let source = input.to_string();

    // Parse the string representation into a syntax tree
    let ast = syn::parse_derive_input(&source).unwrap();

    // Build the output
    let expanded = expand_num_fields(&ast);

    // Return the generated impl as a TokenStream
    expanded.parse().unwrap()
}

fn expand_num_fields(ast: &syn::DeriveInput) -> quote::Tokens {
    let n = match ast.body {
        syn::Body::Struct(ref data) => data.fields().len(),
        syn::Body::Enum(_) => panic!("#[derive(NumFields)] can only be used with structs"),
    };

    // Used in the quasi-quotation below as `#name`
    let name = &ast.ident;

    // Helper is provided for handling complex generic types correctly and effortlessly
    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();

    quote! {
        // The generated impl
        impl #impl_generics ::mycrate::NumFields for #name #ty_generics #where_clause {
            fn num_fields() -> usize {
                #n
            }
        }
    }
}

有关涉及特质边界、枚举和不同类型结构体的更详细的示例,请参阅 DeepClonedeep-clone-derive

测试

宏1.1有一个限制,即您的过程宏crate必须导出除了 proc_macro_derive 函数之外的内容,并且 proc_macro_derive 过程宏不能在定义它们的crate中使用。这些限制可能会在未来取消,但到目前为止,它们使得编写测试比其他类型的代码要复杂一些。

特别是,你将无法直接在你的代码行中编写像这样的测试函数:#[test] fn it_works() { ... }。相反,你可以在一个tests目录中放置测试,或者完全在另一个crate中。

此外,如果你的过程宏实现了一个特定的trait,那么这个trait必须在与过程宏分开的crate中定义。

作为一个具体的例子,假设你的过程宏crate名为my_derive,并且它实现了一个名为my_crate::MyTrait的trait。你的过程宏单元测试可以放在my_derive/tests/test.rs中,或者放入一个单独的crate my_tests/tests/test.rs中。无论哪种方式,测试看起来都像这样

#[macro_use]
extern crate my_derive;

extern crate my_crate;
use my_crate::MyTrait;

#[test]
fn it_works() {
    #[derive(MyTrait)]
    struct S { /* ... */ }

    /* test the thing */
}

调试

在开发过程宏时,查看生成的代码可能会有所帮助。使用cargo rustc -- -Zunstable-options --pretty=expandedcargo expand子命令。

要显示使用你的过程宏的某个crate的展开代码,请在该crate中运行cargo expand。要显示你的某个测试用例的展开代码,请运行cargo expand --test the_test_case,其中最后一个参数是测试文件名,不包括.rs扩展名。

Brandon W Maister的这篇文档详细讨论了调试:调试Rust的新自定义Derive系统

可选功能

Syn将很多功能放在可选功能之后,以优化最常见的用例的编译时间。以下是可用的功能和它们对编译时间的影响。依赖项包含在编译时间中。

功能 编译时间 功能
(无) 3秒 表示Rust结构体、枚举和类型的AST的数据结构。
解析 6秒 解析包含结构体和枚举的Rust源代码到AST。
打印 4秒 将结构体和枚举的AST打印成Rust源代码。
解析、打印 6秒 这是默认设置。解析和打印Rust结构体和枚举。这通常是实现Macros 1.1自定义Derive所需的内容。
full 4秒 表示所有可能Rust代码的完整AST的数据结构。
full、解析 9秒 将任何有效的Rust源代码解析为AST。
full、打印 6秒 将AST转换为Rust源代码。
full、解析、打印 11秒 解析和打印任何Rust语法。

许可证

根据您选择,许可以下之一

at your option.

贡献

除非您明确声明,否则根据Apache-2.0许可证定义,您有意提交以包含在本软件包中的任何贡献,将按上述方式双重许可,不附加任何额外条款或条件。

依赖项

~230KB