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:
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:
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 variable | Purpose |
|---|---|
times: Value<IntT> | Runtime repetition count |
body: InlineFn<VoidT> | A user callback taking one integer and returning no result |
index | Generated iteration index, initially 0 |
limit | The 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:
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
repeat(3) { index ->
if (index == 1) {
return
}
print(index)
}
print("Finished")This prints 0, 2, and Finished:
| Index | User callback behavior | Next loop action |
|---|---|---|
0 | Print 0 | Increment the index |
1 | End this invocation with return | Increment the index |
2 | Print 2 | Increment, 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:
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.