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.