我该如何调试宏?

时间:2015-05-12 20:01:11

标签: rust rust-macros

所以我有以下宏代码我正在尝试调试。我是从“深渊”一节下的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);
}            

3 个答案:

答案 0 :(得分:19)

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

  • trace_macros!
  • log_syntax!

(注意:两者都是特征门控,在同名的功能下,因此需要夜间编译器才能工作,multirust可以在这些工作的版本之间轻松切换。)

trace_macros!(...)采用一个布尔参数来打开或关闭宏跟踪(即它是有状态的),如果它打开,编译器将打印每个宏调用及其参数,因为它们是扩大。通常只想在箱子的顶部抛出trace_macros!(true);电话,例如如果您的代码顶部有adds个以下内容:

#![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参数。注意这需要{{ 1}}传递标志以激活它。)

答案 1 :(得分:1)

cargo-expand是另一个易于查看扩展的好工具。

它可以安装:

cargo install cargo-expand

然后非常简单地用作:

cargo expand

或更精确地定位到特定的测试文件(例如,tests / simple.rs):

cargo expand --test simple

请务必查看--help,其中有很多选项可以缩小展开的范围。您甚至可以针对单个items (structs, fns etc.)进行扩展!

答案 2 :(得分: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);
}