如何在 rust 宏中解析单个标记

3
我开始尝试使用Rust宏,来做这个小练习。我想定义一个宏,该宏扩展为初始化变量(名称不重要)类型为(例如,但不是真正重要)和对该变量的一系列操作,在这种情况下是var += 1var -= 1,最后会调用println!("{}", var)
该宏将基于+-匹配上述操作的一系列标记。
例如:
operate_integer![+++---]

应该扩展为:

let mut var: i32 = 0;
var += 1;
var += 1;
var += 1;
var -= 1;
var -= 1;
var -= 1;
print!("{}", var);

我将使用两个宏来完成此操作,一个用于包装初始化和打印,另一个用于评估+-标记:
基本宏如下:
macro_rules! operate_integer {
    // $($all_tokens:tt)* should match everything, it will be forward to the helper macro
    ($($all_tokens:tt)*) => {
        let mut var : i32 = 0;
        operate_integer_helper![$($all_tokens:tt)*]
        print!("{}", var);
    }
}

辅助程序将扩展操作:
macro_rules! operate_integer_helper {
    // the idea is that it matches a `+` followed by more tokens
    (+$($t:tt)*) => {
        val += 1;
        operate_integer_helper![$($t:tt)*] // we recursively handle the remaining tokens
    }

    (-$($t:tt)*) => {
        val -= 1;
        operate_integer_helper![$($t:tt)*]
    }
}

当然,这并不起作用,它会因以下错误而编译失败(Playground):

error: no rules expected the token `(`
   --> src/lib.rs:102:5
    |
102 |     (+$($t:tt)*) => {
    |     ^ no rules expected this token in macro call

我有些困惑。由于我刚开始接触,可能会错过许多概念,因此我真的很希望能够获得一些帮助,了解如何使用宏。

提前感谢您!

1个回答

3
你实际上已经非常接近成功了!只剩下一些小错误。(如果你想了解更多有关宏的内容,每次只读一个要点,并尝试自己逐步进展!)
  • When using (repeated) meta variables, you don't specify the meta-variable type again. So it's $($t:tt)* in the pattern of the macro, but if you want to use it, it's $($t)*!

  • If you have multiple rules in a macro definition, you need to end every rule with a semicolon.

    macro_rules! {
        (+ $(t:tt)*) => { ... };
        (- $(t:tt)*) => { ... };
    }
    
  • The Rust compiler always needs to know whether you want to expand your macro into an expression or statement(s). Since you are generating a list of statements and not a single expression, you have to add a semicolon to the invocation of your macros! That means, in main() but also all macro invocation of helper macros inside your macro definition.

  • Since yeah macro invocation creates a new syntax context and all identifiers (names) are only accessible in their syntax context, the helper macro cannot use var (even after fixing the typo val -> var). So instead, you have to pass that name to the helper macro:

    macro_rules! operate_integer {
        ($($all_tokens:tt)*) => {
            let mut var: i32 = 0;
            operate_integer_helper![var $($all_tokens)*];  // <-- pass identifier 
            println!("{}", var);
        }
    }
    
    macro_rules! operate_integer_helper {
        ($var:ident +$($t:tt)*) => {              // <- accept identifier
            $var += 1;                            // <- use identifier
            operate_integer_helper![$var $($t)*]
        };
    
        ($var:ident -$($t:tt)*) => {
            $var -= 1;
            operate_integer_helper![$var $($t)*]
        };
    }
    
  • Having done all that you get the error "unexpected end of macro invocation". This is because you don't have a recursion stop rule! So you have to add a new rule to your helper macro: ($var:ident) => {};. This rule is used when there is only the name and no + or - tokens left.

现在: 它已经工作了!

我还想改变最后一件事情:通常不建议使用第二个帮助宏,因为该宏可能不在调用主要宏的范围内。相反,通常使用内部规则。你可以在这里阅读更多关于内部规则的内容。

有了这个,这是生成的代码


非常感谢!我甚至在想如果在不同的宏中使用var是否会起作用,而您已经提前发布了!我正在查看内部规则。谢谢您提供的链接! - Netwave
我试图向前迈进,却发现另一堵(我认为更加复杂)的墙。如果您感兴趣,请查看:https://stackoverflow.com/q/55987950/1695172 - Netwave

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接