Add function calls to parseFactor. When we see name(, it's a call. Evaluate the arguments, then hand off to callFunction which saves the caller's variables, binds parameters, runs the body, and restores everything.
The save/restore needs a stack. Add to Context:
saveNames: [32][256][]const u8 = undefined,
saveValues: [32][256]i64 = undefined,
saveCounts: [32]usize = undefined,
saveDepth: usize = 0,
Thirty-two deep. Fibonacci won't reach that without a lot of recursion -- but easy to raise.
testCase("fn add(a: i64, b: i64) i64 { return a + b; } add(3, 5)", 8, &ctx);
testCase("fn double(x: i64) i64 { return x * 2; } double(21)", 42, &ctx);
Update the name branch of parseFactor to look for ( after the identifier:
if (isAlpha(ctx.source[ctx.pos])) {
const name = readIdentifier(ctx.source, &ctx.pos);
if (ctx.source[ctx.pos] == '(') {
ctx.pos += 1;
skipSpaces(ctx.source, &ctx.pos);
var args: [8]i64 = undefined;
var argCount: usize = 0;
while (ctx.source[ctx.pos] != ')' and ctx.source[ctx.pos] != 0) {
args[argCount] = parseExpression(ctx);
argCount += 1;
if (ctx.source[ctx.pos] == ',') {
ctx.pos += 1;
skipSpaces(ctx.source, &ctx.pos);
}
}
if (ctx.source[ctx.pos] == ')') {
ctx.pos += 1;
skipSpaces(ctx.source, &ctx.pos);
}
return callFunction(ctx, name, &args, argCount);
}
return lookupVariable(ctx, name);
}
callFunction:
fn callFunction(ctx: *Context, name: []const u8, args: *const [8]i64, argCount: usize) i64 {
_ = argCount;
// Find the function
var fi: usize = 0;
while (fi < ctx.fnCount) {
if (stringsEqual(ctx.fnNames[fi], name)) break;
fi += 1;
}
if (fi >= ctx.fnCount) return 0;
// Save caller state
const d = ctx.saveDepth;
ctx.saveCounts[d] = ctx.varCount;
const savedPos = ctx.pos;
var i: usize = 0;
while (i < ctx.varCount) {
ctx.saveNames[d][i] = ctx.varNames[i];
ctx.saveValues[d][i] = ctx.varValues[i];
i += 1;
}
ctx.saveDepth += 1;
// Bind parameters
ctx.varCount = 0;
var p: usize = 0;
while (p < ctx.fnParamCounts[fi]) {
assignVariable(ctx, ctx.fnParams[fi][p], args[p]);
p += 1;
}
// Execute body
ctx.pos = ctx.fnStarts[fi];
ctx.returnFlag = false;
const result = executeBlock(ctx);
// Restore caller state
ctx.saveDepth -= 1;
ctx.pos = savedPos;
ctx.varCount = ctx.saveCounts[d];
var j: usize = 0;
while (j < ctx.varCount) {
ctx.varNames[j] = ctx.saveNames[d][j];
ctx.varValues[j] = ctx.saveValues[d][j];
j += 1;
}
ctx.returnFlag = false;
return result;
}
This is the biggest function in the interpreter. Walk through it with a debugger on the first call to double(21). The caller's variables (none yet, first test) get snapshotted into ctx.saveNames[0], ctx.varCount resets to 0, the parameter x is bound to 21, the body runs, return x * 2; triggers ctx.returnFlag, executeBlock hands back 42, and then we restore the caller's state from the snapshot. Phew.