A real parser
parens

Parentheses. 2*(3+4) should be 14. Whatever sits inside (...) evaluates to one number, and the outer expression treats that number like any other operand.

To support this we split eval into two helpers:

- expression() -- the loop you already have, but reads operands by calling numberOrParens() and stops on ).
- numberOrParens() -- reads one operand: a number, or (, recurse into expression(), then expect ).

eval() becomes a thin wrapper that sets up state and calls expression().

    check("(2+3)", 5);
    check("2*(3+4)", 14);  // without parens: 2*3+4 = 10
    check("8-(3+4)", 1);   // without parens: 8-3+4 = 9
    check("((1+2)*(3+4))", 21);
fn numberOrParens() i64 {
    if (cur() == '(') {
        pos += 1; skip();
        const val: i64 = expression();
        if (cur() == ')') { pos += 1; skip(); }
        return val;
    }
    return number();
}

fn expression() i64 {
    var val: i64 = numberOrParens();
    while (cur() != 0 and cur() != ')') {
        const op: u8 = cur();
        pos += 1; skip();
        const right: i64 = numberOrParens();
        val = switch (op) {
            '+' => val + right,
            '-' => val - right,
            '*' => val * right,
            '/' => @divTrunc(val, right),
            else => val,
        };
    }
    return val;
}

fn eval(input: []const u8) i64 {
    source = input;
    pos = 0;
    skip();
    return expression();
}

Notice that 2+3*4 still gives 20 here -- parens don't fix precedence on their own. But you can write 2+(3*4) to get 14. The escape hatch.