Search code examples
rustrust-macros

Reversing a list via a macro


I am trying reverse a list of "vec2's" at compile time using a macro:

macro_rules! point_reverser {
    ([$([$x:expr, $y:expr]),*]) => {
        point_reverser!([$([$x, $y]),*] reversed: [])
    };
    ([[$x:expr, $y:expr], $([$x_head:expr, $y_head:expr]),*] reversed: [$([$x_tail:expr, $y_tail:expr]),*]) => {
        point_reverser!([$([$x_head, $y_head]),*] reversed: [$([$x_tail, $y_tail]),* , [$x, $y]])
    };
    ([[$x:expr, $y:expr]] reversed: [$([$x_tail:expr, $y_tail:expr]),*]) => {
        [$([$x_tail, $y_tail]),* , [$x, $y]]
    };
}

fn main() {
    let reversed_points = point_reverser!([[1, 2], [3, 4], [5, 6]]);
    println!("{:?}", reversed_points);
    println!("Expected result: [[5, 6], [3, 4], [1, 2]]");
}

The operation is (as I thought) rather straight forward. The macro has three entry points:

1) This should be executed first to make an empty reversed list. This process splits the list into what needs to be reversed (the front) and a list that has already been sorted (the back)

2) Takes a single vec off the head of the main list and puts it in the back of the sorted list. This part will execute reclusively until their is no head to take from.

3) Finally since there is nothing left in the main list, the reversed list is retuned.

Or at least that is the idea in theory. I can put item 3 higher than 2 to ensure that the steps from 2 does not supersede the termination of the macro. But it has currently not changed the outcome.

The current attempt for the macro gives the error:

error: no rules expected the token `,`
  --> src\main.rs:6:85
   |
1  | macro_rules! point_reverser {
   | --------------------------- when calling this macro
...
6  |         point_reverser!([$([$x_head, $y_head]),*] reversed: [$([$x_tail, $y_tail]),*, [$x, $y]])
   |                                                                                     ^ no rules expected this token in macro call
...
13 |     let reversed_points = point_reverser!([[1, 2], [3, 4], [5, 6]]);
   |                           ----------------------------------------- in this macro invocation
   |
note: while trying to match `]`
  --> src\main.rs:5:106
   |
5  |     ([[$x:expr, $y:expr], $([$x_head:expr, $y_head:expr]),*] reversed: [$([$x_tail:expr, $y_tail:expr]),*]) => {
   |                                                                                                          ^
   = note: this error originates in the macro `point_reverser` (in Nightly builds, run with -Z macro-backtrace for more info)

The last note mentions to run this with the option -Z macro-backtrace. I guess it gives me a better insight to a degree. But I do not have much further of an insight to what is going on as it doesn't really show what the input into the macro was that caused the explosion.

error: no rules expected the token `,`
  --> .\src\main.rs:6:86
   |
1  |   macro_rules! point_reverser {
   |   ---------------------------
   |   |
   |  _when calling this macro
   | |
2  | |     ([$([$x:expr, $y:expr]),*]) => {
3  | |         point_reverser!([$([$x, $y]),*] reversed: [])
   | |         --------------------------------------------- in this macro invocation (#2)
4  | |     };
5  | |     ([[$x:expr, $y:expr], $([$x_head:expr, $y_head:expr]),*] reversed: [$([$x_tail:expr, $y_tail:expr]),*]) => {
6  | |         point_reverser!([$([$x_head, $y_head]),*] reversed: [$([$x_tail, $y_tail]),* , [$x, $y]])
   | |                                                                                      ^ no rules expected this token in macro call
...  |
10 | |     };
11 | | }
   | | -
   | | |
   | |_in this expansion of `point_reverser!` (#1)
   |   in this expansion of `point_reverser!` (#2)
...
14 |       let reversed_points = point_reverser!([[1, 2], [3, 4], [5, 6]]);
   |                             ----------------------------------------- in this macro invocation (#1)
   |
note: while trying to match `]`
  --> .\src\main.rs:5:106
   |
5  |     ([[$x:expr, $y:expr], $([$x_head:expr, $y_head:expr]),*] reversed: [$([$x_tail:expr, $y_tail:expr]),*]) => {
   |        

Solution

  • Couple of issues with your code:

    • when $([$x_tail:expr, $y_tail:expr]),* matches nothing you still call the next invocation with a leading ,:
      reversed: [$([$x_tail, $y_tail]),* , [$x, $y]]
      //                                 ^---- explicit, unconditional comma here
      Fix it by moving the comma inside the parentheses and remove the explicit one:
      $($x),* , $y -> $($x,)* $y
    • you take items from the front and add them to the back, that won't reverse them instead add them to the front.
    • your macro is less general than it could be, [a, b] is an expression on it's own. Instead of using the explicit [$x:expr, $y:expr] you can use the simpler and more general $x:expr since an array literal is also an expression.

    Here is a version that fixes all these:

    macro_rules! reverser {
        // done reversing
        ([] reversed: $x:expr) => { $x };
        // Work on empty arrays
        ([]) => { [] };
        // convenience invocation with only the array and no reversed:
        ([$($x:expr),* $(,)?]) => {
            reverser!([$($x),*] reversed: [])
        };
        // do the actual heavy lifting
        ([$head:expr $(, $tail:expr)*] reversed: [$($reversed:expr),*]) => {
            reverser!([$($tail),*] reversed: [$head $(, $reversed)*])
        };
    }
    

    Playground

    I've also simplified the last case to be empty and return the whole expression.