Decl-macros: react to the precence of flags

(I'm fairly new to macros.) My patterns will contain optional flags that shall trigger additional code generation. I fail to find a way to query the presence of such a flag. Matching on any identifier is no problem as it has a name bound to it but the flag doesn't have a name. Due to the error message, I feel it would work as expected if it was possible to bind a syntax variable to the flag:

error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth

(playground: Rust Playground )

struct NodeA(Vec<u32>);
struct NodeB(u8);
struct NodeC(String);

trait Container {}

#[macro_export]
macro_rules! nodes {
    ($($Type:ty, $Variant:ident $(container)?;)*) => {
        pub enum AnyNode {
            $($Variant($Type),)*
        }

        $(
            // this was meant to iterate 0 or 1 times depending on the presence of `container`
            $(
                impl Container for $Type {}
            )*
        )*

    };
}

nodes!(
    NodeA, A container;
    NodeB, B;
    NodeC, C container;
);

I could imagine that breaking this up into multiple patterns (one per entry, one with/out flag, …) could help, but so far It didn't manage to find one - and I was wondering if there's a more straight-forward solution.

pub struct NodeA(Vec<u32>);
pub struct NodeB(u8);
pub struct NodeC(String);

trait Container {}

#[macro_export]
macro_rules! nodes {
    ($($Type:ty, $Variant:ident $($flags:ident)?;)*) => {
        pub enum AnyNode {
            $($Variant($Type),)*
        }

        $(
            // this was meant to iterate 0 or 1 times depending on the presence of `container`
            nodes! { @if container in [$($flags)*] => {
                impl Container for $Type {}
            }}
        )*
    };
    
    // Output nothing if list is empty
    (@if container in [] => $tail:tt) => {};
    
    // Output tail if list starts with `container`
    (@if container in [container $($flags:tt)*] => {$($tail:tt)*}) => { $($tail)* };
    
    // Recurse if list is non-empty, but doesn't start with `container`
    (@if container in [$head:tt $($flags:tt)*] => $tail:tt) => {
        nodes! { @if container in [$($flags)*] => $tail }
    };
}

nodes!(
    NodeA, A container;
    NodeB, B;
    NodeC, C container;
);

fn main() {
}

(Also had to fix a few unrelated things to get it to compile.)

Edit: should mention: you cannot do a repetition substitution based on something you matched but did not capture. There must be a capture involved in any repetition substitution.

2 Likes

Ok, wow :astonished: - that solution wasn't exactly obvious to me :grin:

re your comment: I somehow expected it to be possible to add a capture to the constant (something like $my_constant:(constant)) without having to introduce an identifier (resulting in all those sub-patterns).

There are a lot of new patterns in your example that will help me on other occasions as well. Thank you!

The pattern is called Push-down Accumulation - The Little Book of Rust Macros which was originally written by DanielKeep the first one replying your post :slight_smile:


This is simpler.

#[macro_export]
macro_rules! nodes {
    ($($Type:ty, $Variant:ident $($container:ident)?;)*) => {
        pub enum AnyNode {
           $($Variant($Type),)*
        }
        
        $(
            nodes!(@inner $Type, $($container)?);
        )*
    };
    (@inner $Type:ty, ) => { };
    (@inner $Type:ty, container) => {
        impl Container for $Type {}
    };
}

Rust Playground

2 Likes

Thanks for giving more context. I think this was the only chapter I skipped today :sweat_smile:

I was still hoping for a feature I just missed, but it seems to be patterns all the way down.

(I hope nobody will be mad, but I'll move the solution marker to yours, because it more succinctly addresses my specific problem)