1
\$\begingroup\$

This is my first time writing a somewhat non-trivial declarative + recursive macro in Rust. I drew inspiration from this answer.

The goal is to come up with a recursive macro that allows to assemble some kind of node tree. The general interface function is something like parent.add_child(child). The macro should simplify assembling a deeply nested node tree. For instance:

assemble_tree!(
    root => {
        child_a,
        child_b => { subchild_a },
        child_c,
        child_d => {
            sub_child_b => { sub_sub_child_a },
            sub_child_c,
        }
    }
);

Should expand to:

root.add_child(child_a);
root.add_child(child_b);
child_b.add_child(subchild_a);
root.add_child(child_c);
root.add_child(child_d);
child_d.add_child(sub_child_b);
sub_child_b.add_child(sub_sub_child_a);
child_d.add_child(sub_child_c);

My approach is the following:

macro_rules! assemble_tree {
    ($base:expr => { $($other:tt)* }) => {
        assemble_tree!( @recurse, $base, $($other)*);
    };
    (@recurse, $base:expr, $child:expr $(,)?) => {
        $base.add_child($child);
    };
    (@recurse, $base:expr, $child:expr, $($other:tt)+) => {
        $base.add_child($child);
        assemble_tree!( @recurse, $base, $($other)*);
    };
    (@recurse, $base:expr, $child:expr => { $($children:tt)* } $(,)?) => {
        $base.add_child($child);
        assemble_tree!( $child => { $($children)* });
    };
    (@recurse, $base:expr, $child:expr => { $($children:tt)* }, $($other:tt)+) => {
        $base.add_child($child);
        assemble_tree!( $child => { $($children)* });
        assemble_tree!( @recurse, $base, $($other)*);
    };
}

I'm wondering if there are any gotchas with this approach, or if there is a way to solve this problem more elegantly.

In particular the approach doesn't feel particularly DRY, because each pattern ending with $(,)? has a sibling pattern (the one ending with $($other:tt)+) and the body of these sibling patterns contains some duplication.

\$\endgroup\$
4
  • \$\begingroup\$ You don't actually declare the children (and the root). \$\endgroup\$ Commented Jul 18, 2023 at 3:44
  • \$\begingroup\$ @ChayimRefaelFriedman Indeed. In my real world use case, the node API is coming from the Godot Rust bindings, and complete example would become quite verbose and non-minimal. I thought since this is a problem purely on the macro level I can leave out these distractions. In fact, I was developing the macro purely using cargo expand and the actual code never was in a compiling state (using such undeclared variables), but I could still see the macro expansions and thus the behavior of the macro. \$\endgroup\$ Commented Jul 18, 2023 at 6:46
  • \$\begingroup\$ The question is if you don't need the macro to declare those variables for you. \$\endgroup\$ Commented Jul 18, 2023 at 6:47
  • \$\begingroup\$ @ChayimRefaelFriedman Ah now I get the question. No, declaring the variables is not the responsibility of the macro. The user either declares them upfront, or uses an inline expression to construct them as a temporary. But I'm just realizing that this probably only makes sense if an expression only appears in a child position and has no children itself, otherwise the constructing expression gets executed twice (once in the parent position, and once in the child position). Your question already helped to point out a limitation ;) \$\endgroup\$ Commented Jul 18, 2023 at 6:53

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.