[SOLVED] Macro Error Confusion


#1
macro_rules! mess_around {
    (if ($boolean:expr) $msg:block $($remaining:expr)*) => (
        $msg;
        $(
            mess_around!($remaining);
        )*
    );
}

fn main() {
    mess_around!(
        if (1==1) {
            println!("hello, world");
            println!("hello, worlds");
        }
        if (1==1) {
            println!("hello, world");
            println!("hello, worlds");
        }
    );
}

This gives me the error on Rust playpen:

<anon>:5:21: 5:31 error: no rules expected the token `if (1 == 1) { println!("hello, world"); println!("hello, worlds"); }`
<anon>:5             mess_around!($remaining);
                             ^~~~~~~~~~
playpen: application terminated with error code 101
Compilation failed.

I’m just trying to mess around with macros (hence the macro name), but still can’t figure out why the token was unexpected.

If I am just re-macro-ing with the remaining part (which is identical to the other part), shouldn’t there be no errors?

Thanks!

UPDATE: If I am re-macro-ing any part of the argument that remains, is there something better than expr?


#2

There’s a section on this in TLBoRM: Captures and Expansion Redux (scroll down to “One aspect of substitution that often surprises people…”).

The short version is that literal matching (i.e. matching against a literal if identifier token) is done at the per-token level. When you capture tokens using something like $remaining:expr, this actually bundles all the individual tokens together into a single “expression” token. As a result, it bails when it finds that if doesn’t match if (1 == 1) { etc. etc. }, because of course they aren’t the same.

There are only two matchers that don’t mess with the tokens they capture: :tt and :ident.

Which is a needlessly wordy way to say: use $($remaining:tt)*.


#3

I tried both, but both renders errors:

For :tt

<anon>:16:9: 16:11 error: unexpected end of macro invocation
<anon>:16         if (1==1) {
                  ^~
playpen: application terminated with error code 101
Compilation failed.

For :ident

<anon>:16:12: 16:13 error: expected ident, found (
<anon>:16         if (1==1) {
                     ^
playpen: application terminated with error code 101
Compilation failed.

#4

By the way, your little book was really useful! :wink:


#5

Well ident won’t work because (1 == 1) isn’t an ident.

As for tt it works fine. You just need to fix the repetition, and add a termination rule:

macro_rules! mess_around {
    (if ($boolean:expr) $msg:block $($remaining:tt)*) => {
        $msg;
        mess_around!($($remaining)*);
    };
    () => {};
}

fn main() {
    mess_around!(
        if (1==1) {
            println!("hello, world");
            println!("hello, worlds");
        }
        if (1==1) {
            println!("hello, world");
            println!("hello, worlds");
        }
    );
}

#6

That did it! Thanks!

Back to messing around with macros…

I do have a question, though. What is the difference between the two repetitions?


#7
mess_around!(if);
mess_around!((1 == 1));
mess_around!({ println etc. etc. });

versus

mess_around!(if (1 == 1) { println etc. etc. });

#8

I just adjusted the code, and now am stuck at another error.

I renamed the macro for the sake of aesthetics, slightly changed the code so it works like its supposed to (at least for the if statement). Apparently, something is wrong with implementing the if and else if statements. :neutral_face:

Is it because I need a if statement in front?

Here’s the code for reference


#9

I get it! So it means that I can nicely chain together my token trees without having to mess_around! with them one by one, right?


#10

Macro expansions have to be syntactically valid; you can’t have an else without an if. You have to restructure the macro a bit; I also modified it to make it harder for the input to have an unmatched else (i.e. adding @ to the recursion).

macro_rules! mintex {
    (if ($boolean:expr) $msg:block $($remaining:tt)*) => {
        if $boolean {
            $msg;
        } else {
            mintex!(@ $($remaining)*);
        }
    };
    
    (@ else if ($boolean:expr) $msg:block $($remaining:tt)*) => {
        if $boolean {
            $msg;
        } else {
            mintex!(@ $($remaining)*);
        }
    };
    
    (@ else $msg:block) => {
        $msg;
    };
    
    () => {};
}

fn main() {
    mintex!(
        if (1==5) {
            println!("one is five!");
        } else if (1==12) {
            println!("one is twelve!");
        } else {
            println!("one is one.");
        }
    );
}

#11

Well, you have to. Your original code was trying to mess_around! with individual tokens, as opposed to the entirety of the remaining input.


#12

If I have more issues, I’ll be posting it here! Thank you!