From 27a652e565be753f1e75e15b7ab7123b3d65b65f Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Wed, 13 Nov 2013 11:05:03 -0800 Subject: [PATCH] Start sketching out inheritance. --- src/compiler.c | 13 ++++++-- src/primitives.c | 5 +++- src/vm.c | 59 +++++++++++++++++++++++++++++-------- src/vm.h | 7 ++++- test/class_inheritance.wren | 26 ++++++++++++++++ 5 files changed, 93 insertions(+), 17 deletions(-) create mode 100644 test/class_inheritance.wren diff --git a/src/compiler.c b/src/compiler.c index b0771cfc..0c4cb9a4 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -1201,8 +1201,17 @@ void definition(Compiler* compiler) // Create a variable to store the class in. int symbol = declareVariable(compiler); - // Create the empty class. - emit(compiler, CODE_CLASS); + // Load the superclass (if there is one). + if (match(compiler, TOKEN_IS)) + { + parsePrecedence(compiler, 0, PREC_CALL); + emit(compiler, CODE_SUBCLASS); + } + else + { + // Create the empty class. + emit(compiler, CODE_CLASS); + } // Store it in its name. defineVariable(compiler, symbol); diff --git a/src/primitives.c b/src/primitives.c index 5a4e200f..cf731ba9 100644 --- a/src/primitives.c +++ b/src/primitives.c @@ -228,6 +228,7 @@ DEF_PRIMITIVE(io_write) } static const char* CORE_LIB = +"class Object {}\n" "class Bool {}\n" "class Class {}\n" "class Function {}\n" @@ -273,6 +274,8 @@ void loadCore(VM* vm) PRIMITIVE(vm->numClass, "== ", num_eqeq); PRIMITIVE(vm->numClass, "!= ", num_bangeq); + vm->objectClass = AS_CLASS(findGlobal(vm, "Object")); + vm->stringClass = AS_CLASS(findGlobal(vm, "String")); PRIMITIVE(vm->stringClass, "contains ", string_contains); PRIMITIVE(vm->stringClass, "count", string_count); @@ -284,7 +287,7 @@ void loadCore(VM* vm) ObjClass* ioClass = AS_CLASS(findGlobal(vm, "IO")); PRIMITIVE(ioClass, "write ", io_write); - ObjClass* unsupportedClass = newClass(vm); + ObjClass* unsupportedClass = newClass(vm, vm->objectClass); // TODO(bob): Make this a distinct object type. vm->unsupported = (Value)newInstance(vm, unsupportedClass); diff --git a/src/vm.c b/src/vm.c index e9f5e67f..b34218b7 100644 --- a/src/vm.c +++ b/src/vm.c @@ -265,11 +265,13 @@ Value newBool(VM* vm, int value) return obj; } -static ObjClass* newSingleClass(VM* vm) +static ObjClass* newSingleClass(VM* vm, ObjClass* metaclass, + ObjClass* superclass) { ObjClass* obj = allocate(vm, sizeof(ObjClass)); initObj(vm, &obj->obj, OBJ_CLASS); - obj->metaclass = NULL; + obj->metaclass = metaclass; + obj->superclass = superclass; for (int i = 0; i < MAX_SYMBOLS; i++) { @@ -279,18 +281,29 @@ static ObjClass* newSingleClass(VM* vm) return obj; } -ObjClass* newClass(VM* vm) +ObjClass* newClass(VM* vm, ObjClass* superclass) { - ObjClass* classObj = newSingleClass(vm); + // Make the metaclass. + // TODO(bob): What is the metaclass's metaclass and superclass? + ObjClass* metaclass = newSingleClass(vm, NULL, NULL); - // Make sure this isn't collected when we allocate the metaclass. - pinObj(vm, (Value)classObj); + // Make sure it isn't collected when we allocate the metaclass. + pinObj(vm, (Value)metaclass); - // Make its metaclass. - // TODO(bob): What is the metaclass's metaclass? - classObj->metaclass = newSingleClass(vm); + ObjClass* classObj = newSingleClass(vm, metaclass, superclass); - unpinObj(vm, (Value)classObj); + unpinObj(vm, (Value)metaclass); + + // Inherit methods from its superclass (unless it's Object, which has none). + // TODO(bob): If we want BETA-style inheritance, we'll need to do this after + // the subclass has defined its methods. + if (superclass != NULL) + { + for (int i = 0; i < MAX_SYMBOLS; i++) + { + classObj->methods[i] = superclass->methods[i]; + } + } return classObj; } @@ -461,6 +474,10 @@ void dumpCode(VM* vm, ObjFn* fn) case CODE_CLASS: printf("CLASS\n"); break; + + case CODE_SUBCLASS: + printf("SUBCLASS\n"); + break; case CODE_METACLASS: printf("METACLASS\n"); @@ -608,7 +625,8 @@ Value interpret(VM* vm, ObjFn* fn) { CallFrame* frame = &fiber->frames[fiber->numFrames - 1]; - switch (frame->fn->bytecode[frame->ip++]) + Code instruction = frame->fn->bytecode[frame->ip++]; + switch (instruction) { case CODE_CONSTANT: PUSH(frame->fn->constants[READ_ARG()]); @@ -619,8 +637,23 @@ Value interpret(VM* vm, ObjFn* fn) case CODE_TRUE: PUSH(newBool(vm, 1)); break; case CODE_CLASS: + case CODE_SUBCLASS: { - ObjClass* classObj = newClass(vm); + int isSubclass = instruction == CODE_SUBCLASS; + + ObjClass* superclass; + if (isSubclass) + { + // TODO(bob): Handle the superclass not being a class object! + superclass = AS_CLASS(POP()); + } + else + { + // Implicit Object superclass. + superclass = vm->objectClass; + } + + ObjClass* classObj = newClass(vm, superclass); // Define a "new" method on the metaclass. // TODO(bob): Can this be inherited? @@ -696,7 +729,7 @@ Value interpret(VM* vm, ObjFn* fn) case CODE_CALL_10: { // Add one for the implicit receiver argument. - int numArgs = frame->fn->bytecode[frame->ip - 1] - CODE_CALL_0 + 1; + int numArgs = instruction - CODE_CALL_0 + 1; int symbol = READ_ARG(); Value receiver = fiber->stack[fiber->stackSize - numArgs]; diff --git a/src/vm.h b/src/vm.h index c3b5c423..2127e5b4 100644 --- a/src/vm.h +++ b/src/vm.h @@ -87,6 +87,7 @@ typedef struct sObjClass { Obj obj; struct sObjClass* metaclass; + struct sObjClass* superclass; // TODO(bob): Hack. Probably don't want to use this much space. Method methods[MAX_SYMBOLS]; } ObjClass; @@ -127,6 +128,9 @@ typedef enum // Define a new empty class and push it. CODE_CLASS, + // Pop a superclass off the stack, then push a new class that extends it. + CODE_SUBCLASS, + // Push the metaclass of the class on the top of the stack. Does not discard // the class. CODE_METACLASS, @@ -196,6 +200,7 @@ struct sVM ObjClass* fnClass; ObjClass* nullClass; ObjClass* numClass; + ObjClass* objectClass; ObjClass* stringClass; // The singleton values. @@ -264,7 +269,7 @@ Value newBool(VM* vm, int value); ObjFn* newFunction(VM* vm); // Creates a new class object. -ObjClass* newClass(VM* vm); +ObjClass* newClass(VM* vm, ObjClass* superclass); // Creates a new instance of the given [classObj]. ObjInstance* newInstance(VM* vm, ObjClass* classObj); diff --git a/test/class_inheritance.wren b/test/class_inheritance.wren new file mode 100644 index 00000000..409b76ea --- /dev/null +++ b/test/class_inheritance.wren @@ -0,0 +1,26 @@ +class Foo { + methodOnFoo { io.write("foo") } + method(a) { io.write("foo") } + method(a, b, c) { io.write("foo") } +} + +class Bar is Foo { + methodOnBar { io.write("bar") } + method(a, b) { io.write("bar") } + method(a, b, c, d) { io.write("bar") } +} + +var bar = Bar.new +bar.methodOnFoo // expect: foo +bar.methodOnBar // expect: bar + +// Methods with different arity do not shadow each other. +bar.method(1) // expect: foo +bar.method(1, 2) // expect: bar +bar.method(1, 2, 3) // expect: foo +bar.method(1, 2, 3, 4) // expect: bar + +// TODO(bob): Overriding (or BETA-style refining?). +// TODO(bob): Private fields. +// TODO(bob): Super (or inner) calls. +// TODO(bob): Grammar for what expressions can follow "is".