Idea: Empy designator could reduce macro recursion

I’ve been looking at macro_rules macros, and I’ve seen many people writing extremely recursive macros. This often comes at a significant cost of readability and maintenance of such macros. It can also require increasing the recursion_limit.

My idea is to introduce an empty designator to keep track of repetition, when the repeated part does not contain a meta variable. This would be written as $tracker:empty.

A simple example of a macro that would benefit from this, would be a counting macro such as this:

macro_rules! count {
    () => (0);
    (+$($rest:tt)*) => (count!($($rest)*)+1);
    (-$($rest:tt)*) => (count!($($rest)*)-1);
}

Using an empty meta variable, it could instead be written as:

macro_rules! count {
    ($($start:empty+)*$($sub:empty-$($add:empty+)*)*) => {
        0+$($start+1)*$($sub-1$($add+1)*)*
    };
}

Adding an empty designator to rust should not be a complex addition to the language, as there already exists designators, such as vis, that accept no tokens.

So, if I have understood it correctly, you mean to catch patterns such as $(+)? with a name, your proposed syntax for it being $name:empty+, is that it?

In that case, I like the idea, but I would personally prefer a syntax more akin to $name(+)? (allowing to catch an optional pattern made of more than 1 token, specially consistent with an expansion of $name(+ 1)?). Remains to see if it is / can be (made) backwards-compatible.

By the way, the example you have given would still lead to ambiguous parsing (even if all the parsing choices lead to the same result):

Not quite. I’m proposing keeping track of repetitions such as $(abc)?, $(abc)* and $(abc)+ by inserting a metavariable that accepts the empty string and only the empty string. As such these repetitions would become (abc$tracker:empty)?, (abc$tracker:empty)* and (abc$tracker:empty)+. As such the translation would be this, which is not ambiguous

Ohh, I see, I had indeed gotten it wrong. I wasn’t too far off, though, since the purpose of the empty designator seems to be about being able to mention “constant tokens” using a metavariable.

Hence the possibility for the $name(...)¤ (with any of the three Kleene operators instead of ¤).

I am fine with either form, and if it can be done (retro-compat yadda yadda), it is indeed a clever hack to replace some recursions by repetitions :slight_smile:

I agree that that is a much more elegant solution, but my suggestion was meant to be a minimal change. I don’t know my way around the compiler yet, but it seems quite trivial to add an empty designator

1 Like

(member of wg-grammar, speaking as me, not for the wg)

I think the best macro_rules matching grammar for this (matching a whole repetition) would be $name:()¤.

$name()? (missing_fragment_specifier) is currently a deny-by-default future-compat lint on declaration and error on use (#40107).

By adding the :, we effectively make the ()¤ token trees the fragment specifier as what $name is matching.

The problem remains as to what the use syntax should be. There’s no option that “attaches” a block to the matcher expansion without breaking backwards compat or making the macro_rules grammar data dependent (i.e. parse depends on macro fragment types). $name()? has to continue to work as a matched ident fn call and try. I’d lean towards the matcher just expanding to every tt matched, as do other macro matchers with their respective hygenic AST node.

An empty fragment specifier is a powerful tool for little complexity, and iiuc actually covers any use cases for a repetition matcher (though said matcher might be more elegant).

2 Likes

I’ve stumbled upon a case where naming repetition is more powerful than an empty designator. Naming repetitions directly allow you to combine different separately repeating parts:

macro_rules! {
    ($i:($Name:ident),+;$j:($Par:ty),+) => {
        $i:(struct $Name($j($Par),+);)+
    };
}

As an empty designator wouldn’t allow you to specify what to repeat when, this example would still be ambiguous.

1 Like