如何调试宏?

27
所以我有以下的宏代码,我正在尝试调试。我将其从 Rust Book中的“深入”部分中获取。我已经重命名了宏中的变量,以更紧密地遵循this帖子。
我的目标是使程序打印出BCT程序的每一行。我非常清楚这非常耗费编译器。
Rustc给我的唯一错误是:
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);

我该采取哪些步骤来确定问题出在宏的哪个位置
这是我的代码:
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);
}            

可能是如何查看导致编译错误的扩展宏代码?的重复问题。 - bluenote10
3个回答

47

有两种主要方法可以调试无法扩展的宏:

  • trace_macros!
  • log_syntax!

(注:两者都是特性门控的,使用同名特性,因此需要夜间编译器才能工作,rustup 可以轻松在版本之间切换以进行此类工作。)

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);

然后输出看起来像:
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

希望这可以缩小问题范围:某种方式上,bct_p! 调用是无效的。仔细查看后发现问题所在,bct_p 第二个参数的左侧使用了 data:tt,而应该使用 $data:tt,即缺少了一个 $
    ($($program:tt),* ; $(data:tt),*)

修复这个问题可以让编译进展。在这种情况下,log_syntax!不是立即有用的,但仍然是一个很棒的工具:它可以接受任意参数,并在扩展时将它们打印出来。例如:
#![feature(log_syntax)]

log_syntax!("hello", 1 2 3);

fn main() {}

将会在编译时打印出"hello" , 1 2 3。这对于检查其他宏调用中的内容非常有用。
(一旦您使扩展工作起来,调试生成代码中的任何问题的最佳工具是使用--pretty expanded参数进行rustc。注意:这需要传递-Z unstable-options标志以激活它。)

为什么宏编译步骤不会抱怨 $(not_al_variable),*?在什么情况下这是有效的?此外,不要忘记 --pretty expanded,hygiene,当宏外部变量与宏内部变量同名时非常有用(至少我是这样解释的)。 - Nashenas
2
可能有时候复制粘贴同一段代码是有意义的,例如可以使用 $(0),* 去掉末尾的零,这样只会匹配到 0, 0, 0(等等)。但是,这种情况似乎相对较少,不会特别有用。 - huon

24

另一个非常方便的工具,可以轻松查看扩展是cargo-expand

可使用以下命令进行安装:

cargo install cargo-expand

然后可以非常简单地使用:

cargo expand

或者更精确地针对特定的测试文件(例如tests/simple.rs):

cargo expand --test simple

一定要查看--help,这里有许多选项可以缩小扩展范围。甚至可以针对单个项目(结构体、函数等)进行扩展!


0

调试很有趣。我从最简单的输入开始,逐步增加难度。我发现在打印函数方面遇到了问题(重写它只打印输入而不回溯!)。

我还添加了更明确的规则,然后在一切正常之后逐个删除它们(当然是一边测试一边进行)。一旦我知道每个单独的部分都编译成功,并且打印函数也正常工作,我就能够验证宏的输出。下面的宏有时会在不应该运行时运行,但它可以编译、打印和调试。我对当前状态感到满意,可以在这里发布它。

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

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

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

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

    macro_rules! println_bct {
        () =>
            (println!(""));
        (;) =>
            (println!(":"));

        ($d:tt) =>
            (println!("{}", stringify!($d)));
        ($d:tt, $($data:tt),*) => {
            print!("{}", stringify!($d));
            println_bct!($($data),*);
        };
        ( ; $($data:tt),*) => {
            print!(":");
            println_bct!($($data),*);
        };

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

    macro_rules! pbct {
        ($($program:tt),* ; $($data:tt),*) => {
            println_bct!($($program),* ; $($data),*);
            bct!($($program),* ; $($data),*);
        };
    }

    pbct!(0, 0, 1, 1, 1, 0, 0, 0 ; 1, 0, 1);

    // This one causes the compiler to hit recursion limits, heh
    // pbct!(0, 0, 1, 1, 1, 1, 1, 0 ; 1, 0, 1);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接