Макро-соответствие токенов, рекурсивное расширение

Я пытаюсь реализовать макрос, который расширит программу "мозгового удара" (после того, как я начал с некоторого более простого кода, в котором у меня уже были проблемы с решением: как анализировать одиночные токены в макросах ржавчины). Проблема в том, что в какой-то момент рекурсивного сопоставления он никогда не может соответствовать концу:

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 знает, совместен ли он со следующим токеном или нет; вот как макрос может сказать < < Помимо << (потому что пробелы не являются токенами). Процедурные макросы также не страдают от предела рекурсии.

Другие вопросы по тегам