Temporary chapter name for all chapters
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 parseExpression 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:

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

For this step:

1. Rename numberOrParens to parseFactor. Update the call sites.
2. Introduce parseTerm as a passthrough -- it just returns parseFactor, no loop yet.
3. Have parseExpression call parseTerm instead of parseFactor in its operand reads.

fn parseFactor(source: [*]const u8, pos: *usize) i64 {
    if (source[pos.*] == '(') {
        pos.* += 1;
        skipSpaces(source, pos);
        const val: i64 = parseExpression(source, pos);
        if (source[pos.*] == ')') {
            pos.* += 1;
            skipSpaces(source, pos);
        }
        return val;
    }
    return readNumber(source, pos);
}

fn parseTerm(source: [*]const u8, pos: *usize) i64 {
    return parseFactor(source, pos);
}

fn parseExpression(source: [*]const u8, pos: *usize) i64 {
    var val: i64 = parseTerm(source, pos);
    while (source[pos.*] != 0 and source[pos.*] != ')') {
        const op: u8 = source[pos.*];
        pos.* += 1;
        skipSpaces(source, pos);
        const right: i64 = parseTerm(source, pos);
        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 parseTerm on the next page.