如何在程序宏中获取文字的值和类型?

Pab*_*mos 8 rust

我正在实现一个类似函数的程序宏,它将单个字符串文字作为参数,但我不知道如何获取字符串文字的值。

如果我打印变量,它会显示一堆字段,其中包括类型和值。他们显然就在那里,在某个地方。我如何获得它们?

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这是目前完成这项工作的最佳工具。)

  • @SashaKondrashov 我编辑了我的答案并添加了一些解释。这足以解释它吗? (3认同)

Frx*_*rem 5

如果您正在编写程序宏,我建议您考虑使用 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)

  • 我研究了 syn,发现它使用起来很混乱。对于我想做的事情来说,这似乎有些过分了。 (5认同)