Skip to content

Commit 287fc4e

Browse files
committed
Bound methods
1 parent f6addca commit 287fc4e

6 files changed

Lines changed: 66 additions & 25 deletions

File tree

src/compiler.cpp

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ using lox::scanner::TokenType;
129129
Parser::Parser(std::unique_ptr<Scanner> scanner, ObjFunction* fnptr, GC& gc)
130130
: scanner(std::move(scanner)), current(SENTINEL_EOF),
131131
previous(SENTINEL_EOF), errmsg(std::nullopt), gc(gc),
132-
compiler(std::make_unique<Compiler>(fnptr)) {}
132+
compiler(std::make_unique<Compiler>(fnptr, nullptr, false)) {}
133133

134134
void Parser::advance() {
135135
previous = current;
@@ -139,7 +139,7 @@ void Parser::advance() {
139139
}
140140
}
141141

142-
void Parser::function(bool define) {
142+
void Parser::function(bool is_class_method) {
143143
// Parse the name and arity of the function: that will let us create the
144144
// ObjFunction object
145145
consume_or_error(TokenType::IDENTIFIER, "expected function name");
@@ -150,8 +150,8 @@ void Parser::function(bool define) {
150150
size_t arity = 0;
151151
// TODO: This copies the string. Do we need to?
152152
auto new_fnptr = gc.alloc<ObjFunction>(fn_name, arity);
153-
auto new_compiler =
154-
std::make_unique<Compiler>(new_fnptr, std::move(compiler));
153+
auto new_compiler = std::make_unique<Compiler>(new_fnptr, std::move(compiler),
154+
is_class_method);
155155
compiler = std::move(new_compiler);
156156
compiler->begin_scope();
157157
// Parse parameters (if there are any).
@@ -194,7 +194,7 @@ void Parser::function(bool define) {
194194
emit(upvalue.is_local ? 1 : 0);
195195
emit(static_cast<uint8_t>(upvalue.index));
196196
}
197-
if (define) {
197+
if (!is_class_method) {
198198
// This makes `fn_name` available either as a local variable (if it's in
199199
// an inner scope) or a global variable. However, we don't always want to
200200
// do that: for example if we're parsing a class method, then the
@@ -392,7 +392,7 @@ void Parser::declaration() {
392392
if (consume_if(TokenType::VAR)) {
393393
var_declaration();
394394
} else if (consume_if(TokenType::FUN)) {
395-
function(true);
395+
function(false);
396396
} else if (consume_if(TokenType::CLASS)) {
397397
class_declaration();
398398
} else {
@@ -441,7 +441,7 @@ void Parser::class_declaration() {
441441
void Parser::method() {
442442
// This parses the function body and emits code to create an ObjClosure and
443443
// put it at the top of the stack.
444-
function(false);
444+
function(true);
445445
// We then need to tell the VM to read the ObjClosure at the top of the stack
446446
// and store it in the method table of the current class. The book just calls
447447
// this 'METHOD' but I've chosen to give it a more descriptive name.
@@ -723,6 +723,13 @@ void Parser::number(bool _) {
723723
emit_constant(value);
724724
}
725725

726+
void Parser::this_(bool _) {
727+
// 1. We can't assign to this, hence the false.
728+
// 2. We just treat 'this' like a local variable that happens to be at index
729+
// 0.
730+
variable(false);
731+
}
732+
726733
void Parser::literal(bool _) {
727734
switch (previous.type) {
728735
case TokenType::FALSE:

src/compiler.hpp

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,18 @@ struct Local {
3232

3333
class Compiler {
3434
public:
35-
Compiler(ObjFunction* fnptr)
36-
: scope_depth(0), current_function(fnptr), is_top_level(true),
37-
parent(nullptr) {
38-
// Reserve slot 0 of the stack for VM internal use. Note that this, on its
39-
// own, will mess with the VM's state since this does not actually push to
40-
// the stack at runtime. We have to 'balance' this out in the VM by pushing
41-
// the pointer to the current function onto the stack before we start
42-
// executing the chunk.
43-
declare_local("");
44-
}
45-
Compiler(ObjFunction* fnptr, std::unique_ptr<Compiler> parent)
35+
Compiler(ObjFunction* fnptr, std::unique_ptr<Compiler> parent,
36+
bool is_class_method)
4637
: scope_depth(0), current_function(fnptr), is_top_level(false),
4738
parent(std::move(parent)) {
48-
declare_local("");
39+
if (is_class_method) {
40+
// For a class method, we reserve slot 0 for the 'this' variable.
41+
declare_local("this");
42+
} else {
43+
// For a non-class method, we reserve slot 0 to mimic the above, but
44+
// we give it a name that can't ever be referred to.
45+
declare_local("");
46+
}
4947
}
5048

5149
void mark_function_as_grey(GC& _gc);
@@ -152,6 +150,7 @@ class Parser {
152150
void string(bool can_assign);
153151
void variable(bool can_assign);
154152
void literal(bool can_assign);
153+
void this_(bool can_assign);
155154
void define_variable(std::string_view var_name);
156155
void define_global_variable(std::string_view name);
157156
void named_variable(std::string_view lexeme, bool can_assign);
@@ -251,7 +250,7 @@ class Parser {
251250
case TokenType::SUPER:
252251
return Rule{NULL, NULL, Precedence::NONE};
253252
case TokenType::THIS:
254-
return Rule{NULL, NULL, Precedence::NONE};
253+
return Rule{&Parser::this_, NULL, Precedence::NONE};
255254
case TokenType::TRUE:
256255
return Rule{&Parser::literal, NULL, Precedence::NONE};
257256
case TokenType::VAR:

src/gc.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,13 @@ void GC::gc() {
127127
mark_as_grey(fieldname);
128128
mark_as_grey(value);
129129
}
130+
break;
131+
}
132+
case ObjType::BOUND_METHOD: {
133+
auto p = static_cast<ObjBoundMethod*>(objptr);
134+
mark_as_grey(p->receiver);
135+
mark_as_grey(p->method);
136+
break;
130137
}
131138
}
132139

src/value.hpp

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ enum class ObjType {
2323
CLOSURE,
2424
NATIVE_FUNCTION,
2525
CLASS,
26-
INSTANCE
26+
INSTANCE,
27+
BOUND_METHOD
2728
};
2829

2930
// Forward declarations
@@ -175,6 +176,23 @@ class ObjInstance : public Obj {
175176
static constexpr std::string_view static_type_name = "ObjInstance";
176177
};
177178

179+
class ObjBoundMethod : public Obj {
180+
public:
181+
ObjInstance* receiver;
182+
ObjClosure* method;
183+
184+
ObjBoundMethod(ObjInstance* receiver, ObjClosure* method)
185+
: Obj(static_type), receiver(receiver), method(method) {}
186+
187+
std::string to_repr() const override {
188+
return "<bound method " + method->to_repr() + " of " + receiver->to_repr() +
189+
">";
190+
}
191+
192+
static constexpr ObjType static_type = ObjType::BOUND_METHOD;
193+
static constexpr std::string_view static_type_name = "ObjBoundMethod";
194+
};
195+
178196
template <typename T> T* as_objptr(Value value, const std::string& error_msg) {
179197
if (!std::holds_alternative<Obj*>(value)) {
180198
throw std::runtime_error(error_msg);

src/vm.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -486,9 +486,10 @@ InterpretResult VM::run() {
486486
auto classmethods = instanceptr->klass->methods;
487487
auto method_itr = classmethods.find(property_name);
488488
if (method_itr != classmethods.end()) {
489-
// TODO: bind `this`
490489
ObjClosure* method_closure = method_itr->second;
491-
stack_replace_top(method_closure);
490+
ObjBoundMethod* bound_method =
491+
_gc.alloc<ObjBoundMethod>(instanceptr, method_closure);
492+
stack_replace_top(bound_method);
492493
} else {
493494
// OK, it really wasn't found
494495
throw std::runtime_error("undefined property '" +
@@ -594,6 +595,13 @@ InterpretResult VM::run() {
594595
stack_replace_top(inst);
595596
break;
596597
}
598+
case ObjType::BOUND_METHOD: {
599+
auto bound_method_ptr = static_cast<ObjBoundMethod*>(objptr);
600+
// Stick `this` just before the arguments.
601+
stack[stack.size() - 1 - nargs] = bound_method_ptr->receiver;
602+
call(bound_method_ptr->method, nargs);
603+
break;
604+
}
597605
default: {
598606
throw std::runtime_error("can only call closure or native function");
599607
}

test.lox

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
class Num {
2-
a() { return 1; }
2+
a(x) { return 1 + x + this.b; }
33
}
44

5-
print Num().a();
5+
var n = Num();
6+
n.b = 2;
7+
print n.a(3);

0 commit comments

Comments
 (0)