如何在 Rust 中使用 proc_macro_attribute 修改所有字符串文字?

cam*_*rcu 1 rust rust-proc-macros

我正在摆弄 Rust 的过程宏,我想制作一个自定义的有趣属性,将 TokenStream 中的所有字符串文字转换为 SHOUTING CASE。

到目前为止,我的 proc-macro 库中的内容如下amplify

// lib.rs
extern crate proc_macro;

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, File};

#[proc_macro_attribute]
pub fn amplify(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let input = parse_macro_input!(item as File);

    // TODO: modify string literals (LitStr) in token tree to be upper case

    let quoted = quote! {#input};
    println!("{:#?}", quoted);  // just print at compile time to see what is produced
    quoted.into()
}
Run Code Online (Sandbox Code Playgroud)

当应用于简单的 hello-world 程序时,它应该变成“Hello, World!” 字符串文字转换为“HELLO, WORLD!” 在编译时。例子:

use amplify::amplify;

#[amplify]
pub fn main() {
    println!("Hello, World!");
}
Run Code Online (Sandbox Code Playgroud)

然后运行代码:

cargo run
# prints: HELLO, WORLD!
Run Code Online (Sandbox Code Playgroud)

检查二进制文件中的字符串:

strings target/debug/hello-world | grep -i hello
# prints: HELLO, WORLD! (and possibly other string garbage from the binary)
Run Code Online (Sandbox Code Playgroud)

需要注意的是,我希望属性宏递归地遍历标记树以查找所有字符串文字。我不知道如何检查树中的每个项目是否是字符串文字,或者项目是否需要递归。

任何帮助将不胜感激!

Loc*_*cke 6

我没有太多使用 的经验proc_macro,但基于这个答案,我发现很容易对其进行调整以手动替换令牌树中的文字,而无需使用synquote板条箱。这种方法涉及使用litrscrate 来分离文字的类型。

use proc_macro::{TokenStream, TokenTree, Literal};
use litrs::StringLit;


fn replace_uppercase(item: TokenStream) -> TokenStream {
    item.into_iter()
        .map(|x| {
            match x {
                TokenTree::Group(group) => {
                    // Pass items in group back through replace_uppercase
                    let new_group = Group::new(group.deliminer(), replace_uppercase(group.stream()));
                    TokenTree::Group(new_group)
                },
                TokenTree::Literal(literal) => {
                    // The `litrs` crate was an easy and straightforward approach, so I used it to determine the type of input literals
                    if let Ok(input_string) = StringLit::try_from(literal) {
                        // Convert string literals with uppercase versions
                        TokenTree::Literal(Literal::string(input_string.value().to_uppercase()))
                    } else {
                        TokenTree::Literal(literal)
                    }
                },
                v => v,
            }
          })
        .collect()
}

#[proc_macro_attribute]
pub fn amplify(_attr: TokenStream, item: TokenStream) -> TokenStream {
    replace_uppercase(item)
}
Run Code Online (Sandbox Code Playgroud)

老实说,syn这可能是更正确的选择,但是我发现很难阅读文档,因为该界面主要由宏组成。