宏匹配令牌递归扩展
Macro matching tokens recursive expansion
我正在尝试实现一个可以扩展 brainfuck program (after starting with some simpler code, in which I had problems coming up with a solution already: ) 的宏。问题是在递归匹配的某个点它永远无法匹配到结尾:
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
的扩展方法。
完整的代码编译问题可以在此重现 playground
我对这个搭配不是很有信心:
(@impl $var:ident; [$($t:tt)*] $($ts:tt)*) => {
while $var.getVal() != 0 {
brainfuck!(@impl $var; $($t)*);
}
brainfuck!(@impl $var; $($ts)*);
};
我想到这个 [$($t:tt)*] $($ts:tt)*
来匹配 []
包含的代码部分与里面的任何标记,然后是任何标记。但我不确定它是否应该工作。
我处理这个问题已经有一段时间了,我完全被困住了。
欢迎任何形式的帮助。提前致谢!
您的宏中的最后一个模式匹配 任何东西,因此如果您的 @impl
个案例无法匹配预期的输入,宏将回退到最后一个模式并且基本上重新开始。
让我们让它不匹配所有内容来调试问题。我将在模式的开头添加 @start
:
#[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 ++++++++++[>+++++++>++++++++++>+++++++++++>+++>+<<<<<-]>++.>>+.---.<---.>>++.<+.++++++++.-------.<+++.>+.>+.>.);
}
现在我们可以清楚地看到哪里出了问题:
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!
宏)。您可以通过添加以下规则轻松修复您的宏:
#[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)*);
};
// ...
}
这揭示了另一个有问题的序列:
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
您的示例中未显示 ->
,它也是一个标记。同样,这需要额外的规则:
#[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)*);
};
// ...
}
每个字符 Procedural macros don't have this problem because they always receive punctuation as one Punct
。 A Punct
知道自己是否与下一个token相连;这就是宏可以区分 < <
和 <<
的方式(因为空格不是标记)。过程宏也不受递归限制的影响。
我正在尝试实现一个可以扩展 brainfuck program (after starting with some simpler code, in which I had problems coming up with a solution already:
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
的扩展方法。
完整的代码编译问题可以在此重现 playground
我对这个搭配不是很有信心:
(@impl $var:ident; [$($t:tt)*] $($ts:tt)*) => {
while $var.getVal() != 0 {
brainfuck!(@impl $var; $($t)*);
}
brainfuck!(@impl $var; $($ts)*);
};
我想到这个 [$($t:tt)*] $($ts:tt)*
来匹配 []
包含的代码部分与里面的任何标记,然后是任何标记。但我不确定它是否应该工作。
我处理这个问题已经有一段时间了,我完全被困住了。 欢迎任何形式的帮助。提前致谢!
您的宏中的最后一个模式匹配 任何东西,因此如果您的 @impl
个案例无法匹配预期的输入,宏将回退到最后一个模式并且基本上重新开始。
让我们让它不匹配所有内容来调试问题。我将在模式的开头添加 @start
:
#[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 ++++++++++[>+++++++>++++++++++>+++++++++++>+++>+<<<<<-]>++.>>+.---.<---.>>++.<+.++++++++.-------.<+++.>+.>+.>.);
}
现在我们可以清楚地看到哪里出了问题:
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!
宏)。您可以通过添加以下规则轻松修复您的宏:
#[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)*);
};
// ...
}
这揭示了另一个有问题的序列:
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
您的示例中未显示 ->
,它也是一个标记。同样,这需要额外的规则:
#[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)*);
};
// ...
}
每个字符 Procedural macros don't have this problem because they always receive punctuation as one Punct
。 A Punct
知道自己是否与下一个token相连;这就是宏可以区分 < <
和 <<
的方式(因为空格不是标记)。过程宏也不受递归限制的影响。