Is this possible with macro_rules

I"m currently trying to generate structs like the following (simplified slightly):

struct RegisterBlock{
    _unused: [u8, 0x4],
    register_a: u32,
    _unused: [u8, 0x8],
    register_b: u32,
...
}

This should be generated from an input similar to:

some_macro!(RegisterBlock{
    register_a, 0x04,
    register_b, 0x10
});

The problem I am facing is translating the total offset given in the call into the relative offsets used in the struct itself.
I tried the two ways that came to my mind implementing this, but both failed due to limitations of macro_rules macros:

  • macros cannot be called inside a struct definition
  • macro invocations are not evaluated like normal functions when nested: foo!(bar!()) fails even if the return value of bar!() returns something that could be matched by foo!() since the matching of foo! is done before evaluating bar!

I am pretty sure how this could be implemented with procedural macros but if possible I would like to use macro_rules (I think they have much less friction for understanding the code later)

I might have a third idea, lets see if that works..

You can often use push down accumulation in cases like this. But the results are usually less than readable. I've never done a procedural macro, but I imagine the code would be cleaner in this instance.

1 Like

I think the idea I came up with is basically what you linked to:

macro_rules! createRegisterBlock {
    //call made by the user of the macro
    (
        $struct_name:ident
        {
            $(
                $register_name:ident, $offset:expr
            ),* $(,)?
        }
    ) => {
        createRegisterBlock!{
            $struct_name,
            [],
            [$(
                $register_name,
            )*],
            [0x0, $(
                $offset,
            )*],
        }
    };


    //compute the diff of the previous and the current offset and save it in an accumulator
    (
        $struct_name:ident,
        [$(
            $register_name_acc:ident, $offset_acc:expr,
        )*],
        [
            $register_name_head:ident,
            $(
                $register_name:ident,
            )*
        ],
        [
            $offset_previous:expr,
            $offset_current:expr,
            $(
                $offset:expr,
            )*
        ],
    ) => {
        createRegisterBlock!{
            $struct_name,
            [
                $(
                    $register_name_acc, $offset_acc,
                )*
                $register_name_head, $offset_current - $offset_previous,
            ],
            [
                $(
                    $register_name,
                )*
            ],
            [
                $offset_current,
                $(
                    $offset,
                )*
            ],
        }
    };

    //recursion end: build the struct
    (
        $struct_name:ident,
        [$(
            $register_name_acc:ident, $offset_acc:expr,
        )*],
        [],
        [$offset_previous_head:expr,],
    ) => {
            #[repr(C)]
            struct $struct_name {
            $(
                unused : [u8;$offset_acc - 0x4],
                pub $register_name_acc : u32
            ),*
        }
    };
}

Now the onlz problem remaining is that this does not work if called with multiple registers since the struct can only contain a single unused and it seems rust has no "anonymous" struct fields

solved the naming issue by wrapping the Register itself and the offset into a single struct :slight_smile:

You could probably also solve the naming of the unused field using the paste crate. So the names of the "anonymous" fields would be something like _unused_for_register_a and _unused_for_register_b.

1 Like

As far as I know paste is also a procedural macro and I try to avoid them when possible. But it would certainly be a solution.
For my use case the solution with the wrapper is fine as the wrapper can just forward the (very few) functions to the underlying struct

Makes sense, if you don't need it then of course its better to avoid it. But depending on your reason for avoiding procedural macros paste might not be so bad. It is a procedural macro but it has no dependencies so its build time shouldn't be as bad as most procedural macros (since they usually depend on syn).

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.