diff --git a/src/wren_compiler.c b/src/wren_compiler.c index 67ee975e..2711deb4 100644 --- a/src/wren_compiler.c +++ b/src/wren_compiler.c @@ -211,7 +211,8 @@ typedef struct sCompiler // Adds [constant] to the constant pool and returns its index. static int addConstant(Compiler* compiler, Value constant) { - // TODO(bob): Look for existing equal constant. + // TODO(bob): Look for existing equal constant. Note that we need to *not* + // do that for the placeholder constant created for super calls. // TODO(bob): Check for overflow. compiler->fn->constants[compiler->fn->numConstants++] = constant; return compiler->fn->numConstants - 1; @@ -264,8 +265,16 @@ static void error(Compiler* compiler, const char* format, ...) compiler->parser->hasError = 1; Token* token = &compiler->parser->previous; - fprintf(stderr, "[Line %d] Error on '%.*s': ", - token->line, token->length, token->start); + fprintf(stderr, "[Line %d] Error on ", token->line); + if (token->type == TOKEN_LINE) + { + // Don't print the newline itself since that looks wonky. + fprintf(stderr, "newline: "); + } + else + { + fprintf(stderr, "'%.*s': ", token->length, token->start); + } va_list args; va_start(args, format); @@ -1066,6 +1075,48 @@ static void parameterList(Compiler* compiler, char* name, int* length) } } +// Compiles the method name and argument list for a "<...>.name(...)" call. +static void namedCall(Compiler* compiler, int allowAssignment, Code instruction) +{ + // Build the method name. + consume(compiler, TOKEN_NAME, "Expect method name after '.'."); + char name[MAX_METHOD_SIGNATURE]; + int length = copyName(compiler, name); + + // TODO(bob): Check for "=" here and set assignment and return. + + // Parse the argument list, if any. + int numArgs = 0; + if (match(compiler, TOKEN_LEFT_PAREN)) + { + do + { + // The VM can only handle a certain number of parameters, so check for + // this explicitly and give a usable error. + if (++numArgs == MAX_PARAMETERS + 1) + { + // Only show an error at exactly max + 1 and don't break so that we can + // keep parsing the parameter list and minimize cascaded errors. + error(compiler, "Cannot pass more than %d arguments to a method.", + MAX_PARAMETERS); + } + + expression(compiler); + + // Add a space in the name for each argument. Lets us overload by + // arity. + name[length++] = ' '; + } + while (match(compiler, TOKEN_COMMA)); + consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after arguments."); + } + + int symbol = ensureSymbol(&compiler->parser->vm->methods, name, length); + + emit(compiler, instruction + numArgs); + emit(compiler, symbol); +} + static void grouping(Compiler* compiler, int allowAssignment) { expression(compiler); @@ -1254,6 +1305,22 @@ static void string(Compiler* compiler, int allowAssignment) emit(compiler, constant); } +static void super_(Compiler* compiler, int allowAssignment) +{ + // TODO(bob): Error if this is not in a method. + // The receiver is always stored in the first local slot. + // TODO(bob): Will need to do something different to handle functions + // enclosed in methods. + emit(compiler, CODE_LOAD_LOCAL); + emit(compiler, 0); + + // TODO(bob): Super operator and constructor calls. + consume(compiler, TOKEN_DOT, "Expect '.' after 'super'."); + + // Compile the superclass call. + namedCall(compiler, allowAssignment, CODE_SUPER_0); +} + static void this_(Compiler* compiler, int allowAssignment) { // Walk up the parent chain to see if there is an enclosing method. @@ -1329,44 +1396,7 @@ static void subscript(Compiler* compiler, int allowAssignment) void call(Compiler* compiler, int allowAssignment) { - // Build the method name. - consume(compiler, TOKEN_NAME, "Expect method name after '.'."); - char name[MAX_METHOD_SIGNATURE]; - int length = copyName(compiler, name); - - // TODO(bob): Check for "=" here and set assignment and return. - - // Parse the argument list, if any. - int numArgs = 0; - if (match(compiler, TOKEN_LEFT_PAREN)) - { - do - { - // The VM can only handle a certain number of parameters, so check for - // this explicitly and give a usable error. - if (++numArgs == MAX_PARAMETERS + 1) - { - // Only show an error at exactly max + 1 and don't break so that we can - // keep parsing the parameter list and minimize cascaded errors. - error(compiler, "Cannot pass more than %d arguments to a method.", - MAX_PARAMETERS); - } - - expression(compiler); - - // Add a space in the name for each argument. Lets us overload by - // arity. - name[length++] = ' '; - } - while (match(compiler, TOKEN_COMMA)); - consume(compiler, TOKEN_RIGHT_PAREN, "Expect ')' after arguments."); - } - - int symbol = ensureSymbol(&compiler->parser->vm->methods, name, length); - - // Compile the method call. - emit(compiler, CODE_CALL_0 + numArgs); - emit(compiler, symbol); + namedCall(compiler, allowAssignment, CODE_CALL_0); } void is(Compiler* compiler, int allowAssignment) @@ -1493,7 +1523,7 @@ GrammarRule rules[] = /* TOKEN_NULL */ PREFIX(null), /* TOKEN_RETURN */ UNUSED, /* TOKEN_STATIC */ UNUSED, - /* TOKEN_SUPER */ UNUSED, + /* TOKEN_SUPER */ PREFIX(super_), /* TOKEN_THIS */ PREFIX(this_), /* TOKEN_TRUE */ PREFIX(boolean), /* TOKEN_VAR */ UNUSED, @@ -1848,6 +1878,7 @@ ObjFn* wrenCompile(WrenVM* vm, const char* source) void wrenBindMethod(ObjClass* classObj, ObjFn* fn) { + // TODO(bob): What about functions nested inside [fn]? int ip = 0; for (;;) { @@ -1890,6 +1921,23 @@ void wrenBindMethod(ObjClass* classObj, ObjFn* fn) case CODE_CALL_14: case CODE_CALL_15: case CODE_CALL_16: + case CODE_SUPER_0: + case CODE_SUPER_1: + case CODE_SUPER_2: + case CODE_SUPER_3: + case CODE_SUPER_4: + case CODE_SUPER_5: + case CODE_SUPER_6: + case CODE_SUPER_7: + case CODE_SUPER_8: + case CODE_SUPER_9: + case CODE_SUPER_10: + case CODE_SUPER_11: + case CODE_SUPER_12: + case CODE_SUPER_13: + case CODE_SUPER_14: + case CODE_SUPER_15: + case CODE_SUPER_16: case CODE_JUMP: case CODE_LOOP: case CODE_JUMP_IF: diff --git a/src/wren_debug.c b/src/wren_debug.c index 32145704..fc7c4c0b 100644 --- a/src/wren_debug.c +++ b/src/wren_debug.c @@ -126,7 +126,6 @@ int wrenDebugDumpInstruction(WrenVM* vm, ObjFn* fn, int i) case CODE_CALL_15: case CODE_CALL_16: { - // Add one for the implicit receiver argument. int numArgs = bytecode[i - 1] - CODE_CALL_0; int symbol = bytecode[i++]; printf("CALL_%d \"%s\"\n", numArgs, @@ -135,6 +134,32 @@ int wrenDebugDumpInstruction(WrenVM* vm, ObjFn* fn, int i) break; } + case CODE_SUPER_0: + case CODE_SUPER_1: + case CODE_SUPER_2: + case CODE_SUPER_3: + case CODE_SUPER_4: + case CODE_SUPER_5: + case CODE_SUPER_6: + case CODE_SUPER_7: + case CODE_SUPER_8: + case CODE_SUPER_9: + case CODE_SUPER_10: + case CODE_SUPER_11: + case CODE_SUPER_12: + case CODE_SUPER_13: + case CODE_SUPER_14: + case CODE_SUPER_15: + case CODE_SUPER_16: + { + int numArgs = bytecode[i - 1] - CODE_SUPER_0; + int symbol = bytecode[i++]; + printf("SUPER_%d \"%s\"\n", numArgs, + getSymbolName(&vm->methods, symbol)); + printf("%04d | symbol %d\n", i, symbol); + break; + } + case CODE_JUMP: { int offset = bytecode[i++]; diff --git a/src/wren_vm.c b/src/wren_vm.c index d59265ed..8689aa70 100644 --- a/src/wren_vm.c +++ b/src/wren_vm.c @@ -604,6 +604,23 @@ Value interpret(WrenVM* vm, Value function) &&code_CALL_14, &&code_CALL_15, &&code_CALL_16, + &&code_SUPER_0, + &&code_SUPER_1, + &&code_SUPER_2, + &&code_SUPER_3, + &&code_SUPER_4, + &&code_SUPER_5, + &&code_SUPER_6, + &&code_SUPER_7, + &&code_SUPER_8, + &&code_SUPER_9, + &&code_SUPER_10, + &&code_SUPER_11, + &&code_SUPER_12, + &&code_SUPER_13, + &&code_SUPER_14, + &&code_SUPER_15, + &&code_SUPER_16, &&code_JUMP, &&code_LOOP, &&code_JUMP_IF, @@ -715,7 +732,95 @@ Value interpret(WrenVM* vm, Value function) LOAD_FRAME(); break; } - + + case METHOD_NONE: + printf("Receiver "); + wrenPrintValue(receiver); + printf(" does not implement method \"%s\".\n", + vm->methods.names[symbol]); + // TODO(bob): Throw an exception or halt the fiber or something. + exit(1); + break; + } + DISPATCH(); + } + + CASE_CODE(SUPER_0): + CASE_CODE(SUPER_1): + CASE_CODE(SUPER_2): + CASE_CODE(SUPER_3): + CASE_CODE(SUPER_4): + CASE_CODE(SUPER_5): + CASE_CODE(SUPER_6): + CASE_CODE(SUPER_7): + CASE_CODE(SUPER_8): + CASE_CODE(SUPER_9): + CASE_CODE(SUPER_10): + CASE_CODE(SUPER_11): + CASE_CODE(SUPER_12): + CASE_CODE(SUPER_13): + CASE_CODE(SUPER_14): + CASE_CODE(SUPER_15): + CASE_CODE(SUPER_16): + { + // TODO(bob): Almost completely copied from CALL. Unify somehow. + + // Add one for the implicit receiver argument. + int numArgs = instruction - CODE_CALL_0 + 1; + int symbol = READ_ARG(); + + Value receiver = fiber->stack[fiber->stackSize - numArgs]; + ObjClass* classObj = wrenGetClass(vm, receiver); + + // Ignore methods defined on the receiver's immediate class. + classObj = classObj->superclass; + + Method* method = &classObj->methods[symbol]; + switch (method->type) + { + case METHOD_PRIMITIVE: + { + Value* args = &fiber->stack[fiber->stackSize - numArgs]; + Value result = method->primitive(vm, args); + + fiber->stack[fiber->stackSize - numArgs] = result; + + // Discard the stack slots for the arguments (but leave one for + // the result). + fiber->stackSize -= numArgs - 1; + break; + } + + case METHOD_FIBER: + { + STORE_FRAME(); + Value* args = &fiber->stack[fiber->stackSize - numArgs]; + method->fiberPrimitive(vm, fiber, args); + LOAD_FRAME(); + break; + } + + case METHOD_BLOCK: + STORE_FRAME(); + wrenCallFunction(fiber, method->fn, numArgs); + LOAD_FRAME(); + break; + + case METHOD_CTOR: + { + Value instance = wrenNewInstance(vm, AS_CLASS(receiver)); + + // Store the new instance in the receiver slot so that it can be + // "this" in the body of the constructor and returned by it. + fiber->stack[fiber->stackSize - numArgs] = instance; + + // Invoke the constructor body. + STORE_FRAME(); + wrenCallFunction(fiber, method->fn, numArgs); + LOAD_FRAME(); + break; + } + case METHOD_NONE: printf("Receiver "); wrenPrintValue(receiver); @@ -967,7 +1072,7 @@ Value interpret(WrenVM* vm, Value function) closure->upvalues[i] = upvalues[index]; } } - + DISPATCH(); } diff --git a/src/wren_vm.h b/src/wren_vm.h index 886c1b78..fedc592f 100644 --- a/src/wren_vm.h +++ b/src/wren_vm.h @@ -72,6 +72,26 @@ typedef enum CODE_CALL_15, CODE_CALL_16, + // Invoke a superclass method with symbol [arg]. The number indicates the + // number of arguments (not including the receiver). + CODE_SUPER_0, + CODE_SUPER_1, + CODE_SUPER_2, + CODE_SUPER_3, + CODE_SUPER_4, + CODE_SUPER_5, + CODE_SUPER_6, + CODE_SUPER_7, + CODE_SUPER_8, + CODE_SUPER_9, + CODE_SUPER_10, + CODE_SUPER_11, + CODE_SUPER_12, + CODE_SUPER_13, + CODE_SUPER_14, + CODE_SUPER_15, + CODE_SUPER_16, + // Jump the instruction pointer [arg] forward. CODE_JUMP, diff --git a/test/super/call_other_method.wren b/test/super/call_other_method.wren new file mode 100644 index 00000000..146602df --- /dev/null +++ b/test/super/call_other_method.wren @@ -0,0 +1,22 @@ +class Base { + foo { + io.write("Base.foo") + } +} + +class Derived is Base { + bar { + io.write("Derived.bar") + super.foo + } +} + +Derived.new.bar +// expect: Derived.bar +// expect: Base.foo + +// TODO(bob): Super constructor calls. +// TODO(bob): Super operator calls. +// TODO(bob): Calling super outside of a class. +// TODO(bob): Super calls inside nested functions in methods. +// TODO(bob): Super where there is no inherited method. diff --git a/test/super/call_same_method.wren b/test/super/call_same_method.wren new file mode 100644 index 00000000..3f53d050 --- /dev/null +++ b/test/super/call_same_method.wren @@ -0,0 +1,16 @@ +class Base { + foo { + io.write("Base.foo") + } +} + +class Derived is Base { + foo { + io.write("Derived.foo") + super.foo + } +} + +Derived.new.foo +// expect: Derived.foo +// expect: Base.foo diff --git a/test/super/indirectly_inherited.wren b/test/super/indirectly_inherited.wren new file mode 100644 index 00000000..69510803 --- /dev/null +++ b/test/super/indirectly_inherited.wren @@ -0,0 +1,18 @@ +class A { + foo { + io.write("A.foo") + } +} + +class B is A {} + +class C is B { + foo { + io.write("C.foo") + super.foo + } +} + +C.new.foo +// expect: C.foo +// expect: A.foo