我正在实现一个类似函数的程序宏,它将单个字符串文字作为参数,但我不知道如何获取字符串文字的值。
如果我打印变量,它会显示一堆字段,其中包括类型和值。他们显然就在那里,在某个地方。我如何获得它们?
extern crate proc_macro;
use proc_macro::{TokenStream,TokenTree};
#[proc_macro]
pub fn my_macro(input: TokenStream) -> TokenStream {
let input: Vec<TokenTree> = input.into_iter().collect();
let literal = match &input.get(0) {
Some(TokenTree::Literal(literal)) => literal,
_ => panic!()
};
// can't do anything with "literal"
// println!("{:?}", literal.lit.symbol); says "unknown field"
format!("{:?}", format!("{:?}", literal)).parse().unwrap()
}
Run Code Online (Sandbox Code Playgroud)
#![feature(proc_macro_hygiene)]
extern crate macros;
fn main() {
let value = macros::my_macro!("hahaha");
println!("it is {}", value);
// prints "it is Literal { lit: Lit { kind: Str, symbol: "hahaha", suffix: None }, span: Span { lo: BytePos(100), hi: BytePos(108), ctxt: #0 } }"
}
Run Code Online (Sandbox Code Playgroud)
Luk*_*odt 13
在无数次遇到同样的问题之后,我终于编写了一个库来帮助解决这个问题:litrs在 crates.io 上。它的编译速度比它快syn,并且可以让你检查你的文字。
use std::convert::TryFrom;
use litrs::StringLit;
use proc_macro::TokenStream;
use quote::quote;
#[proc_macro]
pub fn my_macro(input: TokenStream) -> TokenStream {
let input = input.into_iter().collect::<Vec<_>>();
if input.len() != 1 {
let msg = format!("expected exactly one input token, got {}", input.len());
return quote! { compile_error!(#msg) }.into();
}
let string_lit = match StringLit::try_from(&input[0]) {
// Error if the token is not a string literal
Err(e) => return e.to_compile_error(),
Ok(lit) => lit,
};
// `StringLit::value` returns the actual string value represented by the
// literal. Quotes are removed and escape sequences replaced with the
// corresponding value.
let v = string_lit.value();
// TODO: implement your logic here
}
Run Code Online (Sandbox Code Playgroud)
请参阅的文档litrs以获取更多信息。
要获取有关文字的更多信息,litrs请使用Displayimpl ofLiteral获取字符串表示形式(就像在源代码中编写的那样),然后解析该字符串。例如,如果字符串以0x1 开头,则知道它必须是整数文字,如果以r#"1 开头,则知道它是原始字符串文字。板条箱的syn作用完全相同。
当然,考虑到 rustc已经解析了文字,编写和运行第二个解析器似乎有点浪费。是的,这很不幸,如果有更好的 APIproc_literal就更好了。但现在,我认为litrs(或者syn如果你正在使用的syn话)是最好的解决方案。
(PS:我通常不喜欢在 Stack Overflow 上推广自己的库,但我非常熟悉 OP 遇到的问题,并且我非常认为litrs这是目前完成这项工作的最佳工具。)
如果您正在编写程序宏,我建议您考虑使用 crate syn(用于解析)和quote(用于代码生成)而不是proc-macro直接使用,因为它们通常更容易处理。
在这种情况下,您可以使用syn::parse_macro_input将令牌流解析为 Rust 的任何语法元素(例如文字、表达式、函数),并且还会在解析失败时处理错误消息。
您可以使用LitStr来表示字符串文字,如果这正是您所需要的。该.value()函数将为您String提供该文字的内容。
您可以使用quote::quote生成宏的输出,并使用#将变量的内容插入到生成的代码中。
use proc_macro::TokenStream;
use syn::{parse_macro_input, LitStr};
use quote::quote;
#[proc_macro]
pub fn my_macro(input: TokenStream) -> TokenStream {
// macro input must be `LitStr`, which is a string literal.
// if not, a relevant error message will be generated.
let input = parse_macro_input!(input as LitStr);
// get value of the string literal.
let str_value = input.value();
// do something with value...
let str_value = str_value.to_uppercase();
// generate code, include `str_value` variable (automatically encodes
// `String` as a string literal in the generated code)
(quote!{
#str_value
}).into()
}
Run Code Online (Sandbox Code Playgroud)