Rust宏中的嵌套迭代

13

我正在 Rust 中尝试使用宏,并希望进行嵌套扩展,即组合。

这是我编写的代码:

macro_rules! nested {
    (
        $(arg $arg:ident;)*
        $(fun $fun:ident;)*
    ) => {
        $(
            $fun($($arg),*);
        )*
    }
}

fn show1(a: i32, b: i32, c: i32) {
    println!("show1: {} {} {}", a, b, c);
}
fn show2(a: i32, b: i32, c: i32) {
    println!("show2: {} {} {}", a, b, c);
}

fn main() {
    let a = 1;
    let b = 2;
    let c = 3;
    nested! {
        arg a;
        arg b;
        arg c;
        fun show1;
        fun show2;
    }
}

游乐场

我希望这能扩展到

fn main() {
    let a = 1;
    let b = 2;
    let c = 3;
    // iteration over $fun
    show1(/* iteration over $arg */a, b, c);
    show2(/* iteration over $arg */a, b, c);
}

然而,似乎Rust不支持此操作,反而会报错:

error: inconsistent lockstep iteration: 'fun' has 2 items, but 'arg' has 3

显然,它忽略了内部迭代。

但是,如果我删除其中一个参数,使其对两个参数都有2项,它仍然会抱怨:

<anon>:7:18: 7:25 error: attempted to repeat an expression containing no
                  syntax variables matched as repeating at this depth
<anon>:7             $fun($($arg),*);

有没有一种方法可以做到我想要的?

3个回答

16

看起来不可能做到这种扩展。以下是一个解决方法:

macro_rules! nested {
    ($(arg $arg:ident;)* $(fun $fun:ident;)*) => {
        // expand arg to a tuple that will be matched as tt
        // in @call_tuple an will be decomposed back to
        // a list of args in @call
        nested!(@call_tuple $($fun),* @ ($($arg),*))
    };
    (@call_tuple $($fun:ident),* @ $tuple:tt) => {
        $(nested!(@call $fun $tuple))*
    };
    (@call $fun:ident ($($arg:expr),*)) => {
        $fun($($arg),*);
    };
}

@id 只用于保持规则对宏的内部使用。


3
这里有一个稍微简单一些的方法。
macro_rules! nested {
    (($($f:ident),*) $args:tt) => {
        $(nested!(@call $f $args);)*
    };
    (@call $f:ident ($($arg:expr),*)) => {
        $f($($arg),*);
    };
}

nested! {
    (show1, show2)
    (a, b, c)
}

与@malbarbo的答案原则相同,但将宏更改为将参数放在括号中。然后,您可以将args作为单个令牌接受,以便只在第一次扩展中重复函数名称。

0

看起来你不能直接嵌套重复,但是你可以通过第二个宏调用来实现。在这里,我使用一个重复展开所有参数,并在我的宏中调用内部规则。内部规则将它们全部作为combined_args一起查看。然后它对函数进行第二次重复。

macro_rules! nested {
    (@internal $combined_args:tt ; $(fun $fun:ident;)*) => {
        $( $fun$combined_args; )*
    };

    ( $(arg $arg:ident;)* $($funs:tt)* ) => {
        nested!(@internal ($($arg),*) ; $($funs)*)
    };
}

...但由于歧义无法编译:

error: local ambiguity when calling macro `nested`: multiple parsing options: built-in NTs tt ('funs') or 1 other option.
  --> src/main.rs:23:9
   |
23 |         arg a;
   |         ^^^

error: could not compile `playground` due to previous error

显然,macro_rules! 不像正则表达式那样贪婪。因此,现在我将修改它以查找结尾或 fun 以消除歧义:

macro_rules! nested {
    ( @internal $combined_args:tt ; $(fun $fun:ident;)* ) => {
        $( $fun$combined_args; )*
    };

    ( $(arg $arg:ident;)* ) => { };

    ( $(arg $arg:ident;)* fun $($tail:tt)* ) => {
        nested!(@internal ($($arg),*) ; fun $($tail)*)
    };
}

这个可行。

话说,使用@cambunctious表单更容易处理(因为参数已经组合好了),并且看起来更像普通函数调用。multi_fn_call! {(show1, show2)(a, b, c)}听起来不错。如果您不在意当调用者未按预期传递参数时编译错误的格式,您可以在一个规则中重复一次执行:

macro_rules! multi_fn_call {
    ( ( $($fn:path),* ) $args:tt ) => { $($fn$args;)* };
}

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