【问题标题】:How do I write a wrapper for a macro without repeating the rules?如何在不重复规则的情况下为宏编写包装器?
【发布时间】:2015-08-29 18:48:53
【问题描述】:

我正在尝试为宏制作包装器。问题是我不想在两个宏中重复相同的规则。有没有办法做到这一点?

这是我尝试过的:

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

我做错了什么?

【问题讨论】:

    标签: macros rust


    【解决方案1】:

    macro_rules! 比你想象的要聪明

    最初,所有宏的输入以未区分的令牌汤的形式开始。这里是Ident,那里是StrLit 但是,当您匹配并捕获输入的位时,通常会在抽象语法树节点中解析输入; expr 就是这种情况。

    “聪明”之处在于,当您替换此捕获(例如,$expression)时,您不只是替换最初匹配的标记:您将整个 AST 节点替换为单个标记。所以现在在输出中有这个奇怪的 not-really-a-token 是一个完整的语法元素。

    “愚蠢”的一点是,这个过程基本上是不可逆的,而且大多数情况下完全不可见。让我们以你的例子为例:

    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 将匹配 any 令牌树(请参阅Macros chapter of the Rust Book)。 $($tts:tt)* 将匹配 任何 标记序列,而不更改它们。这是将一堆令牌安全地转发到另一个宏的一种方式。

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

    【讨论】:

      【解决方案2】:

      我在 $($stuff: expr),+ 语法上取得了一些成功。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-01-09
        • 2011-05-24
        • 2016-08-13
        • 2020-07-31
        • 2018-07-15
        • 1970-01-01
        • 1970-01-01
        • 2021-12-01
        相关资源
        最近更新 更多