How to recursively take the last argument of a mac

2019-09-06 16:00发布

问题:

Using a simple recursive macro like the example below, its common to take the first argument, then glob the rest.

macro_rules! count_tts {
    () => {0usize};
    ($_head:tt $($tail:tt)*) => {1usize + count_tts!($($tail)*)};
}

Is there a way to recursively take the last argument?

This makes it possible to:

  • Handle the arguments in reverse.
  • Take all the previous arguments into account (count them for example, see related question)

Something like ($($head:tt)* $tail:tt) ... but this doesn't work.

回答1:

There is no "backtracking" in the macro parser, so no you can't do this directly with $($head:tt)* $tail:tt. But you can do it by reversing it yourself.

macro_rules! concat_reverse {
    ([] $($reversed:tt)*) => { 
        concat!($(stringify!($reversed)),*)  // base case
    };
    ([$first:tt $($rest:tt)*] $($reversed:tt)*) => { 
        concat_reverse!([$($rest)*] $first $($reversed)*)  // recursion
    };
}

fn main() {
    println!("{}", concat_reverse!([e d c b a]))
    // output: abcde
}

The macro trace looks like:

   concat_reverse!([e d c b a])
== concat_reverse!([d c b a] e)
== concat_reverse!([c b a] d e)
== concat_reverse!([b a] c d e)
== concat_reverse!([a] b c d e)
== concat_reverse!([] a b c d e)
== concat!(stringify!(a), stringify!(b), stringify!(c), stringify!(d), stringify!(e))

You could do some "map" and "reduce" operation (e.g. for counting) in the recursion phase.

Note that this method will eat your recursion depth, you may need to raise your #![recursion_limit="..."].



标签: macros rust