implementing call frames
also, minor tracing/constants refactoring
This commit is contained in:
parent
0850198784
commit
3a20f5e04a
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
10
src/main.zig
10
src/main.zig
@ -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) {
|
||||||
|
@ -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});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
69
src/vm.zig
69
src/vm.zig
@ -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});
|
||||||
|
Loading…
Reference in New Issue
Block a user