如何在不重复规则的情况下为宏编写包装器?

ant*_*oyo 8 macros rust

我正在尝试为宏创建一个包装器.麻烦的是我不想在两个宏中重复相同的规则.有没有办法做到这一点?

这是我尝试过的:

macro_rules! inner {
    ($test:ident) => { stringify!($test) };
    ($test:ident.run()) => { format!("{}.run()", stringify!($test)) };
}

macro_rules! outer {
    ($expression:expr) => {
        println!("{}", inner!($expression));
    }
}

fn main() {
    println!("{}", inner!(test));
    println!("{}", inner!(test.run()));
    outer!(test);
    outer!(test.run());
}
Run Code Online (Sandbox Code Playgroud)

但是我收到以下错误:

src/main.rs:8:31: 8:42 error: expected ident, found test
src/main.rs:8         println!("{}", inner!($expression));
                                            ^~~~~~~~~~~
Run Code Online (Sandbox Code Playgroud)

如果我outer为此更改宏,代码编译:

macro_rules! outer {
    ($expression:expr) => {
        println!("{}", stringify!($expression));
    }
}
Run Code Online (Sandbox Code Playgroud)

我究竟做错了什么?

DK.*_*DK. 9

macro_rules!既聪明和笨比你可能实现的.

最初,对宏的所有输入都以无差别的令牌汤开始.在Ident这里,StrLit那里,等等 .然而,当你匹配和捕捉位输入的,一般的输入将以抽象语法树节点进行解析; 就是这种情况expr.

"聪明"位是指当您替换此捕获时(例如,$expression),您不只是替换最初匹配的标记:您将整个AST节点替换为单个标记.所以现在输出中的这个奇怪的非真正的标记就是整个语法元素.

"愚蠢"的一点是,这个过程基本上是不可逆转的,而且大多是完全不可见的.让我们举个例子:

outer!(test);
Run Code Online (Sandbox Code Playgroud)

我们通过一个扩展级别运行它,它变为:

println!("{}", inner!(test));
Run Code Online (Sandbox Code Playgroud)

除此之外,这不是它的样子.为了使事情更清楚,我将发明一些非标准语法:

println!("{}", inner!( $(test):expr ));
Run Code Online (Sandbox Code Playgroud)

假装这$(test):expr是一个单一的标记:它是一个可以由标记序列表示的表达式test.这是不是只是该令牌序列test.这很重要,因为当宏解释器扩展该inner!宏时,它会检查第一个规则:

    ($test:ident) => { stringify!($test) };
Run Code Online (Sandbox Code Playgroud)

问题是$(test):expr表达式而不是标识符.是的,它包含一个标识符,但宏解释器看起来并不那么深.它看到了一个表达,只是放弃了.

由于同样的原因,它无法匹配第二条规则.

所以你会怎么做?......嗯,这取决于.如果outer!不对其输入进行任何处理,则可以使用tt匹配器:

macro_rules! outer {
    ($($tts:tt)*) => {
        println!("{}", inner!($($tts)*));
    }
}
Run Code Online (Sandbox Code Playgroud)

tt将匹配任何标记树(请参阅Rust Book的宏章章节). $($tts:tt)*将匹配任何序列的令牌,而不更改它们.这是将一堆令牌安全地转发到另一个宏的方法.

如果你需要对输入进行处理并将其转发到inner!宏...你可能不得不重复这些规则.