Как отлаживать макросы?

Итак, у меня есть следующий макрокод, который я пытаюсь отлаживать. Я взял его из Ручная книга в разделе "Глубокий конец". Я переименовал переменные внутри макроса, чтобы более внимательно следить за это сообщение.

Моя цель состоит в том, чтобы программа распечатывала каждую строку программы BCT. Я хорошо знаю, что это очень тяжелый компилятор.

Единственная ошибка rustc, которая дает мне:

[email protected]:~/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);
}            

Ответ 1

Есть два основных способа отладки макросов, которые не расширяются:

  • trace_macros! и
  • log_syntax!

(NB, оба являются функциями gated, в функциях с тем же именем и поэтому требуют, чтобы ночной компилятор работал, multirust позволяет легко переключаться между версии для такого рода работ.)

trace_macros!(...) принимает логический аргумент, который включает или отключает макрос трассировку (то есть, он является stateful), если он включен, компилятор будет печатать каждый вызов макроса с его аргументами по мере их расширения. Обычно нужно просто вызвать вызов trace_macros!(true); в верхней части ящика, например. если добавляет к верхней части вашего кода:

#![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. NB. Для этого требуется, чтобы флаг -Z unstable-options передавался активируйте его.)

Ответ 2

Еще один замечательный инструмент, позволяющий легко увидеть расширение - это расширение груза.

Может быть установлен с:

cargo install cargo-expand

И тогда используется довольно просто как:

cargo expand

Или с большей точностью нацелиться на конкретный тестовый файл (например, tests/simple.rs):

cargo expand --test simple

Обязательно ознакомьтесь с --help, есть множество вариантов, чтобы сузить то, что расширяется. Вы даже можете нацеливаться на отдельные предметы (структуры, фнс и т.д.) Для расширения!

Ответ 3

Отладка была интересной. Я начал с самого простого ввода и работал оттуда. Я обнаружил, что у меня были проблемы с функциями печати (переписывайте, чтобы он просто печатал входы и не возвращался обратно!).

Я также добавил правила, которые были более явными, а затем удалили их, как только все сработало (один за другим, конечно, тестирование на этом пути). Как только я понял, что каждая отдельная часть компилируется, а функции печати работают, я смог проверить вывод макросов. Этот макрос иногда запускается, когда он не должен, но он компилируется, печатает и отлаживается. Я достаточно счастлив с текущим состоянием, чтобы опубликовать его здесь.

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