简体   繁体   English

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

[英]How do I write a wrapper for a macro without repeating the rules?

I am trying to make a wrapper for a macro.我正在尝试为宏制作一个包装器。 The trouble is that I don't want to repeat the same rules in both macro.问题是我不想在两个宏中重复相同的规则。 Is there a way to do that?有没有办法做到这一点?

Here is what I tried:这是我尝试过的:

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

but I get the following error:但我收到以下错误:

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

If I change the outer macro for this, the code compile:如果我为此更改outer宏,代码编译:

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

What am I doing wrong?我究竟做错了什么?

macro_rules! is both cleverer and dumber than you might realise. 比你可能意识到的更聪明笨拙

Initially, all input to a macro begins life as undifferentiated token soup. 最初,对宏的所有输入都以无差别的令牌汤开始。 An Ident here, StrLit there, etc. However, when you match and capture a bit of the input, generally the input will be parsed in an Abstract Syntax Tree node; 这里是一个IdentStrLit 等等。但是,当你匹配并捕获一些输入时,通常输入将在一个抽象语法树节点中解析; this is the case with expr . 这是expr的情况。

The "clever" bit is that when you substitute this capture (for example, $expression ), you don't just substitute the tokens that were originally matched: you substitute the entire AST node as a single token . “聪明”位是当你替换这个捕获(例如, $expression )时,你不只是替换最初匹配的标记:你将整个AST节点替换为单个标记 So there's now this weird not-really-a-token in the output that's an entire syntax element. 所以现在输出中的这个奇怪的非真正的标记就是整个语法元素。

The "dumb" bit is that this process is basically irreversible and mostly totally invisible . “愚蠢”的一点是,这个过程基本上是不可逆转的,而且大多是完全不可见的 So let's take your example: 让我们举个例子:

outer!(test);

We run this through one level of expansion, and it becomes this: 我们通过一个扩展级别运行它,它变为:

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

Except, that's not what it looks like. 除此之外,这不是它的样子。 To make things clearer, I'm going to invent some non-standard syntax : 为了使事情更清楚,我将发明一些非标准语法

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

Pretend that $(test):expr is a single token: it's an expression which can be represented by the token sequence test . 假设$(test):expr是一个单一的标记:它是一个表达式,可以用标记序列test来表示。 It is not simply the token sequence test . 不仅是该令牌序列test This is important, because when the macro interpreter goes to expand that inner! 这很重要,因为当宏解释器扩展inner! macro, it checks the first rule: 宏,它检查第一条规则:

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

The problem is that $(test):expr is an expression, not an identifier . 问题是$(test):expr是一个表达式, 而不是标识符 Yes, it contains an identifier, but the macro interpreter doesn't look that deep. 是的,它包含一个标识符,但宏解释器看起来并不那么深。 It sees an expression and just gives up . 它看到了一个表达,只是放弃了

It fails to match the second rule for the same reason. 由于同样的原因,它无法匹配第二条规则。

So what do you do? 所以你会怎么做? ... Well, that depends. ......嗯,这取决于。 If outer! outer! doesn't do any sort of processing on its input, you can use a tt matcher instead: 不对其输入进行任何处理,您可以使用tt匹配器代替:

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

tt will match any token tree (see the Macros chapter of the Rust Book ). tt将匹配任何令牌树(请参阅Rust Book宏章章节 )。 $($tts:tt)* will match any sequence of tokens, without changing them. $($tts:tt)*将匹配任何序列的标记,而不更改它们。 This of this as a way to safely forward a bunch of tokens to another macro. 这是将一堆令牌安全地转发到另一个宏的方法。

If you need to do processing on the input and forward it on to the inner! 如果你需要对输入进行处理并将其转发到inner! macro... you're probably going to have to repeat the rules. 宏......你可能不得不重复这些规则。

I had some success with the $($stuff: expr),+ syntax.我在$($stuff: expr),+语法上取得了一些成功。

macro_rules! println {
    ( $($stuff: expr),+) => {
    avr_device::interrupt::free(|cs| {
    uwriteln!(unsafe { &SERIAL_STATIC}.borrow(cs).borrow_mut().as_mut().unwrap(),
    $($stuff),+)
    })
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM