Now make term() do its job.
Move the * and / handling out of expression() into term(). expression() loops only on +/-. term() loops only on *// and asks factor() for each operand.
When expression() calls term() for its right operand on 2+3*4, term() sees 3, then *4, and returns 12. Then expression() adds: 2+12 = 14.
check("2+3*4", 14);
check("3*4+2", 14);
check("2*3+4*5", 26);
check("(2+3)*4", 20);
check("100 - 55 * 2", -10);
fn term() i64 {
var val: i64 = factor();
while (cur() == '*' or cur() == '/') {
const op: u8 = cur();
pos += 1; skip();
const right: i64 = factor();
val = switch (op) {
'*' => val * right,
'/' => @divTrunc(val, right),
else => val,
};
}
return val;
}
fn expression() i64 {
var val: i64 = term();
while (cur() == '+' or cur() == '-') {
const op: u8 = cur();
pos += 1; skip();
const right: i64 = term();
val = switch (op) {
'+' => val + right,
'-' => val - right,
else => val,
};
}
return val;
}
Three functions, three precedence layers: expression -> term -> factor. Each layer loops on its own operators and asks the next-tighter layer for operands. Operands come back fully resolved. Recursion does the rest.
This is recursive descent. To add another precedence level (say, ^ for power between term and factor), you insert one more function in the chain. Comparison operators come next; they slot in as a layer above expression.