diff --git a/empty.lox b/empty.lox new file mode 100644 index 0000000..e69de29 diff --git a/samples/ch28_initializers.lox b/samples/ch28_initializers.lox new file mode 100644 index 0000000..6507820 --- /dev/null +++ b/samples/ch28_initializers.lox @@ -0,0 +1,14 @@ +class CoffeeMaker { + init(coffee) { + this.coffee = coffee; + } + brew() { + print "Enjoy your cup of " + this.coffee; + // No reusing the grounds! + this.coffee = nil; + } +} + +var maker = CoffeeMaker("coffee and chicory"); +print maker; +maker.brew(); diff --git a/samples/ch28_methods_again.lox b/samples/ch28_methods_again.lox new file mode 100644 index 0000000..d6f5d8b --- /dev/null +++ b/samples/ch28_methods_again.lox @@ -0,0 +1,12 @@ +class Brunch { + bacon() { + print "calling bacon()"; + } + eggs() { + print "calling eggs()"; + } +} + +var brunch = Brunch(); +print brunch; +brunch.bacon(); diff --git a/samples/ch28_methods_initializers.lox b/samples/ch28_methods_initializers.lox index da47f8d..302860b 100644 --- a/samples/ch28_methods_initializers.lox +++ b/samples/ch28_methods_initializers.lox @@ -1,8 +1,4 @@ class Brunch { - bacon() {} - eggs() {} + init(food, drink) {} } - -var brunch = Brunch(); -print brunch; -brunch.bacon(); +Brunch("eggs", "coffee"); diff --git a/samples/ch8_initalizers.lox b/samples/ch8_initalizers.lox new file mode 100644 index 0000000..96ced2b --- /dev/null +++ b/samples/ch8_initalizers.lox @@ -0,0 +1,17 @@ +class Brunch { + var food; + var drink; + + init(food, drink) { + this.food = food; + this.drink = drink; + } + + fun get_recipe() { + return this.food + " " + this.drink; + } +} + +var instance = Brunch("eggs", "coffee"); +print instance; +print instance.get_recipe(); diff --git a/src/compile.zig b/src/compile.zig index ac11be0..97a7705 100644 --- a/src/compile.zig +++ b/src/compile.zig @@ -144,7 +144,12 @@ pub const Parser = struct { } fn emit_return(self: *Parser) ParsingError!void { - try self.emit_byte(@intFromEnum(OpCode.OP_NIL)); + if (self.compiler.function_type == FunctionType.Initializer) { + try self.emit_bytes(@intFromEnum(OpCode.OP_GET_LOCAL), 0); + } else { + try self.emit_byte(@intFromEnum(OpCode.OP_NIL)); + } + try self.emit_byte(@intFromEnum(OpCode.OP_RETURN)); } @@ -865,6 +870,9 @@ pub const Parser = struct { if (self.match(TokenType.SEMICOLON)) { try self.emit_return(); } else { + if (self.compiler.function_type == FunctionType.Initializer) { + self.error_msg("Can't return a value from an initialiaer"); + } try self.expression(); self.consume(TokenType.SEMICOLON, "Expect ';' after return value."); try self.emit_byte(@intFromEnum(OpCode.OP_RETURN)); @@ -914,7 +922,12 @@ pub const Parser = struct { self.consume(TokenType.IDENTIFIER, "Expect method name."); const constant = try self.identifier_constant(self.previous.?); - try self.function(FunctionType.Method); + var function_type: FunctionType = FunctionType.Method; + // std.debug.print("len: {d} {s}\n", .{self.previous.?.length, }); + if (self.previous.?.length == 4 and std.mem.eql(u8, self.previous.?.start[0..4], "init")) { + function_type = FunctionType.Initializer; + } + try self.function(function_type); try self.emit_bytes(@intFromEnum(OpCode.OP_METHOD), constant); } @@ -933,6 +946,7 @@ const FunctionType = enum { Function, Script, Method, + Initializer, }; pub const Compiler = struct { diff --git a/src/main.zig b/src/main.zig index 5f7bbd2..af6e71d 100644 --- a/src/main.zig +++ b/src/main.zig @@ -72,7 +72,6 @@ pub fn main() !void { } else { vm.init_vm(allocator); } - defer vm.destroy(); if (args.len == 1) { diff --git a/src/memory.zig b/src/memory.zig index ccbb0ad..9872b21 100644 --- a/src/memory.zig +++ b/src/memory.zig @@ -25,7 +25,7 @@ pub const ZloxAllocator = struct { .parent_allocator = parent_allocator, .vm = vm, .bytes_allocated = 0, - .next_gc = 1024, + .next_gc = 4096, .current_gc = false, }; } @@ -145,6 +145,8 @@ pub const ZloxAllocator = struct { self.mark_table(&self.vm.globals); self.mark_compiler_roots(); + + self.mark_object(&self.vm.init_string.?.obj); } pub fn mark_value(self: *Self, value: *Value) void { @@ -264,6 +266,9 @@ pub const ZloxAllocator = struct { for (0..table.capacity) |idx| { const entry: *Entry = &table.entries[idx]; if (entry.key != null and !entry.key.?.obj.is_marked) { + if (comptime constants.DEBUG_LOG_GC) { + std.debug.print("GC: table_remove_white: deleting {s}\n", .{entry.key.?.chars}); + } _ = table.del(entry.key.?); } } @@ -288,7 +293,7 @@ pub const ZloxAllocator = struct { self.vm.objects = object; } - if (comptime constants.DEBUG_LOG_GC == true) { + if (comptime constants.DEBUG_LOG_GC) { std.debug.print("GC: sweeping {*}: ", .{unreached}); unreached.print(); std.debug.print("\n", .{}); diff --git a/src/table.zig b/src/table.zig index 1b67a09..bc87dc2 100644 --- a/src/table.zig +++ b/src/table.zig @@ -65,7 +65,6 @@ pub const Table = struct { while (true) { const entry = &entries[index]; - if (entry.key == null) { if (entry.value.is_nil()) { // Empty entry. @@ -121,7 +120,7 @@ pub const Table = struct { std.debug.print("== Hash table count:{} capacity:{} ==\n", .{ self.count, self.capacity }); for (self.entries, 0..) |entry, idx| { if (entry.key != null) { - std.debug.print("{d} ({d}) - {s}: ", .{ idx, entry.key.?.hash, entry.key.?.chars }); + std.debug.print("{d} {*} (size: {d} hash:{d}) - {s}: ", .{ idx, entry.key, entry.key.?.chars.len, entry.key.?.hash, entry.key.?.chars }); entry.value.type_print(); std.debug.print("\n", .{}); } diff --git a/src/vm.zig b/src/vm.zig index 0d3332d..57cd6e7 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -49,6 +49,7 @@ pub const VM = struct { gray_count: usize, gray_capacity: usize, gray_stack: ?[]*Obj, + init_string: ?*Obj.String, pub fn new() VM { const vm = VM{ @@ -65,6 +66,7 @@ pub const VM = struct { .gray_capacity = 0, .gray_count = 0, .gray_stack = &.{}, + .init_string = null, }; return vm; @@ -75,6 +77,9 @@ pub const VM = struct { self.globals = Table.new(self.allocator); self.strings = Table.new(self.allocator); + _ = try self.push(Value.obj_val(&self.copy_string("init").obj)); + self.init_string = self.pop().as_string(); + self.define_native("clock", natives.clock); self.define_native("power", natives.power); self.define_native("str2num", natives.str2num); @@ -88,6 +93,7 @@ pub const VM = struct { self.strings.destroy(); self.globals.destroy(); + self.init_string = null; self.destroy_objects(); if (self.gray_stack != null) { @@ -108,7 +114,9 @@ pub const VM = struct { var obj = self.objects; while (obj != null) { const obj_next = obj.?.next; - std.debug.print("OBJ: {*}\n", .{obj.?}); + std.debug.print("OBJ: {*} {any}", .{ obj.?, obj.?.kind }); + // obj.?.print(); + std.debug.print("\n", .{}); obj = obj_next; } } @@ -500,6 +508,14 @@ pub const VM = struct { const class = callee.as_obj().as_class(); self.stack[self.stack_top - arg_count - 1] = Value.obj_val(&Obj.Instance.new(self, class).obj); + var initializer = Value.nil_val(); + + if (class.methods.get(self.init_string.?, &initializer) == true) { + return self.call(initializer.as_obj().as_closure(), arg_count); + } else if (arg_count != 0) { + self.runtime_error("Expected 0 arguments."); // XXX show number of arguments. + } + return true; }, ObjType.BoundMethod => { @@ -518,7 +534,10 @@ pub const VM = struct { var method: Value = Value.nil_val(); if (!class.methods.get(name, &method)) { - self.runtime_error("Undefined property."); // XXX add name.chars as '%s'. + const err_msg = std.fmt.allocPrint(self.allocator, "Undefined property '{s}'.", .{name.chars}) catch unreachable; + defer self.allocator.free(err_msg); + self.runtime_error(err_msg); + return false; } @@ -531,8 +550,11 @@ pub const VM = struct { pub fn call(self: *VM, closure: *Obj.Closure, arg_count: usize) bool { if (arg_count != closure.function.arity) { - self.runtime_error("Invalid argument count."); - // runtimeError("Expected %d arguments but got %d.", function->arity, argCount); + const err_msg = std.fmt.allocPrint(self.allocator, "Expected {d} arguments but got {d}.", .{ closure.function.arity, arg_count }) catch unreachable; + defer self.allocator.free(err_msg); + + self.runtime_error(err_msg); + return false; }