Temporary chapter name for all chapters
if-else

Add if/else. When we see the word if, parse the condition in (), then execute or skip the { } block.

Two helpers:

- skipBlock -- jump past { ... } without executing, counting brace depth. Only needs source and pos.
- executeBlock -- execute statements inside { ... }. Needs the full parameter parade since the statements inside might touch variables.

Then add an if branch to executeStatement. From now on we'll stop spelling out the five-parameter signature in prose -- assume every parser/executor function takes source: [*]const u8, pos: *usize, names: *[256][]const u8, values: *[256]i64, count: *usize unless said otherwise.

    testCase("if (5 > 3) { 10 } else { 20 }", 10, &varNames, &varValues, &varCount);
    testCase("if (2 > 7) { 10 } else { 20 }", 20, &varNames, &varValues, &varCount);
    testCase("var x: i64 = 10; if (x > 5) { x = 99; } x", 99, &varNames, &varValues, &varCount);
fn skipBlock(source: [*]const u8, pos: *usize) void {
    if (source[pos.*] == '{') {
        pos.* += 1;
        skipSpaces(source, pos);
    }
    var depth: i32 = 1;
    while (depth > 0 and source[pos.*] != 0) {
        if (source[pos.*] == '{') depth += 1;
        if (source[pos.*] == '}') depth -= 1;
        if (depth > 0) pos.* += 1;
    }
    if (source[pos.*] == '}') {
        pos.* += 1;
        skipSpaces(source, pos);
    }
}

fn executeBlock(
    source: [*]const u8,
    pos: *usize,
    names: *[256][]const u8,
    values: *[256]i64,
    count: *usize,
) i64 {
    if (source[pos.*] == '{') {
        pos.* += 1;
        skipSpaces(source, pos);
    }
    var last: i64 = 0;
    while (source[pos.*] != '}' and source[pos.*] != 0) {
        last = executeStatement(source, pos, names, values, count);
    }
    if (source[pos.*] == '}') {
        pos.* += 1;
        skipSpaces(source, pos);
    }
    return last;
}

Inside executeStatement, right after the var/const handler, add:

        if (stringsEqual(firstWord, "if")) {
            if (source[pos.*] == '(') {
                pos.* += 1;
                skipSpaces(source, pos);
            }
            const cond = parseExpression(source, pos, names, values, count);
            if (source[pos.*] == ')') {
                pos.* += 1;
                skipSpaces(source, pos);
            }
            if (cond != 0) {
                const val = executeBlock(source, pos, names, values, count);
                if (isAlpha(source[pos.*])) {
                    const savedPos2 = pos.*;
                    const nextWord = readIdentifier(source, pos);
                    if (stringsEqual(nextWord, "else")) {
                        skipBlock(source, pos);
                    } else {
                        pos.* = savedPos2;
                    }
                }
                return val;
            } else {
                skipBlock(source, pos);
                if (isAlpha(source[pos.*])) {
                    const savedPos2 = pos.*;
                    const nextWord = readIdentifier(source, pos);
                    if (stringsEqual(nextWord, "else")) {
                        return executeBlock(source, pos, names, values, count);
                    }
                    pos.* = savedPos2;
                }
                return 0;
            }
        }

The else is optional -- we peek at the next identifier; if it's "else", we use it, otherwise we rewind pos.* and forget we ever looked.