宏匹配令牌递归扩展 [英] Macro matching tokens recursive expansion
问题描述
我正在尝试实现一个宏来扩展 brainfuck 程序(在开始使用一些更简单的代码之后,我已经提出了解决方案的问题:How to parse single tokens inRust 宏).问题是在递归匹配的某个时刻,它永远无法匹配到结尾:
I'm triying to implement a macro that will expand a brainfuck program (after starting with some simpler code, in which I had problems coming up with a solution already: How to parse single tokens in rust macros). The problem is that at some point of the recursive matching it can never match the end:
error: recursion limit reached while expanding the macro `brainfuck`
--> src/lib.rs:119:9
|
119 | brainfuck!(@impl cell; $($all_tokens)*);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...
124 | brainfuck!(++++++++++[>+++++++>++++++++++>+++++++++++>+++>+<<<<<-]>++.>>+.---.<---.>>++.<+.++++++++.-------.<+++.>+.>+.>.);
| --------------------------------------------------------------------------------------------------------------------------- in this macro invocation
|
= help: consider adding a `#![recursion_limit="2000"]` attribute to your crate
这里是宏代码:
#[macro_export]
macro_rules! brainfuck {
(@impl $var:ident;) => {};
(@impl $var:ident; + $($t:tt)*) => {
$var.inc();
brainfuck!(@impl $var; $($t)*);
};
(@impl $var:ident; - $($t:tt)*) => {
$var.dec();
brainfuck!(@impl $var; $($t)*);
};
(@impl $var:ident; > $($t:tt)*) => {
$var.next();
brainfuck!(@impl $var; $($t)*);
};
(@impl $var:ident; < $($t:tt)*) => {
$var.prev();
brainfuck!(@impl $var; $($t)*);
};
(@impl $var:ident; . $($t:tt)*) => {
$var.printVal();
brainfuck!(@impl $var; $($t)*);
};
(@impl $var:ident; , $($t:tt)*) => {
$var.getInput();
brainfuck!(@impl $var; $($t)*);
};
(@impl $var:ident; [$($t:tt)*] $($ts:tt)*) => {
while $var.getVal() != 0 {
brainfuck!(@impl $var; $($t)*);
}
brainfuck!(@impl $var; $($ts)*);
};
($($all_tokens:tt)*) => {
let mut cell = CellData::new();
brainfuck!(@impl cell; $($all_tokens)*);
};
}
它基于自定义struct
的扩展方法.完整代码编译问题可以在这个游乐场
It is based on expanding methods from a custom struct
.
The full code compilation problem can be reproduced in this playground
我对这种匹配不太有信心:
I'm not really confident in this matching:
(@impl $var:ident; [$($t:tt)*] $($ts:tt)*) => {
while $var.getVal() != 0 {
brainfuck!(@impl $var; $($t)*);
}
brainfuck!(@impl $var; $($ts)*);
};
我想到了这个 [$($t:tt)*] $($ts:tt)*
来匹配由 []
包围的代码部分令牌有里面,然后是任何令牌.但我不确定它是否应该起作用.
I thought of this [$($t:tt)*] $($ts:tt)*
to match portions of code enclosed by []
with whatever tokens have inside, followed by whatever tokens. But I'm not sure if it should work.
我处理这个问题已经有一段时间了,我完全被卡住了.欢迎任何形式的帮助.提前致谢!
I've been dealing with this for some time and I am completly stuck. Any kind of help is welcome. Thanks in advance!
推荐答案
宏中的最后一个模式匹配任何内容,因此如果你的 @impl
案例无法匹配预期的输入,宏将回退到最后一个模式并基本上重新开始.
The last pattern in your macro matches anything, so if your @impl
cases fail to match an expected input, the macro will fall back to the last pattern and essentially start over.
让我们让它不匹配所有东西来调试问题.我将在模式的开头添加 @start
:
Let's make it not match everything to debug the issue. I'll add @start
at the start of the pattern:
#[macro_export]
macro_rules! brainfuck {
// @impl cases elided
(@start $($all_tokens:tt)*) => {
let mut cell = CellData::new();
brainfuck!(@impl cell; $($all_tokens)*);
};
}
fn hello_world() {
brainfuck!(@start ++++++++++[>+++++++>++++++++++>+++++++++++>+++>+<<<<<-]>++.>>+.---.<---.>>++.<+.++++++++.-------.<+++.>+.>+.>.);
}
现在我们可以清楚地看到出了什么问题:
Now we can clearly see what's wrong:
error: no rules expected the token `<<`
--> src/main.rs:124:71
|
77 | macro_rules! brainfuck {
| ---------------------- when calling this macro
...
124 | brainfuck!(@start ++++++++++[>+++++++>++++++++++>+++++++++++>+++>+<<<<<-]>++.>>+.---.<---.>>++.<+.++++++++.-------.<+++.>+.>+.>.);
| ^^ no rules expected this token in macro call
error: no rules expected the token `>>`
--> src/main.rs:124:82
|
77 | macro_rules! brainfuck {
| ---------------------- when calling this macro
...
124 | brainfuck!(@start ++++++++++[>+++++++>++++++++++>+++++++++++>+++>+<<<<<-]>++.>>+.---.<---.>>++.<+.++++++++.-------.<+++.>+.>+.>.);
| ^^ no rules expected this token in macro call
问题是序列 <<
和 >>
在 Rust 中是单个标记(至少对于 macro_rules!
宏).您可以通过添加以下规则轻松修复您的宏:
The problem is that the sequences <<
and >>
are a single token in Rust (at least for macro_rules!
macros). You can easily fix your macro by adding these rules:
#[macro_export]
macro_rules! brainfuck {
// ...
(@impl $var:ident; >> $($t:tt)*) => {
$var.next();
$var.next();
brainfuck!(@impl $var; $($t)*);
};
(@impl $var:ident; << $($t:tt)*) => {
$var.prev();
$var.prev();
brainfuck!(@impl $var; $($t)*);
};
// ...
}
这揭示了另一个有问题的序列:
This reveals another problematic sequence:
error: no rules expected the token `<-`
--> src/main.rs:136:75
|
77 | macro_rules! brainfuck {
| ---------------------- when calling this macro
...
109 | brainfuck!(@impl $var; $($t)*);
| - help: missing comma here
...
136 | brainfuck!(@start ++++++++++[>+++++++>++++++++++>+++++++++++>+++>+<<<<<-]>++.>>+.---.<---.>>++.<+.++++++++.-------.<+++.>+.>+.>.);
| ^^ no rules expected this token in macro call
您的示例中未显示的是 ->
,它也是一个标记.同样,这需要额外的规则:
Not shown in your example is ->
, which is also a single token. Again, this needs additional rules:
#[macro_export]
macro_rules! brainfuck {
// ...
(@impl $var:ident; <- $($t:tt)*) => {
$var.prev();
$var.dec();
brainfuck!(@impl $var; $($t)*);
};
(@impl $var:ident; -> $($t:tt)*) => {
$var.dec();
$var.next();
brainfuck!(@impl $var; $($t)*);
};
// ...
}
过程宏没有这个问题,因为它们总是接收标点符号作为一个 Punct
对于每个字符.Punct
知道它是否与下一个标记连接;这就是宏如何告诉 <<
除了 <<
(因为空格不是标记).程序宏也不受递归限制的影响.
Procedural macros don't have this problem because they always receive punctuation as one Punct
for each character. A Punct
knows whether it is joint with the next token or not; that's how a macro can tell < <
apart from <<
(because spaces are not tokens). Procedural macros also don't suffer from the recursion limit.
这篇关于宏匹配令牌递归扩展的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!