通过 Rust 宏自定义文字?
Posted
技术标签:
【中文标题】通过 Rust 宏自定义文字?【英文标题】:Custom literals via Rust macros? 【发布时间】:2020-07-02 12:56:00 【问题描述】:是否可以在 Rust 中定义一个可以解析自定义文字的宏,例如类似于
的东西vector!(3x + 15y)
为了澄清,我希望能够尽可能接近上述语法(当然是在可能的范围内)。
【问题讨论】:
您是在询问此特定语法(不,3x
不是有效标记)还是一般情况下(当然,例如见this)?
这个特定的语法(或至少类似的东西);编辑了问题,希望能更清楚。
@trentcl 3x
是一个有效的标记,Rust reference 明确表示允许宏接受带有任意后缀的文字。详情见我的回答。
【参考方案1】:
我将假设“自定义文字”是指“一个常规的 Rust 文字(不包括原始文字),紧跟一个自定义标识符”。这包括:
"str"x
,带有自定义后缀 x
的字符串文字 "str"
123x
,带有自定义后缀 x
的数字文字 123
b"bytes"x
,一个字节文字b"bytes"
,带有自定义后缀x
如果上面的定义对你来说足够了,那么你很幸运,因为上面确实是 Rust 中所有有效的文字标记,根据the Rust reference:
后缀是紧跟在文字主要部分之后的非原始标识符(没有空格)。
任何带有任何后缀的文字(字符串、整数等)都可以作为标记有效,并且可以传递给宏而不会产生错误。宏本身将决定如何解释这样的标记以及是否产生错误。
但是,被解析为 Rust 代码的文字标记的后缀受到限制。非数字文字标记上的任何后缀都将被拒绝,而数字文字标记仅接受以下列表中的后缀。
所以 Rust 明确地 允许宏支持自定义字符串文字。
现在,您将如何编写这样的宏?您不能使用macro_rules!
编写声明性宏,因为无法通过其简单的模式匹配来检测和操作自定义文字后缀。但是,可以编写一个 procedural macro 来执行此操作。
我不会详细介绍如何编写过程宏,因为在单个 *** 答案中写得太多了。但是,我将给您这个过程宏的示例,它按照您的要求做一些事情,作为起点。它采用给定表达式中的任何自定义整数文字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)
。
【讨论】:
这很好用!另外,我发现我必须在夜间使用feature(proc_macro_hygiene)
,因为程序宏通常不能用作表达式。
@MrMobster 很高兴听到这个消息!您还可以使用proc-macro-hack
crate 作为解决方法,在 Rust 稳定版中使用表达式宏。以上是关于通过 Rust 宏自定义文字?的主要内容,如果未能解决你的问题,请参考以下文章