Escaping commas in macro output

后端 未结 1 1452
星月不相逢
星月不相逢 2021-01-02 13:19

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 fa

相关标签:
1条回答
  • 2021-01-02 13:51

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

    0 讨论(0)
提交回复
热议问题