implementing classes and instances (ch27)
This commit is contained in:
parent
2e6f977b34
commit
c8cd24afbf
@ -17,7 +17,7 @@ While reading [Crafting Interpreters](https://craftinginterpreters.com/), after
|
|||||||
- [x] 24 - Calls and Functions
|
- [x] 24 - Calls and Functions
|
||||||
- [x] 25 - Closures
|
- [x] 25 - Closures
|
||||||
- [x] 26 - Garbage Collection
|
- [x] 26 - Garbage Collection
|
||||||
- [ ] 27 - Classes and Instances
|
- [x] 27 - Classes and Instances
|
||||||
- [ ] 28 - Method and Initializers
|
- [ ] 28 - Method and Initializers
|
||||||
- [ ] 29 - Superclasses
|
- [ ] 29 - Superclasses
|
||||||
- [ ] 30 - Optimization
|
- [ ] 30 - Optimization
|
||||||
|
16
samples/ch27_classes_instances.lox
Normal file
16
samples/ch27_classes_instances.lox
Normal 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;
|
@ -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_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_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_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 => {
|
else => {
|
||||||
debug.print("unknown opcode {d}\n", .{instruction});
|
debug.print("unknown opcode {d}\n", .{instruction});
|
||||||
return offset + 1;
|
return offset + 1;
|
||||||
|
@ -249,7 +249,7 @@ pub const Parser = struct {
|
|||||||
TokenType.LEFT_BRACE => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
TokenType.LEFT_BRACE => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||||
TokenType.RIGHT_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.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.MINUS => ParserRule{ .prefix = unary, .infix = binary, .precedence = Precedence.Term },
|
||||||
TokenType.PLUS => ParserRule{ .prefix = null, .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 },
|
TokenType.SEMICOLON => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||||
@ -363,7 +363,9 @@ pub const Parser = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn declaration(self: *Parser) ParsingError!void {
|
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();
|
try self.fun_declaration();
|
||||||
} else if (self.match(TokenType.VAR)) {
|
} else if (self.match(TokenType.VAR)) {
|
||||||
try self.var_declaration();
|
try self.var_declaration();
|
||||||
@ -858,6 +860,30 @@ pub const Parser = struct {
|
|||||||
try self.emit_byte(@intFromEnum(OpCode.OP_RETURN));
|
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 {
|
const FunctionType = enum {
|
||||||
|
@ -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);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ const debug = std.debug;
|
|||||||
const Allocator = std.mem.Allocator;
|
const Allocator = std.mem.Allocator;
|
||||||
|
|
||||||
const Chunk = @import("./chunk.zig").Chunk;
|
const Chunk = @import("./chunk.zig").Chunk;
|
||||||
|
const Table = @import("./table.zig").Table;
|
||||||
const Value = @import("./values.zig").Value;
|
const Value = @import("./values.zig").Value;
|
||||||
const VM = @import("./vm.zig").VM;
|
const VM = @import("./vm.zig").VM;
|
||||||
|
|
||||||
@ -14,6 +15,8 @@ pub const ObjType = enum {
|
|||||||
Native,
|
Native,
|
||||||
Closure,
|
Closure,
|
||||||
Upvalue,
|
Upvalue,
|
||||||
|
Class,
|
||||||
|
Instance,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const NativeFn = *const fn (vm: *VM, arg_count: usize, args: []Value) Value;
|
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.Native => self.as_native().destroy(),
|
||||||
ObjType.Closure => self.as_closure().destroy(),
|
ObjType.Closure => self.as_closure().destroy(),
|
||||||
ObjType.Upvalue => self.as_upvalue().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 {
|
pub fn is_type(self: *Obj, kind: ObjType) bool {
|
||||||
return self.kind == kind;
|
return self.kind == kind;
|
||||||
}
|
}
|
||||||
@ -182,6 +224,14 @@ pub const Obj = struct {
|
|||||||
return self.is_type(ObjType.Upvalue);
|
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 {
|
pub fn print(self: *Obj) void {
|
||||||
switch (self.kind) {
|
switch (self.kind) {
|
||||||
ObjType.String => {
|
ObjType.String => {
|
||||||
@ -206,6 +256,14 @@ pub const Obj = struct {
|
|||||||
ObjType.Upvalue => {
|
ObjType.Upvalue => {
|
||||||
debug.print("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);
|
std.debug.assert(self.kind == ObjType.Upvalue);
|
||||||
return @fieldParentPtr("obj", self);
|
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);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -11,6 +11,8 @@ pub const OpCode = enum(u8) {
|
|||||||
OP_SET_LOCAL,
|
OP_SET_LOCAL,
|
||||||
OP_GET_UPVALUE,
|
OP_GET_UPVALUE,
|
||||||
OP_SET_UPVALUE,
|
OP_SET_UPVALUE,
|
||||||
|
OP_GET_PROPERTY,
|
||||||
|
OP_SET_PROPERTY,
|
||||||
OP_EQUAL,
|
OP_EQUAL,
|
||||||
OP_GREATER,
|
OP_GREATER,
|
||||||
OP_LESS,
|
OP_LESS,
|
||||||
@ -28,4 +30,5 @@ pub const OpCode = enum(u8) {
|
|||||||
OP_CLOSURE,
|
OP_CLOSURE,
|
||||||
OP_CLOSE_UPVALUE,
|
OP_CLOSE_UPVALUE,
|
||||||
OP_RETURN,
|
OP_RETURN,
|
||||||
|
OP_CLASS,
|
||||||
};
|
};
|
||||||
|
@ -316,7 +316,7 @@ pub const Scanner = struct {
|
|||||||
pub fn identifier_type(self: *Scanner) TokenType {
|
pub fn identifier_type(self: *Scanner) TokenType {
|
||||||
return switch (self.source[self.start]) {
|
return switch (self.source[self.start]) {
|
||||||
'a' => self.check_keyword(1, 2, "nd", TokenType.AND),
|
'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),
|
'e' => self.check_keyword(1, 3, "lse", TokenType.ELSE),
|
||||||
'f' => if (self.current - self.start > 1) {
|
'f' => if (self.current - self.start > 1) {
|
||||||
return switch (self.source[self.start + 1]) {
|
return switch (self.source[self.start + 1]) {
|
||||||
|
42
src/vm.zig
42
src/vm.zig
@ -290,6 +290,42 @@ pub const VM = struct {
|
|||||||
self.close_upvalues(&self.stack[self.stack_top - 1]);
|
self.close_upvalues(&self.stack[self.stack_top - 1]);
|
||||||
_ = self.pop();
|
_ = 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 => {
|
else => {
|
||||||
debug.print("Invalid instruction: {d}\n", .{instruction});
|
debug.print("Invalid instruction: {d}\n", .{instruction});
|
||||||
return InterpretResult.RUNTIME_ERROR;
|
return InterpretResult.RUNTIME_ERROR;
|
||||||
@ -456,6 +492,12 @@ pub const VM = struct {
|
|||||||
ObjType.Closure => {
|
ObjType.Closure => {
|
||||||
return self.call(callee.as_obj().as_closure(), arg_count);
|
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 => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user