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

时间:2015-08-29 18:48:54

标签: 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());
}

但是我收到以下错误:

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

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

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

我做错了什么?

1 个答案:

答案 0 :(得分:9)

macro_rules! cleverer dumber 比您可能意识到的要多。

最初,对宏的所有输入开始作为无差别的令牌汤。这里有IdentStrLit等。但是,当您匹配并捕获一些输入时,通常输入将在抽象语法树节点中解析;这是expr的情况。

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

"哑巴"一点是,这个过程基本上是不可逆转的,而且大多数完全不可见。所以,让我们举个例子:

outer!(test);

我们通过一个扩展级别来运行它,它变成了这个:

println!("{}", inner!(test));

除此之外它看起来像什么。为了使事情更清楚,我将发明一些非标准语法

println!("{}", inner!( $(test):expr ));

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

    ($test:ident) => { stringify!($test) };

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

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

那你做什么? ......嗯,这取决于。如果outer!未对其输入进行任何类处理,则可以改为使用tt匹配器:

macro_rules! outer {
    ($($tts:tt)*) => {
        println!("{}", inner!($($tts)*));
    }
}

tt将匹配任何令牌树(请参阅Macros chapter of the Rust Book)。 $($tts:tt)*将匹配任何令牌序列,而不更改它们。这是将一堆令牌安全地转发给另一个宏的一种方法。

如果您需要对输入进行处理,请将其转发到inner!宏...您可能需要重复规则。