宏匹配令牌递归扩展 [英] Macro matching tokens recursive expansion

查看:48
本文介绍了宏匹配令牌递归扩展的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试实现一个宏来扩展 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆