diff --git a/samples/ch28_calling_methods.lox b/samples/ch28_calling_methods.lox new file mode 100644 index 0000000..231baf6 --- /dev/null +++ b/samples/ch28_calling_methods.lox @@ -0,0 +1,9 @@ +class Scone { + topping(first, second) { + return "scone with " + first + " and " + second; + } +} + +var scone = Scone(); +var result = scone.topping("berries", "cream"); +print result; diff --git a/samples/ch28_methods_initializers.lox b/samples/ch28_methods_initializers.lox new file mode 100644 index 0000000..da47f8d --- /dev/null +++ b/samples/ch28_methods_initializers.lox @@ -0,0 +1,8 @@ +class Brunch { + bacon() {} + eggs() {} +} + +var brunch = Brunch(); +print brunch; +brunch.bacon(); diff --git a/samples/ch28_say_name.lox b/samples/ch28_say_name.lox new file mode 100644 index 0000000..ed9255e --- /dev/null +++ b/samples/ch28_say_name.lox @@ -0,0 +1,11 @@ +class Person { + sayName() { + print this.name; + } +} + +var jane = Person(); +jane.name = "Jane"; + +var method = jane.sayName; +method(); \ No newline at end of file diff --git a/src/chunk.zig b/src/chunk.zig index d5282b8..bfbbddf 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -151,6 +151,7 @@ pub const Chunk = struct { @intFromEnum(OpCode.OP_CLASS) => return utils.constant_instruction("OP_CLASS", self, offset), @intFromEnum(OpCode.OP_GET_PROPERTY) => return utils.constant_instruction("OP_GET_PROPERTY", self, offset), @intFromEnum(OpCode.OP_SET_PROPERTY) => return utils.constant_instruction("OP_SET_PROPERTY", self, offset), + @intFromEnum(OpCode.OP_METHOD) => return utils.constant_instruction("OP_METHOD", self, offset), else => { debug.print("unknown opcode {d}\n", .{instruction}); return offset + 1; diff --git a/src/compile.zig b/src/compile.zig index f9676bc..e30fe6a 100644 --- a/src/compile.zig +++ b/src/compile.zig @@ -863,14 +863,22 @@ pub const Parser = struct { fn class_declaration(self: *Parser) ParsingError!void { self.consume(TokenType.IDENTIFIER, "Expect class name."); + const class_name = self.previous.?; + const name_constant = try self.identifier_constant(self.previous.?); self.declare_variable(); try self.emit_bytes(@intFromEnum(OpCode.OP_CLASS), name_constant); try self.define_variable(name_constant); + try self.named_variable(class_name, false); + self.consume(TokenType.LEFT_BRACE, "Expect '{' before class body."); + while (!self.check(TokenType.RIGHT_BRACE) and !self.check(TokenType.EOF)) { + try self.method(); + } self.consume(TokenType.RIGHT_BRACE, "Expect '}' after class body."); + try self.emit_byte(@intFromEnum(OpCode.OP_POP)); } fn dot(self: *Parser, can_assign: bool) ParsingError!void { @@ -884,6 +892,14 @@ pub const Parser = struct { try self.emit_bytes(@intFromEnum(OpCode.OP_GET_PROPERTY), name); } } + + fn method(self: *Parser) ParsingError!void { + self.consume(TokenType.IDENTIFIER, "Expect method name."); + const constant = try self.identifier_constant(self.previous.?); + + try self.function(FunctionType.Function); + try self.emit_bytes(@intFromEnum(OpCode.OP_METHOD), constant); + } }; const FunctionType = enum { diff --git a/src/memory.zig b/src/memory.zig index d29876f..ccbb0ad 100644 --- a/src/memory.zig +++ b/src/memory.zig @@ -237,12 +237,18 @@ pub const ZloxAllocator = struct { ObjType.Class => { const class: *Obj.Class = obj.as_class(); self.mark_object(&class.name.obj); + self.mark_table(&class.methods); }, ObjType.Instance => { const instance: *Obj.Instance = obj.as_instance(); self.mark_object(&instance.class.obj); self.mark_table(&instance.fields); }, + ObjType.BoundMethod => { + const bound_method: *Obj.BoundMethod = obj.as_bound_method(); + self.mark_value(&bound_method.receiver); + self.mark_object(&bound_method.method.obj); + }, } } diff --git a/src/object.zig b/src/object.zig index 8653bd7..b071c60 100644 --- a/src/object.zig +++ b/src/object.zig @@ -17,6 +17,7 @@ pub const ObjType = enum { Upvalue, Class, Instance, + BoundMethod, }; pub const NativeFn = *const fn (vm: *VM, arg_count: usize, args: []Value) Value; @@ -51,6 +52,7 @@ pub const Obj = struct { ObjType.Upvalue => self.as_upvalue().destroy(), ObjType.Class => self.as_class().destroy(), ObjType.Instance => self.as_instance().destroy(), + ObjType.BoundMethod => self.as_bound_method().destroy(), } } @@ -166,16 +168,19 @@ pub const Obj = struct { pub const Class = struct { obj: Obj, name: *Obj.String, + methods: Table, pub fn new(vm: *VM, name: *Obj.String) *Class { const class_obj = Obj.new(Class, vm, ObjType.Class); class_obj.name = name; + class_obj.methods = Table.new(vm.allocator); return class_obj; } pub fn destroy(self: *Class) void { + self.methods.destroy(); self.obj.allocator.destroy(self); } }; @@ -200,6 +205,25 @@ pub const Obj = struct { } }; + pub const BoundMethod = struct { + obj: Obj, + receiver: Value, + method: *Obj.Closure, + + pub fn new(vm: *VM, receiver: Value, method: *Obj.Closure) *BoundMethod { + const bound_method = Obj.new(BoundMethod, vm, ObjType.BoundMethod); + + bound_method.receiver = receiver; + bound_method.method = method; + + return bound_method; + } + + pub fn destroy(self: *BoundMethod) void { + self.obj.allocator.destroy(self); + } + }; + pub fn is_type(self: *Obj, kind: ObjType) bool { return self.kind == kind; } @@ -232,6 +256,10 @@ pub const Obj = struct { return self.is_type(ObjType.Instance); } + pub fn is_bound_method(self: *Obj) bool { + return self.is_type(ObjType.BoundMethod); + } + pub fn print(self: *Obj) void { switch (self.kind) { ObjType.String => { @@ -264,6 +292,10 @@ pub const Obj = struct { const obj = self.as_instance(); debug.print("{s} instance", .{obj.class.name.chars}); }, + ObjType.BoundMethod => { + const obj = self.as_bound_method(); + obj.method.function.obj.print(); + }, } } @@ -301,4 +333,9 @@ pub const Obj = struct { std.debug.assert(self.kind == ObjType.Instance); return @fieldParentPtr("obj", self); } + + pub fn as_bound_method(self: *Obj) *BoundMethod { + std.debug.assert(self.kind == ObjType.BoundMethod); + return @fieldParentPtr("obj", self); + } }; diff --git a/src/opcode.zig b/src/opcode.zig index 96dce07..de2cc10 100644 --- a/src/opcode.zig +++ b/src/opcode.zig @@ -31,4 +31,5 @@ pub const OpCode = enum(u8) { OP_CLOSE_UPVALUE, OP_RETURN, OP_CLASS, + OP_METHOD, }; diff --git a/src/vm.zig b/src/vm.zig index d75126d..460fb35 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -311,8 +311,9 @@ pub const VM = struct { continue; } - self.runtime_error("Undefined property"); // XXX to complete with name.chars - return InterpretResult.RUNTIME_ERROR; + if (!self.bind_method(instance.class, name)) { + return InterpretResult.RUNTIME_ERROR; + } }, @intFromEnum(OpCode.OP_SET_PROPERTY) => { if (!self.peek(1).is_obj() or !self.peek(1).as_obj().is_instance()) { @@ -326,6 +327,9 @@ pub const VM = struct { _ = self.pop(); _ = try self.push(value); }, + @intFromEnum(OpCode.OP_METHOD) => { + self.define_method(self.read_constant().as_string()); + }, else => { debug.print("Invalid instruction: {d}\n", .{instruction}); return InterpretResult.RUNTIME_ERROR; @@ -498,6 +502,11 @@ pub const VM = struct { return true; }, + ObjType.BoundMethod => { + const bound_method = callee.as_obj().as_bound_method(); + + return self.call(bound_method.method, arg_count); + }, else => {}, } } @@ -505,6 +514,21 @@ pub const VM = struct { return false; } + pub fn bind_method(self: *VM, class: *Obj.Class, name: *Obj.String) bool { + var method: Value = Value.nil_val(); + + if (!class.methods.get(name, &method)) { + self.runtime_error("Undefined property."); // XXX add name.chars as '%s'. + return false; + } + + const bound: *Obj.BoundMethod = Obj.BoundMethod.new(self, self.peek(0), method.as_obj().as_closure()); + _ = self.pop(); + try self.push(Value.obj_val(&bound.obj)); + + return true; + } + pub fn call(self: *VM, closure: *Obj.Closure, arg_count: usize) bool { if (arg_count != closure.function.arity) { self.runtime_error("Invalid argument count."); @@ -571,4 +595,12 @@ pub const VM = struct { self.open_upvalues = upvalue.next; } } + + fn define_method(self: *VM, name: *Obj.String) void { + const method = self.peek(0); + const class = self.peek(1).as_obj().as_class(); + + _ = class.methods.set(name, method); + _ = self.pop(); + } };