Macro to split by `expr` and then match on array literal in nested macro

I tried to do something like this where an outer macro takes comma separate expressions and an inner macro takes different behaviour depending on whether it is a literal or not. I also wanted to handle array literals and it doesn't work. I'm assuming this is a limitation of macros, but is there a workaround?

Playground

macro_rules! example {
    ($($e:expr),+) => {
        $(inner!($e)),+
    };
}


macro_rules! inner {
    ($e:literal) => {
        // logic for literals
    };
    ([$($e:literal),+]) => {  // <-- added this branch
        // logic for array literals
    };
    ($e:expr) => {
        // otherwise    
    }
}

fn main() {
    example!(123);   // works
    example!([123]); // doesn't work, goes into $e:expr branch
}

Yes it is a limitation of macros which is documented by the tool which would be the direct solution for your problem:


Otherwise, the way people usually work around the problem is by mixing both macros into a single-expression muncher:

macro_rules! example {( $($input:tt)* ) => (
    muncher! {
        out: { /* this will be filled as we recurse */ }
        $($input)*
    }
)}

macro_rules! muncher {
    (
        out: { $($out:tt)* }

        // CASE:
        $e:literal

        $(, $($rest:tt)*)?
    ) => (muncher! {
        out: { $($out)*
             /* inline here the logic for literals */ ,
        }
        $($($rest)*)?
    });

    (
        out: { $($out:tt)* }

        // CASE:
        [$($e:literal),+ $(,)?]

        $(, $($rest:tt)*)?
    ) => (muncher! {
        out: { $($out)*
            /* inline here the logic for array literals */ ,
        }
        $($($rest)*)?
    });


    (
        out: { $($out:tt)* }

        // CASE:
        $e:expr

        $(, $($rest:tt)*)?
    ) => (muncher! {
        out: { $($out)*
            /* otherwise */ ,
        }
        $($($rest)*)?
    });

    (
        out: {
            /* the shape of your output, _e.g._, */
            $($out:expr ,)*
        }

        // CASE:
        /* nothing left, end of recursion */
    ) => (
        // your actual combined output, _e.g._, */
        [ $($out),* ]
    );
}
1 Like