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

How to modify all string literals with a proc_macro_attribute in Rust?

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

这是我目前在名为 amplify:

的 proc-macro 库中的内容
// 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()
}

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

use amplify::amplify;

#[amplify]
pub fn main() {
    println!("Hello, World!");
}

然后运行代码:

cargo run
# prints: HELLO, WORLD!

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

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

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

如有任何帮助,我们将不胜感激!

我没有太多使用 proc_macro 的经验,但基于 I found it was easy to adapt it to manually replace literals in the token tree without the use of the syn or quote crates. This approach involved using the litrs 板条箱来分离文字类型。

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)
}

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

仅出于演示目的,下面是使用 syn:

时的样子
use proc_macro::TokenStream;
use quote::ToTokens;

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

    struct StringsModifier;
    impl syn::visit_mut::VisitMut for StringsModifier {
        // You can also implement `visit_lit_byte_str_mut()` to transform `b"..."` strings
        fn visit_lit_str_mut(&mut self, lit: &mut syn::LitStr) {
            *lit = syn::LitStr::new(&lit.value().to_uppercase(), lit.span());
        }
    }
    syn::visit_mut::visit_file_mut(&mut StringsModifier, &mut input);

    input.into_token_stream().into()
}

但请注意,它不会下降到宏(println!("a{}", "b") 根本不会被转换),因为 syn 没有办法解析它们。