我有一个简单的宏,有三个非常相似的规则:
macro_rules! c {
($exp:expr, for $i:ident in $iter:expr) => (
{
let mut r = vec![];
for $i in $iter {
r.push($exp);
}
r
}
);
($exp:expr, for $i:ident in $iter:expr, for $i2:ident in $iter2:expr) => (
{
let mut r = vec![];
for $i2 in $iter2 {
for $i in $iter {
r.push($exp);
}
}
r
}
);
($exp:expr, for $i:ident in $iter:expr, for $i2:ident in $iter2:expr, for $i3:ident in $iter3:expr) => (
{
let mut r = vec![];
for $i in $iter {
for $i2 in $iter2 {
for $i3 in $iter3 {
r.push($exp);
}
}
}
r
}
);
}
每个规则与其他规则的不同之处在于匹配的for $i:ident in $iter:exp
模式的数量。逻辑同样相同。
有没有办法使用$(...)*
或$(...)+
这样的重复模式将这些规则简化为一个,并且仍然能够在宏逻辑中表达嵌套循环?
答案 0 :(得分:5)
您可以使用recursive TT (token tree) munching macro:
macro_rules! c {
(@loop $v:ident, $exp:expr, for $i:ident in $iter:expr) => (
for $i in $iter {
$v.push($exp);
}
);
(@loop $v:ident, $exp:expr, for $i:ident in $iter:expr, $($tail:tt)*) => (
for $i in $iter {
c!(@loop $v, $exp, $($tail)*);
}
);
($exp:expr, $(for $i:ident in $iter:expr),*) => (
{
let mut r = vec![];
c!(@loop r, $exp, $(for $i in $iter),*);
r
}
);
}
标有@loop
的规则可以完成所有工作。
TT咀嚼递归宏与递归函数非常相似。在每次调用时,它只处理(咀嚼)输入的一部分,生成中间输出,并发送剩余的" unmunched"输入尾部到另一个宏调用。最终,输入足够小,不再需要任何宏调用,并达到递归递归的基本情况。
此处,递归@loop
规则捕获与for $i:ident in $iter:expr
匹配的单标记树,并将剩余的输入(其他此类for $i in $iter
表达式)存储在$($tail:tt)*
。宏规则然后为捕获的for $i in $iter
表达式生成循环,并通过使用unmunched输入($($tail)*
)调用相同的规则来生成循环体。
最终,$($tail)*
仅包含一个令牌树,可以与for $i:ident in $iter:expr
匹配。在这种情况下,调用基本案例@loop
规则,生成最内层的循环,将表达式推送到Vec
。
此宏应该适用于任意数量的for $i in $iter
表达式,只要它保持在宏递归限制内。如果您发现自己遇到了递归限制,则可以通过在递归for $i:ident in $iter:expr
规则中一次处理两个 @loop
表达式来减少递归调用的数量。