implementing call frames

also, minor tracing/constants refactoring
This commit is contained in:
Patrick MARIE 2024-08-28 10:11:24 +02:00
parent 0850198784
commit 3a20f5e04a
5 changed files with 97 additions and 58 deletions

View File

@ -46,10 +46,9 @@ const Parser = struct {
scanner: *Scanner, scanner: *Scanner,
had_error: bool, had_error: bool,
panic_mode: bool, panic_mode: bool,
chunk: *Chunk,
vm: *VM, vm: *VM,
fn new(vm: *VM, compiler: *Compiler, scanner: *Scanner, chunk: *Chunk) Parser { fn new(vm: *VM, compiler: *Compiler, scanner: *Scanner) Parser {
return Parser{ return Parser{
.compiler = compiler, .compiler = compiler,
.current = null, .current = null,
@ -57,13 +56,12 @@ const Parser = struct {
.scanner = scanner, .scanner = scanner,
.had_error = false, .had_error = false,
.panic_mode = false, .panic_mode = false,
.chunk = chunk,
.vm = vm, .vm = vm,
}; };
} }
inline fn current_chunk(self: *Parser) *Chunk { inline fn current_chunk(self: *Parser) *Chunk {
return self.chunk; return &self.compiler.function.chunk;
} }
fn advance(self: *Parser) void { fn advance(self: *Parser) void {
@ -137,11 +135,14 @@ const Parser = struct {
try self.emit_byte(@intFromEnum(OpCode.OP_RETURN)); try self.emit_byte(@intFromEnum(OpCode.OP_RETURN));
} }
fn end_parser(self: *Parser) !void { fn end_parser(self: *Parser) !*Obj.Function {
if (!self.had_error and self.vm.has_tracing()) { if (!self.had_error and constants.DEBUG_PRINT_CODE) {
self.current_chunk().dissassemble("code"); self.current_chunk().dissassemble("code");
} }
try self.emit_return(); try self.emit_return();
return self.compiler.function;
} }
fn number(self: *Parser, can_assign: bool) ParsingError!void { fn number(self: *Parser, can_assign: bool) ParsingError!void {
@ -702,17 +703,41 @@ const Parser = struct {
} }
}; };
const FunctionType = enum {
Function,
Script,
};
const Compiler = struct { const Compiler = struct {
function: *Obj.Function,
function_type: FunctionType,
locals: [constants.UINT8_COUNT]Local, locals: [constants.UINT8_COUNT]Local,
local_count: usize, local_count: usize,
scope_depth: usize, scope_depth: usize,
fn new() Compiler { fn new(allocator: std.mem.Allocator, function_type: FunctionType) Compiler {
return Compiler{ const obj_function = Obj.Function.new(allocator);
var compiler = Compiler{
.locals = undefined, .locals = undefined,
.local_count = 0, .local_count = 0,
.scope_depth = 0, .scope_depth = 0,
.function = obj_function,
.function_type = function_type,
}; };
compiler.locals[0].depth = 0;
compiler.locals[0].name = Token{
.token_type = TokenType.EOF,
.start = "",
.length = 0,
.line = 0,
};
compiler.local_count += 1;
return compiler;
} }
}; };
@ -721,13 +746,11 @@ const Local = struct {
depth: ?usize, depth: ?usize,
}; };
pub fn compile(allocator: Allocator, vm: *VM, contents: []const u8, chunk: *Chunk) !bool { pub fn compile(allocator: Allocator, vm: *VM, contents: []const u8) !?*Obj.Function {
_ = allocator; var compiler = Compiler.new(allocator, FunctionType.Script);
var compiler = Compiler.new();
var scanner = Scanner.init(contents); var scanner = Scanner.init(contents);
var parser = Parser.new(vm, &compiler, &scanner, chunk); var parser = Parser.new(vm, &compiler, &scanner);
parser.advance(); parser.advance();
@ -735,7 +758,11 @@ pub fn compile(allocator: Allocator, vm: *VM, contents: []const u8, chunk: *Chun
try parser.declaration(); try parser.declaration();
} }
try parser.end_parser(); const function = try parser.end_parser();
return !parser.had_error; if (!parser.had_error) {
return function;
} else {
return null;
}
} }

View File

@ -7,4 +7,10 @@ pub const UINT16_MAX = std.math.maxInt(u16);
pub const UINT8_COUNT = UINT8_MAX + 1; pub const UINT8_COUNT = UINT8_MAX + 1;
pub const STACK_MAX = 256; pub const FRAMES_MAX = 64;
pub const STACK_MAX = (FRAMES_MAX * UINT8_MAX);
pub const DEBUG_PRINT_CODE = true;
pub const DEBUG_TRACE_EXECUTION = true;
pub const DEBUG_PRINT_INTERNAL_STRINGS = false;
pub const DEBUG_PRINT_GLOBALS = false;

View File

@ -2,6 +2,8 @@ const std = @import("std");
const debug = std.debug; const debug = std.debug;
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const constants = @import("./constant.zig");
const Chunk = @import("./chunk.zig").Chunk; const Chunk = @import("./chunk.zig").Chunk;
const OpCode = @import("./opcode.zig").OpCode; const OpCode = @import("./opcode.zig").OpCode;
const VM = @import("./vm.zig").VM; const VM = @import("./vm.zig").VM;
@ -17,7 +19,7 @@ pub fn repl(allocator: Allocator, vm: *VM) !void {
const stdout = std.io.getStdOut().writer(); const stdout = std.io.getStdOut().writer();
while (true) { while (true) {
if (vm.has_tracing()) { if (constants.DEBUG_PRINT_GLOBALS) {
vm.globals.dump(); vm.globals.dump();
} }
@ -63,12 +65,6 @@ pub fn main() !void {
var vm = VM.new(allocator); var vm = VM.new(allocator);
defer vm.free(); defer vm.free();
var env = try std.process.getEnvMap(allocator);
defer env.deinit();
if (env.get("TRACE") != null) {
vm.set_trace(true);
}
if (args.len == 1) { if (args.len == 1) {
try repl(allocator, &vm); try repl(allocator, &vm);
} else if (args.len == 2) { } else if (args.len == 2) {

View File

@ -45,8 +45,8 @@ pub const Obj = struct {
pub const Function = struct { pub const Function = struct {
obj: Obj, obj: Obj,
arity: usize, arity: usize,
chunk: *Chunk, chunk: Chunk,
name: *Obj.String, name: ?*Obj.String,
pub fn new(allocator: std.mem.Allocator) *Function { pub fn new(allocator: std.mem.Allocator) *Function {
const obj = Obj{ const obj = Obj{
@ -58,6 +58,7 @@ pub const Obj = struct {
function_obj.obj = obj; function_obj.obj = obj;
function_obj.arity = 0; function_obj.arity = 0;
function_obj.chunk = Chunk.new(allocator); function_obj.chunk = Chunk.new(allocator);
function_obj.name = null;
return function_obj; return function_obj;
} }
@ -89,7 +90,11 @@ pub const Obj = struct {
}, },
ObjType.Function => { ObjType.Function => {
const obj = self.as_function(); const obj = self.as_function();
debug.print("<fn {s}>", .{obj.name.chars}); if (obj.name == null) {
debug.print("<script>", .{});
} else {
debug.print("<fn {s}>", .{obj.name.?.chars});
}
}, },
} }
} }

View File

@ -13,8 +13,6 @@ const Table = @import("./table.zig").Table;
const compile = @import("./compile.zig").compile; const compile = @import("./compile.zig").compile;
const compute_hash = @import("./utils.zig").compute_hash; const compute_hash = @import("./utils.zig").compute_hash;
const DEBUG_TRACE_EXECUTION = @import("./main.zig").DEBUG_TRACE_EXECUTION;
const print_value = @import("./values.zig").print_value; const print_value = @import("./values.zig").print_value;
pub const InterpretResult = enum { pub const InterpretResult = enum {
@ -23,10 +21,15 @@ pub const InterpretResult = enum {
RUNTIME_ERROR, RUNTIME_ERROR,
}; };
pub const CallFrame = struct {
function: *Obj.Function,
ip: usize,
// pointer to stack index provided to this frame
slots_idx: usize,
};
pub const VM = struct { pub const VM = struct {
allocator: Allocator, allocator: Allocator,
chunk: ?*Chunk,
ip: ?usize,
stack: [constants.STACK_MAX]Value, stack: [constants.STACK_MAX]Value,
stack_top: usize, stack_top: usize,
// Keeping creating objects in references to destroy objects on cleaning. // Keeping creating objects in references to destroy objects on cleaning.
@ -34,24 +37,24 @@ pub const VM = struct {
references: std.ArrayList(*Obj), references: std.ArrayList(*Obj),
strings: Table, strings: Table,
globals: Table, globals: Table,
tracing: bool, frames: [constants.FRAMES_MAX]CallFrame,
frame_count: usize,
pub fn new(allocator: Allocator) VM { pub fn new(allocator: Allocator) VM {
return VM{ return VM{
.allocator = allocator, .allocator = allocator,
.chunk = null,
.ip = null,
.stack = undefined, .stack = undefined,
.stack_top = 0, .stack_top = 0,
.references = std.ArrayList(*Obj).init(allocator), .references = std.ArrayList(*Obj).init(allocator),
.strings = Table.new(allocator), .strings = Table.new(allocator),
.globals = Table.new(allocator), .globals = Table.new(allocator),
.tracing = false, .frames = undefined,
.frame_count = 0,
}; };
} }
pub fn free(self: *VM) void { pub fn free(self: *VM) void {
if (self.has_tracing()) { if (constants.DEBUG_PRINT_INTERNAL_STRINGS) {
self.strings.dump(); self.strings.dump();
} }
@ -62,35 +65,37 @@ pub const VM = struct {
} }
inline fn current_chunk(self: *VM) *Chunk { inline fn current_chunk(self: *VM) *Chunk {
return self.chunk.?; return &self.frames[self.frame_count - 1].function.chunk;
} }
pub fn set_trace(self: *VM, tracing: bool) void { inline fn current_frame(self: *VM) *CallFrame {
self.tracing = tracing; return &self.frames[self.frame_count - 1];
}
pub fn has_tracing(self: *VM) bool {
return self.tracing;
} }
pub fn interpret(self: *VM, allocator: Allocator, content: []const u8) !InterpretResult { pub fn interpret(self: *VM, allocator: Allocator, content: []const u8) !InterpretResult {
var chunk = Chunk.new(allocator); var chunk = Chunk.new(allocator);
defer chunk.deinit(); defer chunk.deinit();
const res = try compile(allocator, self, content, &chunk); const function = try compile(allocator, self, content);
if (!res) { if (function == null) {
return InterpretResult.COMPILE_ERROR; return InterpretResult.COMPILE_ERROR;
} }
defer function.?.destroy();
self.chunk = &chunk; _ = try self.push(Value.obj_val(&function.?.obj));
self.ip = 0;
const frame = &self.frames[self.frame_count];
self.frame_count += 1;
frame.function = function.?;
frame.ip = 0;
frame.slots_idx = self.stack_top;
return try self.run(); return try self.run();
} }
pub fn run(self: *VM) !InterpretResult { pub fn run(self: *VM) !InterpretResult {
while (true) { while (true) {
if (self.has_tracing()) { if (constants.DEBUG_TRACE_EXECUTION) {
if (self.stack_top > 0) { if (self.stack_top > 0) {
debug.print("{s:32}", .{""}); debug.print("{s:32}", .{""});
for (0..self.stack_top) |item_idx| { for (0..self.stack_top) |item_idx| {
@ -100,7 +105,7 @@ pub const VM = struct {
} }
debug.print("\n", .{}); debug.print("\n", .{});
} }
_ = self.current_chunk().dissassemble_instruction(self.ip.?); _ = self.current_chunk().dissassemble_instruction(self.current_frame().ip);
} }
const instruction = self.read_byte(); const instruction = self.read_byte();
@ -180,25 +185,25 @@ pub const VM = struct {
}, },
@intFromEnum(OpCode.OP_GET_LOCAL) => { @intFromEnum(OpCode.OP_GET_LOCAL) => {
const slot = self.read_byte(); const slot = self.read_byte();
try self.push(self.stack[slot]); try self.push(self.stack[self.current_frame().slots_idx + slot - 1]);
}, },
@intFromEnum(OpCode.OP_SET_LOCAL) => { @intFromEnum(OpCode.OP_SET_LOCAL) => {
const slot = self.read_byte(); const slot = self.read_byte();
self.stack[slot] = self.peek(0); self.stack[self.current_frame().slots_idx + slot - 1] = self.peek(0);
}, },
@intFromEnum(OpCode.OP_JUMP) => { @intFromEnum(OpCode.OP_JUMP) => {
const offset = self.read_short(); const offset = self.read_short();
self.ip.? += offset; self.current_frame().ip += offset;
}, },
@intFromEnum(OpCode.OP_JUMP_IF_FALSE) => { @intFromEnum(OpCode.OP_JUMP_IF_FALSE) => {
const offset = self.read_short(); const offset = self.read_short();
if (self.peek(0).is_falsey()) { if (self.peek(0).is_falsey()) {
self.ip.? += offset; self.current_frame().ip += offset;
} }
}, },
@intFromEnum(OpCode.OP_LOOP) => { @intFromEnum(OpCode.OP_LOOP) => {
const offset = self.read_short(); const offset = self.read_short();
self.ip.? -= offset; self.current_frame().ip -= offset;
}, },
else => { else => {
debug.print("Invalid instruction: {d}\n", .{instruction}); debug.print("Invalid instruction: {d}\n", .{instruction});
@ -213,16 +218,16 @@ pub const VM = struct {
// XXX In the book, we're using a ptr to data directly, to avoid dereferencing to a given offset // XXX In the book, we're using a ptr to data directly, to avoid dereferencing to a given offset
// How to do that in Zig? // How to do that in Zig?
pub fn read_byte(self: *VM) u8 { pub fn read_byte(self: *VM) u8 {
const ret = self.current_chunk().code[self.ip.?]; const ret = self.current_chunk().code[self.current_frame().ip];
self.ip.? += 1; self.current_frame().ip += 1;
return ret; return ret;
} }
pub fn read_short(self: *VM) u16 { pub fn read_short(self: *VM) u16 {
self.ip.? += 2; self.current_frame().ip += 2;
return (@as(u16, self.current_chunk().code[self.ip.? - 2]) << 8) | (@as(u16, self.current_chunk().code[self.ip.? - 1])); return (@as(u16, self.current_chunk().code[self.current_frame().ip - 2]) << 8) | (@as(u16, self.current_chunk().code[self.current_frame().ip - 1]));
} }
pub fn read_constant(self: *VM) Value { pub fn read_constant(self: *VM) Value {
@ -287,7 +292,7 @@ pub const VM = struct {
} }
pub fn runtime_error(self: *VM, err_msg: []const u8) void { pub fn runtime_error(self: *VM, err_msg: []const u8) void {
const instruction = self.ip.?; const instruction = self.current_frame().ip;
const line = self.current_chunk().lines[instruction]; const line = self.current_chunk().lines[instruction];
debug.print("err: {s}\n", .{err_msg}); debug.print("err: {s}\n", .{err_msg});