Макро-соответствие токенов, рекурсивное расширение
Я пытаюсь реализовать макрос, который расширит программу "мозгового удара" (после того, как я начал с некоторого более простого кода, в котором у меня уже были проблемы с решением: как анализировать одиночные токены в макросах ржавчины). Проблема в том, что в какой-то момент рекурсивного сопоставления он никогда не может соответствовать концу:
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
, Проблема полной компиляции кода может быть воспроизведена на этой площадке
Я не совсем уверен в этом соответствии:
(@impl $var:ident; [$($t:tt)*] $($ts:tt)*) => {
while $var.getVal() != 0 {
brainfuck!(@impl $var; $($t)*);
}
brainfuck!(@impl $var; $($ts)*);
};
Я думал об этом [$($t:tt)*] $($ts:tt)*
чтобы соответствовать частям кода, заключенного в []
с какими бы то ни было жетонами внутри, за которыми следуют все жетоны. Но я не уверен, должно ли это работать.
Я имел дело с этим в течение некоторого времени, и я полностью застрял. Любая помощь приветствуется. Заранее спасибо!
1 ответ
Последний шаблон в вашем макросе соответствует чему угодно, так что если ваш @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)*);
};
// ...
}
Процедурные макросы не имеют этой проблемы, потому что они всегда получают пунктуацию как один Punct
за каждого персонажа. Punct
знает, совместен ли он со следующим токеном или нет; вот как макрос может сказать < <
Помимо <<
(потому что пробелы не являются токенами). Процедурные макросы также не страдают от предела рекурсии.