refactor: using linked list for objects
This commit is contained in:
parent
ab00c0f785
commit
96e8b1742c
1
samples/hello_world.lox
Normal file
1
samples/hello_world.lox
Normal file
@ -0,0 +1 @@
|
|||||||
|
print "hello world!";
|
@ -326,9 +326,7 @@ const Parser = struct {
|
|||||||
|
|
||||||
const str = self.previous.?.start[1 .. self.previous.?.length - 1];
|
const str = self.previous.?.start[1 .. self.previous.?.length - 1];
|
||||||
|
|
||||||
var string_obj = self.vm.copy_string(str);
|
const string_obj = self.vm.copy_string(str);
|
||||||
|
|
||||||
self.vm.add_reference(&string_obj.obj);
|
|
||||||
|
|
||||||
try self.emit_constant(Value.obj_val(&string_obj.obj));
|
try self.emit_constant(Value.obj_val(&string_obj.obj));
|
||||||
}
|
}
|
||||||
@ -472,7 +470,6 @@ const Parser = struct {
|
|||||||
|
|
||||||
fn identifier_constant(self: *Parser, token: Token) ParsingError!u8 {
|
fn identifier_constant(self: *Parser, token: Token) ParsingError!u8 {
|
||||||
const copy = &self.vm.copy_string(token.start[0..token.length]).obj;
|
const copy = &self.vm.copy_string(token.start[0..token.length]).obj;
|
||||||
self.vm.add_reference(copy);
|
|
||||||
return self.make_constant(Value.obj_val(copy));
|
return self.make_constant(Value.obj_val(copy));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -774,7 +771,7 @@ const Parser = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn function(self: *Parser, function_type: FunctionType) ParsingError!void {
|
fn function(self: *Parser, function_type: FunctionType) ParsingError!void {
|
||||||
var compiler = Compiler.new(self.vm.allocator, self.compiler, function_type);
|
var compiler = Compiler.new(self.vm, self.compiler, function_type);
|
||||||
|
|
||||||
self.compiler = &compiler;
|
self.compiler = &compiler;
|
||||||
if (function_type != FunctionType.Script) {
|
if (function_type != FunctionType.Script) {
|
||||||
@ -879,8 +876,8 @@ const Compiler = struct {
|
|||||||
upvalues: [constants.UINT8_COUNT]Upvalue,
|
upvalues: [constants.UINT8_COUNT]Upvalue,
|
||||||
scope_depth: usize,
|
scope_depth: usize,
|
||||||
|
|
||||||
fn new(allocator: std.mem.Allocator, enclosing: ?*Compiler, function_type: FunctionType) Compiler {
|
fn new(vm: *VM, enclosing: ?*Compiler, function_type: FunctionType) Compiler {
|
||||||
const obj_function = Obj.Function.new(allocator);
|
const obj_function = Obj.Function.new(vm);
|
||||||
|
|
||||||
var compiler = Compiler{
|
var compiler = Compiler{
|
||||||
.locals = undefined,
|
.locals = undefined,
|
||||||
@ -924,7 +921,7 @@ const Upvalue = struct {
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub fn compile(vm: *VM, contents: []const u8) !?*Obj.Function {
|
pub fn compile(vm: *VM, contents: []const u8) !?*Obj.Function {
|
||||||
var compiler = Compiler.new(vm.allocator, null, FunctionType.Script);
|
var compiler = Compiler.new(vm, null, FunctionType.Script);
|
||||||
var scanner = Scanner.init(contents);
|
var scanner = Scanner.init(contents);
|
||||||
var parser = Parser.new(vm, &compiler, &scanner);
|
var parser = Parser.new(vm, &compiler, &scanner);
|
||||||
|
|
||||||
|
@ -55,7 +55,7 @@ pub fn run_file(allocator: Allocator, vm: *VM, filepath: []const u8) !void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn main() !void {
|
pub fn main() !void {
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{ .safety = false }){};
|
var gpa = std.heap.GeneralPurposeAllocator(.{ .safety = true }){};
|
||||||
defer _ = debug.assert(gpa.deinit() == .ok);
|
defer _ = debug.assert(gpa.deinit() == .ok);
|
||||||
const allocator = gpa.allocator();
|
const allocator = gpa.allocator();
|
||||||
|
|
||||||
|
107
src/object.zig
107
src/object.zig
@ -20,21 +20,40 @@ pub const NativeFn = *const fn (vm: *VM, arg_count: usize, args: []Value) Value;
|
|||||||
|
|
||||||
pub const Obj = struct {
|
pub const Obj = struct {
|
||||||
kind: ObjType,
|
kind: ObjType,
|
||||||
allocator: std.mem.Allocator,
|
allocator: Allocator,
|
||||||
|
next: ?*Obj,
|
||||||
|
|
||||||
|
fn new(comptime T: type, vm: *VM, kind: ObjType) *T {
|
||||||
|
const created_obj = vm.allocator.create(T) catch unreachable;
|
||||||
|
|
||||||
|
created_obj.obj = Obj{
|
||||||
|
.kind = kind,
|
||||||
|
.allocator = vm.allocator,
|
||||||
|
.next = vm.objects,
|
||||||
|
};
|
||||||
|
|
||||||
|
vm.objects = &created_obj.obj;
|
||||||
|
|
||||||
|
return created_obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *Obj) void {
|
||||||
|
switch (self.kind) {
|
||||||
|
ObjType.String => self.as_string().destroy(),
|
||||||
|
ObjType.Function => self.as_function().destroy(),
|
||||||
|
ObjType.Native => self.as_native().destroy(),
|
||||||
|
ObjType.Closure => self.as_closure().destroy(),
|
||||||
|
ObjType.Upvalue => self.as_upvalue().destroy(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub const String = struct {
|
pub const String = struct {
|
||||||
obj: Obj,
|
obj: Obj,
|
||||||
chars: []const u8,
|
chars: []const u8,
|
||||||
hash: u32,
|
hash: u32,
|
||||||
|
|
||||||
pub fn new(allocator: std.mem.Allocator, chars: []const u8) *String {
|
pub fn new(vm: *VM, chars: []const u8) *String {
|
||||||
const obj = Obj{
|
const str_obj = Obj.new(String, vm, ObjType.String);
|
||||||
.kind = ObjType.String,
|
|
||||||
.allocator = allocator,
|
|
||||||
};
|
|
||||||
|
|
||||||
const str_obj = allocator.create(String) catch unreachable;
|
|
||||||
str_obj.obj = obj;
|
|
||||||
|
|
||||||
str_obj.chars = chars;
|
str_obj.chars = chars;
|
||||||
str_obj.hash = compute_hash(str_obj.chars);
|
str_obj.hash = compute_hash(str_obj.chars);
|
||||||
@ -55,17 +74,12 @@ pub const Obj = struct {
|
|||||||
chunk: *Chunk,
|
chunk: *Chunk,
|
||||||
name: ?*Obj.String,
|
name: ?*Obj.String,
|
||||||
|
|
||||||
pub fn new(allocator: std.mem.Allocator) *Function {
|
pub fn new(vm: *VM) *Function {
|
||||||
const obj = Obj{
|
const function_obj = Obj.new(Function, vm, ObjType.Function);
|
||||||
.kind = ObjType.Function,
|
|
||||||
.allocator = allocator,
|
|
||||||
};
|
|
||||||
|
|
||||||
const function_obj = allocator.create(Function) catch unreachable;
|
|
||||||
function_obj.obj = obj;
|
|
||||||
function_obj.arity = 0;
|
function_obj.arity = 0;
|
||||||
function_obj.upvalue_count = 0;
|
function_obj.upvalue_count = 0;
|
||||||
function_obj.chunk = Chunk.new(allocator);
|
function_obj.chunk = Chunk.new(vm.allocator);
|
||||||
function_obj.name = null;
|
function_obj.name = null;
|
||||||
|
|
||||||
return function_obj;
|
return function_obj;
|
||||||
@ -81,14 +95,9 @@ pub const Obj = struct {
|
|||||||
obj: Obj,
|
obj: Obj,
|
||||||
native: NativeFn,
|
native: NativeFn,
|
||||||
|
|
||||||
pub fn new(allocator: std.mem.Allocator, native: NativeFn) *Native {
|
pub fn new(vm: *VM, native: NativeFn) *Native {
|
||||||
const obj = Obj{
|
const native_obj = Obj.new(Native, vm, ObjType.Native);
|
||||||
.kind = ObjType.Native,
|
|
||||||
.allocator = allocator,
|
|
||||||
};
|
|
||||||
|
|
||||||
const native_obj = allocator.create(Native) catch unreachable;
|
|
||||||
native_obj.obj = obj;
|
|
||||||
native_obj.native = native;
|
native_obj.native = native;
|
||||||
|
|
||||||
return native_obj;
|
return native_obj;
|
||||||
@ -105,18 +114,13 @@ pub const Obj = struct {
|
|||||||
upvalues: []?*Obj.Upvalue,
|
upvalues: []?*Obj.Upvalue,
|
||||||
upvalue_count: usize,
|
upvalue_count: usize,
|
||||||
|
|
||||||
pub fn new(allocator: std.mem.Allocator, function: *Obj.Function) *Closure {
|
pub fn new(vm: *VM, function: *Obj.Function) *Closure {
|
||||||
const obj = Obj{
|
const closure_obj = Obj.new(Closure, vm, ObjType.Closure);
|
||||||
.kind = ObjType.Closure,
|
|
||||||
.allocator = allocator,
|
|
||||||
};
|
|
||||||
|
|
||||||
const closure_obj = allocator.create(Closure) catch unreachable;
|
|
||||||
closure_obj.obj = obj;
|
|
||||||
closure_obj.function = function;
|
closure_obj.function = function;
|
||||||
closure_obj.upvalue_count = function.upvalue_count;
|
closure_obj.upvalue_count = function.upvalue_count;
|
||||||
|
|
||||||
closure_obj.upvalues = allocator.alloc(?*Obj.Upvalue, function.upvalue_count) catch unreachable;
|
closure_obj.upvalues = vm.allocator.alloc(?*Obj.Upvalue, function.upvalue_count) catch unreachable;
|
||||||
|
|
||||||
for (0..function.upvalue_count) |i| {
|
for (0..function.upvalue_count) |i| {
|
||||||
closure_obj.upvalues[i] = null;
|
closure_obj.upvalues[i] = null;
|
||||||
@ -137,14 +141,9 @@ pub const Obj = struct {
|
|||||||
next: ?*Obj.Upvalue,
|
next: ?*Obj.Upvalue,
|
||||||
closed: Value,
|
closed: Value,
|
||||||
|
|
||||||
pub fn new(allocator: std.mem.Allocator, slot: *Value) *Upvalue {
|
pub fn new(vm: *VM, slot: *Value) *Upvalue {
|
||||||
const obj = Obj{
|
const upvalue_obj = Obj.new(Upvalue, vm, ObjType.Upvalue);
|
||||||
.kind = ObjType.Upvalue,
|
|
||||||
.allocator = allocator,
|
|
||||||
};
|
|
||||||
|
|
||||||
const upvalue_obj = allocator.create(Upvalue) catch unreachable;
|
|
||||||
upvalue_obj.obj = obj;
|
|
||||||
upvalue_obj.location = slot;
|
upvalue_obj.location = slot;
|
||||||
upvalue_obj.next = null;
|
upvalue_obj.next = null;
|
||||||
upvalue_obj.closed = Value.nil_val();
|
upvalue_obj.closed = Value.nil_val();
|
||||||
@ -208,31 +207,6 @@ pub const Obj = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy(self: *Obj) void {
|
|
||||||
switch (self.kind) {
|
|
||||||
ObjType.String => {
|
|
||||||
const obj: *String = @fieldParentPtr("obj", self);
|
|
||||||
obj.destroy();
|
|
||||||
},
|
|
||||||
ObjType.Function => {
|
|
||||||
const obj: *Function = @fieldParentPtr("obj", self);
|
|
||||||
obj.destroy();
|
|
||||||
},
|
|
||||||
ObjType.Native => {
|
|
||||||
const obj: *Native = @fieldParentPtr("obj", self);
|
|
||||||
obj.destroy();
|
|
||||||
},
|
|
||||||
ObjType.Closure => {
|
|
||||||
const obj: *Closure = @fieldParentPtr("obj", self);
|
|
||||||
obj.destroy();
|
|
||||||
},
|
|
||||||
ObjType.Upvalue => {
|
|
||||||
const obj: *Upvalue = @fieldParentPtr("obj", self);
|
|
||||||
obj.destroy();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_string(self: *Obj) *String {
|
pub fn as_string(self: *Obj) *String {
|
||||||
std.debug.assert(self.kind == ObjType.String);
|
std.debug.assert(self.kind == ObjType.String);
|
||||||
return @fieldParentPtr("obj", self);
|
return @fieldParentPtr("obj", self);
|
||||||
@ -252,4 +226,9 @@ pub const Obj = struct {
|
|||||||
std.debug.assert(self.kind == ObjType.Closure);
|
std.debug.assert(self.kind == ObjType.Closure);
|
||||||
return @fieldParentPtr("obj", self);
|
return @fieldParentPtr("obj", self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_upvalue(self: *Obj) *Upvalue {
|
||||||
|
std.debug.assert(self.kind == ObjType.Upvalue);
|
||||||
|
return @fieldParentPtr("obj", self);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
50
src/vm.zig
50
src/vm.zig
@ -36,26 +36,24 @@ pub const VM = struct {
|
|||||||
allocator: Allocator,
|
allocator: Allocator,
|
||||||
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.
|
|
||||||
// In the book, a linked list between objects is used to handle this.
|
|
||||||
references: std.ArrayList(*Obj),
|
|
||||||
strings: Table,
|
strings: Table,
|
||||||
globals: Table,
|
globals: Table,
|
||||||
frames: [constants.FRAMES_MAX]CallFrame,
|
frames: [constants.FRAMES_MAX]CallFrame,
|
||||||
frame_count: usize,
|
frame_count: usize,
|
||||||
open_upvalues: ?*Obj.Upvalue,
|
open_upvalues: ?*Obj.Upvalue,
|
||||||
|
objects: ?*Obj,
|
||||||
|
|
||||||
pub fn new(allocator: Allocator) VM {
|
pub fn new(allocator: Allocator) VM {
|
||||||
return VM{
|
return VM{
|
||||||
.allocator = allocator,
|
.allocator = allocator,
|
||||||
.stack = undefined,
|
.stack = undefined,
|
||||||
.stack_top = 0,
|
.stack_top = 0,
|
||||||
.references = std.ArrayList(*Obj).init(allocator),
|
|
||||||
.strings = Table.new(allocator),
|
.strings = Table.new(allocator),
|
||||||
.globals = Table.new(allocator),
|
.globals = Table.new(allocator),
|
||||||
.frames = undefined,
|
.frames = undefined,
|
||||||
.frame_count = 0,
|
.frame_count = 0,
|
||||||
.open_upvalues = null,
|
.open_upvalues = null,
|
||||||
|
.objects = null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,8 +70,16 @@ pub const VM = struct {
|
|||||||
|
|
||||||
self.strings.destroy();
|
self.strings.destroy();
|
||||||
self.globals.destroy();
|
self.globals.destroy();
|
||||||
self.clean_references();
|
self.destroy_objects();
|
||||||
self.references.deinit();
|
}
|
||||||
|
|
||||||
|
pub fn destroy_objects(self: *VM) void {
|
||||||
|
var obj = self.objects;
|
||||||
|
while (obj != null) {
|
||||||
|
const obj_next = obj.?.next;
|
||||||
|
obj.?.destroy();
|
||||||
|
obj = obj_next;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fn current_chunk(self: *VM) *Chunk {
|
inline fn current_chunk(self: *VM) *Chunk {
|
||||||
@ -89,10 +95,9 @@ pub const VM = struct {
|
|||||||
if (function == null) {
|
if (function == null) {
|
||||||
return InterpretResult.COMPILE_ERROR;
|
return InterpretResult.COMPILE_ERROR;
|
||||||
}
|
}
|
||||||
defer function.?.destroy();
|
|
||||||
|
|
||||||
_ = try self.push(Value.obj_val(&function.?.obj));
|
_ = try self.push(Value.obj_val(&function.?.obj));
|
||||||
const closure: *Obj.Closure = Obj.Closure.new(self.allocator, function.?);
|
const closure: *Obj.Closure = Obj.Closure.new(self, function.?);
|
||||||
_ = self.pop();
|
_ = self.pop();
|
||||||
_ = try self.push(Value.obj_val(&closure.obj));
|
_ = try self.push(Value.obj_val(&closure.obj));
|
||||||
_ = self.call(closure, 0);
|
_ = self.call(closure, 0);
|
||||||
@ -229,7 +234,7 @@ pub const VM = struct {
|
|||||||
},
|
},
|
||||||
@intFromEnum(OpCode.OP_CLOSURE) => {
|
@intFromEnum(OpCode.OP_CLOSURE) => {
|
||||||
const function = self.read_constant().as_obj().as_function();
|
const function = self.read_constant().as_obj().as_function();
|
||||||
const closure = Obj.Closure.new(self.allocator, function);
|
const closure = Obj.Closure.new(self, function);
|
||||||
_ = try self.push(Value.obj_val(&closure.obj));
|
_ = try self.push(Value.obj_val(&closure.obj));
|
||||||
for (0..closure.upvalue_count) |i| {
|
for (0..closure.upvalue_count) |i| {
|
||||||
const is_local = self.read_byte();
|
const is_local = self.read_byte();
|
||||||
@ -329,9 +334,7 @@ pub const VM = struct {
|
|||||||
|
|
||||||
const concat_str = try std.mem.concat(self.current_chunk().allocator, u8, &.{ a, b });
|
const concat_str = try std.mem.concat(self.current_chunk().allocator, u8, &.{ a, b });
|
||||||
|
|
||||||
var string_obj = self.take_string(concat_str);
|
const string_obj = self.take_string(concat_str);
|
||||||
|
|
||||||
self.add_reference(&string_obj.obj);
|
|
||||||
|
|
||||||
try self.push(Value.obj_val(&string_obj.obj));
|
try self.push(Value.obj_val(&string_obj.obj));
|
||||||
}
|
}
|
||||||
@ -365,23 +368,6 @@ pub const VM = struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_reference(self: *VM, obj: *Obj) void {
|
|
||||||
// do not add duplicate references
|
|
||||||
for (self.references.items) |item| {
|
|
||||||
if (item == obj) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// XXX TODO catch unreachable to prevents
|
|
||||||
self.references.append(obj) catch unreachable;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clean_references(self: *VM) void {
|
|
||||||
for (self.references.items) |item| {
|
|
||||||
item.destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn copy_string(self: *VM, source: []const u8) *Obj.String {
|
pub fn copy_string(self: *VM, source: []const u8) *Obj.String {
|
||||||
const hash = compute_hash(source);
|
const hash = compute_hash(source);
|
||||||
const obj_string = self.strings.find_string(source, hash);
|
const obj_string = self.strings.find_string(source, hash);
|
||||||
@ -408,7 +394,7 @@ pub const VM = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn allocate_string(self: *VM, source: []const u8) *Obj.String {
|
pub fn allocate_string(self: *VM, source: []const u8) *Obj.String {
|
||||||
const obj_string = Obj.String.new(self.allocator, source);
|
const obj_string = Obj.String.new(self, source);
|
||||||
_ = self.strings.set(obj_string, Value.nil_val());
|
_ = self.strings.set(obj_string, Value.nil_val());
|
||||||
|
|
||||||
return obj_string;
|
return obj_string;
|
||||||
@ -465,7 +451,7 @@ pub const VM = struct {
|
|||||||
|
|
||||||
pub fn define_native(self: *VM, name: []const u8, native_fn: NativeFn) void {
|
pub fn define_native(self: *VM, name: []const u8, native_fn: NativeFn) void {
|
||||||
_ = try self.push(Value.obj_val(&self.copy_string(name).obj));
|
_ = try self.push(Value.obj_val(&self.copy_string(name).obj));
|
||||||
_ = try self.push(Value.obj_val(&Obj.Native.new(self.allocator, native_fn).obj));
|
_ = try self.push(Value.obj_val(&Obj.Native.new(self, native_fn).obj));
|
||||||
|
|
||||||
_ = self.globals.set(self.stack[0].as_string(), self.stack[1]);
|
_ = self.globals.set(self.stack[0].as_string(), self.stack[1]);
|
||||||
|
|
||||||
@ -486,7 +472,7 @@ pub const VM = struct {
|
|||||||
return upvalue.?;
|
return upvalue.?;
|
||||||
}
|
}
|
||||||
|
|
||||||
const created_upvalue = Obj.Upvalue.new(self.allocator, local);
|
const created_upvalue = Obj.Upvalue.new(self, local);
|
||||||
created_upvalue.next = upvalue;
|
created_upvalue.next = upvalue;
|
||||||
|
|
||||||
if (prev_upvalue == null) {
|
if (prev_upvalue == null) {
|
||||||
|
Loading…
Reference in New Issue
Block a user