Enum Parsing

macro_rules! parse_unitary_variants { (@as_expr $e:expr) => {$e}; (@as_item $($i:item)+) => {$($i)+}; // Exit rules. ( @collect_unitary_variants ($callback:ident ( $($args:tt)* )), ($(,)*) -> ($($var_names:ident,)*) ) => { parse_unitary_variants! { @as_expr $callback!{ $($args)* ($($var_names),*) } } }; ( @collect_unitary_variants ($callback:ident { $($args:tt)* }), ($(,)*) -> ($($var_names:ident,)*) ) => { parse_unitary_variants! { @as_item $callback!{ $($args)* ($($var_names),*) } } }; // Consume an attribute. ( @collect_unitary_variants $fixed:tt, (#[$_attr:meta] $($tail:tt)*) -> ($($var_names:tt)*) ) => { parse_unitary_variants! { @collect_unitary_variants $fixed, ($($tail)*) -> ($($var_names)*) } }; // Handle a variant, optionally with an with initialiser. ( @collect_unitary_variants $fixed:tt, ($var:ident $(= $_val:expr)*, $($tail:tt)*) -> ($($var_names:tt)*) ) => { parse_unitary_variants! { @collect_unitary_variants $fixed, ($($tail)*) -> ($($var_names)* $var,) } }; // Abort on variant with a payload. ( @collect_unitary_variants $fixed:tt, ($var:ident $_struct:tt, $($tail:tt)*) -> ($($var_names:tt)*) ) => { const _error: () = "cannot parse unitary variants from enum with non-unitary variants"; }; // Entry rule. (enum $name:ident {$($body:tt)*} => $callback:ident $arg:tt) => { parse_unitary_variants! { @collect_unitary_variants ($callback $arg), ($($body)*,) -> () } }; } fn main() { assert_eq!( parse_unitary_variants!( enum Dummy { A, B, C } => stringify(variants:) ), "variants : ( A , B , C )" ); }
macro_rules! parse_unitary_variants {
    (@as_expr $e:expr) => {$e};
    (@as_item $($i:item)+) => {$($i)+};
    
    // Exit rules.
    (
        @collect_unitary_variants ($callback:ident ( $($args:tt)* )),
        ($(,)*) -> ($($var_names:ident,)*)
    ) => {
        parse_unitary_variants! {
            @as_expr
            $callback!{ $($args)* ($($var_names),*) }
        }
    };

    (
        @collect_unitary_variants ($callback:ident { $($args:tt)* }),
        ($(,)*) -> ($($var_names:ident,)*)
    ) => {
        parse_unitary_variants! {
            @as_item
            $callback!{ $($args)* ($($var_names),*) }
        }
    };

    // Consume an attribute.
    (
        @collect_unitary_variants $fixed:tt,
        (#[$_attr:meta] $($tail:tt)*) -> ($($var_names:tt)*)
    ) => {
        parse_unitary_variants! {
            @collect_unitary_variants $fixed,
            ($($tail)*) -> ($($var_names)*)
        }
    };

    // Handle a variant, optionally with an with initialiser.
    (
        @collect_unitary_variants $fixed:tt,
        ($var:ident $(= $_val:expr)*, $($tail:tt)*) -> ($($var_names:tt)*)
    ) => {
        parse_unitary_variants! {
            @collect_unitary_variants $fixed,
            ($($tail)*) -> ($($var_names)* $var,)
        }
    };

    // Abort on variant with a payload.
    (
        @collect_unitary_variants $fixed:tt,
        ($var:ident $_struct:tt, $($tail:tt)*) -> ($($var_names:tt)*)
    ) => {
        const _error: () = "cannot parse unitary variants from enum with non-unitary variants";
    };
    
    // Entry rule.
    (enum $name:ident {$($body:tt)*} => $callback:ident $arg:tt) => {
        parse_unitary_variants! {
            @collect_unitary_variants
            ($callback $arg), ($($body)*,) -> ()
        }
    };
}

This macro shows how you can use an incremental tt muncher and push-down accumulation to parse the variants of an enum where all variants are unitary (i.e. they have no payload). Upon completion, parse_unitary_variants! invokes a callback macro with the list of variants (plus any other arbitrary arguments supplied).

This can be modified to also parse struct fields, compute tag values for the variants, or even extract the names of all variants in an arbitrary enum.