Macro_rules help part 3

desired macro output

#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
pub struct Animal_Id_Cat {}

#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
pub struct Animal_Id_Dog {}

#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
pub struct Animal_Id_Bird {}

pub struct Animal_Strut {
    cat: Animal_Id_Cat,
    dog: Animal_Id_Dog,
    bird: Animal_Id_Bird,
}

pub enum Animal_Enum {
    Cat(Animal_Id_Cat),
    Dog(Animal_Id_Dog),
    Bird(Animal_Id_Bird),
}

how I want to call the macro

make_id_enum_struct! {
    Animal_Struct Animal_Enum
#[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
(cat, Cat, Animal_Id_Cat);
(dog, Dog, Animal_Id_Dog);
(bird, Bird, Animal_Id_Bird);
}

what I have so far

#[macro_export]
macro_rules! make_id_enum_struct {

    {
        $struct_name:ident $enum_name:ident
        $( #[$meta:meta] )*
        $( (  $a:tt, $b:tt, $c:tt ); )*
    } => {
        $( #[$meta] )*
        $( pub struct $c {} )*

        pub struct $struct_name {
            $( $a : $c ),*
        }

        pub enum $enum_name {
            $( $b ( $c ) ),*
        }
    }
}

what is broken

how do I repeat the #derive[...] part FOR EVERY STRUCT decl ?

This is the problem I am running into. I have captured the meta tags for the struct via repetition. Now I want to repeat it ONCE PER STRUCT.

Thanks!

Change this

        $( #[$meta] )*
        $( pub struct $c {} )*

to this

        $(
            $( #[$meta] )*
            pub struct $c {}
        )*

Hopefully it makes sense why. If not, I can explain :slight_smile:

Good try, but I don't think it works. :slight_smile:

meta-variable `meta` repeats 1 time, but `c` repeats 3 times

Currently trying:

#[macro_export]
macro_rules! make_id_enum_struct {

    {
        $struct_name:ident
        $enum_name:ident

        $( #[$meta:meta] )*

        $( ( $a:tt, $b:tt, $c:tt ); )*


    } => {

        $( 
        $( #[$meta] )*
        pub struct $c {} )* 

        pub struct $struct_name {
            $( $a : $c ),*
        }

        pub enum $enum_name {
            $( $b ( $c ) ),*
        }
    }
}

Hmm...I know it's possible, even if it requires some trickery. I'm working on something else at the moment, though, so I'll defer to someone who has more time.

Here is one way you could make it works:

#[macro_export]
macro_rules! make_id_enum_struct {
    (
        $struct_name:ident $enum_name:ident
        $(#[$outer:meta])+
        $($tail:tt)*
    ) => {
        $crate::make_id_enum_struct!(@expand_structs $(#[$outer])+ $($tail)*);
        $crate::make_id_enum_struct!(@expand_remaining $struct_name $enum_name $($tail)*);
    };
    (
        @expand_structs
        $(#[$outer:meta])+
        $a:tt, $b:tt, $c:tt;
        $($tail:tt)*
    ) => {
        $( #[$outer] )*
        pub struct $c { }

        $crate::make_id_enum_struct!(@expand_structs $(#[$outer])* $($tail)*);
    };
    (
        @expand_structs
        $(#[$outer:meta])+
    ) => {};
    (
        @expand_remaining
        $struct_name:ident $enum_name:ident
        $( $a:ident, $b:ident, $c:ident; )*
    ) => {
        pub struct $struct_name {
            $( $a : $c ),*
        }

        pub enum $enum_name {
            $( $b ( $c ) ),*
        }
    };
}

make_id_enum_struct! {
    Animal_Struct Animal_Enum
    #[derive(Debug, Hash, Eq, PartialEq, Copy, Clone)]
    cat, Cat, Animal_Id_Cat;
    dog, Dog, Animal_Id_Dog;
    bird, Bird, Animal_Id_Bird;
}

Link to the playground

Feel free to adjust the rules to match your preferences. In my opinion, a good rule of thumb is "the closer to the actual Rust syntax, the better".

You’ll recognize a "TT muncher", which is used as an implementation detail to incrementally generate all the inner structs.

Note: I used @ as a prefix for internal rules. You could remove these rules and turn them into full-fledged separate "internal" macros that you hide from the documentation. This may be faster to compile: AFAIK, the less macro branches, the less the compiler will backtrack because a pattern does not match a given rule.

2 Likes

Interesting. So the key idea is basically writing out in macro_rules!

map f [] = []
map f (hd :: rst) = (f hd) :: (map f rst)

?

I don't recognise the language, but I believe that to be accurate.

Do you have any recommended reading on understanding how "repetition" works in the "output" stage of macro_rules! ?

The "pattern matching" stage feels quite intuitive, it's almost like an regular expression over typed tokens.

The "do some processing, and call self w/ modified input" is a but surprising but also mechanically understandable.

However, how repetition works -- especially in nested cases and in cases where nodes from "different parts of matching" are mixed -- how this works -- I don't understand. I have no mental model for mechanically expanding out what macro_rules! emits in the case of repetition.

In particular, in the original post, what got me stuck was not the input / pattern matching stage, but the emit / output / dealing with repetition in output part.

The closest I have is this thread about a similar problem. Hopefully one of the explanations there will help.

1 Like

Thanks, that was a goldmine. It seems, on a high level, the most important rules are:

  1. For any variable, its "capture depth" and "expansion depth" must be the same. In both cases, we count depth via $ LEFT_DELIM ... RIGHT_DELIM

  2. If we are expanding something of the form $( $a $b $c)* then we have to make sure that $a, $b, $c all have the same len.

From these two rules, pretty much everything else follows intuitively right ?

I sure hope so, since that's what I'm operating on. :slight_smile:

1 Like