Compare commits
2 Commits
c8cd24afbf
...
b522f05c8c
Author | SHA1 | Date | |
---|---|---|---|
b522f05c8c | |||
3c1c37799c |
9
samples/ch28_calling_methods.lox
Normal file
9
samples/ch28_calling_methods.lox
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
class Scone {
|
||||||
|
topping(first, second) {
|
||||||
|
return "scone with " + first + " and " + second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var scone = Scone();
|
||||||
|
var result = scone.topping("berries", "cream");
|
||||||
|
print result;
|
8
samples/ch28_methods_initializers.lox
Normal file
8
samples/ch28_methods_initializers.lox
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
class Brunch {
|
||||||
|
bacon() {}
|
||||||
|
eggs() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
var brunch = Brunch();
|
||||||
|
print brunch;
|
||||||
|
brunch.bacon();
|
11
samples/ch28_say_name.lox
Normal file
11
samples/ch28_say_name.lox
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
class Person {
|
||||||
|
sayName() {
|
||||||
|
print this.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var jane = Person();
|
||||||
|
jane.name = "Jane";
|
||||||
|
|
||||||
|
var method = jane.sayName;
|
||||||
|
method();
|
5
samples/ch28_this_outside.lox
Normal file
5
samples/ch28_this_outside.lox
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
print this;
|
||||||
|
|
||||||
|
fun invalid() {
|
||||||
|
print this;
|
||||||
|
}
|
@ -151,6 +151,7 @@ pub const Chunk = struct {
|
|||||||
@intFromEnum(OpCode.OP_CLASS) => return utils.constant_instruction("OP_CLASS", self, 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_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_SET_PROPERTY) => return utils.constant_instruction("OP_SET_PROPERTY", self, offset),
|
||||||
|
@intFromEnum(OpCode.OP_METHOD) => return utils.constant_instruction("OP_METHOD", self, offset),
|
||||||
else => {
|
else => {
|
||||||
debug.print("unknown opcode {d}\n", .{instruction});
|
debug.print("unknown opcode {d}\n", .{instruction});
|
||||||
return offset + 1;
|
return offset + 1;
|
||||||
|
@ -35,6 +35,10 @@ const Precedence = enum {
|
|||||||
|
|
||||||
const ParserFn = *const fn (*Parser, bool) ParsingError!void;
|
const ParserFn = *const fn (*Parser, bool) ParsingError!void;
|
||||||
|
|
||||||
|
const ClassCompiler = struct {
|
||||||
|
enclosing: ?*ClassCompiler,
|
||||||
|
};
|
||||||
|
|
||||||
const ParserRule = struct {
|
const ParserRule = struct {
|
||||||
prefix: ?ParserFn,
|
prefix: ?ParserFn,
|
||||||
infix: ?ParserFn,
|
infix: ?ParserFn,
|
||||||
@ -49,6 +53,7 @@ pub const Parser = struct {
|
|||||||
had_error: bool,
|
had_error: bool,
|
||||||
panic_mode: bool,
|
panic_mode: bool,
|
||||||
vm: *VM,
|
vm: *VM,
|
||||||
|
class_compiler: ?*ClassCompiler,
|
||||||
|
|
||||||
fn new(vm: *VM, compiler: *Compiler, scanner: *Scanner) Parser {
|
fn new(vm: *VM, compiler: *Compiler, scanner: *Scanner) Parser {
|
||||||
return Parser{
|
return Parser{
|
||||||
@ -59,6 +64,7 @@ pub const Parser = struct {
|
|||||||
.had_error = false,
|
.had_error = false,
|
||||||
.panic_mode = false,
|
.panic_mode = false,
|
||||||
.vm = vm,
|
.vm = vm,
|
||||||
|
.class_compiler = null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,6 +72,10 @@ pub const Parser = struct {
|
|||||||
return self.compiler.function.chunk;
|
return self.compiler.function.chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fn current_class_compiler(self: *Parser) ?*ClassCompiler {
|
||||||
|
return self.class_compiler;
|
||||||
|
}
|
||||||
|
|
||||||
fn advance(self: *Parser) void {
|
fn advance(self: *Parser) void {
|
||||||
self.previous = self.current;
|
self.previous = self.current;
|
||||||
|
|
||||||
@ -278,7 +288,7 @@ pub const Parser = struct {
|
|||||||
TokenType.PRINT => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
TokenType.PRINT => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||||
TokenType.RETURN => 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 = null, .infix = null, .precedence = Precedence.None },
|
||||||
TokenType.THIS => ParserRule{ .prefix = null, .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.TRUE => ParserRule{ .prefix = literal, .infix = null, .precedence = Precedence.None },
|
||||||
TokenType.VAR => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
TokenType.VAR => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||||
TokenType.WHILE => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
TokenType.WHILE => ParserRule{ .prefix = null, .infix = null, .precedence = Precedence.None },
|
||||||
@ -863,14 +873,29 @@ pub const Parser = struct {
|
|||||||
|
|
||||||
fn class_declaration(self: *Parser) ParsingError!void {
|
fn class_declaration(self: *Parser) ParsingError!void {
|
||||||
self.consume(TokenType.IDENTIFIER, "Expect class name.");
|
self.consume(TokenType.IDENTIFIER, "Expect class name.");
|
||||||
|
const class_name = self.previous.?;
|
||||||
|
|
||||||
const name_constant = try self.identifier_constant(self.previous.?);
|
const name_constant = try self.identifier_constant(self.previous.?);
|
||||||
self.declare_variable();
|
self.declare_variable();
|
||||||
|
|
||||||
try self.emit_bytes(@intFromEnum(OpCode.OP_CLASS), name_constant);
|
try self.emit_bytes(@intFromEnum(OpCode.OP_CLASS), name_constant);
|
||||||
try self.define_variable(name_constant);
|
try self.define_variable(name_constant);
|
||||||
|
|
||||||
|
var class_compiler = ClassCompiler{
|
||||||
|
.enclosing = self.current_class_compiler(),
|
||||||
|
};
|
||||||
|
self.class_compiler = &class_compiler;
|
||||||
|
|
||||||
|
try self.named_variable(class_name, false);
|
||||||
|
|
||||||
self.consume(TokenType.LEFT_BRACE, "Expect '{' before class body.");
|
self.consume(TokenType.LEFT_BRACE, "Expect '{' before class body.");
|
||||||
|
while (!self.check(TokenType.RIGHT_BRACE) and !self.check(TokenType.EOF)) {
|
||||||
|
try self.method();
|
||||||
|
}
|
||||||
self.consume(TokenType.RIGHT_BRACE, "Expect '}' after class body.");
|
self.consume(TokenType.RIGHT_BRACE, "Expect '}' after class body.");
|
||||||
|
try self.emit_byte(@intFromEnum(OpCode.OP_POP));
|
||||||
|
|
||||||
|
self.class_compiler = self.current_class_compiler().?.enclosing;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dot(self: *Parser, can_assign: bool) ParsingError!void {
|
fn dot(self: *Parser, can_assign: bool) ParsingError!void {
|
||||||
@ -884,11 +909,30 @@ pub const Parser = struct {
|
|||||||
try self.emit_bytes(@intFromEnum(OpCode.OP_GET_PROPERTY), name);
|
try self.emit_bytes(@intFromEnum(OpCode.OP_GET_PROPERTY), name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn method(self: *Parser) ParsingError!void {
|
||||||
|
self.consume(TokenType.IDENTIFIER, "Expect method name.");
|
||||||
|
const constant = try self.identifier_constant(self.previous.?);
|
||||||
|
|
||||||
|
try self.function(FunctionType.Method);
|
||||||
|
try self.emit_bytes(@intFromEnum(OpCode.OP_METHOD), constant);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn this_(self: *Parser, can_assign: bool) ParsingError!void {
|
||||||
|
if (self.current_class_compiler() == null) {
|
||||||
|
self.error_msg("Can't use 'this' outside of a class.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = can_assign;
|
||||||
|
try self.variable(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const FunctionType = enum {
|
const FunctionType = enum {
|
||||||
Function,
|
Function,
|
||||||
Script,
|
Script,
|
||||||
|
Method,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Compiler = struct {
|
pub const Compiler = struct {
|
||||||
@ -915,6 +959,8 @@ pub const Compiler = struct {
|
|||||||
.enclosing = enclosing,
|
.enclosing = enclosing,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
compiler.local_count += 1;
|
||||||
|
|
||||||
compiler.locals[0].depth = 0;
|
compiler.locals[0].depth = 0;
|
||||||
compiler.locals[0].name = Token{
|
compiler.locals[0].name = Token{
|
||||||
.token_type = TokenType.EOF,
|
.token_type = TokenType.EOF,
|
||||||
@ -924,7 +970,10 @@ pub const Compiler = struct {
|
|||||||
};
|
};
|
||||||
compiler.locals[0].is_captured = false;
|
compiler.locals[0].is_captured = false;
|
||||||
|
|
||||||
compiler.local_count += 1;
|
if (function_type != FunctionType.Function) {
|
||||||
|
compiler.locals[0].name.start = "this";
|
||||||
|
compiler.locals[0].name.length = 4;
|
||||||
|
}
|
||||||
|
|
||||||
return compiler;
|
return compiler;
|
||||||
}
|
}
|
||||||
|
@ -237,12 +237,18 @@ pub const ZloxAllocator = struct {
|
|||||||
ObjType.Class => {
|
ObjType.Class => {
|
||||||
const class: *Obj.Class = obj.as_class();
|
const class: *Obj.Class = obj.as_class();
|
||||||
self.mark_object(&class.name.obj);
|
self.mark_object(&class.name.obj);
|
||||||
|
self.mark_table(&class.methods);
|
||||||
},
|
},
|
||||||
ObjType.Instance => {
|
ObjType.Instance => {
|
||||||
const instance: *Obj.Instance = obj.as_instance();
|
const instance: *Obj.Instance = obj.as_instance();
|
||||||
self.mark_object(&instance.class.obj);
|
self.mark_object(&instance.class.obj);
|
||||||
self.mark_table(&instance.fields);
|
self.mark_table(&instance.fields);
|
||||||
},
|
},
|
||||||
|
ObjType.BoundMethod => {
|
||||||
|
const bound_method: *Obj.BoundMethod = obj.as_bound_method();
|
||||||
|
self.mark_value(&bound_method.receiver);
|
||||||
|
self.mark_object(&bound_method.method.obj);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ pub const ObjType = enum {
|
|||||||
Upvalue,
|
Upvalue,
|
||||||
Class,
|
Class,
|
||||||
Instance,
|
Instance,
|
||||||
|
BoundMethod,
|
||||||
};
|
};
|
||||||
|
|
||||||
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;
|
||||||
@ -51,6 +52,7 @@ pub const Obj = struct {
|
|||||||
ObjType.Upvalue => self.as_upvalue().destroy(),
|
ObjType.Upvalue => self.as_upvalue().destroy(),
|
||||||
ObjType.Class => self.as_class().destroy(),
|
ObjType.Class => self.as_class().destroy(),
|
||||||
ObjType.Instance => self.as_instance().destroy(),
|
ObjType.Instance => self.as_instance().destroy(),
|
||||||
|
ObjType.BoundMethod => self.as_bound_method().destroy(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,16 +168,19 @@ pub const Obj = struct {
|
|||||||
pub const Class = struct {
|
pub const Class = struct {
|
||||||
obj: Obj,
|
obj: Obj,
|
||||||
name: *Obj.String,
|
name: *Obj.String,
|
||||||
|
methods: Table,
|
||||||
|
|
||||||
pub fn new(vm: *VM, name: *Obj.String) *Class {
|
pub fn new(vm: *VM, name: *Obj.String) *Class {
|
||||||
const class_obj = Obj.new(Class, vm, ObjType.Class);
|
const class_obj = Obj.new(Class, vm, ObjType.Class);
|
||||||
|
|
||||||
class_obj.name = name;
|
class_obj.name = name;
|
||||||
|
class_obj.methods = Table.new(vm.allocator);
|
||||||
|
|
||||||
return class_obj;
|
return class_obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy(self: *Class) void {
|
pub fn destroy(self: *Class) void {
|
||||||
|
self.methods.destroy();
|
||||||
self.obj.allocator.destroy(self);
|
self.obj.allocator.destroy(self);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -200,6 +205,25 @@ pub const Obj = struct {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const BoundMethod = struct {
|
||||||
|
obj: Obj,
|
||||||
|
receiver: Value,
|
||||||
|
method: *Obj.Closure,
|
||||||
|
|
||||||
|
pub fn new(vm: *VM, receiver: Value, method: *Obj.Closure) *BoundMethod {
|
||||||
|
const bound_method = Obj.new(BoundMethod, vm, ObjType.BoundMethod);
|
||||||
|
|
||||||
|
bound_method.receiver = receiver;
|
||||||
|
bound_method.method = method;
|
||||||
|
|
||||||
|
return bound_method;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destroy(self: *BoundMethod) void {
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
@ -232,6 +256,10 @@ pub const Obj = struct {
|
|||||||
return self.is_type(ObjType.Instance);
|
return self.is_type(ObjType.Instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_bound_method(self: *Obj) bool {
|
||||||
|
return self.is_type(ObjType.BoundMethod);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn print(self: *Obj) void {
|
pub fn print(self: *Obj) void {
|
||||||
switch (self.kind) {
|
switch (self.kind) {
|
||||||
ObjType.String => {
|
ObjType.String => {
|
||||||
@ -264,6 +292,10 @@ pub const Obj = struct {
|
|||||||
const obj = self.as_instance();
|
const obj = self.as_instance();
|
||||||
debug.print("{s} instance", .{obj.class.name.chars});
|
debug.print("{s} instance", .{obj.class.name.chars});
|
||||||
},
|
},
|
||||||
|
ObjType.BoundMethod => {
|
||||||
|
const obj = self.as_bound_method();
|
||||||
|
obj.method.function.obj.print();
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -301,4 +333,9 @@ pub const Obj = struct {
|
|||||||
std.debug.assert(self.kind == ObjType.Instance);
|
std.debug.assert(self.kind == ObjType.Instance);
|
||||||
return @fieldParentPtr("obj", self);
|
return @fieldParentPtr("obj", self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_bound_method(self: *Obj) *BoundMethod {
|
||||||
|
std.debug.assert(self.kind == ObjType.BoundMethod);
|
||||||
|
return @fieldParentPtr("obj", self);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
@ -31,4 +31,5 @@ pub const OpCode = enum(u8) {
|
|||||||
OP_CLOSE_UPVALUE,
|
OP_CLOSE_UPVALUE,
|
||||||
OP_RETURN,
|
OP_RETURN,
|
||||||
OP_CLASS,
|
OP_CLASS,
|
||||||
|
OP_METHOD,
|
||||||
};
|
};
|
||||||
|
34
src/vm.zig
34
src/vm.zig
@ -311,8 +311,9 @@ pub const VM = struct {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.runtime_error("Undefined property"); // XXX to complete with name.chars
|
if (!self.bind_method(instance.class, name)) {
|
||||||
return InterpretResult.RUNTIME_ERROR;
|
return InterpretResult.RUNTIME_ERROR;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
@intFromEnum(OpCode.OP_SET_PROPERTY) => {
|
@intFromEnum(OpCode.OP_SET_PROPERTY) => {
|
||||||
if (!self.peek(1).is_obj() or !self.peek(1).as_obj().is_instance()) {
|
if (!self.peek(1).is_obj() or !self.peek(1).as_obj().is_instance()) {
|
||||||
@ -326,6 +327,9 @@ pub const VM = struct {
|
|||||||
_ = self.pop();
|
_ = self.pop();
|
||||||
_ = try self.push(value);
|
_ = try self.push(value);
|
||||||
},
|
},
|
||||||
|
@intFromEnum(OpCode.OP_METHOD) => {
|
||||||
|
self.define_method(self.read_constant().as_string());
|
||||||
|
},
|
||||||
else => {
|
else => {
|
||||||
debug.print("Invalid instruction: {d}\n", .{instruction});
|
debug.print("Invalid instruction: {d}\n", .{instruction});
|
||||||
return InterpretResult.RUNTIME_ERROR;
|
return InterpretResult.RUNTIME_ERROR;
|
||||||
@ -498,6 +502,11 @@ pub const VM = struct {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
ObjType.BoundMethod => {
|
||||||
|
const bound_method = callee.as_obj().as_bound_method();
|
||||||
|
self.stack[self.stack_top - arg_count - 1] = bound_method.receiver;
|
||||||
|
return self.call(bound_method.method, arg_count);
|
||||||
|
},
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -505,6 +514,21 @@ pub const VM = struct {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bind_method(self: *VM, class: *Obj.Class, name: *Obj.String) bool {
|
||||||
|
var method: Value = Value.nil_val();
|
||||||
|
|
||||||
|
if (!class.methods.get(name, &method)) {
|
||||||
|
self.runtime_error("Undefined property."); // XXX add name.chars as '%s'.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bound: *Obj.BoundMethod = Obj.BoundMethod.new(self, self.peek(0), method.as_obj().as_closure());
|
||||||
|
_ = self.pop();
|
||||||
|
try self.push(Value.obj_val(&bound.obj));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn call(self: *VM, closure: *Obj.Closure, arg_count: usize) bool {
|
pub fn call(self: *VM, closure: *Obj.Closure, arg_count: usize) bool {
|
||||||
if (arg_count != closure.function.arity) {
|
if (arg_count != closure.function.arity) {
|
||||||
self.runtime_error("Invalid argument count.");
|
self.runtime_error("Invalid argument count.");
|
||||||
@ -571,4 +595,12 @@ pub const VM = struct {
|
|||||||
self.open_upvalues = upvalue.next;
|
self.open_upvalues = upvalue.next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn define_method(self: *VM, name: *Obj.String) void {
|
||||||
|
const method = self.peek(0);
|
||||||
|
const class = self.peek(1).as_obj().as_class();
|
||||||
|
|
||||||
|
_ = class.methods.set(name, method);
|
||||||
|
_ = self.pop();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user