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);
}
There's two main ways to debug macros that are failing to expand:
trace_macros!
andlog_syntax!
(NB. both are feature gated, under features of the same name, and so require the nightly compiler to work, multirust
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.)
Another great tool to use for easily seeing the expansion is cargo-expand.
It can be installed with:
cargo install cargo-expand
And then used quite simply as:
cargo expand
Or with more precision to target a particular test file (tests/simple.rs for example):
cargo expand --test simple
Be sure to check out the --help
, there are a bunch of options to narrow down what is expanded. You can even target individual items (structs, fns etc.) for expansion!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With