diff --git a/samples/ch29_superclass.lox b/samples/ch29_superclass.lox new file mode 100644 index 0000000..ce70e90 --- /dev/null +++ b/samples/ch29_superclass.lox @@ -0,0 +1,21 @@ +class Doughnut { + cook() { + print "Dunk in the fryer."; + } + + zzz() { + print "Bla"; + } +} + +class Cruller < Doughnut { + finish() { + print "Glaze with icing."; + } +} + +var o2 = Cruller(); +o2.cook(); +o2.finish(); + + diff --git a/samples/ch29_superclass2.lox b/samples/ch29_superclass2.lox new file mode 100644 index 0000000..4ced98d --- /dev/null +++ b/samples/ch29_superclass2.lox @@ -0,0 +1,19 @@ +class A { + method() { + print "A method"; + } +} + +class B < A { + method() { + print "B method"; + } + + test() { + this.method(); + super.method(); + } +} + +class C < B {} +C().test(); diff --git a/samples/ch29_superclass_error.lox b/samples/ch29_superclass_error.lox new file mode 100644 index 0000000..5142ec4 --- /dev/null +++ b/samples/ch29_superclass_error.lox @@ -0,0 +1,2 @@ +class Cake < Cake { +} diff --git a/samples/ch29_superclass_error2.lox b/samples/ch29_superclass_error2.lox new file mode 100644 index 0000000..14f0aec --- /dev/null +++ b/samples/ch29_superclass_error2.lox @@ -0,0 +1,2 @@ +var NotClass = "So not a class"; +class OhNo < NotClass {} diff --git a/src/chunk.zig b/src/chunk.zig index f3f3e31..476b63f 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -155,6 +155,9 @@ pub const Chunk = struct { @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), + @intFromEnum(OpCode.OP_INHERIT) => return utils.simple_instruction("OP_INHERIT", offset), + @intFromEnum(OpCode.OP_GET_SUPER) => return utils.constant_instruction("OP_GET_SUPER", self, offset), + @intFromEnum(OpCode.OP_SUPER_INVOKE) => return utils.invoke_instruction("OP_SUPER_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 67ea23d..fdece35 100644 --- a/src/compile.zig +++ b/src/compile.zig @@ -38,6 +38,7 @@ const ParserFn = *const fn (*Parser, bool) ParsingError!void; const ClassCompiler = struct { enclosing: ?*ClassCompiler, + has_super_class: bool, }; const ParserRule = struct { @@ -307,7 +308,7 @@ pub const Parser = struct { TokenType.OR => ParserRule{ .prefix = null, .infix = or_, .precedence = Precedence.Or }, TokenType.PRINT => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, TokenType.RETURN => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, - TokenType.SUPER => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, + TokenType.SUPER => ParserRule{ .prefix = super_, .infix = null, .precedence = Precedence.None }, TokenType.THIS => ParserRule{ .prefix = this_, .infix = null, .precedence = Precedence.None }, TokenType.TRUE => ParserRule{ .prefix = literal, .infix = null, .precedence = Precedence.None }, TokenType.VAR => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None }, @@ -917,9 +918,28 @@ pub const Parser = struct { var class_compiler = ClassCompiler{ .enclosing = self.current_class_compiler(), + .has_super_class = false, }; self.class_compiler = &class_compiler; + if (self.match(TokenType.LESS)) { + self.consume(TokenType.IDENTIFIER, "Expect superclass name."); + try self.variable(false); + + if (identifiers_equals(class_name, self.previous.?)) { + self.error_msg("A class can't inherit from itself."); + } + + self.begin_scope(); + self.add_local(self.synthetic_token("super")); + try self.define_variable(0); + + try self.named_variable(class_name, false); + try self.emit_byte(@intFromEnum(OpCode.OP_INHERIT)); + + self.current_class_compiler().?.has_super_class = true; + } + try self.named_variable(class_name, false); self.consume(TokenType.LEFT_BRACE, "Expect '{' before class body."); @@ -929,9 +949,23 @@ pub const Parser = struct { self.consume(TokenType.RIGHT_BRACE, "Expect '}' after class body."); try self.emit_byte(@intFromEnum(OpCode.OP_POP)); + if (self.current_class_compiler().?.has_super_class) { + try self.end_scope(); + } + self.class_compiler = self.current_class_compiler().?.enclosing; } + fn synthetic_token(self: *Parser, text: []const u8) Token { + _ = self; + return Token{ + .token_type = TokenType.EOF, + .start = text, + .length = text.len, + .line = 0, + }; + } + fn dot(self: *Parser, can_assign: bool) ParsingError!void { self.consume(TokenType.IDENTIFIER, "Expect property name after '.'."); const name = try self.identifier_constant(self.previous.?); @@ -970,6 +1004,32 @@ pub const Parser = struct { _ = can_assign; try self.variable(false); } + + fn super_(self: *Parser, can_assign: bool) ParsingError!void { + _ = can_assign; + + if (self.current_class_compiler() == null) { + self.error_msg("Can't use 'super' outside of a class."); + } else if (!self.current_class_compiler().?.has_super_class) { + self.error_msg("Can't use 'super' in a class with no superclass."); + } + + self.consume(TokenType.DOT, "Expect '.' after 'super'."); + self.consume(TokenType.IDENTIFIER, "Expect superclass method name."); + const name = try self.identifier_constant(self.previous.?); + + try self.named_variable(self.synthetic_token("this"), false); + + if (self.match(TokenType.LEFT_PAREN)) { + const arg_count = try self.argument_list(); + try self.named_variable(self.synthetic_token("super"), false); + try self.emit_bytes(@intFromEnum(OpCode.OP_SUPER_INVOKE), name); + try self.emit_byte(@intCast(arg_count)); + } else { + try self.named_variable(self.synthetic_token("super"), false); + try self.emit_bytes(@intFromEnum(OpCode.OP_GET_SUPER), name); + } + } }; const FunctionType = enum { diff --git a/src/constant.zig b/src/constant.zig index 0f390ff..83d1904 100644 --- a/src/constant.zig +++ b/src/constant.zig @@ -10,14 +10,14 @@ pub const UINT8_COUNT = UINT8_MAX + 1; pub const FRAMES_MAX = 64; pub const STACK_MAX = (FRAMES_MAX * UINT8_MAX); -pub const DEBUG_PRINT_CODE = false; -pub const DEBUG_TRACE_EXECUTION = false; +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; pub const DEBUG_STRESS_GC = true; pub const DEBUG_LOG_GC = false; -pub const USE_CUSTON_ALLOCATOR = true; +pub const USE_CUSTON_ALLOCATOR = false; pub const GC_HEAP_GROW_FACTOR = 2; diff --git a/src/opcode.zig b/src/opcode.zig index 19f0127..8c30872 100644 --- a/src/opcode.zig +++ b/src/opcode.zig @@ -35,4 +35,7 @@ pub const OpCode = enum(u8) { OP_INDEX_SET, OP_INDEX_GET, OP_INVOKE, + OP_INHERIT, + OP_GET_SUPER, + OP_SUPER_INVOKE, }; diff --git a/src/table.zig b/src/table.zig index bc87dc2..6ad4847 100644 --- a/src/table.zig +++ b/src/table.zig @@ -132,12 +132,12 @@ pub const Table = struct { std.debug.print("== End of hash table ==\n\n", .{}); } - pub fn add_all(self: *Table, from: Table) void { - for (from.entries) |entry| { + pub fn add_all(self: *Table, to: *Table) void { + for (self.entries) |entry| { if (entry.key == null) { continue; } - _ = self.set(entry.key.?, entry.value); + _ = to.set(entry.key.?, entry.value); } } diff --git a/src/vm.zig b/src/vm.zig index a6bfaaa..c3448d6 100644 --- a/src/vm.zig +++ b/src/vm.zig @@ -379,6 +379,35 @@ pub const VM = struct { return InterpretResult.RUNTIME_ERROR; } }, + @intFromEnum(OpCode.OP_INHERIT) => { + const superclass = self.peek(1); + const subclass = self.peek(0).as_obj().as_class(); + + if (!superclass.is_obj() or !superclass.as_obj().is_class()) { + self.runtime_error("Superclass must be a class."); + return InterpretResult.RUNTIME_ERROR; + } + + superclass.as_obj().as_class().methods.add_all(&subclass.methods); + _ = self.pop(); // subclass + }, + @intFromEnum(OpCode.OP_GET_SUPER) => { + const name: *Obj.String = self.read_constant().as_string(); + const superclass = self.pop().as_obj().as_class(); + + if (!self.bind_method(superclass, name)) { + return InterpretResult.RUNTIME_ERROR; + } + }, + @intFromEnum(OpCode.OP_SUPER_INVOKE) => { + const method = self.read_constant().as_string(); + const arg_count = self.read_byte(); + const superclass = self.pop().as_obj().as_class(); + + if (!self.invoke_from_class(superclass, method, arg_count)) { + return InterpretResult.RUNTIME_ERROR; + } + }, else => { debug.print("Invalid instruction: {d}\n", .{instruction}); return InterpretResult.RUNTIME_ERROR;