Minecraft Datapack: Research on How to Implement Function Calls
May 29, 2026Exploration to find a proper way to implement function calls inside Minecraft datapack.
This article is an exploration to find a proper way to implement function calls, which will be used when converting high-level programming language code into datapack functions, so the method I discuss here is not for normal use like in normal datapack development.
In C language, when a function is called, there will be a stack frame pushed into the call stack, so the calling can just stacked up and every function calls won't bother others. We should do similar things inside Minecraft datapack.
Stack Frame
One difference is that, in datapack, the function will automatically return to its caller, which means we don't need to record the returning address. The stack frame is still needed. To be more concise, we need to put these information into the stack frame:
- parameters
- local variables
Return value is neccessary too, because if we want to tackle multiple types of return values, the original function return machanism is not enough. We will put return value in storage path return.
The Storage
We should use a storage to store all the stack frames and return value. Say we create a storage called minecraft:s. We hope its format is like this:
stack[index].parameters is where we deliver the parameters the function we're calling needs. stack[index].local is where we store local variables of the function.
Every time before a function call, we push an empty stack frame into the stack, then put variables into the frame as parameters for this function call. We will just use integers from zero as the keys name. Since we always know how many parameters a function will need during compilation time, it's okay that we just put those parameters to constant keys instead of pushing them into an array, another reason is that the array can't support varying types of elements.
Local Variables
Note that we store all the variables, including scoreboard variables and storage variables, inside stack[index].local. All the variables will take a place, with keys name starting from "0", no matter if they are storage variables or scoreboard variables.
Storage variables can be there all the time, because storage variables are stored in storage naturally and we don't need to store them somewhere else. However, scoreboard variables are different. Although scoreboard variables are stored in stack[index].local too, we will load them into scoreboard when we need to do operations between them. That means we should put them back to storage before any function calls to protect them, or they might be overrided under recursive calls.
We should protect local variables before creating callee's stack frame and preparing parameters, because local variables are stored inside current function's stack frame, not the function to be called. So we will be able to visit current function's stack frame using stack[-1]. Say we have a scoreboard variable(player name var, objective name temp) to protect. It can be achieved by doing this:
Actually it's not a must to protect local variables before creating callee's stack frame, since we can still do protection after the insertion of new stack frame by using stack[-2]. However, I just think it is more natural to do the protection step before insertion. Any orders are ok.
Parameters
Caller is responsible to put all the parameters it wants to deliver into callee's stack frame. That's why it's caller to create callee's stack frame. Before a function call, caller should append an empty stack frame using this command:
Now the storage stack[-1] is this new empty stack frame. We can directly put all parameters into it. Like if we want to deliver three parameters(text: string, opacity: float, duration: int). No matter what the type a parameter is, we put them into stack[-1].parameters. Here we can just arrange them as the original order, with keys name "0", "1" and "2".
Note that we only use set value xxx here, but you can get the value using whatever methods you like. The final goal is to put the desired values into correct position of stack[-1].parameters. Like if your duration is calculated from some scoreboard operations and the calculated value is inside a player named var with objective named temp. You should copy that value into the corresponding storage path:
So there are a lot of methods to prepare parameters. You just need to make sure that stack[-1].parameters will have all the neccessary values before you call the function.
Full Steps
Here I use natural language to describe all the steps of calling a function: