「用 macro! 实现逆波兰表达式」匹配&入栈

「这是我参与11月更文挑战的第 21 天,活动详情查看:2021最后一次更文挑战


宏目前不允许匹配字面意义,而且expr对我们不起作用,因为它可能会意外地匹配像 2+3... 这样的序列,而不是只取一个数字,所以我们将求助于 tt —— 一个通用的标记匹配器。

它只匹配一个标记树(无论是像 literal/identifier/lifetime/etc 这样的原始标记还是包含更多标记的 ()/[]/{})。

macro_rules! rpn {
  ($num:tt) => {
    // TODO
  };
}
复制代码

现在,我们需要一个栈的变量。

宏不能使用真正的变量,因为我们希望这个栈只在编译时存在。所以,取而代之的是,有一个单独的标记序列,它可以被传递,因此被用作一种累积器。

在我们的例子中,让我们把它表示为逗号分隔的expr序列(因为我们不仅要用它来表示简单的数字,还要用它来表示中间的infix表达式),并把它包在括号里,与输入的其他部分分开。

macro_rules! rpn {
  ([ $($stack:expr),* ] $num:tt) => {
    // TODO
  };
}
复制代码

现在,一个标记序列并不是一个真正的变量 —— 你不能在原地修改它,然后再做一些事情。

相反,你可以用必要的修改来创建这个标记序列的新副本,然后再次递归调用同一个宏。

如果你有函数式语言的背景,或者曾经使用过任何提供不可变数据的库,那么这两种方法:通过创建一个修改过的副本和用递归处理列表来改变数据。

macro_rules! rpn {
  ([ $($stack:expr),* ] $num:tt) => {
    rpn!([ $num $(, $stack)* ])
  };
}
复制代码

现在,很明显,只有一个数字的情况是相当不可能的,对我们来说也不是很实用,所以我们需要把这个数字之后的任何其他东西匹配成一串零或更多的tt标记,这些标记可以被传递给我们的宏的下一个调用,以便进一步匹配和处理。

macro_rules! rpn {
  ([ $($stack:expr),* ] $num:tt $($rest:tt)*) => {
      rpn!([ $num $(, $stack)* ] $($rest)*)
  };
}
复制代码

猜你喜欢

转载自juejin.im/post/7035990374061441060