diff --git a/src/chunk.zig b/src/chunk.zig index 289ef33..76d4bb9 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -28,6 +28,9 @@ const AllOpcodes = struct { pub Print: u8 = 15, pub Pop: u8 = 16, + + pub DefineGlobal: u8 = 17, + pub DefineGlobalLong: u8 = 18, }; pub const OpCode = AllOpcodes{}; @@ -78,6 +81,16 @@ fn constantLongInstruction( return offset + 4; } +pub const ConstantIndexTag = enum { + Small, + Long, +}; + +pub const ConstantIndex = union(ConstantIndexTag) { + Small: u8, + Long: [3]u8, +}; + pub const Chunk = struct { count: usize, lines: []usize, @@ -119,13 +132,20 @@ pub const Chunk = struct { return self.constants.count - 1; } - pub fn writeConstant(self: *Chunk, val: value.Value, line: usize) !void { + pub fn writeConstant( + self: *Chunk, + val: value.Value, + line: usize, + ) !ConstantIndex { try self.constants.write(val); var constant_idx = self.constants.count - 1; if (constant_idx < 256) { try self.write(OpCode.Constant, line); - try self.write(@intCast(u8, constant_idx), line); + + var idx_small = @intCast(u8, constant_idx); + try self.write(idx_small, line); + return ConstantIndex{ .Small = idx_small }; } else { var idx_u24: u24 = @intCast(u24, constant_idx); @@ -139,6 +159,7 @@ pub const Chunk = struct { try self.write(v3, line); try self.write(v2, line); try self.write(v1, line); + return ConstantIndex{ .Long = []u8{ v3, v2, v1 } }; } } @@ -196,6 +217,10 @@ pub const Chunk = struct { return try simpleInstruction(stdout, "OP_PRINT", index); } else if (instruction == OpCode.Pop) { return try simpleInstruction(stdout, "OP_POP", index); + } else if (instruction == OpCode.DefineGlobal) { + return try simpleInstruction(stdout, "OP_DEFGLOBAL", index); + } else if (instruction == OpCode.DefineGlobalLong) { + return try simpleInstruction(stdout, "OP_DEFGLOBAL_LONG", index); } else { try stdout.print("Unknown opcode: {}\n", instruction); return index + 1; diff --git a/src/compiler.zig b/src/compiler.zig index 71750da..1b44624 100644 --- a/src/compiler.zig +++ b/src/compiler.zig @@ -215,7 +215,7 @@ pub const Compiler = struct { } fn emitConstant(self: *Compiler, value: Value) !void { - try self.currentChunk().writeConstant( + _ = try self.currentChunk().writeConstant( value, self.parser.previous.line, ); @@ -352,8 +352,54 @@ pub const Compiler = struct { } } + /// Write an identifier constant to the bytecode. + fn identifierConstant( + self: *Compiler, + token: *Token, + ) !chunks.ConstantIndex { + return try self.currentChunk().writeConstant(values.ObjVal(try objects.copyString( + self.vmach, + token.lexeme, + )), token.line); + } + + fn parseVariable(self: *Compiler, msg: []const u8) !chunks.ConstantIndex { + try self.consume(.IDENTIFIER, msg); + return try self.identifierConstant(&self.parser.previous); + } + + fn defineVariable(self: *Compiler, global: chunks.ConstantIndex) !void { + switch (global) { + .Small => |val| try self.emitBytes(chunks.OpCode.DefineGlobal, val), + .Long => |val| blk: { + try self.emitByte(chunks.OpCode.DefineGlobalLong); + try self.emitByte(val[0]); + try self.emitByte(val[1]); + try self.emitByte(val[2]); + }, + else => unreachable, + } + } + + fn varDecl(self: *Compiler) !void { + var global = try self.parseVariable("Expect variable name."); + + if (try self.match(.EQUAL)) { + try self.expression(); + } else { + try self.emitByte(chunks.OpCode.Nil); + } + + try self.consume(.SEMICOLON, "Expect ';' after variable declaration."); + try self.defineVariable(global); + } + fn declaration(self: *Compiler) !void { - try self.statement(); + if (try self.match(.VAR)) { + try self.varDecl(); + } else { + try self.statement(); + } if (self.parser.panicMode) try self.synchronize(); } diff --git a/src/vm.zig b/src/vm.zig index 98211c6..0b6e3fb 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -36,6 +36,8 @@ fn valuesEqual(a: value.Value, b: value.Value) bool { } } +pub const ValueMap = std.AutoHashMap([]const u8, values.Value); + pub const VM = struct { chk: *Chunk = undefined, src: []const u8, @@ -49,6 +51,7 @@ pub const VM = struct { pub allocator: *std.mem.Allocator, objs: ?*objects.Object = null, + globals: ValueMap, fn resetStack(self: *VM) void { self.stackTop = 0; @@ -67,6 +70,8 @@ pub const VM = struct { .stdout = stdout, .debug_flag = debug_flag, .allocator = allocator, + + .globals = ValueMap.init(allocator), }; self.resetStack(); @@ -219,6 +224,11 @@ pub const VM = struct { self.resetStack(); } + fn defGlobal(self: *VM, name: []const u8) !void { + _ = try self.globals.put(name, self.peek(0)); + _ = self.pop(); + } + fn run(self: *VM) !void { while (true) { if (self.debug_flag) { @@ -260,6 +270,18 @@ pub const VM = struct { break :blk; }, + // extracting the name is different depending of the + // op code since one just uses a single byte, the other + // uses three bytes since its a u24. + chunk.OpCode.DefineGlobal => blk: { + try self.defGlobal(self.readConst().as.Object.value.String); + break :blk; + }, + chunk.OpCode.DefineGlobalLong => blk: { + try self.defGlobal(self.readConstLong().as.Object.value.String); + break :blk; + }, + chunk.OpCode.Equal => blk: { var a = self.pop(); var b = self.pop();