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)
}
}
}
1条答案
按热度按时间tjjdgumg1#
我将假设“自定义文字”是指“一个常规的Rust文字(不包括原始文字),后面紧跟一个自定义标识符”。这包括:
"str"x
,字符串文字"str"
,带有自定义后缀x
123x
,带有自定义后缀x
的数字文本123
b"bytes"x
,字节文字b"bytes"
,带自定义后缀x
如果上面的定义对你来说足够了,那么你很幸运,因为根据Rust参考,上面的确实是Rust中所有有效的文字标记:
后缀是紧接在文字的主要部分之后的非原始标识符(没有空格)。
任何带后缀的文字(字符串、整数等)都是有效的token,可以传递给宏而不会产生错误,宏自己决定如何解释这样的token,是否产生错误。
但是,解析为Rust代码的文字标记的后缀是受限制的。任何非数字文字标记的后缀都被拒绝,数字文字标记只接受下面列表中的后缀。
所以Rust * 显式地 * 允许宏支持自定义文字。
现在,你将如何编写这样一个宏呢?你不能用
macro_rules!
编写一个声明性宏,因为它不可能通过简单的模式匹配来检测和操作自定义的文字后缀。但是,可以编写一个procedural macro来做到这一点。我不会详细介绍如何编写过程宏,因为在一个StackOverflow的答案中写太多了。但是,我会给予你一个过程宏的例子,它沿着你要求的方式做一些事情,作为起点。它接受给定表达式中的任何自定义整数文字
123x
或123y
,并将它们转换为函数调用x_literal(123)
和y_literal(123)
:例如,宏将把
vector!(3x + 4y)
转换为x_literal(3) + y_literal(4)
。