Search code examples
rustrust-macros

How do I debug macros?


So I've got the following macro code I'm trying to debug. I've taken it from the Rust Book under the section "The deep end". I renamed the variables within the macro to more closely follow this post.

My goal is to have the program print out each line of the BCT program. I'm well aware that this is very compiler heavy.

The only error rustc is giving me is:

user@debian:~/rust/macros$ rustc --pretty expanded src/main.rs -Z unstable-options > src/main.precomp.rs
src/main.rs:151:34: 151:35 error: no rules expected the token `0`
src/main.rs:151     bct!(0, 1, 1, 1, 0, 0, 0; 1, 0);

What steps can I take to figure out where in the macro the problem is coming from?

Here's my code:

fn main() {
{
    // "Bitwise Cyclic Tag" automation through macros
    macro_rules! bct {
        // cmd 0:  0 ... => ...
        (0, $($program:tt),* ; $_head:tt)
            => (bct_p!($($program),*, 0 ; ));
        (0, $($program:tt),* ; $_head:tt, $($tail:tt),*)
            => (bct_p!($($program),*, 0 ; $($tail),*));

        // cmd 1x:  1 ... => 1 ... x
        (1, $x:tt, $($program:tt),* ; 1)
            => (bct_p!($($program),*, 1, $x ; 1, $x));
        (1, $x:tt, $($program:tt),* ; 1, $($tail:tt),*)
            => (bct_p!($($program),*, 1, $x ; 1, $($tail),*, $x));

        // cmd 1x:  0 ... => 0 ...
        (1, $x:tt, $($program:tt),* ; $($tail:tt),*)
            => (bct_p!($($program),*, 1, $x ; $($tail),*));

        // halt on empty data string
        ( $($program:tt),* ; )
            => (());
        }

    macro_rules! print_bct {
        ($x:tt ; )
            => (print!("{}", stringify!($x)));
        ( ; $d:tt)
            => (print!("{}", stringify!($d)));
        ($x:tt, $($program:tt),* ; )
            => {
                print!("{}", stringify!($x));
                print_bct!($program ;);
            };
        ($x:tt, $($program:tt),* ; $($data:tt),*)
            => {
                print!("{}", stringify!($x));
                print_bct!($program ; $data);
            };
        ( ; $d:tt, $($data:tt),*)
            => {
                print!("{}", stringify!($d));
                print_bct!( ; $data);
            };
    }

    macro_rules! bct_p {
        ($($program:tt),* ; )
            => {
                print_bct!($($program:tt),* ; );
                println!("");
                bct!($($program),* ; );
            };
        ($($program:tt),* ; $(data:tt),*)
            => {
                print_bct!($($program),* ; $($data),*);
                println!("");
                bct!($($program),* ; $($data),*);
            };
    }

    // the compiler is going to hate me...
    bct!(0, 1, 1, 1, 0, 0, 0; 1, 0);
}            

Solution

  • There's two main ways to debug macros that are failing to expand:

    • trace_macros! and
    • log_syntax!

    (NB. both are feature gated, under features of the same name, and so require the nightly compiler to work, rustup makes it easy to switch between versions for this sort of work.)

    trace_macros!(...) takes a boolean argument that switches macro tracing on or off (i.e. it's stateful), if it's on, the compiler will print each macro invocation with its arguments as they are expanded. Usually one just wants to throw a trace_macros!(true); call at the top of the crate, e.g. if one adds the following to the top of your code:

    #![feature(trace_macros)]
    
    trace_macros!(true);
    

    Then the output looks like:

    bct! { 0 , 1 , 1 , 1 , 0 , 0 , 0 ; 1 , 0 }
    bct_p! { 1 , 1 , 1 , 0 , 0 , 0 , 0 ; 0 }
    <anon>:68:34: 68:35 error: no rules expected the token `0`
    <anon>:68     bct!(0, 1, 1, 1, 0, 0, 0; 1, 0);
                                               ^
    playpen: application terminated with error code 101
    

    which hopefully narrows down the problem: the bct_p! call is invalid in some way. Looking at it carefully reveals the problem, the left-hand side of second arm of bct_p uses data:tt when it should use $data:tt, i.e. a missing $.

        ($($program:tt),* ; $(data:tt),*)
    

    Fixing that allows compilation to make progress.

    log_syntax! isn't as immediately useful in this case, but is still a neat tool: it takes arbitrary arguments and prints them out when it is expanded, e.g.

    #![feature(log_syntax)]
    
    log_syntax!("hello", 1 2 3);
    
    fn main() {}
    

    will print "hello" , 1 2 3 as it compiles. This is most useful to inspect things inside other macro invocations.

    (Once you've got expansion to work, the best tool to debug any problems in the generated code is to use the --pretty expanded argument to rustc. NB. this requires the -Z unstable-options flag to be passed to activate it.)