const std = @import("std"); const chunk = @import("chunk.zig"); const value = @import("value.zig"); const values = value; const compiler = @import("compiler.zig"); const Chunk = chunk.Chunk; const Value = value.Value; const Compiler = compiler.Compiler; pub const StdOut = *std.io.OutStream(std.fs.File.WriteError); pub const InterpretResult = error{ Ok, CompileError, RuntimeError, }; fn isFalsey(val: value.Value) bool { return val.vtype == .Nil or (val.vtype == .Bool and !val.as.Bool); } pub const VM = struct { chk: *Chunk = undefined, src: []const u8, ip: usize = 0, stack: []Value, stackTop: usize = 0, stdout: StdOut, debug_flag: bool, allocator: *std.mem.Allocator, fn resetStack(self: *VM) void { self.stackTop = 0; } pub fn init( allocator: *std.mem.Allocator, stdout: StdOut, source: []const u8, debug_flag: bool, ) !VM { var self = VM{ .src = source, .stack = try allocator.alloc(Value, 256), .stdout = stdout, .debug_flag = debug_flag, .allocator = allocator, }; self.resetStack(); return self; } pub fn debug(self: *VM, comptime fmt: []const u8, args: ...) void { if (self.debug_flag) { std.debug.warn(fmt, args); } } fn readByte(self: *VM) u8 { var byte: u8 = self.chk.code[self.ip]; self.ip += 1; return byte; } fn readConst(self: *VM) Value { return self.chk.constants.values[self.readByte()]; } fn readConstLong(self: *VM) Value { const v3 = self.readByte(); const v2 = self.readByte(); const v1 = self.readByte(); const const_idx = (@intCast(u24, v3) << 16) | (@intCast(u24, v2) << 8) | v1; return self.chk.constants.values[const_idx]; } fn debugStack(self: *VM) !void { try self.stdout.print(" "); for (self.stack) |val, idx| { if (idx >= self.stackTop) break; try self.stdout.print("[ "); try value.printValue(self.stdout, val); try self.stdout.print(" ]"); } try self.stdout.print("\n"); } /// gets a f64 out of a value on the top of the stack. fn popNum(self: *VM) !f64 { var val: Value = self.pop(); switch (val.vtype) { .Number => return val.as.Number, else => |vtype| blk: { self.runtimeError("Expected number, got {x}", vtype); return InterpretResult.RuntimeError; }, } } fn doAdd(self: *VM) !void { var b = try self.popNum(); var a = try self.popNum(); try self.push(values.NumberVal(a + b)); } fn doSub(self: *VM) !void { var b = try self.popNum(); var a = try self.popNum(); try self.push(values.NumberVal(a - b)); } fn doMul(self: *VM) !void { var b = try self.popNum(); var a = try self.popNum(); try self.push(values.NumberVal(a * b)); } fn doDiv(self: *VM) !void { var b = try self.popNum(); var a = try self.popNum(); try self.push(values.NumberVal(a / b)); } fn runtimeError(self: *VM, comptime fmt: []const u8, args: ...) void { std.debug.warn(fmt, args); std.debug.warn("\n[line {}] in script\n", self.chk.lines[self.ip]); self.resetStack(); } fn run(self: *VM) !void { while (true) { if (self.debug_flag) { try self.debugStack(); _ = try self.chk.disassembleInstruction(self.stdout, self.ip); } var instruction = self.readByte(); switch (instruction) { chunk.OpCode.Constant => blk: { var constant = self.readConst(); try self.push(constant); break :blk; }, chunk.OpCode.ConstantLong => blk: { var constant = self.readConstLong(); try self.push(constant); break :blk; }, chunk.OpCode.Return => blk: { try value.printValue(self.stdout, self.pop()); try self.stdout.print("\n"); return InterpretResult.Ok; }, chunk.OpCode.Nil => try self.push(values.NilVal()), chunk.OpCode.True => try self.push(values.BoolVal(true)), chunk.OpCode.False => try self.push(values.BoolVal(false)), chunk.OpCode.Add => try self.doAdd(), chunk.OpCode.Subtract => try self.doSub(), chunk.OpCode.Multiply => try self.doMul(), chunk.OpCode.Divide => try self.doDiv(), chunk.OpCode.Not => blk: { try self.push(values.BoolVal(isFalsey(self.pop()))); }, chunk.OpCode.Negate => blk: { var val = self.peek(0); if (val.vtype != .Bool) { self.runtimeError("Operand must be a number."); return InterpretResult.RuntimeError; } val = self.pop(); switch (val.as) { .Number => |num| { try self.push(values.NumberVal(-num)); }, else => unreachable, } }, else => blk: { std.debug.warn("Unknown instruction: {x}\n", instruction); return InterpretResult.RuntimeError; }, } } } pub fn interpret(self: *VM) !void { //self.ip = 0; //self.debug("VM start\n"); //var res = try self.run(); //self.debug("VM end\n"); //return res; var chk = try Chunk.init(self.allocator); var cmpr = Compiler.init( self.allocator, &chk, self.stdout, self.src, self.debug_flag, ); if (!try cmpr.compile(&chk)) { return InterpretResult.CompileError; } self.chk = &chk; self.ip = 0; return try self.run(); } pub fn push(self: *VM, val: Value) !void { if (self.stackTop > 0 and self.stackTop - 1 > self.stack.len) { self.stack = try self.allocator.realloc(self.stack, self.stack.len + 1); } self.stack[self.stackTop] = val; self.stackTop += 1; } pub fn pop(self: *VM) Value { self.stackTop -= 1; return self.stack[self.stackTop]; } pub fn peek(self: *VM, distance: usize) Value { return self.stack[self.stackTop - 1 - distance]; } };