diff --git a/src/pk_compiler.c b/src/pk_compiler.c index 139af8a..8fd9994 100644 --- a/src/pk_compiler.c +++ b/src/pk_compiler.c @@ -1926,7 +1926,11 @@ static void exprMap(Compiler* compiler) { consume(compiler, TK_RBRACE, "Expected '}' after map elements."); } -static void exprCall(Compiler* compiler) { +// This function is reused between calls and method calls. if the [call_type] +// is OP_METHOD_CALL the [method] should refer a string in the module's +// constant pool, otherwise it's ignored. +static void _compileCall(Compiler* compiler, Opcode call_type, int method) { + ASSERT((call_type == OP_CALL) || (call_type == OP_METHOD_CALL), OOPS); // Compile parameters. int argc = 0; @@ -1940,14 +1944,24 @@ static void exprCall(Compiler* compiler) { consume(compiler, TK_RPARAN, "Expected ')' after parameter list."); } - emitOpcode(compiler, OP_CALL); + emitOpcode(compiler, call_type); + emitByte(compiler, argc); + if (call_type == OP_METHOD_CALL) { + ASSERT_INDEX(method, (int)compiler->module->constants.count); + emitShort(compiler, method); + } + // After the call the arguments will be popped and the callable // will be replaced with the return value. compilerChangeStack(compiler, -argc); } +static void exprCall(Compiler* compiler) { + _compileCall(compiler, OP_CALL, -1); +} + static void exprAttrib(Compiler* compiler) { consume(compiler, TK_NAME, "Expected an attribute name after '.'."); const char* name = compiler->parser.previous.start; @@ -1958,6 +1972,12 @@ static void exprAttrib(Compiler* compiler) { moduleAddString(compiler->module, compiler->parser.vm, name, length, &index); + // Check if it's a method call. + if (match(compiler, TK_LPARAN)) { + _compileCall(compiler, OP_METHOD_CALL, index); + return; + } + if (compiler->l_value && matchAssignment(compiler)) { TokenType assignment = compiler->parser.previous.type; skipNewLines(compiler); @@ -3168,7 +3188,7 @@ PkResult compile(PKVM* vm, Module* module, const char* source, } #if DUMP_BYTECODE - dumpFunctionCode(compiler->parser.vm, module->body); + dumpFunctionCode(compiler->parser.vm, module->body->fn); #endif // Return the compilation result. diff --git a/src/pk_core.c b/src/pk_core.c index e8bd365..456d750 100644 --- a/src/pk_core.c +++ b/src/pk_core.c @@ -115,9 +115,8 @@ void pkClassAddMethod(PKVM* vm, PkHandle* cls, } void* pkGetSelf(const PKVM* vm) { - Var self = vm->fiber->frames[vm->fiber->frame_count - 1].self; - ASSERT(IS_OBJ_TYPE(self, OBJ_INST), OOPS); - Instance* inst = (Instance*)AS_OBJ(self); + ASSERT(IS_OBJ_TYPE(vm->fiber->self, OBJ_INST), OOPS); + Instance* inst = (Instance*)AS_OBJ(vm->fiber->self); ASSERT(inst->native != NULL, OOPS); return inst->native; } @@ -1243,11 +1242,15 @@ static void initializeCoreModules(PKVM* vm) { #undef DEF /*****************************************************************************/ -/* BUILTIN CLASS METHODS */ +/* BUILTIN CLASS CONSTRUCTORS */ /*****************************************************************************/ -static forceinline void _setSelf(PKVM* vm, Var value) { - vm->fiber->frames[vm->fiber->frame_count - 1].self = value; +static inline void _setSelf(PKVM* vm, Var value) { + vm->fiber->self = value; +} + +static inline Var _getSelf(PKVM* vm) { + return vm->fiber->self; } static void _ctorNull(PKVM* vm) { @@ -1301,6 +1304,17 @@ static void _ctorFiber(PKVM* vm) { _setSelf(vm, VAR_OBJ(newFiber(vm, closure))); } +/*****************************************************************************/ +/* BUILTIN CLASS METHODS */ +/*****************************************************************************/ + +static void _listAppend(PKVM* vm) { + Var self = _getSelf(vm); + ASSERT(IS_OBJ_TYPE(self, OBJ_LIST), OOPS); + listAppend(vm, ((List*)AS_OBJ(self)), ARG(1)); + RET(self); +} + /*****************************************************************************/ /* BUILTIN CLASS INITIALIZATION */ /*****************************************************************************/ @@ -1333,7 +1347,23 @@ static void initializePrimitiveClasses(PKVM* vm) { ADD_CTOR(PK_MAP, "@ctorMap", _ctorMap, 0); ADD_CTOR(PK_FIBER, "@ctorFiber", _ctorFiber, 1); - // TODO: add methods. +#undef ADD_CTOR + +#define ADD_METHOD(type, name, ptr, arity_) \ + do { \ + Function* fn = newFunction(vm, name, (int)strlen(name), \ + NULL, true, NULL, NULL); \ + fn->native = ptr; \ + fn->arity = arity_; \ + vmPushTempRef(vm, &fn->_super); /* fn. */ \ + pkClosureBufferWrite(&vm->builtin_classes[type]->methods, \ + vm, newClosure(vm, fn)); \ + vmPopTempRef(vm); /* fn. */ \ + } while (false) + + ADD_METHOD(PK_LIST, "append", _listAppend, 1); + +#undef ADD_METHOD } diff --git a/src/pk_debug.c b/src/pk_debug.c index 1a0f593..2525731 100644 --- a/src/pk_debug.c +++ b/src/pk_debug.c @@ -282,6 +282,24 @@ void dumpFunctionCode(PKVM* vm, Function* func) { break; } + case OP_METHOD_CALL: + { + int argc = READ_BYTE(); + int index = READ_SHORT(); + String* name = moduleGetStringAt(func->owner, index); + ASSERT(name != NULL, OOPS); + + // Prints: %5d (argc) %d '%s'\n + PRINT_INT(argc); + PRINT(" (argc) "); + + _PRINT_INT(index, 0); + PRINT(" '"); + PRINT(name->data); + PRINT("'\n"); + break; + } + case OP_CALL: // Prints: %5d (argc)\n PRINT_INT(READ_BYTE()); diff --git a/src/pk_opcodes.h b/src/pk_opcodes.h index f329f96..bf6fd73 100644 --- a/src/pk_opcodes.h +++ b/src/pk_opcodes.h @@ -123,6 +123,11 @@ OPCODE(POP, 0, -1) // params: 2 byte name index. OPCODE(IMPORT, 2, 1) +// Call a method on the variable at the stack top. See opcode CALL for detail. +// params: 2 bytes method name index in the constant pool. +// 1 byte argc. +OPCODE(METHOD_CALL, 3, -0) //< Stack size will be calculated at compile time. + // Calls a function using stack's top N values as the arguments and once it // done the stack top should be stored otherwise it'll be disregarded. The // function should set the 0 th argment to return value. diff --git a/src/pk_value.c b/src/pk_value.c index ebfb174..dba3176 100644 --- a/src/pk_value.c +++ b/src/pk_value.c @@ -495,6 +495,7 @@ Fiber* newFiber(PKVM* vm, Closure* closure) { } fiber->open_upvalues = NULL; + fiber->self = VAR_UNDEFINED; // Initialize the return value to null (doesn't really have to do that here // but if we're trying to debut it may crash when dumping the return value). diff --git a/src/pk_value.h b/src/pk_value.h index 13a6609..20d128f 100644 --- a/src/pk_value.h +++ b/src/pk_value.h @@ -466,6 +466,13 @@ struct Fiber { // overflowed. Var* ret; + // The self pointer to of the current method. It'll be updated before + // calling a native method. (Because native methods doesn't have a call + // frame we're doing it this way). Also updated just before calling a + // script method, and will be captured by the next allocated callframe + // and reset to VAR_UNDEFINED. + Var self; + // Heap allocated array of call frames will grow as needed. CallFrame* frames; int frame_capacity; //< Capacity of the frames array. diff --git a/src/pk_vm.c b/src/pk_vm.c index 5c975f4..2b9847a 100644 --- a/src/pk_vm.c +++ b/src/pk_vm.c @@ -550,7 +550,10 @@ static inline void pushCallFrame(PKVM* vm, const Closure* closure, Var* rbp) { frame->rbp = rbp; frame->closure = closure; frame->ip = closure->fn->fn->opcodes.data; - frame->self = VAR_UNDEFINED; + + // Eat the self. + frame->self = vm->fiber->self; + vm->fiber->self = VAR_UNDEFINED; } static inline void reuseCallFrame(PKVM* vm, const Closure* closure) { @@ -564,6 +567,7 @@ static inline void reuseCallFrame(PKVM* vm, const Closure* closure) { CallFrame* frame = fb->frames + fb->frame_count - 1; frame->closure = closure; frame->ip = closure->fn->fn->opcodes.data; + frame->self = VAR_UNDEFINED; ASSERT(*frame->rbp == VAR_NULL, OOPS); @@ -791,12 +795,18 @@ L_vm_main_loop: // defined, the next line become a declaration (Opcode instruction;). NO_OP; +#define _DUMP_STACK() \ + do { \ + system("cls"); /* FIXME: */ \ + dumpGlobalValues(vm); \ + dumpStackFrame(vm); \ + DEBUG_BREAK(); \ + } while (false) + #if DUMP_STACK - system("cls"); // FIXME: - dumpGlobalValues(vm); - dumpStackFrame(vm); - DEBUG_BREAK(); + _DUMP_STACK(); #endif +#undef _DUMP_STACK SWITCH() { @@ -1045,32 +1055,49 @@ L_vm_main_loop: DISPATCH(); } + { + uint8_t argc; + Var callable; + const Closure* closure; + + OPCODE(METHOD_CALL): + argc = READ_BYTE(); + fiber->ret = (fiber->sp - argc - 1); + fiber->self = *fiber->ret; //< Self for the next call. + + uint16_t index = READ_SHORT(); + bool is_method; + String* name = moduleGetStringAt(module, (int)index); + callable = getMethod(vm, fiber->self, name, &is_method); + CHECK_ERROR(); + goto L_do_call; + OPCODE(CALL): OPCODE(TAIL_CALL): - { - const uint8_t argc = READ_BYTE(); - Var* callable = fiber->sp - argc - 1; - - const Closure* closure = NULL; + argc = READ_BYTE(); + fiber->ret = fiber->sp - argc - 1; + callable = *fiber->ret; +L_do_call: // Raw functions cannot be on the stack, since they're not first class // citizens. - ASSERT(!IS_OBJ_TYPE(*callable, OBJ_FUNC), OOPS); + ASSERT(!IS_OBJ_TYPE(callable, OBJ_FUNC), OOPS); - if (IS_OBJ_TYPE(*callable, OBJ_CLOSURE)) { - closure = (const Closure*)AS_OBJ(*callable); + if (IS_OBJ_TYPE(callable, OBJ_CLOSURE)) { + closure = (const Closure*)AS_OBJ(callable); - } else if (IS_OBJ_TYPE(*callable, OBJ_CLASS)) { - closure = (const Closure*)((Class*)AS_OBJ(*callable))->ctor; + } else if (IS_OBJ_TYPE(callable, OBJ_CLASS)) { + closure = (const Closure*)((Class*)AS_OBJ(callable))->ctor; } else { RUNTIME_ERROR(stringFormat(vm, "$ $(@).", "Expected a callable to " "call, instead got", - varTypeName(*callable), toString(vm, *callable))); + varTypeName(callable), toString(vm, callable))); DISPATCH(); } // If we reached here it's a valid callable. + ASSERT(closure != NULL, OOPS); // -1 argument means multiple number of args. if (closure->fn->arity != -1 && closure->fn->arity != argc) { @@ -1080,8 +1107,6 @@ L_vm_main_loop: RUNTIME_ERROR(msg); } - // Next call frame starts here. (including return value). - fiber->ret = callable; *(fiber->ret) = VAR_NULL; //< Set the return value to null. if (closure->fn->is_native) { @@ -1116,9 +1141,9 @@ L_vm_main_loop: } else { - if (instruction == OP_CALL) { + if (instruction == OP_CALL || instruction == OP_METHOD_CALL) { UPDATE_FRAME(); //< Update the current frame's ip. - pushCallFrame(vm, closure, callable); + pushCallFrame(vm, closure, fiber->ret); LOAD_FRAME(); //< Load the top frame to vm's execution variables. } else {