我将假设“自定义文字”是指“一个常规的 Rust 文字(不包括原始文字),紧跟一个自定义标识符”。这包括:
-
"str"x,带有自定义后缀 x 的字符串文字 "str"
-
123x,带有自定义后缀 x 的数字文字 123
-
b"bytes"x,一个字节文字b"bytes",带有自定义后缀x
如果上面的定义对你来说足够了,那么你很幸运,因为上面确实是 Rust 中所有有效的文字标记,根据the Rust reference:
后缀是紧跟在文字主要部分之后的非原始标识符(没有空格)。
任何带有任何后缀的文字(字符串、整数等)都可以作为标记有效,并且可以传递给宏而不会产生错误。宏本身将决定如何解释这样的标记以及是否产生错误。
但是,被解析为 Rust 代码的文字标记的后缀受到限制。非数字文字标记上的任何后缀都将被拒绝,而数字文字标记仅接受以下列表中的后缀。
所以 Rust 明确地 允许宏支持自定义字符串文字。
现在,您将如何编写这样的宏?您不能使用macro_rules! 编写声明性宏,因为无法通过其简单的模式匹配来检测和操作自定义文字后缀。但是,可以编写一个 procedural macro 来执行此操作。
我不会详细介绍如何编写过程宏,因为在单个 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)
}
}
}
例如,宏会将vector!(3x + 4y) 转换为x_literal(3) + y_literal(4)。