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,
had_error: bool,
panic_mode: bool,
chunk: *Chunk,
vm: *VM,
fn new(vm: *VM, compiler: *Compiler, scanner: *Scanner, chunk: *Chunk) Parser {
fn new(vm: *VM, compiler: *Compiler, scanner: *Scanner) Parser {
return Parser{
.compiler = compiler,
.current = null,
@ -57,13 +56,12 @@ const Parser = struct {
.scanner = scanner,
.had_error = false,
.panic_mode = false,
.chunk = chunk,
.vm = vm,
};
}
inline fn current_chunk(self: *Parser) *Chunk {
return self.chunk;
return &self.compiler.function.chunk;
}
fn advance(self: *Parser) void {
@ -137,11 +135,14 @@ const Parser = struct {
try self.emit_byte(@intFromEnum(OpCode.OP_RETURN));
}
fn end_parser(self: *Parser) !void {
if (!self.had_error and self.vm.has_tracing()) {
fn end_parser(self: *Parser) !*Obj.Function {
if (!self.had_error and constants.DEBUG_PRINT_CODE) {
self.current_chunk().dissassemble("code");
}
try self.emit_return();
return self.compiler.function;
}
fn number(self: *Parser, can_assign: bool) ParsingError!void {
@ -702,17 +703,41 @@ const Parser = struct {
}
};
const FunctionType = enum {
Function,
Script,
};
const Compiler = struct {
function: *Obj.Function,
function_type: FunctionType,
locals: [constants.UINT8_COUNT]Local,
local_count: usize,
scope_depth: usize,
fn new() Compiler {
return Compiler{
fn new(allocator: std.mem.Allocator, function_type: FunctionType) Compiler {
const obj_function = Obj.Function.new(allocator);
var compiler = Compiler{
.locals = undefined,
.local_count = 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,
};
pub fn compile(allocator: Allocator, vm: *VM, contents: []const u8, chunk: *Chunk) !bool {
_ = allocator;
var compiler = Compiler.new();
pub fn compile(allocator: Allocator, vm: *VM, contents: []const u8) !?*Obj.Function {
var compiler = Compiler.new(allocator, FunctionType.Script);
var scanner = Scanner.init(contents);
var parser = Parser.new(vm, &compiler, &scanner, chunk);
var parser = Parser.new(vm, &compiler, &scanner);
parser.advance();
@ -735,7 +758,11 @@ pub fn compile(allocator: Allocator, vm: *VM, contents: []const u8, chunk: *Chun
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 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 Allocator = std.mem.Allocator;
const constants = @import("./constant.zig");
const Chunk = @import("./chunk.zig").Chunk;
const OpCode = @import("./opcode.zig").OpCode;
const VM = @import("./vm.zig").VM;
@ -17,7 +19,7 @@ pub fn repl(allocator: Allocator, vm: *VM) !void {
const stdout = std.io.getStdOut().writer();
while (true) {
if (vm.has_tracing()) {
if (constants.DEBUG_PRINT_GLOBALS) {
vm.globals.dump();
}
@ -63,12 +65,6 @@ pub fn main() !void {
var vm = VM.new(allocator);
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) {
try repl(allocator, &vm);
} else if (args.len == 2) {

View File

@ -45,8 +45,8 @@ pub const Obj = struct {
pub const Function = struct {
obj: Obj,
arity: usize,
chunk: *Chunk,
name: *Obj.String,
chunk: Chunk,
name: ?*Obj.String,
pub fn new(allocator: std.mem.Allocator) *Function {
const obj = Obj{
@ -58,6 +58,7 @@ pub const Obj = struct {
function_obj.obj = obj;
function_obj.arity = 0;
function_obj.chunk = Chunk.new(allocator);
function_obj.name = null;
return function_obj;
}
@ -89,7 +90,11 @@ pub const Obj = struct {
},
ObjType.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 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;
pub const InterpretResult = enum {
@ -23,10 +21,15 @@ pub const InterpretResult = enum {
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 {
allocator: Allocator,
chunk: ?*Chunk,
ip: ?usize,
stack: [constants.STACK_MAX]Value,
stack_top: usize,
// Keeping creating objects in references to destroy objects on cleaning.
@ -34,24 +37,24 @@ pub const VM = struct {
references: std.ArrayList(*Obj),
strings: Table,
globals: Table,
tracing: bool,
frames: [constants.FRAMES_MAX]CallFrame,
frame_count: usize,
pub fn new(allocator: Allocator) VM {
return VM{
.allocator = allocator,
.chunk = null,
.ip = null,
.stack = undefined,
.stack_top = 0,
.references = std.ArrayList(*Obj).init(allocator),
.strings = Table.new(allocator),
.globals = Table.new(allocator),
.tracing = false,
.frames = undefined,
.frame_count = 0,
};
}
pub fn free(self: *VM) void {
if (self.has_tracing()) {
if (constants.DEBUG_PRINT_INTERNAL_STRINGS) {
self.strings.dump();
}
@ -62,35 +65,37 @@ pub const VM = struct {
}
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 {
self.tracing = tracing;
}
pub fn has_tracing(self: *VM) bool {
return self.tracing;
inline fn current_frame(self: *VM) *CallFrame {
return &self.frames[self.frame_count - 1];
}
pub fn interpret(self: *VM, allocator: Allocator, content: []const u8) !InterpretResult {
var chunk = Chunk.new(allocator);
defer chunk.deinit();
const res = try compile(allocator, self, content, &chunk);
if (!res) {
const function = try compile(allocator, self, content);
if (function == null) {
return InterpretResult.COMPILE_ERROR;
}
defer function.?.destroy();
self.chunk = &chunk;
self.ip = 0;
_ = try self.push(Value.obj_val(&function.?.obj));
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();
}
pub fn run(self: *VM) !InterpretResult {
while (true) {
if (self.has_tracing()) {
if (constants.DEBUG_TRACE_EXECUTION) {
if (self.stack_top > 0) {
debug.print("{s:32}", .{""});
for (0..self.stack_top) |item_idx| {
@ -100,7 +105,7 @@ pub const VM = struct {
}
debug.print("\n", .{});
}
_ = self.current_chunk().dissassemble_instruction(self.ip.?);
_ = self.current_chunk().dissassemble_instruction(self.current_frame().ip);
}
const instruction = self.read_byte();
@ -180,25 +185,25 @@ pub const VM = struct {
},
@intFromEnum(OpCode.OP_GET_LOCAL) => {
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) => {
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) => {
const offset = self.read_short();
self.ip.? += offset;
self.current_frame().ip += offset;
},
@intFromEnum(OpCode.OP_JUMP_IF_FALSE) => {
const offset = self.read_short();
if (self.peek(0).is_falsey()) {
self.ip.? += offset;
self.current_frame().ip += offset;
}
},
@intFromEnum(OpCode.OP_LOOP) => {
const offset = self.read_short();
self.ip.? -= offset;
self.current_frame().ip -= offset;
},
else => {
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
// How to do that in Zig?
pub fn read_byte(self: *VM) u8 {
const ret = self.current_chunk().code[self.ip.?];
self.ip.? += 1;
const ret = self.current_chunk().code[self.current_frame().ip];
self.current_frame().ip += 1;
return ret;
}
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 {
@ -287,7 +292,7 @@ pub const VM = struct {
}
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];
debug.print("err: {s}\n", .{err_msg});