A real parser
terms-and-factors

The parser is at 2+3*4. It just consumed the 2 and the +. It's sitting on the 3. Why can't it just add 2 + 3 right now?

Because we're in a sum, and the right operand of a sum isn't a single value -- it's a term. A term is one or more factors multiplied (or divided) together. A factor is a number, or something inside parentheses (the numberOrParens we already wrote). Even a lone 3 is a term -- a term made of one factor.

So when the expression() reads its right operand, it should ask for a term, not just a factor. A term function reads factors and grinds through any *// chain it finds, then hands back one number. The + only happens after that.

The structure we're heading toward:

- expression -- loops on +/-, asks for terms between them.
- term -- loops on *//, asks for factors between them.
- factor -- a number, or a parenthesized expression. (This is what we've been calling numberOrParens -- now rename it to factor. The grammar vocabulary clicks.)

For this step:
1. Rename numberOrParens() to factor(). Update the call sites in expression().
2. Introduce term() as a passthrough -- it just returns factor(), no loop yet.
3. Have expression() call term() instead of factor() in its operand reads.

fn factor() i64 {
    if (cur() == '(') {
        pos += 1; skip();
        const val: i64 = expression();
        if (cur() == ')') { pos += 1; skip(); }
        return val;
    }
    return number();
}

fn term() i64 {
    return factor();
}

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

The bug isn't fixed yet. Stare at this for one minute and guess what you will put in term() in the next page.