commit 807cfbd512863405f87ce32539e9cd7cf2ca988b
parent 8ccf4e893ff6704a047a0810818ca4f80f0d3552
Author: Szymon Mikulicz <szymon.mikulicz@aptiv.com>
Date: Thu, 1 Aug 2024 12:46:03 +0200
If, else and ternary
Diffstat:
6 files changed, 90 insertions(+), 7 deletions(-)
diff --git a/zlox/src/array.zig b/zlox/src/array.zig
@@ -28,6 +28,12 @@ pub fn Array(comptime T: type, comptime S: type, comptime size: S) type {
return self.data[idx];
}
+ pub fn set(self: *const @This(), idx: S, val: T) !void {
+ if (idx >= self.len)
+ return error.IndexOutOfBounds;
+ self.data[idx] = val;
+ }
+
pub fn last(self: *const @This()) !T {
if (self.len == 0)
return error.IndexOutOfBounds;
diff --git a/zlox/src/chunk.zig b/zlox/src/chunk.zig
@@ -26,6 +26,8 @@ pub const OP = enum(u8) {
SET_GLOBAL,
GET_LOCAL,
SET_LOCAL,
+ JUMP_IF_FALSE,
+ JUMP,
};
pub const Chunk = struct {
diff --git a/zlox/src/compiler.zig b/zlox/src/compiler.zig
@@ -360,15 +360,18 @@ pub fn Compiler(size: comptime_int) type {
fn ternary(self: *Self, _: bool) void {
const operatorType = self.previous.type catch unreachable;
- self.parsePrecedence(getRule(operatorType).precedence.inc());
- // emit bytecode
+ const thenJump = self.emitJump(OP.JUMP_IF_FALSE);
+ self.emitOP(OP.POP);
+ self.parsePrecedence(getRule(operatorType).precedence.inc());
+ const elseJump = self.emitJump(OP.JUMP);
+ self.patchJump(thenJump);
self.consume(Token.COLON, "Expected ':' in ternary expression.");
+ self.emitOP(OP.POP);
self.parsePrecedence(getRule(operatorType).precedence.inc());
-
- // emit bytecode
+ self.patchJump(elseJump);
}
fn declaration(self: *Self) void {
@@ -490,6 +493,8 @@ pub fn Compiler(size: comptime_int) type {
fn statement(self: *Self) void {
if (self.match(Token.PRINT)) {
self.printStatement();
+ } else if (self.match(Token.IF)) {
+ self.ifStatement();
} else if (self.match(Token.LEFT_BRACE)) {
self.beginScope();
self.block();
@@ -499,6 +504,44 @@ pub fn Compiler(size: comptime_int) type {
}
}
+ fn ifStatement(self: *Self) void {
+ self.consume(Token.LEFT_PAREN, "Expect '(' after 'if'.");
+ self.expression();
+ self.consume(Token.RIGHT_PAREN, "Expect ')' after condition");
+
+ const thenJump = self.emitJump(OP.JUMP_IF_FALSE);
+ self.emitOP(OP.POP);
+ self.statement();
+ const elseJump = self.emitJump(OP.JUMP);
+ self.patchJump(thenJump);
+ self.emitOP(OP.POP);
+ if (self.match(Token.ELSE)) self.statement();
+ self.patchJump(elseJump);
+ }
+
+ fn emitJump(self: *Self, instruction: OP) usize {
+ self.emitOP(instruction);
+ self.emitByte(0xff);
+ self.emitByte(0xff);
+ return self.currentChunk().code.len - 2;
+ }
+
+ fn patchJump(self: *Self, offset: usize) void {
+ const jump = self.currentChunk().code.len - offset - 2;
+ if (jump > std.math.maxInt(u16)) {
+ self.errorAtPrevious("Jump too large");
+ return;
+ }
+
+ self.currentChunk().code.set(offset, @intCast((jump >> 8) & 0xff)) catch {
+ self.errorAtPrevious("Invalid jump offset");
+ };
+ self.currentChunk().code.set(offset + 1, @intCast(jump & 0xff)) catch {
+ self.errorAtPrevious("Invalid jump offset");
+ };
+
+ }
+
fn block(self: *Self) void {
while(!self.check(Token.RIGHT_BRACE) and !self.check(Token.EOF)) {
self.declaration();
diff --git a/zlox/src/debug.zig b/zlox/src/debug.zig
@@ -44,6 +44,8 @@ pub fn disassembleInstruction(ch: chunk.Chunk, offset: usize) !usize {
@intFromEnum(OP.POP) => simpleInstruction("OP_POP", offset),
@intFromEnum(OP.GET_LOCAL) => try byteInstruction("OP_GET_LOCAL", ch, offset),
@intFromEnum(OP.SET_LOCAL) => try byteInstruction("OP_SET_LOCAL", ch, offset),
+ @intFromEnum(OP.JUMP_IF_FALSE) => try jumpInstruction("OP_JUMP_IF_FALSE", 1, ch, offset),
+ @intFromEnum(OP.JUMP) => try jumpInstruction("OP_JUMP", 1, ch, offset),
else => blk: {
print("Unknown opcode {}\n", .{try ch.code.get(offset)});
break :blk offset + 1;
@@ -68,3 +70,12 @@ fn byteInstruction(name: []const u8, ch: chunk.Chunk, offset: usize) !usize {
print("{s:<16} {d:4}\n", .{name, try ch.code.get(offset+1)});
return offset + 2;
}
+
+fn jumpInstruction(name: []const u8, sign: u1, ch: chunk.Chunk, offset: usize) !usize {
+ const msb: u16 = try ch.code.get(offset + 1);
+ const lsb: u16 = try ch.code.get(offset + 2);
+ const jump = (msb << 8) | lsb;
+
+ print("{s:<16} {d:4} -> {d}\n", .{name, offset, offset + 3 + sign * jump});
+ return offset + 3;
+}
diff --git a/zlox/src/main.zig b/zlox/src/main.zig
@@ -47,7 +47,7 @@ pub fn repl(allocator: std.mem.Allocator, dbg: bool) anyerror!void {
while (Linenoise.linenoise("lox> ")) |line| {
defer Linenoise.linenoiseFree(line);
VM.interpret(std.mem.span(line), dbg) catch |err| {
- std.debug.print("Error: {}\n", .{err});
+ std.debug.print("\nError: {}\n", .{err});
};
_ = Linenoise.linenoiseHistoryAdd(line);
}
diff --git a/zlox/src/vm.zig b/zlox/src/vm.zig
@@ -28,6 +28,8 @@ pub const VM = struct {
var chunk = try compiler.Compiler(stackSize).compile(source, &self.objects, self.allocator);
defer chunk.deinit();
+ //try debug.disassembleChunk(chunk, "Main");
+
try Interpreter(stackSize).run(self, &chunk, dbg);
}
@@ -45,14 +47,24 @@ pub const VM = struct {
try self.execute(dbg);
}
+ fn ip_advance(self: *@This(), adv: usize) void {
+ self.ip += adv;
+ }
+
fn read_byte(self: *@This()) u8 {
const out: u8 = self.ip[0];
- self.ip += 1;
+ self.ip_advance(1);
return out;
}
+ fn read_short(self: *@This()) u16 {
+ const msb: u16 = self.read_byte();
+ const lsb: u16 = self.read_byte();
+ return (msb << 8) | lsb;
+ }
+
fn read_constant(self: *@This()) Value {
- return self.chunk.constants.data[self.read_byte()];
+ return self.chunk.constants.get(self.read_byte()) catch unreachable;
}
fn read_string(self: *@This()) *const Obj.String {
@@ -124,6 +136,15 @@ pub const VM = struct {
try self.binary_op(Value.number, Value.number, Callback.add);
}
},
+ @intFromEnum(OP.JUMP_IF_FALSE) => {
+ const offset = self.read_short();
+ if (!self.peek(0).isTruthy()) {
+ self.ip_advance(offset);
+ }
+ },
+ @intFromEnum(OP.JUMP) => {
+ self.ip_advance(self.read_short());
+ },
@intFromEnum(OP.GET_LOCAL) => {
self.push(self.stack[self.read_byte()]);
},