Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Escaping commas in macro output

Tags:

macros

rust

I am trying to write a macro which enables me to transform (a, b, c, d) to (a, a + b, a + b + c, a + b + c + d), etc. Here is what I have got so far:

macro_rules! pascal_next {
    ($x: expr) => ($x);
    ($x: expr, $y: expr) => (
        ($x, $x + $y)
    );
    ($x: expr, $y: expr, $($rest: expr),+) => (
        ($x, pascal_next!(
                $x + $y, $($rest),+
            )
        )
    );
}

However, there is a problem that it would actually output (a, (a + b, (a + b + c, a + b + c +d))). The origin is that the second matching rule ($x: expr, $y: expr) => (($x, $x + $y));, produces an extra bracket, so that there would be nested brackets. If I don't put a bracket outside, I would get the error error:

unexpected token: ,

So is it possible to output a comma , in Rust macros?

like image 466
Colliot Avatar asked Jul 02 '15 22:07

Colliot


1 Answers

No; the result of a macro must be a complete grammar construct like an expression or an item. You absolutely cannot have random bits of syntax like a comma or a closing brace.

You can get around this by simply not outputting anything until you have a complete, final expression. Behold!

#![feature(trace_macros)]

macro_rules! pascal_impl {
    /*
    The input to this macro takes the following form:

    ```ignore
    (
        // The current output accumulator.
        ($($out:tt)*);

        // The current additive prefix.
        $prefix:expr;

        // The remaining, comma-terminated elements.
        ...
    )
    ```
    */

    /*
    Termination condition: there is no input left.  As
    such, dump the output.
    */
    (
        $out:expr;
        $_prefix:expr;
    ) => {
        $out
    };

    /*
    Otherwise, we have more to scrape!
    */
    (
        ($($out:tt)*);
        $prefix:expr;
        $e:expr, $($rest:tt)*
    ) => {
        pascal_impl!(
            ($($out)* $prefix+$e,);
            $prefix+$e;
            $($rest)*
        )
    };
}

macro_rules! pascal {
    ($($es:expr),+) => { pascal_impl!((); 0; $($es),+,) };
}

trace_macros!(true);

fn main() {
    println!("{:?}", pascal!(1, 2, 3, 4));
}

Note: To use this on a stable compiler, you will need to delete the #![feature(trace_macros)] and trace_macros!(true); lines. Everything else should be fine.

What this does is it recursively munches away at the input, passing the partial (and potentially semantically invalid) output as input to the next level of recursion. This lets us build up an "open list", which we couldn't otherwise do.

Then, once we're out of input, we just re-interpret our partial output as a complete expression and... done.

The reason I including the tracing stuff is so I could show you what it looks like as it runs:

pascal! { 1 , 2 , 3 , 4 }
pascal_impl! { (  ) ; 0 ; 1 , 2 , 3 , 4 , }
pascal_impl! { ( 0 + 1 , ) ; 0 + 1 ; 2 , 3 , 4 , }
pascal_impl! { ( 0 + 1 , 0 + 1 + 2 , ) ; 0 + 1 + 2 ; 3 , 4 , }
pascal_impl! { ( 0 + 1 , 0 + 1 + 2 , 0 + 1 + 2 + 3 , ) ; 0 + 1 + 2 + 3 ; 4 , }
pascal_impl! { ( 0 + 1 , 0 + 1 + 2 , 0 + 1 + 2 + 3 , 0 + 1 + 2 + 3 + 4 , ) ; 0 + 1 + 2 + 3 + 4 ; }

And the output is:

(1, 3, 6, 10)

One thing to be aware of: large numbers of un-annotated integer literals can cause a dramatic increase in compile times. If this happens, you can solve it by simply annotating all of your integer literals (like 1i32).

like image 133
DK. Avatar answered Oct 08 '22 02:10

DK.