[英]How do I get the value and type of a Literal in a procedural macro?
我正在實現一個類似函數的過程宏,它將單個字符串文字作為參數,但我不知道如何獲取字符串文字的值。
如果我打印變量,它會顯示一堆字段,其中包括類型和值。 它們顯然就在那里,某處。 我如何得到它們?
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()
}
#![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 } }"
}
如果您正在編寫過程宏,我建議您考慮使用 crates syn
(用於解析)和quote
(用於代碼生成)而不是直接使用proc-macro
,因為它們通常更容易處理。
在這種情況下,您可以使用syn::parse_macro_input
將令牌 stream 解析為 Rust 的任何語法元素(例如文字、表達式、函數),並且還會在解析失敗時處理錯誤消息。
如果這正是您所需要的,您可以使用LitStr
來表示字符串文字。 .value()
function 將為您提供一個包含該文字內容的String
。
您可以使用quote::quote
生成宏的output,並使用#
將變量的內容插入到生成的代碼中。
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()
}
在無數次遇到同樣的問題之后,我終於寫了一個庫來幫助解決這個問題: litrs
on 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
}
有關更多信息,請參閱litrs
的文檔。
為了獲取有關文字的更多信息, litrs
使用Literal
的Display
impl 來獲取字符串表示形式(因為它將在源代碼中編寫),然后解析該字符串。 例如,如果字符串以0x
,則知道它必須是 integer 文字,如果以r#"
開頭,則知道它是原始字符串文字。 crate syn
的作用完全相同。
當然,鑒於 rustc已經解析了文字,編寫並運行第二個解析器似乎有點浪費。 是的,這很不幸,在 proc_literal 中有一個更好的proc_literal
會更好。 但是現在,我認為litrs
(或者syn
,如果你正在使用syn
)是最好的解決方案。
(PS:我通常不喜歡在 Stack Overflow 上推廣自己的庫,但我對 OP 遇到的問題非常熟悉,我非常認為litrs
是目前完成這項工作的最佳工具。)
我一直想要一個字符串文字,所以我發現這個解決方案已經足夠好了。 Literal
實現ToString
,然后我可以將其與.parse()
一起使用。
#[proc_macro]
pub fn my_macro(input: TokenStream) -> TokenStream {
let input: Vec<TokenTree> = input.into_iter().collect();
let value = match &input.get(0) {
Some(TokenTree::Literal(literal)) => literal.to_string(),
_ => panic!()
};
let str_value: String = value.parse().unwrap();
// do whatever
format!("{:?}", str_value).parse().unwrap()
}
我在解析doc
屬性時遇到了類似的問題。 它也表示為TokenStream
。 這不是確切的答案,但可能會引導正確的方向:
fn from(value: &Vec<Attribute>) -> Vec<String> {
let mut lines = Vec::new();
for attr in value {
if !attr.path.is_ident("doc") {
continue;
}
if let Ok(Meta::NameValue(nv)) = attr.parse_meta() {
if let Lit::Str(lit) = nv.lit {
lines.push(lit.value());
}
}
}
lines
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.