是否可以在 Rust 中定义一个可以解析自定义文字的宏,例如
vector!(3x + 15y)
Run Code Online (Sandbox Code Playgroud)
澄清一下,我希望能够尽可能接近上述语法(当然在可能的范围内)。
我将假设“自定义文字”是指“常规 Rust 文字(不包括原始文字),后面紧跟自定义标识符”。这包括:
"str"x,"str"带有自定义后缀的字符串文字x123x,123带有自定义后缀的数字文字xb"bytes"x,b"bytes"带有自定义后缀的字节文字x如果以上定义对您来说已经足够了,那么您很幸运,因为根据Rust 参考资料,以上确实是 Rust 中所有有效的文字标记:
后缀是紧跟在文字主要部分之后的非原始标识符(没有空格)。
具有任何后缀的任何类型的文字(字符串、整数等)都可以作为标记有效,并且可以传递给宏而不会产生错误。宏本身将决定如何解释这样的标记以及是否产生错误。
但是,解析为 Rust 代码的文字标记的后缀受到限制。非数字文字标记上的任何后缀都将被拒绝,并且数字文字标记仅接受以下列表中的后缀。
所以 Rust明确 允许宏支持自定义字符串文字。
现在,您将如何编写这样的宏?您不能使用 编写声明性宏macro_rules!,因为无法通过简单的模式匹配来检测和操作自定义文字后缀。但是,可以编写执行此操作的过程宏。
我不会详细介绍如何编写程序宏,因为在单个 StackOverflow 答案中写太多。但是,作为起点,我将向您提供这个程序宏示例,它按照您的要求执行某些操作。它接受任何自定义整数文字123x或123y在给定的表达式中,并将它们转换为函数调用x_literal(123),y_literal(123)而不是:
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::ToTokens;
use syn::{
parse_macro_input, parse_quote,
visit_mut::{self, VisitMut},
Expr, ExprLit, Lit, LitInt,
};
// actual procedural macro
#[proc_macro]
pub fn vector(input: TokenStream) -> TokenStream {
let mut input = parse_macro_input!(input as Expr);
LiteralReplacer.visit_expr_mut(&mut input);
input.into_token_stream().into()
}
// "visitor" that visits every node in the syntax tree
// we add our own behavior to replace custom literals with proper Rust code
struct LiteralReplacer;
impl VisitMut for LiteralReplacer {
fn visit_expr_mut(&mut self, i: &mut Expr) {
if let Expr::Lit(ExprLit { lit, .. }) = i {
match lit {
Lit::Int(lit) => {
// get literal suffix
let suffix = lit.suffix();
// get literal without suffix
let lit_nosuffix = LitInt::new(lit.base10_digits(), lit.span());
match suffix {
// replace literal expression with new expression
"x" => *i = parse_quote! { x_literal(#lit_nosuffix) },
"y" => *i = parse_quote! { y_literal(#lit_nosuffix) },
_ => (), // other literal suffix we won't modify
}
}
_ => (), // other literal type we won't modify
}
} else {
// not a literal, use default visitor method
visit_mut::visit_expr_mut(self, i)
}
}
}
Run Code Online (Sandbox Code Playgroud)
例如,宏将转换vector!(3x + 4y)为x_literal(3) + y_literal(4).