Macro writing: help with "unexpected end of macro invocation"

I'm writing a wrapper macro around the bitflags! macro. (I think) I'm using a push-down accumulation and TT munching. Current source: Macro that wraps bitflags! from bitflags · GitHub

I'm invoking it as:

libc_bitflags!(
    flags MsFlags: c_ulong {
        MS_RDONLY,
    }
);

but getting an error (with trace_macros!(true)):

libc_bitflags! { flags MsFlags : c_ulong { MS_RDONLY , } }
libc_bitflags! { @ accumulate_flags MsFlags ; c_ulong ; (  ) ; (  ) ; MS_RDONLY , }
libc_bitflags! { @ accumulate_flags MsFlags ; c_ulong ; (  ) ; (
const MS_RDONLY = libc:: MS_RDONLY , ) ; }
src/macros.rs:57:58: 57:59 error: unexpected end of macro invocation
src/macros.rs:57                   ($($flags)* const $flag = libc::$flag,); $($tail)*);
                                                                          ^

I'm confused by this. I've tried adding the semicolon in the patterns, removing it, surrounding the tail with parens and a whole bunch of other things. Am I trying to do something impossible, or just missing something?

I'm afraid I don't know what the problem is. See, because your macro code is so dense and just specifies everything by position, I found it impossible to read. So I rewrote it to be easier to understand... and accidentally fixed whatever the problem was.

*cough*

macro_rules! libc_bitflags {
    // Exit rule.
    (@call_bitflags
        {
            name: $BitFlags:ident,
            type: $T:ty,
            attrs: [$($attrs:tt)*],
            flags: [$($flags:tt)*],
        }
    ) => {
        bitflags! {
            $($attrs)*
            flags $BitFlags: $T {
                $($flags)*
            }
        }
    };

    // Done accumulating.
    (@accumulate_flags
        {
            name: $BitFlags:ident,
            type: $T:ty,
            attrs: $attrs:tt,
        },
        $flags:tt;
    ) => {
        libc_bitflags! {
            @call_bitflags
            {
                name: $BitFlags,
                type: $T,
                attrs: $attrs,
                flags: $flags,
            }
        }
    };

    // Munch an attr.
    (@accumulate_flags
        $prefix:tt,
        [$($flags:tt)*];
        #[$attr:meta] $($tail:tt)*
    ) => {
        libc_bitflags! {
            @accumulate_flags
            $prefix,
            [
                $($flags)*
                #[$attr]
            ];
            $($tail)*
        }
    };

    // Munch last ident if not followed by a comma.
    (@accumulate_flags
        $prefix:tt,
        [$($flags:tt)*];
        $flag:ident
    ) => {
        libc_bitflags! {
            @accumulate_flags
            $prefix,
            [
                $($flags)*
                const $flag = libc::$flag,
            ];
        }
    };

    // Munch an ident; covers terminating comma case.
    (@accumulate_flags
        $prefix:tt,
        [$($flags:tt)*];
        $flag:ident, $($tail:tt)*
    ) => {
        libc_bitflags! {
            @accumulate_flags
            $prefix,
            [
                $($flags)*
                const $flag = libc::$flag,
            ];
            $($tail)*
        }
    };

    // Entry rules without `pub`.
    (
        $(#[$attr:meta])*
        flags $BitFlags:ident: $T:ty {
            $($vals:tt)*
        }
    ) => {
        libc_bitflags! {
            @accumulate_flags
            {
                name: $BitFlags,
                type: $T,
                attrs: [$(#[$attr])*],
            },
            [];
            $($vals)*
        }
    };
}
2 Likes

It was my first non-trivial macro following TLBORM, so that's not surprising!

That is significantly easier to read! And as you say, it works. Thanks!