Compare commits

...

8 Commits

21 changed files with 398 additions and 33 deletions

View File

@ -18,9 +18,9 @@ While reading [Crafting Interpreters](https://craftinginterpreters.com/), after
- [x] 25 - Closures
- [x] 26 - Garbage Collection
- [x] 27 - Classes and Instances
- [ ] 28 - Method and Initializers
- [ ] 29 - Superclasses
- [ ] 30 - Optimization
- [x] 28 - Method and Initializers
- [x] 29 - Superclasses
- [x] 30 - Optimization (without NaN boxing/NaN tagging)
## Build & run

0
empty.lox Normal file
View File

View File

@ -0,0 +1,14 @@
class CoffeeMaker {
init(coffee) {
this.coffee = coffee;
}
brew() {
print "Enjoy your cup of " + this.coffee;
// No reusing the grounds!
this.coffee = nil;
}
}
var maker = CoffeeMaker("coffee and chicory");
print maker;
maker.brew();

View File

@ -0,0 +1,12 @@
class Brunch {
bacon() {
print "calling bacon()";
}
eggs() {
print "calling eggs()";
}
}
var brunch = Brunch();
print brunch;
brunch.bacon();

View File

@ -1,8 +1,4 @@
class Brunch {
bacon() {}
eggs() {}
init(food, drink) {}
}
var brunch = Brunch();
print brunch;
brunch.bacon();
Brunch("eggs", "coffee");

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -0,0 +1,2 @@
class Cake < Cake {
}

View File

@ -0,0 +1,2 @@
var NotClass = "So not a class";
class OhNo < NotClass {}

View File

@ -0,0 +1,17 @@
class Brunch {
var food;
var drink;
init(food, drink) {
this.food = food;
this.drink = drink;
}
fun get_recipe() {
return this.food + " " + this.drink;
}
}
var instance = Brunch("eggs", "coffee");
print instance;
print instance.get_recipe();

3
samples/indices.lox Normal file
View File

@ -0,0 +1,3 @@
var alphabet = "abcdefghijklmnopqrstuvwxyz"; var X = alphabet; print "hello " + X[3] + X[0] + X[10];
var s = " "; s[0] = "d"; s[1] = "a"; s[2] = "k"; var hey = "hello " + s + "!"; print hey;

View File

@ -73,18 +73,18 @@ pub const Chunk = struct {
debug.print("== end of chunk dump \n\n", .{});
}
pub fn dissassemble(self: Chunk, name: []const u8) void {
pub fn disassemble(self: Chunk, name: []const u8) void {
debug.print("== {s} ==\n", .{name});
var offset: usize = 0;
while (offset < self.count) {
offset = self.dissassemble_instruction(offset);
offset = self.disassemble_instruction(offset);
}
debug.print("== end of {s} ==\n\n", .{name});
}
pub fn dissassemble_instruction(self: Chunk, offset: usize) usize {
pub fn disassemble_instruction(self: Chunk, offset: usize) usize {
var current_offset = offset;
debug.print("{d:0>4} ", .{offset});
@ -152,6 +152,12 @@ pub const Chunk = struct {
@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),
@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;

View File

@ -29,6 +29,7 @@ const Precedence = enum {
Term,
Factor,
Unary,
Index,
Call,
Primary,
};
@ -37,6 +38,7 @@ const ParserFn = *const fn (*Parser, bool) ParsingError!void;
const ClassCompiler = struct {
enclosing: ?*ClassCompiler,
has_super_class: bool,
};
const ParserRule = struct {
@ -144,15 +146,26 @@ pub const Parser = struct {
}
fn emit_return(self: *Parser) ParsingError!void {
if (self.compiler.function_type == FunctionType.Initializer) {
try self.emit_bytes(@intFromEnum(OpCode.OP_GET_LOCAL), 0);
} else {
try self.emit_byte(@intFromEnum(OpCode.OP_NIL));
}
try self.emit_byte(@intFromEnum(OpCode.OP_RETURN));
}
fn end_parser(self: *Parser) !*Obj.Function {
try self.emit_return();
const compiler_function = self.compiler.function;
if (!self.had_error and constants.DEBUG_PRINT_CODE) {
self.current_chunk().dissassemble("code");
var label: []const u8 = "<script>";
if (compiler_function.name != null) {
label = compiler_function.name.?.chars;
}
self.current_chunk().disassemble(label);
}
const function_obj = self.compiler.function;
@ -201,6 +214,18 @@ pub const Parser = struct {
self.consume(TokenType.RIGHT_PAREN, "Expect ')' after expression.");
}
fn index_(self: *Parser, can_assign: bool) ParsingError!void {
try self.expression();
self.consume(TokenType.RIGHT_BRACKET, "Expecting ']");
if (can_assign and self.match(TokenType.EQUAL)) {
try self.expression();
try self.emit_byte(@intFromEnum(OpCode.OP_INDEX_SET));
} else {
try self.emit_byte(@intFromEnum(OpCode.OP_INDEX_GET));
}
}
fn unary(self: *Parser, can_assign: bool) ParsingError!void {
_ = can_assign;
@ -262,6 +287,8 @@ pub const Parser = struct {
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.LEFT_BRACKET => ParserRule{ .prefix = null, .infix = index_, .precedence = Precedence.Unary },
TokenType.RIGHT_BRACKET => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
TokenType.SEMICOLON => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
TokenType.SLASH => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Factor },
TokenType.STAR => ParserRule{ .prefix = null, .infix = binary, .precedence = Precedence.Factor },
@ -287,7 +314,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 },
@ -364,6 +391,17 @@ pub const Parser = struct {
set_op = OpCode.OP_SET_GLOBAL;
}
// handle assignments by index:
// - push value to the stack
// - modify it (through a OP_INDEX_SET)
// - update the variable
if (can_assign and self.match(TokenType.LEFT_BRACKET)) {
try self.emit_bytes(@intFromEnum(get_op), @intCast(arg));
try self.index_(can_assign);
try self.emit_bytes(@intFromEnum(set_op), @intCast(arg));
return;
}
if (can_assign and self.match(TokenType.EQUAL)) {
try self.expression();
try self.emit_bytes(@intFromEnum(set_op), @intCast(arg));
@ -865,6 +903,9 @@ pub const Parser = struct {
if (self.match(TokenType.SEMICOLON)) {
try self.emit_return();
} else {
if (self.compiler.function_type == FunctionType.Initializer) {
self.error_msg("Can't return a value from an initialiaer");
}
try self.expression();
self.consume(TokenType.SEMICOLON, "Expect ';' after return value.");
try self.emit_byte(@intFromEnum(OpCode.OP_RETURN));
@ -883,9 +924,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.");
@ -895,9 +955,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.?);
@ -905,6 +979,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);
}
@ -914,7 +992,12 @@ pub const Parser = struct {
self.consume(TokenType.IDENTIFIER, "Expect method name.");
const constant = try self.identifier_constant(self.previous.?);
try self.function(FunctionType.Method);
var function_type: FunctionType = FunctionType.Method;
// std.debug.print("len: {d} {s}\n", .{self.previous.?.length, });
if (self.previous.?.length == 4 and std.mem.eql(u8, self.previous.?.start[0..4], "init")) {
function_type = FunctionType.Initializer;
}
try self.function(function_type);
try self.emit_bytes(@intFromEnum(OpCode.OP_METHOD), constant);
}
@ -927,12 +1010,39 @@ 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 {
Function,
Script,
Method,
Initializer,
};
pub const Compiler = struct {

View File

@ -72,7 +72,6 @@ pub fn main() !void {
} else {
vm.init_vm(allocator);
}
defer vm.destroy();
if (args.len == 1) {

View File

@ -25,7 +25,7 @@ pub const ZloxAllocator = struct {
.parent_allocator = parent_allocator,
.vm = vm,
.bytes_allocated = 0,
.next_gc = 1024,
.next_gc = 4096,
.current_gc = false,
};
}
@ -145,6 +145,8 @@ pub const ZloxAllocator = struct {
self.mark_table(&self.vm.globals);
self.mark_compiler_roots();
self.mark_object(&self.vm.init_string.?.obj);
}
pub fn mark_value(self: *Self, value: *Value) void {
@ -264,6 +266,9 @@ pub const ZloxAllocator = struct {
for (0..table.capacity) |idx| {
const entry: *Entry = &table.entries[idx];
if (entry.key != null and !entry.key.?.obj.is_marked) {
if (comptime constants.DEBUG_LOG_GC) {
std.debug.print("GC: table_remove_white: deleting {s}\n", .{entry.key.?.chars});
}
_ = table.del(entry.key.?);
}
}
@ -288,7 +293,7 @@ pub const ZloxAllocator = struct {
self.vm.objects = object;
}
if (comptime constants.DEBUG_LOG_GC == true) {
if (comptime constants.DEBUG_LOG_GC) {
std.debug.print("GC: sweeping {*}: ", .{unreached});
unreached.print();
std.debug.print("\n", .{});

View File

@ -32,4 +32,10 @@ pub const OpCode = enum(u8) {
OP_RETURN,
OP_CLASS,
OP_METHOD,
OP_INDEX_SET,
OP_INDEX_GET,
OP_INVOKE,
OP_INHERIT,
OP_GET_SUPER,
OP_SUPER_INVOKE,
};

View File

@ -6,6 +6,8 @@ pub const TokenType = enum {
RIGHT_PAREN,
LEFT_BRACE,
RIGHT_BRACE,
LEFT_BRACKET,
RIGHT_BRACKET,
COMMA,
DOT,
MINUS,
@ -55,6 +57,8 @@ pub const TokenType = enum {
TokenType.RIGHT_PAREN => "RIGHT_PAREN",
TokenType.LEFT_BRACE => "LEFT_BRACE",
TokenType.RIGHT_BRACE => "RIGHT_BRACE",
TokenType.LEFT_BRACKET => "LEFT_BRACKET",
TokenType.RIGHT_BRACKET => "RIGHT_BRACKET",
TokenType.COMMA => "COMMA",
TokenType.DOT => "DOT",
TokenType.MINUS => "MINUS",
@ -140,6 +144,8 @@ pub const Scanner = struct {
')' => self.make_token(TokenType.RIGHT_PAREN),
'{' => self.make_token(TokenType.LEFT_BRACE),
'}' => self.make_token(TokenType.RIGHT_BRACE),
'[' => self.make_token(TokenType.LEFT_BRACKET),
']' => self.make_token(TokenType.RIGHT_BRACKET),
';' => self.make_token(TokenType.SEMICOLON),
',' => self.make_token(TokenType.COMMA),
'.' => self.make_token(TokenType.DOT),

View File

@ -61,11 +61,11 @@ pub const Table = struct {
pub fn find_entry(entries: []Entry, key: *Obj.String) ?*Entry {
var tombstone: ?*Entry = null;
var index = key.hash % entries.len;
// var index = key.hash % entries.len;
var index = key.hash & (entries.len - 1);
while (true) {
const entry = &entries[index];
if (entry.key == null) {
if (entry.value.is_nil()) {
// Empty entry.
@ -85,7 +85,8 @@ pub const Table = struct {
return entry;
}
index = (index + 1) % entries.len;
// index = (index + 1) % entries.len;
index = (index + 1) & (entries.len - 1);
}
}
@ -121,7 +122,7 @@ pub const Table = struct {
std.debug.print("== Hash table count:{} capacity:{} ==\n", .{ self.count, self.capacity });
for (self.entries, 0..) |entry, idx| {
if (entry.key != null) {
std.debug.print("{d} ({d}) - {s}: ", .{ idx, entry.key.?.hash, entry.key.?.chars });
std.debug.print("{d} {*} (size: {d} hash:{d}) - {s}: ", .{ idx, entry.key, entry.key.?.chars.len, entry.key.?.hash, entry.key.?.chars });
entry.value.type_print();
std.debug.print("\n", .{});
}
@ -133,12 +134,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);
}
}
@ -180,7 +181,8 @@ pub const Table = struct {
return null;
}
var index = hash % self.capacity;
// var index = hash % self.capacity;
var index = hash & (self.capacity - 1);
while (true) {
const entry = &self.entries[index];
if (entry.key == null) {
@ -192,7 +194,8 @@ pub const Table = struct {
return entry.key;
}
index = (index + 1) % self.capacity;
// index = (index + 1) % self.capacity;
index = (index + 1) & (self.capacity - 1);
}
}
};

View File

@ -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;
}

View File

@ -49,6 +49,7 @@ pub const VM = struct {
gray_count: usize,
gray_capacity: usize,
gray_stack: ?[]*Obj,
init_string: ?*Obj.String,
pub fn new() VM {
const vm = VM{
@ -65,6 +66,7 @@ pub const VM = struct {
.gray_capacity = 0,
.gray_count = 0,
.gray_stack = &.{},
.init_string = null,
};
return vm;
@ -75,6 +77,9 @@ pub const VM = struct {
self.globals = Table.new(self.allocator);
self.strings = Table.new(self.allocator);
_ = try self.push(Value.obj_val(&self.copy_string("init").obj));
self.init_string = self.pop().as_string();
self.define_native("clock", natives.clock);
self.define_native("power", natives.power);
self.define_native("str2num", natives.str2num);
@ -88,6 +93,7 @@ pub const VM = struct {
self.strings.destroy();
self.globals.destroy();
self.init_string = null;
self.destroy_objects();
if (self.gray_stack != null) {
@ -108,7 +114,9 @@ pub const VM = struct {
var obj = self.objects;
while (obj != null) {
const obj_next = obj.?.next;
std.debug.print("OBJ: {*}\n", .{obj.?});
std.debug.print("OBJ: {*} {any}", .{ obj.?, obj.?.kind });
// obj.?.print();
std.debug.print("\n", .{});
obj = obj_next;
}
}
@ -148,7 +156,7 @@ pub const VM = struct {
}
debug.print("\n", .{});
}
_ = self.current_chunk().dissassemble_instruction(self.current_frame().ip);
_ = self.current_chunk().disassemble_instruction(self.current_frame().ip);
}
const instruction = self.read_byte();
@ -330,6 +338,76 @@ pub const VM = struct {
@intFromEnum(OpCode.OP_METHOD) => {
self.define_method(self.read_constant().as_string());
},
@intFromEnum(OpCode.OP_INDEX_GET) => {
if (!self.peek(0).is_number() or !self.peek(1).is_string()) {
self.runtime_error("A number and a string are required for indexes.");
return InterpretResult.RUNTIME_ERROR;
}
const index_val = self.pop();
const value = self.pop();
const index: usize = @as(usize, @intFromFloat(index_val.as_number()));
if (index >= value.as_cstring().len) {
self.runtime_error("The index must be set between 0 and string len.");
return InterpretResult.RUNTIME_ERROR;
}
const c = value.as_cstring()[index .. index + 1];
_ = try self.push(Value.obj_val(&self.copy_string(c).obj));
},
@intFromEnum(OpCode.OP_INDEX_SET) => {
const value = self.pop();
const index_val = self.pop();
const origin = self.pop();
if (!value.is_string() or value.as_cstring().len != 1) {
self.runtime_error("Value to assign must be one byte.");
return InterpretResult.RUNTIME_ERROR;
}
const index: usize = @as(usize, @intFromFloat(index_val.as_number()));
var str = self.allocator.dupe(u8, origin.as_cstring()) catch unreachable;
str[index] = value.as_cstring()[0];
_ = 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;
}
},
@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;
@ -500,6 +578,14 @@ pub const VM = struct {
const class = callee.as_obj().as_class();
self.stack[self.stack_top - arg_count - 1] = Value.obj_val(&Obj.Instance.new(self, class).obj);
var initializer = Value.nil_val();
if (class.methods.get(self.init_string.?, &initializer) == true) {
return self.call(initializer.as_obj().as_closure(), arg_count);
} else if (arg_count != 0) {
self.runtime_error("Expected 0 arguments."); // XXX show number of arguments.
}
return true;
},
ObjType.BoundMethod => {
@ -518,7 +604,10 @@ pub const VM = struct {
var method: Value = Value.nil_val();
if (!class.methods.get(name, &method)) {
self.runtime_error("Undefined property."); // XXX add name.chars as '%s'.
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;
}
@ -531,8 +620,9 @@ pub const VM = struct {
pub fn call(self: *VM, closure: *Obj.Closure, arg_count: usize) bool {
if (arg_count != closure.function.arity) {
self.runtime_error("Invalid argument count.");
// runtimeError("Expected %d arguments but got %d.", function->arity, argCount);
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;
}
@ -603,4 +693,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);
}
};