const std = @import("std"); const value = @import("value.zig"); const Allocator = std.mem.Allocator; // hack. ugly hack. zig has compiler crash. const AllOpcodes = struct { Return: u8 = 0, Constant: u8 = 1, ConstantLong: u8 = 2, Add: u8 = 3, Subtract: u8 = 4, Multiply: u8 = 5, Divide: u8 = 6, Negate: u8 = 7, // basic type op codes Nil: u8 = 8, True: u8 = 9, False: u8 = 10, Not: u8 = 11, // comparison op codes! Equal: u8 = 12, Greater: u8 = 13, Less: u8 = 14, Print: u8 = 15, Pop: u8 = 16, DefineGlobal: u8 = 17, DefineGlobalLong: u8 = 18, GetGlobal: u8 = 19, GetGlobalLong: u8 = 20, SetGlobal: u8 = 21, SetGlobalLong: u8 = 22, GetLocal: u8 = 23, SetLocal: u8 = 24, }; pub const OpCode = AllOpcodes{}; fn simpleInstruction( stdout: var, comptime name: []const u8, index: usize, ) !usize { try stdout.print("{}\n", name); return index + 1; } fn constantInstruction( stdout: var, comptime name: []const u8, chunk: *Chunk, index: usize, ) !usize { // get the constant's index in constants slice var idx = chunk.code[index + 1]; try stdout.print("\t{}\t{} '", name, idx); try value.printValue(stdout, chunk.constants.values[idx]); try stdout.print("'\n"); return index + 2; } fn constantLongInstruction( stdout: var, comptime name: []const u8, chunk: *Chunk, offset: usize, ) !usize { // constantLong uses three u8's that encode a u24 as the // contants' index. var v3: u8 = chunk.code[offset + 1]; var v2: u8 = chunk.code[offset + 2]; var v1: u8 = chunk.code[offset + 3]; var idx: u24 = (@intCast(u24, v3) << 16) | (@intCast(u24, v2) << 8) | v1; try stdout.print("\t{}\t{} '", name, idx); try value.printValue(stdout, chunk.constants.values[idx]); try stdout.print("'\n"); return offset + 4; } fn byteInstruction( stdout: var, name: []const u8, chunk: *Chunk, index: usize, ) !usize { var slot: u8 = chunk.code[index + 1]; try stdout.print("{} {}", name, slot); return index + 2; } pub const ConstantIndexTag = enum { Small, Long, }; pub const ConstantIndex = union(ConstantIndexTag) { Small: u8, Long: [3]u8, }; pub const Chunk = struct { count: usize, lines: []usize, code: []u8, allocator: *Allocator, constants: value.ValueList, pub fn init(allocator: *Allocator) !Chunk { return Chunk{ .count = 0, .allocator = allocator, .code = try allocator.alloc(u8, 0), .lines = try allocator.alloc(usize, 0), .constants = try value.ValueList.init(allocator), }; } pub fn write(self: *Chunk, byte: u8, line: usize) !void { if (self.code.len < self.count + 1) { self.code = try self.allocator.realloc( self.code, self.count + 1, ); self.lines = try self.allocator.realloc( self.lines, self.count + 1, ); } self.code[self.count] = byte; self.lines[self.count] = line; self.count += 1; } pub fn addConstant(self: *Chunk, val: value.Value) !u8 { try self.constants.write(val); return self.constants.count - 1; } pub fn writeConstantRaw( self: *Chunk, val: value.Value, line: usize, ) !ConstantIndex { try self.constants.write(val); var constant_idx = self.constants.count - 1; if (constant_idx < 256) { var idx_small = @intCast(u8, constant_idx); return ConstantIndex{ .Small = idx_small }; } else { var idx_u24: u24 = @intCast(u24, constant_idx); const mask = @intCast(u24, 0xff); const v1: u8 = @intCast(u8, idx_u24 & mask); const v2: u8 = @intCast(u8, (idx_u24 >> 8) & mask); const v3: u8 = @intCast(u8, (idx_u24 >> 16) & mask); return ConstantIndex{ .Long = [_]u8{ v3, v2, v1 } }; } } pub fn writeConstant( self: *Chunk, val: value.Value, line: usize, ) !ConstantIndex { var idx = try self.writeConstantRaw(val, line); switch (idx) { .Small => |idx_small| blk: { try self.write(OpCode.Constant, line); try self.write(idx_small, line); break :blk; }, .Long => |long_u8| blk: { try self.write(OpCode.ConstantLong, line); try self.write(long_u8[0], line); try self.write(long_u8[1], line); try self.write(long_u8[2], line); }, else => unreachable, } return idx; } pub fn disassembleInstruction( self: *Chunk, stdout: var, index: usize, ) !usize { try stdout.print("{} ", index); if (index > 0 and self.lines[index] == self.lines[index - 1]) { try stdout.print(" | "); } else { try stdout.print("{} ", self.lines[index]); } var instruction = self.code[index]; if (instruction == OpCode.Return) { return try simpleInstruction(stdout, "OP_RETURN", index); } else if (instruction == OpCode.Constant) { return try constantInstruction(stdout, "OP_CONSTANT", self, index); } else if (instruction == OpCode.ConstantLong) { return try constantLongInstruction( stdout, "OP_CONSTANT_LONG", self, index, ); } else if (instruction == OpCode.Negate) { return try simpleInstruction(stdout, "OP_NEGATE", index); } else if (instruction == OpCode.Add) { return try simpleInstruction(stdout, "OP_ADD", index); } else if (instruction == OpCode.Subtract) { return try simpleInstruction(stdout, "OP_SUBTRACT", index); } else if (instruction == OpCode.Multiply) { return try simpleInstruction(stdout, "OP_MULTIPLY", index); } else if (instruction == OpCode.Divide) { return try simpleInstruction(stdout, "OP_DIVIDE", index); } else if (instruction == OpCode.Nil) { return try simpleInstruction(stdout, "OP_NIL", index); } else if (instruction == OpCode.True) { return try simpleInstruction(stdout, "OP_TRUE", index); } else if (instruction == OpCode.False) { return try simpleInstruction(stdout, "OP_FALSE", index); } else if (instruction == OpCode.Not) { return try simpleInstruction(stdout, "OP_NOT", index); } else if (instruction == OpCode.Equal) { return try simpleInstruction(stdout, "OP_EQUAL", index); } else if (instruction == OpCode.Greater) { return try simpleInstruction(stdout, "OP_GREATER", index); } else if (instruction == OpCode.Less) { return try simpleInstruction(stdout, "OP_LESS", index); } else if (instruction == OpCode.Print) { 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 constantInstruction(stdout, "OP_DEFGLOBAL", self, index); } else if (instruction == OpCode.DefineGlobalLong) { return try constantLongInstruction(stdout, "OP_DEFGLOBAL_LONG", self, index); } else if (instruction == OpCode.GetGlobal) { return try constantInstruction(stdout, "OP_GETGLOBAL", self, index); } else if (instruction == OpCode.GetGlobalLong) { return try constantLongInstruction(stdout, "OP_GETGLOBAL_LONG", self, index); } else if (instruction == OpCode.SetGlobal) { return try constantInstruction(stdout, "OP_SETGLOBAL", self, index); } else if (instruction == OpCode.SetGlobalLong) { return try constantLongInstruction(stdout, "OP_SETGLOBAL_LONG", self, index); } else if (instruction == OpCode.GetLocal) { return try byteInstruction(stdout, "OP_GETLOCAL", self, index); } else if (instruction == OpCode.SetLocal) { return try byteInstruction(stdout, "OP_GETLOCAL", self, index); } else { try stdout.print("Unknown opcode: {}\n", instruction); return index + 1; } } pub fn disassemble(self: *Chunk, stdout: var, name: []const u8) !void { try stdout.print("== {} ==\n", name); var i: usize = 0; while (i < self.count) { i = try self.disassembleInstruction(stdout, i); } } };