#repeat #template #duplicate #macro #code

macro repeated

Allows you to repeat a block of code a number of times

3 releases

0.1.2 Nov 21, 2020
0.1.1 Nov 18, 2020
0.1.0 Nov 17, 2020

#1353 in Rust patterns

MIT license

17KB
173 lines

repeated

This is a procedural macro that allows you to repeat a template of code a number of times.

Examples

From the tests:

This will push each value from 0 to 255 (inclusive) to the vec v.

let mut v = Vec::<u32>::with_capacity(256);
repeated!(for z in [0;255] {
    v.push(%%z%%);
});
println!("{:?}", v);

After defining your for loop to describe your repeated block you can pass a string literal to describe how each repitition should be joined to the prior iteration. Here we use an empty string literal to keep each repitition next to each other. We first create an int n with a value of 111 and continue on to define another repeated block. The second repeated block has an option third parameter on the range definition indicating the step by which we should be incrementing.

let n = repeated!(for _unused in [0;3] {1}, "");
repeated!(for i in [0;4;2] { println!("%%i%%"); }, "");
println!("Tested match position! Here is n: {}", n);

The previous block will print the following:

0
2
4
Tested match position! Here is n: 1111

The following example demonstrates nested usage where a block is repeated a variable number of times based off of the current iteration of the outer block:

repeated!(for x in [0;9;3] {
    fn Welcome_%%x%%() {
        repeated!(for y in [1;%%x%%;2] {
            println!("From within the macro %%x%%:%%y%%!");
        });
    }
});
Welcome_3();

Finally, there are certain scenarios where it isn't valid to make a call to a macro. One such scenario is in a match branch position. In the example below you can see how you can continue to make use of the repeated macro even when attempting to create similar match branches. You can define a prelude or a postlude to your main for body. In order to do so, you must provide an identifier for your pre/post-lude. The example below provides an example of defining a prelude and postlude that each use the identifier of my.

let t = 3;
repeated!(
    %% my prelude
    match t {
    prelude my %%
    for x in [0;15] {
        %%x%% => {
            println!("{}", repeated!(for y in [0;%%x%%] {%%x%%},""));
        }
    }
    %% my postlude
    _ => panic!(),
    }
    postlude my %%
);

This will print 3333 to the console.

Performance

Large repititions can become very slow to compile if you aren't careful. It can be beneficial to slowly increment the number of repititions to the number you would like instead of immediately setting the number of repititions to the final number immediately. If you find that compile times are being significantly impacted it might be useful to avoid large functions. As an example, the following code takes an extremely large amount of time to compile and might simply error out at some point:

repeated!(
    %%s prelude
    match x {
    prelude s%%
    for j in [0;255] {
        %%j%% => {
            repeated!(for i in [0;%%j%%] { println!("%%i%%"); });
        }
    }
    %%e postlude
        _ => {
            println!("No match was found!!");
        }
    }
    postlude e%%);

The above code creates a match statement that has more than 32,000 println! statements in it. Having all of this in a single function can slow down the compiler significantly. Instead we can refactor the code above to an example we have in the tests:

repeated!(
    for j in [0;255] {
        fn repeat_%%j%%() {
            repeated!(for i in [0;%%j%%] { println!("%%i%%"); });
        }
    }
);

#[test]
fn large_expansions_are_still_performant() {
    let x = 128;
    repeated!(
        %%s prelude
        match x {
        prelude s%%
        for j in [0;255] {
            %%j%% => {
                repeat_%%j%%();
            }
        }
        %%e postlude
            _ => {
                println!("No match was found!!");
            }
        }
        postlude e%%);
}

We still have a nested repeat statement but this time we are defining separate functions that execute the println!'s. The repeated match branches now no longer have hundreds of lines of code within them but instead only contain a simple call to one of the functions we've defined. This code compiles much faster than the previous example.

Contribution guidelines

Tests and a warning/error free build.

I'd like to improve error handling in the case you have a prelude or postlude defined to match the error handling you get without them.

Another nice to have would be to support iteration over something other than integers.

Dependencies

~3.5–4.5MB
~90K SLoC