diff --git a/samples/ch28_optimized_invocation.lox b/samples/ch28_optimized_invocation.lox new file mode 100644 index 0000000..55d80a4 --- /dev/null +++ b/samples/ch28_optimized_invocation.lox @@ -0,0 +1,17 @@ +class Oops { + init() { + fun f() { + print "not a method"; + return "returned value from f"; + } + this.field = f; + } + + blah() { + return "returned value from blah"; + } +} + +var oops = Oops(); +print oops.field(); +print oops.blah(); diff --git a/src/chunk.zig b/src/chunk.zig index 1a9fe77..f3f3e31 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -154,6 +154,7 @@ pub const Chunk = struct { @intFromEnum(OpCode.OP_METHOD) => return utils.constant_instruction("OP_METHOD", self, offset), @intFromEnum(OpCode.OP_INDEX_GET) => return utils.simple_instruction("OP_INDEX_GET", offset), @intFromEnum(OpCode.OP_INDEX_SET) => return utils.simple_instruction("OP_INDEX_SET", offset), + @intFromEnum(OpCode.OP_INVOKE) => return utils.invoke_instruction("OP_INVOKE", self, offset), else => { debug.print("unknown opcode {d}\n", .{instruction}); return offset + 1; diff --git a/src/compile.zig b/src/compile.zig index 1fb5cf0..67ea23d 100644 --- a/src/compile.zig +++ b/src/compile.zig @@ -939,6 +939,10 @@ pub const Parser = struct { if (can_assign and self.match(TokenType.EQUAL)) { try self.expression(); try self.emit_bytes(@intFromEnum(OpCode.OP_SET_PROPERTY), name); + } else if (self.match(TokenType.LEFT_PAREN)) { + const arg_count = try self.argument_list(); + try self.emit_bytes(@intFromEnum(OpCode.OP_INVOKE), name); + try self.emit_byte(@intCast(arg_count)); } else { try self.emit_bytes(@intFromEnum(OpCode.OP_GET_PROPERTY), name); } diff --git a/src/opcode.zig b/src/opcode.zig index 0960094..19f0127 100644 --- a/src/opcode.zig +++ b/src/opcode.zig @@ -34,4 +34,5 @@ pub const OpCode = enum(u8) { OP_METHOD, OP_INDEX_SET, OP_INDEX_GET, + OP_INVOKE, }; diff --git a/src/utils.zig b/src/utils.zig index 91f25ba..ff026f7 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -55,3 +55,14 @@ pub fn identifiers_equals(a: Token, b: Token) bool { return std.mem.eql(u8, a.start[0..a.length], b.start[0..b.length]); } + +pub fn invoke_instruction(opcode_name: []const u8, chunk: Chunk, offset: usize) usize { + const constant = chunk.code[offset + 1]; + const arg_count = chunk.code[offset + 2]; + + std.debug.print("{s:<16} ({d} args) {d:4} '", .{ opcode_name, arg_count, constant }); + chunk.constants.values[constant].print(); + std.debug.print("'\n", .{}); + + return offset + 3; +} diff --git a/src/vm.zig b/src/vm.zig index f8b16b9..a6bfaaa 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -372,6 +372,13 @@ pub const VM = struct { _ = try self.push(Value.obj_val(&self.take_string(str).obj)); }, + @intFromEnum(OpCode.OP_INVOKE) => { + const method = self.read_constant().as_string(); + const arg_count = self.read_byte(); + if (!self.invoke(method, arg_count)) { + return InterpretResult.RUNTIME_ERROR; + } + }, else => { debug.print("Invalid instruction: {d}\n", .{instruction}); return InterpretResult.RUNTIME_ERROR; @@ -586,9 +593,7 @@ pub const VM = struct { if (arg_count != closure.function.arity) { 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; } @@ -659,4 +664,30 @@ pub const VM = struct { _ = class.methods.set(name, method); _ = self.pop(); } + + fn invoke(self: *VM, name: *Obj.String, arg_count: usize) bool { + const receiver = self.peek(arg_count); + const instance = receiver.as_obj().as_instance(); + + var value = Value.nil_val(); + if (instance.fields.get(name, &value)) { + self.stack[self.stack_top - arg_count - 1] = value; + + return self.call_value(value, arg_count); + } + + return self.invoke_from_class(instance.class, name, arg_count); + } + + fn invoke_from_class(self: *VM, class: *Obj.Class, name: *Obj.String, arg_count: usize) bool { + var method = Value.nil_val(); + if (!class.methods.get(name, &method)) { + 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; + } + + return self.call(method.as_obj().as_closure(), arg_count); + } };