implementing classes and instances (ch27)

This commit is contained in:
Patrick MARIE 2024-08-31 10:01:39 +02:00
parent 2e6f977b34
commit c8cd24afbf
9 changed files with 171 additions and 4 deletions

View File

@ -17,7 +17,7 @@ While reading [Crafting Interpreters](https://craftinginterpreters.com/), after
- [x] 24 - Calls and Functions
- [x] 25 - Closures
- [x] 26 - Garbage Collection
- [ ] 27 - Classes and Instances
- [x] 27 - Classes and Instances
- [ ] 28 - Method and Initializers
- [ ] 29 - Superclasses
- [ ] 30 - Optimization

View File

@ -0,0 +1,16 @@
class Brioche {}
print Brioche();
class Toast {}
var toast = Toast();
print toast;
print toast.jam = "grape";
print toast.jam;
class Pair {}
var pair = Pair();
pair.first = 1;
pair.second = 2;
print pair.first + pair.second;

View File

@ -148,6 +148,9 @@ pub const Chunk = struct {
@intFromEnum(OpCode.OP_GET_UPVALUE) => return utils.byte_instruction("OP_GET_UPVALUE", self, offset),
@intFromEnum(OpCode.OP_SET_UPVALUE) => return utils.byte_instruction("OP_SET_UPVALUE", self, offset),
@intFromEnum(OpCode.OP_CLOSE_UPVALUE) => return utils.simple_instruction("OP_CLOSE_UPVALUE", offset),
@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),
else => {
debug.print("unknown opcode {d}\n", .{instruction});
return offset + 1;

View File

@ -249,7 +249,7 @@ pub const Parser = struct {
TokenType.LEFT_BRACE => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
TokenType.RIGHT_BRACE => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
TokenType.COMMA => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
TokenType.DOT => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
TokenType.DOT => ParserRule{ .prefix = null, .infix = dot, .precedence = Precedence.Call },
TokenType.MINUS => ParserRule{ .prefix = unary, .infix = binary, .precedence = Precedence.Term },
TokenType.PLUS => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Term },
TokenType.SEMICOLON => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
@ -363,7 +363,9 @@ pub const Parser = struct {
}
fn declaration(self: *Parser) ParsingError!void {
if (self.match(TokenType.FUN)) {
if (self.match(TokenType.CLASS)) {
try self.class_declaration();
} else if (self.match(TokenType.FUN)) {
try self.fun_declaration();
} else if (self.match(TokenType.VAR)) {
try self.var_declaration();
@ -858,6 +860,30 @@ pub const Parser = struct {
try self.emit_byte(@intFromEnum(OpCode.OP_RETURN));
}
}
fn class_declaration(self: *Parser) ParsingError!void {
self.consume(TokenType.IDENTIFIER, "Expect class name.");
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);
self.consume(TokenType.LEFT_BRACE, "Expect '{' before class body.");
self.consume(TokenType.RIGHT_BRACE, "Expect '}' after class body.");
}
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.?);
if (can_assign and self.match(TokenType.EQUAL)) {
try self.expression();
try self.emit_bytes(@intFromEnum(OpCode.OP_SET_PROPERTY), name);
} else {
try self.emit_bytes(@intFromEnum(OpCode.OP_GET_PROPERTY), name);
}
}
};
const FunctionType = enum {

View File

@ -234,6 +234,15 @@ pub const ZloxAllocator = struct {
}
}
},
ObjType.Class => {
const class: *Obj.Class = obj.as_class();
self.mark_object(&class.name.obj);
},
ObjType.Instance => {
const instance: *Obj.Instance = obj.as_instance();
self.mark_object(&instance.class.obj);
self.mark_table(&instance.fields);
},
}
}

View File

@ -3,6 +3,7 @@ const debug = std.debug;
const Allocator = std.mem.Allocator;
const Chunk = @import("./chunk.zig").Chunk;
const Table = @import("./table.zig").Table;
const Value = @import("./values.zig").Value;
const VM = @import("./vm.zig").VM;
@ -14,6 +15,8 @@ pub const ObjType = enum {
Native,
Closure,
Upvalue,
Class,
Instance,
};
pub const NativeFn = *const fn (vm: *VM, arg_count: usize, args: []Value) Value;
@ -46,6 +49,8 @@ pub const Obj = struct {
ObjType.Native => self.as_native().destroy(),
ObjType.Closure => self.as_closure().destroy(),
ObjType.Upvalue => self.as_upvalue().destroy(),
ObjType.Class => self.as_class().destroy(),
ObjType.Instance => self.as_instance().destroy(),
}
}
@ -158,6 +163,43 @@ pub const Obj = struct {
}
};
pub const Class = struct {
obj: Obj,
name: *Obj.String,
pub fn new(vm: *VM, name: *Obj.String) *Class {
const class_obj = Obj.new(Class, vm, ObjType.Class);
class_obj.name = name;
return class_obj;
}
pub fn destroy(self: *Class) void {
self.obj.allocator.destroy(self);
}
};
pub const Instance = struct {
obj: Obj,
class: *Obj.Class,
fields: Table,
pub fn new(vm: *VM, class: *Obj.Class) *Instance {
const instance_obj = Obj.new(Instance, vm, ObjType.Instance);
instance_obj.class = class;
instance_obj.fields = Table.new(vm.allocator);
return instance_obj;
}
pub fn destroy(self: *Instance) void {
self.fields.destroy();
self.obj.allocator.destroy(self);
}
};
pub fn is_type(self: *Obj, kind: ObjType) bool {
return self.kind == kind;
}
@ -182,6 +224,14 @@ pub const Obj = struct {
return self.is_type(ObjType.Upvalue);
}
pub fn is_class(self: *Obj) bool {
return self.is_type(ObjType.Class);
}
pub fn is_instance(self: *Obj) bool {
return self.is_type(ObjType.Instance);
}
pub fn print(self: *Obj) void {
switch (self.kind) {
ObjType.String => {
@ -206,6 +256,14 @@ pub const Obj = struct {
ObjType.Upvalue => {
debug.print("upvalue", .{});
},
ObjType.Class => {
const obj = self.as_class();
debug.print("{s}", .{obj.name.chars});
},
ObjType.Instance => {
const obj = self.as_instance();
debug.print("{s} instance", .{obj.class.name.chars});
},
}
}
@ -233,4 +291,14 @@ pub const Obj = struct {
std.debug.assert(self.kind == ObjType.Upvalue);
return @fieldParentPtr("obj", self);
}
pub fn as_class(self: *Obj) *Class {
std.debug.assert(self.kind == ObjType.Class);
return @fieldParentPtr("obj", self);
}
pub fn as_instance(self: *Obj) *Instance {
std.debug.assert(self.kind == ObjType.Instance);
return @fieldParentPtr("obj", self);
}
};

View File

@ -11,6 +11,8 @@ pub const OpCode = enum(u8) {
OP_SET_LOCAL,
OP_GET_UPVALUE,
OP_SET_UPVALUE,
OP_GET_PROPERTY,
OP_SET_PROPERTY,
OP_EQUAL,
OP_GREATER,
OP_LESS,
@ -28,4 +30,5 @@ pub const OpCode = enum(u8) {
OP_CLOSURE,
OP_CLOSE_UPVALUE,
OP_RETURN,
OP_CLASS,
};

View File

@ -316,7 +316,7 @@ pub const Scanner = struct {
pub fn identifier_type(self: *Scanner) TokenType {
return switch (self.source[self.start]) {
'a' => self.check_keyword(1, 2, "nd", TokenType.AND),
'c' => self.check_keyword(1, 4, "class", TokenType.CLASS),
'c' => self.check_keyword(1, 4, "lass", TokenType.CLASS),
'e' => self.check_keyword(1, 3, "lse", TokenType.ELSE),
'f' => if (self.current - self.start > 1) {
return switch (self.source[self.start + 1]) {

View File

@ -290,6 +290,42 @@ pub const VM = struct {
self.close_upvalues(&self.stack[self.stack_top - 1]);
_ = self.pop();
},
@intFromEnum(OpCode.OP_CLASS) => {
const name: *Obj.String = self.read_constant().as_string();
_ = try self.push(Value.obj_val(&Obj.Class.new(self, name).obj));
},
@intFromEnum(OpCode.OP_GET_PROPERTY) => {
if (!self.peek(0).is_obj() or !self.peek(0).as_obj().is_instance()) {
self.runtime_error("Only instances have properties.");
return InterpretResult.RUNTIME_ERROR;
}
const instance = self.peek(0).as_obj().as_instance();
const name = self.read_constant().as_string();
var value = Value.nil_val();
if (instance.fields.get(name, &value)) {
_ = self.pop();
_ = try self.push(value);
continue;
}
self.runtime_error("Undefined property"); // XXX to complete with name.chars
return InterpretResult.RUNTIME_ERROR;
},
@intFromEnum(OpCode.OP_SET_PROPERTY) => {
if (!self.peek(1).is_obj() or !self.peek(1).as_obj().is_instance()) {
self.runtime_error("Only instances have fields.");
return InterpretResult.RUNTIME_ERROR;
}
const instance = self.peek(1).as_obj().as_instance();
_ = instance.fields.set(self.read_constant().as_string(), self.peek(0));
const value = self.pop();
_ = self.pop();
_ = try self.push(value);
},
else => {
debug.print("Invalid instruction: {d}\n", .{instruction});
return InterpretResult.RUNTIME_ERROR;
@ -456,6 +492,12 @@ pub const VM = struct {
ObjType.Closure => {
return self.call(callee.as_obj().as_closure(), arg_count);
},
ObjType.Class => {
const class = callee.as_obj().as_class();
self.stack[self.stack_top - arg_count - 1] = Value.obj_val(&Obj.Instance.new(self, class).obj);
return true;
},
else => {},
}
}