A real parser
term-multiplies

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.