diff --git a/clox/src/memory.c b/clox/src/memory.c index 13891e4..b93b6d6 100644 --- a/clox/src/memory.c +++ b/clox/src/memory.c @@ -76,6 +76,11 @@ static void blackenObject(Obj *object) { printf("\n"); #endif switch (object->type) { + case OBJ_BOUND_METHOD: { + ObjBoundMethod *bound = (ObjBoundMethod *)object; + markValue(bound->receiver); + markObject((Obj *)bound->method); + } case OBJ_CLASS: { ObjClass *klass = (ObjClass *)object; markObject((Obj *)klass->name); @@ -117,6 +122,9 @@ static void freeObject(Obj *object) { #endif // DEBUG_LOG_GC switch (object->type) { + case OBJ_BOUND_METHOD: + FREE(ObjBoundMethod, object); + break; case OBJ_CLASS: { ObjClass *klass = (ObjClass *)object; freeTable(&klass->methods); diff --git a/clox/src/object.c b/clox/src/object.c index 7e0dd6c..3732406 100644 --- a/clox/src/object.c +++ b/clox/src/object.c @@ -25,6 +25,13 @@ static Obj *allocateObject(size_t size, ObjType type) { return object; } +ObjBoundMethod *newBoundMethod(Value receiver, ObjClosure *method) { + ObjBoundMethod *bound = ALLOCATE_OBJ(ObjBoundMethod, OBJ_BOUND_METHOD); + bound->receiver = receiver; + bound->method = method; + return bound; +} + ObjClass *newClass(ObjString *name) { ObjClass *klass = ALLOCATE_OBJ(ObjClass, OBJ_CLASS); klass->name = name; @@ -130,6 +137,9 @@ static void printFunction(ObjFunction *function) { void printObject(Value value) { switch (OBJ_TYPE(value)) { + case OBJ_BOUND_METHOD: + printFunction(AS_BOUND_METHOD(value)->method->function); + break; case OBJ_CLASS: printf("%s", AS_CLASS(value)->name->chars); break; diff --git a/clox/src/object.h b/clox/src/object.h index 4c115ce..e51d7d8 100644 --- a/clox/src/object.h +++ b/clox/src/object.h @@ -8,6 +8,7 @@ #define OBJ_TYPE(value) (AS_OBJ(value)->type) +#define IS_BOUND_METHOD(value) isObjType(value, OBJ_BOUND_METHOD) #define IS_CLASS(value) isObjType(value, OBJ_CLASS) #define IS_CLOSURE(value) isObjType(value, OBJ_CLOSURE) #define IS_FUNCTION(value) isObjType(value, OBJ_FUNCTION) @@ -15,6 +16,7 @@ #define IS_NATIVE(value) isObjType(value, OBJ_NATIVE) #define IS_STRING(value) isObjType(value, OBJ_STRING) +#define AS_BOUND_METHOD(value) ((ObjBoundMethod *)AS_OBJ(value)) #define AS_CLASS(value) ((ObjClass *)AS_OBJ(value)) #define AS_CLOSURE(value) ((ObjClosure *)AS_OBJ(value)) #define AS_FUNCTION(value) ((ObjFunction *)AS_OBJ(value)) @@ -24,6 +26,7 @@ #define AS_CSTRING(value) (((ObjString *)AS_OBJ(value))->chars) typedef enum { + OBJ_BOUND_METHOD, OBJ_CLASS, OBJ_CLOSURE, OBJ_FUNCTION, @@ -87,6 +90,13 @@ typedef struct { Table fields; } ObjInstance; +typedef struct { + Obj obj; + Value receiver; + ObjClosure *method; +} ObjBoundMethod; + +ObjBoundMethod *newBoundMethod(Value receiver, ObjClosure *method); ObjClass *newClass(ObjString *name); ObjClosure *newClosure(ObjFunction *function); ObjFunction *newFunction(); diff --git a/clox/src/vm.c b/clox/src/vm.c index fb359c2..4b0fe48 100644 --- a/clox/src/vm.c +++ b/clox/src/vm.c @@ -108,6 +108,10 @@ static bool call(ObjClosure *closure, int argCount) { static bool callValue(Value callee, int argCount) { if (IS_OBJ(callee)) { switch (OBJ_TYPE(callee)) { + case OBJ_BOUND_METHOD: { + ObjBoundMethod *bound = AS_BOUND_METHOD(callee); + return call(bound->method, argCount); + } case OBJ_CLASS: { ObjClass *klass = AS_CLASS(callee); vm.stackTop[-argCount - 1] = OBJ_VAL(newInstance(klass)); @@ -130,6 +134,19 @@ static bool callValue(Value callee, int argCount) { return false; } +static bool bindMethod(ObjClass *klass, ObjString *name) { + Value method; + if (!tableGet(&klass->methods, name, &method)) { + runtimeError("Undefined property '%s'.", name->chars); + return false; + } + + ObjBoundMethod *bound = newBoundMethod(peek(0), AS_CLOSURE(method)); + pop(); + push(OBJ_VAL(bound)); + return true; +} + static ObjUpvalue *captureUpvalue(Value *local) { ObjUpvalue *prevUpvalue = NULL; ObjUpvalue *upvalue = vm.openUpvalues; @@ -305,8 +322,10 @@ static InterpretResult run() { break; } - runtimeError("Undefined property '%s'.", name->chars); - return INTERPRET_RUNTIME_ERROR; + if (!bindMethod(instance->klass, name)) { + return INTERPRET_RUNTIME_ERROR; + } + break; } case OP_SET_PROPERTY: { if (!IS_INSTANCE(peek(1))) {