Skip to content

Macro callbacks: implement a repeat loop ​

The previous page generated integer addition. This page inserts a user lambda into a generated loop to implement repeat(times, body).

Specify the source behavior ​

Continue with the prepared macro library. Inside main, write:

mcfpp
repeat(3) { index ->
    print(index)
}
print("Finished")

The intended output is 0, 1, 2, and Finished. times sets the iteration count. body supplies the user callback. index starts at zero.

Complete Java implementation ​

example/macros/RepeatMacros.java:

java
package example.macros;

import java.util.List;
import top.mcfpp.annotations.MNIMacro;
import top.mcfpp.annotations.MacroParam;
import top.mcfpp.mni.api.*;

public final class RepeatMacros {
    @MNIMacro(params = {
        @MacroParam(name = "times", type = "int"),
        @MacroParam(name = "body", type = "(int)->void", mode = ParamMode.INLINE)
    }, returnType = "void")
    public static Block repeat(
            MacroContext context, Value<IntT> times, InlineFn<VoidT> body) {
        return context.quote().block(code -> {
            Local<IntT> index = code.local(
                "index", context.types().intType(), code.intLiteral(0));
            Local<IntT> limit = code.local(
                "limit", context.types().intType(), times.expr());
            code.whileLoop(
                condition -> condition.lt(index.load(), limit.load()),
                loop -> {
                    loop.inlineVoid(body, List.of(InlineArgument.value(index.load())));
                    loop.assign(index, loop.add(index.load(), loop.intLiteral(1)));
                });
        });
    }
}

Read the inputs before the loop ​

Parameter or variablePurpose
times: Value<IntT>Runtime repetition count
body: InlineFn<VoidT>A user callback taking one integer and returning no result
indexGenerated iteration index, initially 0
limitThe upper bound saved for this call

The annotation's "(int)->void" corresponds to source { index -> ... }. INLINE tells the compiler to insert that user code into generated code.

Read whileLoop and inlineVoid ​

whileLoop accepts two Java code builders. The first constructs index < limit. The second constructs the loop body.

The body first calls inlineVoid(body, ...) to insert the user callback. InlineArgument.value(index.load()) supplies the current index as its integer argument. assign then generates the increment.

The generated behavior corresponds to:

text
index = 0
limit = times
while (index < limit) {
    Run the user callback with index
    index = index + 1
}

Java constructs this code during compilation. The loop generated by whileLoop executes in the game. Thus, times can be a runtime integer.

A return inside the callback ​

mcfpp
repeat(3) { index ->
    if (index == 1) {
        return
    }
    print(index)
}
print("Finished")

This prints 0, 2, and Finished:

IndexUser callback behaviorNext loop action
0Print 0Increment the index
1End this invocation with returnIncrement the index
2Print 2Increment, then finish the loop

The macro owns the increment. return ends only the current inserted callback invocation.

A callback with a result ​

The repository example examples/mni/repeat also supplies an accumulating overload:

mcfpp
var total = repeat(3, 0) { sum, index -> sum + index }
print(total)

Its inputs are a repetition count, an initial accumulator, and a callback. The result is 3. The callback signature is (int,int)->int. Java receives an InlineFn<IntT>. inline produces the callback result, which becomes the next accumulator value.

The complete implementation is in examples/mni/repeat/provider/java/example/mnirepeat/JavaRepeatMacro.java. For ordinary lists, use the built-in fold and foreach.