How to resolve a local ambiguity error when recursively handling comma-separated tokens in macro_rules?

91 Views Asked by At

While trying to create a macro to ease the reading of my code, I found a "local ambiguity" in the macro. I cannot really understand how this is ambiguous though: in each arm of my bracket, the value I'm parsing is preceded by a unique prefix.

macro_rules! test {
    (rule_a=$value: expr $(, $($rest: tt)*),*) => {
        println!("Rule A: {}", stringify!($value));
        test!($($($rest)*),*)
    };
    (rule_b=$value: ident $(, $($rest: tt)*),*) => {
        println!("Rule B: {}", stringify!($value));
        test!($($($rest)*),*)
    };
    (rule_c=$value: ident $(, $($rest: tt)*),*) => {
        println!("Rule C: {}", stringify!($value));
        test!($($($rest)*),*)
    };
    () => {
        println!("End");
    };
}

fn main() {
    // Working
    test!(rule_a="Great test", rule_b=i32);
    // Working
    test!(rule_a="Great test", rule_c=i64);
    // "Local ambiguity"
    test!(rule_a="Great test", rule_b=i32, rule_c=i64);
}
error: local ambiguity when calling macro `test`: multiple parsing options: built-in NTs tt ('rest') or 1 other option.
  --> src/main.rs:25:42
   |
25 |     test!(rule_a="Great test", rule_b=i32, rule_c=i64);
   |                                          ^

Rust Playground

How am I supposed to solve this problem? I tried looking around, but did not really found any useful resources to help solve this...

1

There are 1 best solutions below

0
On BEST ANSWER

, is a valid token, so it matches both $rest:tt and the , in ),*. You need to make the first , optional, then have any further , be part of $rest.

macro_rules! test {
    (rule_a = $value:expr $(, $($rest:tt)* )?) => {
        println!("Rule A: {}", stringify!($value));
        test!($($($rest)*),*)
    };
    (rule_b = $value:ident $(, $($rest:tt)* )?) => {
        println!("Rule B: {}", stringify!($value));
        test!($($($rest)*),*)
    };
    (rule_c = $value:ident $(, $($rest:tt)* )?) => {
        println!("Rule C: {}", stringify!($value));
        test!($($($rest)*)?)
    };
    () => {
        println!("End");
    };
}