Temporary chapter name for all chapters
skip-whitespace

Skip whitespace. Add two helpers: isSpace tells us whether a byte is whitespace, and skipSpaces advances past a run of them.

Treat any of these four bytes as whitespace: space ' ', tab '\t', newline '\n', carriage return '\r'.

Strategy: the cursor should always sit on a non-whitespace character when we're about to read something. Call skipSpaces(source, &pos) once at the start of eval, and after every consume so the next reader doesn't have to think about spaces.

testCase("  3  +  5  ", 8);
testCase("100 + 23", 123);
fn isSpace(c: u8) bool {
    return c == ' ' or c == '\n' or c == '\t' or c == '\r';
}

fn skipSpaces(
    source: []const u8,
    pos: *usize,
) void {
    var current_char: u8 = curChar(source, pos.*);
    while (isSpace(current_char)) {
        pos.* += 1;
        current_char = curChar(source, pos.*);
    }
}

curChar handles end-of-input -- past source.len it returns 0, and isSpace(0) is false, so the loop stops on its own even if the input ends with whitespace.

Three places to add skipSpaces(source, &pos);:

1. At the start of eval, before parsing begins.
2. At the end of readNumber, before returning.
3. After the pos += 1 that consumes the operator character. Easy to forget -- the operator is just one byte, so there's no readOperator helper that would skip for us.

Updated readNumber:

fn readNumber(
    source: []const u8,
    pos: *usize,
) i64 {
    var value: i64 = 0;
    var current_char: u8 = curChar(source, pos.*);
    while (isDigit(current_char)) {
        value = value * 10 + digitValue(current_char);
        pos.* += 1;
        current_char = curChar(source, pos.*);
    }
    skipSpaces(source, pos);
    return value;
}

Updated eval:

fn eval(source: []const u8) i64 {
    var pos: usize = 0;
    skipSpaces(source, &pos);

    var value: i64 = readNumber(source, &pos);

    var current_char: u8 = curChar(source, pos);
    while (current_char != 0) {
        const op: u8 = current_char;
        pos += 1;
        skipSpaces(source, &pos);
        const right: i64 = readNumber(source, &pos);

        value = switch (op) {
            '+' => value + right,
            '-' => value - right,
            '*' => value * right,
            '/' => @divTrunc(value, right),
            else => value,
        };
        current_char = curChar(source, pos);
    }

    return value;
}