From 9901d4d4415a543129c78f98f0eb216bcb86b644 Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Mon, 7 Jun 2021 22:26:56 +0530 Subject: [PATCH] REPL refactor [3/3] --- cli/TODO.txt | 2 + cli/main.c | 64 ++++++++++--- cli/modules/path.c | 2 +- src/common.h | 5 + src/compiler.c | 102 +++++++++++---------- src/core.c | 126 +++++++++----------------- src/debug.c | 7 +- src/include/pocketlang.h | 39 ++++++-- src/opcodes.h | 3 - src/utils.c | 18 ++-- src/var.c | 51 ++++------- src/var.h | 9 +- src/vm.c | 191 ++++++++++++++++++++++++++++++++------- src/vm.h | 17 +++- 14 files changed, 391 insertions(+), 245 deletions(-) diff --git a/cli/TODO.txt b/cli/TODO.txt index c601f6b..094ef84 100644 --- a/cli/TODO.txt +++ b/cli/TODO.txt @@ -28,6 +28,8 @@ - Hex, binary literals and floats like ".5". - Docs build to a single directory, and replace url space (%20) with a hyphen. - Complete all the TODO; macros. +- implement MAX_ARGC checks (would cause a buffer overflow if not) + when compiling and calling a function (also in fibers). // Low priority. - Ignore line with '\' character. diff --git a/cli/main.c b/cli/main.c index b179c06..7663e0f 100644 --- a/cli/main.c +++ b/cli/main.c @@ -14,7 +14,7 @@ void registerModules(PKVM* vm); // Path public functions (TODO: should I add a header for that?) -void pathInit(); +void pathInit(void); bool pathIsAbsolute(const char* path); void pathGetDirName(const char* path, size_t* length); size_t pathNormalize(const char* path, char* buff, size_t buff_size); @@ -23,6 +23,11 @@ size_t pathJoin(const char* from, const char* path, char* buffer, // --------------------------------------- +// FIXME: +typedef struct { + bool repl_mode; +} VmUserData; + void onResultDone(PKVM* vm, PkStringPtr result) { if ((bool)result.user_data) { @@ -31,15 +36,22 @@ void onResultDone(PKVM* vm, PkStringPtr result) { } void errorFunction(PKVM* vm, PkErrorType type, const char* file, int line, - const char* message) { + const char* message) { + + VmUserData* ud = (VmUserData*)pkGetUserData(vm); + bool repl = (ud) ? ud->repl_mode : false; + if (type == PK_ERROR_COMPILE) { - fprintf(stderr, "Error: %s\n at \"%s\":%i\n", message, file, line); + + if (repl) fprintf(stderr, "Error: %s\n", message); + else fprintf(stderr, "Error: %s\n at \"%s\":%i\n", message, file, line); } else if (type == PK_ERROR_RUNTIME) { fprintf(stderr, "Error: %s\n", message); } else if (type == PK_ERROR_STACKTRACE) { - fprintf(stderr, " %s() [\"%s\":%i]\n", message, file, line); + if (repl) fprintf(stderr, " %s() [line:%i]\n", message, line); + else fprintf(stderr, " %s() [\"%s\":%i]\n", message, file, line); } } @@ -47,7 +59,7 @@ void writeFunction(PKVM* vm, const char* text) { fprintf(stdout, "%s", text); } -static const char* read_line() { +static const char* read_line(void) { // FIXME: use fgetc char by char till reach a new line. const int size = 1024; char* mem = (char*) malloc(size); @@ -141,15 +153,15 @@ int main(int argc, char** argv) { "PocketLang " PK_VERSION_STRING " (https://github.com/ThakeeNathees/pocketlang/)\n" "Copyright(c) 2020 - 2021 ThakeeNathees.\n" "Free and open source software under the terms of the MIT license.\n"; - const char* help = "usage: pocket [-c cmd | file]\n"; + const char* usage = "usage: pocket [-c cmd | file]\n"; // TODO: implement arg parse, REPL. - if (argc < 2) { - printf("%s\n%s", notice, help); - return 0; - } + //if (argc < 2) { + // printf("%s\n%s", notice, help); + // return 0; + //} // Initialize cli. pathInit(); @@ -166,13 +178,39 @@ int main(int argc, char** argv) { PKVM* vm = pkNewVM(&config); registerModules(vm); - PkResult result; // FIXME: this is temp till arg parse implemented. + PkResult result; if (argc == 1) { - // TODO: - //PkHandle* module = pkNewModule(vm, "$(REPL)"); + PkHandle* module = pkNewModule(vm, "$(REPL)"); + options.repl_mode = true; + + VmUserData user_data; + user_data.repl_mode = true; + pkSetUserData(vm, &user_data); + + printf("%s\n", notice); + bool done = false; + do { + printf(">>> "); + PkStringPtr line = { read_line(), onResultDone, (void*)true }; + // TODO: if line is empty continue. + + result = pkCompileModule(vm, module, line, &options); + if (result != PK_RESULT_SUCCESS) continue; + + PkHandle* main_fn = pkGetFunction(vm, module, PK_IMPLICIT_MAIN_NAME); + PkHandle* fiber = pkNewFiber(vm, main_fn); + result = pkRunFiber(vm, fiber, 0, NULL); + + pkReleaseHandle(vm, main_fn); + pkReleaseHandle(vm, fiber); + + } while (!done); + + pkReleaseHandle(vm, module); + } if (argc >= 3 && strcmp(argv[1], "-c") == 0) { diff --git a/cli/modules/path.c b/cli/modules/path.c index d3df6b2..f6fba9c 100644 --- a/cli/modules/path.c +++ b/cli/modules/path.c @@ -40,7 +40,7 @@ /* PUBLIC FUNCTIONS */ /*****************************************************************************/ -void pathInit() { +void pathInit(void) { cwk_path_set_style(CWK_STYLE_UNIX); } diff --git a/src/common.h b/src/common.h index a9cb1e3..fc7de32 100644 --- a/src/common.h +++ b/src/common.h @@ -41,6 +41,11 @@ // header for more information on Nan-tagging. #define VAR_NAN_TAGGING 1 +// The maximum number of argument a pocketlang function supported to call. This +// value is arbitary and feel free to change it. (Just used this limit for an +// internal buffer to store values before calling a new fiber). +#define MAX_ARGC 32 + // The factor by which a buffer will grow when it's capacity reached. #define GROW_FACTOR 2 diff --git a/src/compiler.c b/src/compiler.c index 607de49..4789388 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -251,7 +251,7 @@ typedef enum { typedef struct { const char* name; //< Directly points into the source string. - int length; //< Length of the name. + uint32_t length; //< Length of the name. int depth; //< The depth the local is defined in. int line; //< The line variable declared for debugging. } Local; @@ -643,7 +643,7 @@ static void lexToken(Compiler* compiler) { case ' ': case '\t': case '\r': { - char c = peekChar(compiler); + c = peekChar(compiler); while (c == ' ' || c == '\t' || c == '\r') { eatChar(compiler); c = peekChar(compiler); @@ -735,11 +735,6 @@ static TokenType peek(Compiler* self) { return self->current.type; } -// Returns next token type without lexing a new token. -static TokenType peekNext(Compiler* self) { - return self->next.type; -} - // Consume the current token if it's expected and lex for the next token // and return true otherwise reutrn false. static bool match(Compiler* self, TokenType expected) { @@ -794,7 +789,7 @@ static bool matchEndStatement(Compiler* compiler) { // Consume semi collon, multiple new lines or peek 'end' keyword. static void consumeEndStatement(Compiler* compiler) { if (!matchEndStatement(compiler)) { - parseError(compiler, "Expected statement end with newline or ';'."); + parseError(compiler, "Expected statement end with '\\n' or ';'."); } } @@ -924,9 +919,9 @@ static void patchForward(Compiler* compiler, Fn* fn, int index, int name); static int compilerAddConstant(Compiler* compiler, Var value); static int compilerGetVariable(Compiler* compiler, const char* name, - int length); + uint32_t length); static int compilerAddVariable(Compiler* compiler, const char* name, - int length, int line); + uint32_t length, int line); static void compilerAddForward(Compiler* compiler, int instruction, Fn* fn, const char* name, int length, int line); @@ -1491,7 +1486,7 @@ static void compilerInit(Compiler* compiler, PKVM* vm, const char* source, // Return the index of the variable if it's already defined in the current // scope otherwise returns -1. static int compilerGetVariable(Compiler* compiler, const char* name, - int length) { + uint32_t length) { for (int i = compiler->local_count - 1; i >= 0; i--) { Local* local = &compiler->locals[i]; if (length == local->length && strncmp(name, local->name, length) == 0) { @@ -1504,13 +1499,13 @@ static int compilerGetVariable(Compiler* compiler, const char* name, // Add a variable and return it's index to the context. Assumes that the // variable name is unique and not defined before in the current scope. static int compilerAddVariable(Compiler* compiler, const char* name, - int length, int line) { + uint32_t length, int line) { // TODO: should I validate the name for pre-defined, etc? // Check if maximum variable count is reached. bool max_vars_reached = false; - const char* var_type; // For max variables reached error message. + const char* var_type = ""; // For max variables reached error message. if (compiler->scope_depth == DEPTH_GLOBAL) { if (compiler->local_count == MAX_VARIABLES) { max_vars_reached = true; @@ -1726,7 +1721,7 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) { argc++; const char* param_name = compiler->previous.start; - int param_len = compiler->previous.length; + uint32_t param_len = compiler->previous.length; // TODO: move this to a functions. bool predefined = false; @@ -1757,7 +1752,12 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) { compileBlockBody(compiler, BLOCK_FUNC); consume(compiler, TK_END, "Expected 'end' after function definition end."); - emitOpcode(compiler, OP_PUSH_NULL); + // TODO: This is the function end return, if we pop all the parameters the + // below push_null is redundent (because we always have a null at the rbp + // of the call frame. (for i in argc : emit(pop)) emit(return); but this + // might be faster (right?). + + emitOpcode(compiler, OP_PUSH_NULL); emitOpcode(compiler, OP_RETURN); emitOpcode(compiler, OP_END); } @@ -1931,6 +1931,8 @@ static inline Script* compilerImport(Compiler* compiler) { // before executing the below instructions. static void compilerImportAll(Compiler* compiler, Script* script) { + ASSERT(script != NULL, OOPS); + // Line number of the variables which will be bind to the imported sybmols. int line = compiler->previous.line; @@ -2048,7 +2050,7 @@ static void compileFromImport(Compiler* compiler) { emitStoreVariable(compiler, var_index, true); emitOpcode(compiler, OP_POP); - } while (match(compiler, TK_COMMA)); + } while (match(compiler, TK_COMMA) && (skipNewLines(compiler), true)); } // Done getting all the attributes, now pop the lib from the stack. @@ -2115,12 +2117,11 @@ static void compileRegularImport(Compiler* compiler) { emitOpcode(compiler, OP_POP); } - } while (match(compiler, TK_COMMA)); + } while (match(compiler, TK_COMMA) && (skipNewLines(compiler), true)); consumeEndStatement(compiler); } - // Compiles an expression. An expression will result a value on top of the // stack. static void compileExpression(Compiler* compiler) { @@ -2260,10 +2261,13 @@ static void compileForStatement(Compiler* compiler) { static void compileStatement(Compiler* compiler) { // is_temproary will be set to true if the statement is an temporary - // expression, it'll used to be pop from the stack. If running REPL mode used - // to print it's value. + // expression, it'll used to be pop from the stack. bool is_temproary = false; + // This will be set to true if the statement is an expression. It'll used to + // print it's value when running in REPL mode. + bool is_expression = false; + if (match(compiler, TK_BREAK)) { if (compiler->loop == NULL) { parseError(compiler, "Cannot use 'break' outside a loop."); @@ -2321,19 +2325,23 @@ static void compileStatement(Compiler* compiler) { compiler->new_local = false; compileExpression(compiler); consumeEndStatement(compiler); + + is_expression = true; if (!compiler->new_local) is_temproary = true; + compiler->new_local = false; } - // If running REPL mode, print the expression's evaluvated value. Python - // does print local depth expression too. (it's just a design decision). + // If running REPL mode, print the expression's evaluvated value. Only if + // we're at the top level. Python does print local depth expression too. + // (it's just a design decision). if (compiler->options && compiler->options->repl_mode && - is_temproary /*&& compiler->scope_depth == DEPTH_GLOBAL*/) { + compiler->func->ptr == compiler->script->body && + is_expression /*&& compiler->scope_depth == DEPTH_GLOBAL*/) { emitOpcode(compiler, OP_REPL_PRINT); } if (is_temproary) emitOpcode(compiler, OP_POP); - } // Compile statements that are only valid at the top level of the script. Such @@ -2377,11 +2385,16 @@ bool compile(PKVM* vm, Script* script, const char* source, compiler->next_compiler = vm->compiler; vm->compiler = compiler; - // Remember the count of the (valid) code of the provided script body. For - // new scripts it'll be null, but if we're compiling it multiple times we - // already have some code before. And if the compilation failed we discard - // all the compiled opcodes and jump back to the valid_code. - uint32_t valid_code = script->body->fn->opcodes.count; + // If we're compiling for a script that was already compiled (when running + // REPL or evaluvating an expression) we don't need the old main anymore. + // just use the globals and functions of the script and use a new body func. + ASSERT(script->body != NULL, OOPS); + byteBufferClear(&script->body->fn->opcodes, vm); + + // Remember the count of the globals and functions, If the compilation failed + // discard all the globals and functions added by the compilation. + uint32_t globals_count = script->globals.count; + uint32_t functions_count = script->functions.count; Func curr_fn; curr_fn.depth = DEPTH_SCRIPT; @@ -2416,14 +2429,9 @@ bool compile(PKVM* vm, Script* script, const char* source, skipNewLines(compiler); } - if (compiler->options == NULL || !compiler->options->repl_mode) { - emitOpcode(compiler, OP_PUSH_NULL); - emitOpcode(compiler, OP_RETURN); - emitOpcode(compiler, OP_END); - - } else { - emitOpcode(compiler, OP_YIELD); - } + // Already a null at the stack top, added when the fiber for the function created. + emitOpcode(compiler, OP_RETURN); + emitOpcode(compiler, OP_END); // Resolve forward names (function names that are used before defined). for (int i = 0; i < compiler->forwards_count; i++) { @@ -2441,23 +2449,21 @@ bool compile(PKVM* vm, Script* script, const char* source, vm->compiler = compiler->next_compiler; + // If compilation failed, discard all the invalid functions and globals. + if (compiler->has_errors) { + script->globals.count = script->global_names.count = globals_count; + script->functions.count = script->function_names.count = functions_count; + } + #if DEBUG_DUMP_COMPILED_CODE dumpFunctionCode(vm, script->body); #endif - - // If the compilation failed discard all the compiled invalid code. - // TODO: May be shrink the buffer. - if (compiler->has_errors) { - script->body->fn->opcodes.count = valid_code; - return false; - } - - // If we reach here, it means the compilation is success and return true. - return true; + + return !compiler->has_errors; } PkResult pkCompileModule(PKVM* vm, PkHandle* module, PkStringPtr source, - const PkCompileOptions* options) { + const PkCompileOptions* options) { __ASSERT(module != NULL, "Argument module was NULL."); Var scr = module->value; __ASSERT(IS_OBJ_TYPE(scr, OBJ_SCRIPT), "Given handle is not a module"); diff --git a/src/core.c b/src/core.c index b83dda2..ed47a37 100644 --- a/src/core.c +++ b/src/core.c @@ -218,6 +218,28 @@ void pkReturnValue(PKVM* vm, PkVar value) { RET(*(Var*)value); } +const char* pkStringGetData(const PkVar value) { + const Var str = (*(const Var*)value); + __ASSERT(IS_OBJ_TYPE(str, OBJ_STRING), "Value should be of type string."); + return ((String*)AS_OBJ(str))->data; +} + +PkVar pkFiberGetReturnValue(const PkHandle* fiber) { + __ASSERT(fiber != NULL, "Handle fiber was NULL."); + Var fb = fiber->value; + __ASSERT(IS_OBJ_TYPE(fb, OBJ_FIBER), "Given handle is not a fiber"); + Fiber* _fiber = (Fiber*)AS_OBJ(fb); + return (PkVar)_fiber->ret; +} + +bool pkFiberIsDone(const PkHandle* fiber) { + __ASSERT(fiber != NULL, "Handle fiber was NULL."); + Var fb = fiber->value; + __ASSERT(IS_OBJ_TYPE(fb, OBJ_FIBER), "Given handle is not a fiber"); + Fiber* _fiber = (Fiber*)AS_OBJ(fb); + return _fiber->state == FIBER_DONE; +} + /*****************************************************************************/ /* VALIDATORS */ /*****************************************************************************/ @@ -294,7 +316,7 @@ static inline bool validateIndex(PKVM* vm, int32_t index, int32_t size, // findBuiltinFunction implementation (see core.h for description). int findBuiltinFunction(const PKVM* vm, const char* name, uint32_t length) { - for (int i = 0; i < vm->builtins_count; i++) { + for (uint32_t i = 0; i < vm->builtins_count; i++) { if (length == vm->builtins[i].length && strncmp(name, vm->builtins[i].name, length) == 0) { return i; @@ -456,7 +478,7 @@ PK_DOC(coreStrLower, String* result = newStringLength(vm, str->data, str->length); char* data = result->data; - for (; *data; ++data) *data = tolower(*data); + for (; *data; ++data) *data = (char)tolower(*data); // Since the string is modified re-hash it. result->hash = utilHashString(result->data); @@ -471,7 +493,7 @@ PK_DOC(coreStrUpper, String* result = newStringLength(vm, str->data, str->length); char* data = result->data; - for (; *data; ++data) *data = toupper(*data); + for (; *data; ++data) *data = (char)toupper(*data); // Since the string is modified re-hash it. result->hash = utilHashString(result->data); @@ -586,56 +608,19 @@ PK_DOC(coreFiberRun, Fiber* fb; if (!validateArgFiber(vm, 1, &fb)) return; - ASSERT(fb->func->arity >= -1 , OOPS " (Forget to initialize arity.)"); + // Buffer of argument to call vmPrepareFiber(). + Var* args[MAX_ARGC]; - if (argc - 1 != fb->func->arity) { - char buff[STR_INT_BUFF_SIZE]; sprintf(buff, "%d", fb->func->arity); - RET_ERR(stringFormat(vm, "Expected excatly $ argument(s).", buff)); - } - - if (fb->state != FIBER_NEW) { - switch (fb->state) { - case FIBER_NEW: UNREACHABLE(); - case FIBER_RUNNING: - RET_ERR(newString(vm, "The fiber has already been running.")); - case FIBER_YIELDED: - RET_ERR(newString(vm, "Cannot run a fiber which is yielded, use " - "fiber_resume() instead.")); - case FIBER_DONE: - RET_ERR(newString(vm, "The fiber has done running.")); - } - UNREACHABLE(); - } - - ASSERT(fb->stack != NULL && fb->sp == fb->stack, OOPS); - ASSERT(fb->ret == fb->sp, OOPS); - - fb->state = FIBER_RUNNING; - fb->caller = vm->fiber; - - // Pass the function arguments. - - // Assert we have the first frame (to push the arguments). And assert we have - // enought stack space for parameters. - ASSERT(fb->frame_count == 1, OOPS); - ASSERT(fb->frames[0].rbp == fb->ret, OOPS); - ASSERT((fb->stack + fb->stack_size) - fb->sp >= argc, OOPS); - - // ARG1 is fiber, function arguments are ARG(2), ARG(3), ... ARG(argc). - // And ret[0] is the return value, parameters starts at ret[1], ... + // ARG(1) is fiber, function arguments are ARG(2), ARG(3), ... ARG(argc). for (int i = 1; i < argc; i++) { - fb->ret[i] = ARG(i + 1); + args[i - 1] = &ARG(i + 1); } - fb->sp += argc; // Parameters and return value. - // Set the new fiber as the vm's fiber. - vm->fiber = fb; - - // fb->ret is "un initialized" and will be initialized by the fiber_resume() - // call. But we're setting the value to VAR_NULL below to make it initialized - // for the debugger, it'll prevent from crashing when we're trying to read - // the value to dump. - RET(VAR_NULL); + // Switch fiber and start execution. + if (vmPrepareFiber(vm, fb, argc - 1, args)) { + ASSERT(fb == vm->fiber, OOPS); + fb->state = FIBER_RUNNING; + } } PK_DOC(coreFiberResume, @@ -652,36 +637,13 @@ PK_DOC(coreFiberResume, Fiber* fb; if (!validateArgFiber(vm, 1, &fb)) return; - if (fb->state != FIBER_YIELDED) { - switch (fb->state) { - case FIBER_NEW: - RET_ERR(newString(vm, "The fiber hasn't started. call fiber_run() to " - "start.")); - case FIBER_RUNNING: - RET_ERR(newString(vm, "The fiber has already been running.")); - case FIBER_YIELDED: UNREACHABLE(); - case FIBER_DONE: - RET_ERR(newString(vm, "The fiber has done running.")); - } - UNREACHABLE(); + Var value = (argc == 1) ? VAR_NULL : ARG(2); + + // Switch fiber and resume execution. + if (vmSwitchFiber(vm, fb, &value)) { + ASSERT(fb == vm->fiber, OOPS); + fb->state = FIBER_RUNNING; } - - fb->state = FIBER_RUNNING; - fb->caller = vm->fiber; - - // Pass the resume argument if it has any. - - // Assert if we have a call frame and the stack size enough for the return - // value and the resumed value. - ASSERT(fb->frame_count != 0, OOPS); - ASSERT((fb->stack + fb->stack_size) - fb->sp >= 2, OOPS); - - // fb->ret will points to the return value of the 'yield()' call. - if (argc == 1) *fb->ret = VAR_NULL; - else *fb->ret = ARG(2); - - // Set the new fiber as the vm's fiber. - vm->fiber = fb; } /*****************************************************************************/ @@ -1126,7 +1088,7 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) { varBufferWrite(&list->elements, vm, VAR_NUM(i)); } } else { - newList(vm, 0); + list = newList(vm, 0); } return VAR_OBJ(list); } @@ -1139,7 +1101,7 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) { Script* scr = (Script*)obj; // Search in functions. - uint32_t index = scriptGetFunc(scr, attrib->data, attrib->length); + int index = scriptGetFunc(scr, attrib->data, attrib->length); if (index != -1) { ASSERT_INDEX(index, scr->functions.count); return VAR_OBJ(scr->functions.data[index]); @@ -1167,7 +1129,6 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) { CHECK_MISSING_OBJ_TYPE(7); UNREACHABLE(); - return VAR_NULL; } void varSetAttrib(PKVM* vm, Var on, String* attrib, Var value) { @@ -1211,7 +1172,7 @@ do { \ Script* scr = (Script*)obj; // Check globals. - uint32_t index = scriptGetGlobals(scr, attrib->data, attrib->length); + int index = scriptGetGlobals(scr, attrib->data, attrib->length); if (index != -1) { ASSERT_INDEX(index, scr->globals.count); scr->globals.data[index] = value; @@ -1315,7 +1276,6 @@ Var varGetSubscript(PKVM* vm, Var on, Var key) { CHECK_MISSING_OBJ_TYPE(7); UNREACHABLE(); - return VAR_NULL; } void varsetSubscript(PKVM* vm, Var on, Var key, Var value) { diff --git a/src/debug.c b/src/debug.c index ef256d9..f0b3616 100644 --- a/src/debug.c +++ b/src/debug.c @@ -26,7 +26,7 @@ static void _dumpValue(PKVM* vm, Var value, bool recursive) { return; } if (IS_BOOL(value)) { - printf((AS_BOOL(value)) ? "true" : "false"); + printf("%s", (AS_BOOL(value)) ? "true" : "false"); return; } if (IS_NUM(value)) { @@ -199,7 +199,7 @@ void dumpFunctionCode(PKVM* vm, Function* func) { break; case OP_PUSH_LOCAL_N: - READ_BYTE(); + BYTE_ARG(); break; case OP_STORE_LOCAL_0: @@ -215,7 +215,7 @@ void dumpFunctionCode(PKVM* vm, Function* func) { break; case OP_STORE_LOCAL_N: - READ_BYTE(); + BYTE_ARG(); break; case OP_PUSH_GLOBAL: @@ -315,7 +315,6 @@ void dumpFunctionCode(PKVM* vm, Function* func) { case OP_RANGE: case OP_IN: case OP_REPL_PRINT: - case OP_YIELD: case OP_END: NO_ARGS(); break; diff --git a/src/include/pocketlang.h b/src/include/pocketlang.h index 3ad55f2..3e2086a 100644 --- a/src/include/pocketlang.h +++ b/src/include/pocketlang.h @@ -67,7 +67,7 @@ extern "C" { // Name of the implicit function for a module. When a module is parsed all of // it's statements are wrapped around an implicit function with this name. -#define PK_BODY_FN_NAME "$(SourceBody)" +#define PK_IMPLICIT_MAIN_NAME "$(SourceBody)" /*****************************************************************************/ /* POCKETLANG TYPES */ @@ -177,12 +177,12 @@ typedef PkStringPtr (*pkLoadScriptFn) (PKVM* vm, const char* path); // Create a new pkConfiguraition with the default values and return it. // Override those default configuration to adopt to another hosting // application. -PK_PUBLIC PkConfiguration pkNewConfiguration(); +PK_PUBLIC PkConfiguration pkNewConfiguration(void); // Create a new pkCompilerOptions with the default values and return it. // Override those default configuration to adopt to another hosting // application. -PK_PUBLIC PkCompileOptions pkNewCompilerOptions(); +PK_PUBLIC PkCompileOptions pkNewCompilerOptions(void); // Allocate initialize and returns a new VM PK_PUBLIC PKVM* pkNewVM(PkConfiguration* config); @@ -221,9 +221,8 @@ PK_PUBLIC void pkModuleAddFunction(PKVM* vm, PkHandle* module, PK_PUBLIC PkHandle* pkGetFunction(PKVM* vm, PkHandle* module, const char* name); -// Compile the [module] with the provided [source] and return true if the -// compilation is success. Set the compiler options with the the [options] -// argument or it can be set to NULL for default options. +// Compile the [module] with the provided [source]. Set the compiler options +// with the the [options] argument or set to NULL for default options. PK_PUBLIC PkResult pkCompileModule(PKVM* vm, PkHandle* module, PkStringPtr source, const PkCompileOptions* options); @@ -237,9 +236,18 @@ PK_PUBLIC PkResult pkInterpretSource(PKVM* vm, PkStringPtr path, const PkCompileOptions* options); +// Runs the fiber's function with the provided arguments (param [arc] is the +// argument count and [argv] are the values). It'll returns it's run status +// reslt (success or failure) if you need the yielded or returned value use the +// pkFiberGetReturnValue() function, and use pkFiberIsDone() function to check +// if the fiber can be resumed with pkFiberResume() function. +PK_PUBLIC PkResult pkRunFiber(PKVM* vm, PkHandle* fiber, + int argc, PkHandle** argv); -//PK_PUBLIC PkResult pkRunFiber(PKVM* vm, PkHandle* fiber, -// int argc, PkHandle** argv); +// Resume a yielded fiber with an optional [value]. (could be set to NULL) +// It'll returns it's run status reslt (success or failure) if you need the +// yielded or returned value use the pkFiberGetReturnValue() function. +PK_PUBLIC PkResult pkResumeFiber(PKVM* vm, PkHandle* fiber, PkVar value); /*****************************************************************************/ /* POCKETLANG PUBLIC TYPE DEFINES */ @@ -336,6 +344,19 @@ PK_PUBLIC void pkReturnString(PKVM* vm, const char* value); PK_PUBLIC void pkReturnStringLength(PKVM* vm, const char* value, size_t len); PK_PUBLIC void pkReturnValue(PKVM* vm, PkVar value); +// Returns the cstring pointer of the given string. Make sure if the [value] is +// a string before calling this function, otherwise it'll fail an assertion. +PK_PUBLIC const char* pkStringGetData(const PkVar value); + +// Returns the return value or if it's yielded, the yielded value of the fiber +// as PkVar, this value lives on stack and will die (popped) once the fiber +// resumed use handle to keep it alive. +PK_PUBLIC PkVar pkFiberGetReturnValue(const PkHandle* fiber); + +// Returns true if the fiber is finished it's execution and cannot be resumed +// anymore. +PK_PUBLIC bool pkFiberIsDone(const PkHandle* fiber); + /*****************************************************************************/ /* POCKETLANG TYPE FUNCTIONS */ /*****************************************************************************/ @@ -355,8 +376,6 @@ PK_PUBLIC PkHandle* pkNewModule(PKVM* vm, const char* name); // Create and return a new fiber around the function [fn]. PK_PUBLIC PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn); -PK_PUBLIC const char* pkStringGetData(const PkVar value); - // TODO: // The below functions will push the primitive values on the stack and return // it's pointer as a PkVar it's usefull to convert your primitive values as diff --git a/src/opcodes.h b/src/opcodes.h index 7423d3d..8fd3772 100644 --- a/src/opcodes.h +++ b/src/opcodes.h @@ -192,9 +192,6 @@ OPCODE(IN, 0, -1) // This will not pop the value. OPCODE(REPL_PRINT, 0, 0) -// Yield the current fiber. Used in REPL mode. -OPCODE(YIELD, 0, 0) - // A sudo instruction which will never be called. A function's last opcode // used for debugging. OPCODE(END, 0, 0) diff --git a/src/utils.c b/src/utils.c index ebc1a47..c336956 100644 --- a/src/utils.c +++ b/src/utils.c @@ -128,17 +128,17 @@ int utf8_encodeValue(int value, uint8_t* bytes) { // 2 byte character 110xxxxx 10xxxxxx -> last 6 bits write to 2nd byte and // first 5 bit write to first byte if (value <= 0x7ff) { - *(bytes++) = 0b11000000 | ((value & 0b11111000000) >> 6); - *(bytes) = 0b10000000 | ((value & 111111)); + *(bytes++) = (uint8_t)(0b11000000 | ((value & 0b11111000000) >> 6)); + *(bytes) = (uint8_t)(0b10000000 | ((value & 111111))); return 2; } // 3 byte character 1110xxxx 10xxxxxx 10xxxxxx -> from last, 6 bits write // to 3rd byte, next 6 bits write to 2nd byte, and 4 bits to first byte. if (value <= 0xffff) { - *(bytes++) = 0b11100000 | ((value & 0b1111000000000000) >> 12); - *(bytes++) = 0b10000000 | ((value & 0b111111000000) >> 6); - *(bytes) = 0b10000000 | ((value & 0b111111)); + *(bytes++) = (uint8_t)(0b11100000 | ((value & 0b1111000000000000) >> 12)); + *(bytes++) = (uint8_t)(0b10000000 | ((value & 0b111111000000) >> 6)); + *(bytes) = (uint8_t)(0b10000000 | ((value & 0b111111))); return 3; } @@ -146,10 +146,10 @@ int utf8_encodeValue(int value, uint8_t* bytes) { // to 4th byte, next 6 bits to 3rd byte, next 6 bits to 2nd byte, 3 bits // first byte. if (value <= 0x10ffff) { - *(bytes++) = 0b11110000 | ((value & 0b111000000000000000000) >> 18); - *(bytes++) = 0b10000000 | ((value & 0b111111000000000000) >> 12); - *(bytes++) = 0b10000000 | ((value & 0b111111000000) >> 6); - *(bytes) = 0b10000000 | ((value & 0b111111)); + *(bytes++) = (uint8_t)(0b11110000 | ((value & 0b111000000000000000000) >> 18)); + *(bytes++) = (uint8_t)(0b10000000 | ((value & 0b111111000000000000) >> 12)); + *(bytes++) = (uint8_t)(0b10000000 | ((value & 0b111111000000) >> 6)); + *(bytes) = (uint8_t)(0b10000000 | ((value & 0b111111))); return 4; } diff --git a/src/var.c b/src/var.c index 271f5ee..536e56d 100644 --- a/src/var.c +++ b/src/var.c @@ -36,7 +36,6 @@ PkVarType pkGetValueType(const PkVar value) { } UNREACHABLE(); - return (PkVarType)0; } PkHandle* pkNewString(PKVM* vm, const char* value) { @@ -62,12 +61,6 @@ PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn) { return vmNewHandle(vm, VAR_OBJ(fiber)); } -const char* pkStringGetData(const PkVar value) { - const Var str = (*(const Var*)value); - __ASSERT(IS_OBJ_TYPE(str, OBJ_STRING), "Value should be of type string."); - return ((String*)AS_OBJ(str))->data; -} - /*****************************************************************************/ /* VAR INTERNALS */ /*****************************************************************************/ @@ -340,7 +333,7 @@ Script* newScript(PKVM* vm, String* path) { stringBufferInit(&script->names); vmPushTempRef(vm, &script->_super); - const char* fn_name = PK_BODY_FN_NAME; + const char* fn_name = PK_IMPLICIT_MAIN_NAME; script->body = newFunction(vm, fn_name, (int)strlen(fn_name), script, false); script->body->arity = 0; // TODO: should it be 1 (ARGV)?. vmPopTempRef(vm); @@ -401,8 +394,8 @@ Fiber* newFiber(PKVM* vm, Function* fn) { int stack_size = utilPowerOf2Ceil(fn->arity + 1); fiber->stack = ALLOCATE_ARRAY(vm, Var, stack_size); fiber->stack_size = stack_size; - fiber->sp = fiber->stack; fiber->ret = fiber->stack; + fiber->sp = fiber->stack + 1; } else { // Allocate stack. @@ -410,8 +403,8 @@ Fiber* newFiber(PKVM* vm, Function* fn) { if (stack_size < MIN_STACK_SIZE) stack_size = MIN_STACK_SIZE; fiber->stack = ALLOCATE_ARRAY(vm, Var, stack_size); fiber->stack_size = stack_size; - fiber->sp = fiber->stack; fiber->ret = fiber->stack; + fiber->sp = fiber->stack + 1; // Allocate call frames. fiber->frame_capacity = INITIAL_CALL_FRAMES; @@ -424,6 +417,10 @@ Fiber* newFiber(PKVM* vm, Function* fn) { fiber->frames[0].rbp = fiber->ret; } + // 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 vaue). + *fiber->ret = VAR_NULL; + return fiber; } @@ -500,7 +497,6 @@ static uint32_t _hashObject(Object* obj) { } UNREACHABLE(); - return -1; } uint32_t varHashValue(Var v) { @@ -754,7 +750,6 @@ const char* getPkVarTypeName(PkVarType type) { } UNREACHABLE(); - return NULL; } const char* getObjectTypeName(ObjectType type) { @@ -767,9 +762,8 @@ const char* getObjectTypeName(ObjectType type) { case OBJ_FUNC: return "Func"; case OBJ_FIBER: return "Fiber"; case OBJ_USER: return "UserObj"; - default: - UNREACHABLE(); } + UNREACHABLE(); } const char* varTypeName(Var v) { @@ -821,11 +815,11 @@ bool isValuesEqual(Var v1, Var v2) { */ List *l1 = (List*)o1, *l2 = (List*)o2; if (l1->elements.count != l2->elements.count) return false; - Var* v1 = l1->elements.data; - Var* v2 = l2->elements.data; + Var* _v1 = l1->elements.data; + Var* _v2 = l2->elements.data; for (uint32_t i = 0; i < l1->elements.count; i++) { - if (!isValuesEqual(*v1, *v2)) return false; - v1++, v2++; + if (!isValuesEqual(*_v1, *_v2)) return false; + _v1++, _v2++; } return true; } @@ -918,12 +912,7 @@ static void _toStringInternal(PKVM* vm, const Var v, ByteBuffer* buff, byteBufferWrite(buff, vm, '"'); return; } - - // If recursive return with quotes (ex: [42, "hello", 0..10]). - byteBufferWrite(buff, vm, '"'); - byteBufferAddString(buff, vm, str->data, str->length); - byteBufferWrite(buff, vm, '"'); - return; + UNREACHABLE(); } case OBJ_LIST: @@ -994,14 +983,13 @@ static void _toStringInternal(PKVM* vm, const Var v, ByteBuffer* buff, } if (_done) break; - if (!_first) { - byteBufferAddString(buff, vm, ", ", 2); - _first = false; - } + if (!_first) byteBufferAddString(buff, vm, ", ", 2); + _toStringInternal(vm, map->entries[i].key, buff, &seq_map, true); byteBufferWrite(buff, vm, ':'); _toStringInternal(vm, map->entries[i].value, buff, &seq_map, true); - i++; + + i++; _first = false; } while (i < map->capacity); byteBufferWrite(buff, vm, '}'); @@ -1106,13 +1094,12 @@ bool toBool(Var v) { case OBJ_RANGE: // [[FALLTHROUGH]] case OBJ_SCRIPT: case OBJ_FUNC: + case OBJ_FIBER: case OBJ_USER: return true; - default: - UNREACHABLE(); } - return true; + UNREACHABLE(); } String* stringFormat(PKVM* vm, const char* fmt, ...) { diff --git a/src/var.h b/src/var.h index 355ef31..5deec59 100644 --- a/src/var.h +++ b/src/var.h @@ -24,10 +24,9 @@ */ // To use dynamic variably-sized struct with a tail array add an array at the -// end of the struct with size \ref DYNAMIC_TAIL_ARRAY. This method was a -// legacy standard called "struct hack". -#if __STDC_VERSION__ >= 199901L - /** for std >= c99 it's just `arr[]` */ +// end of the struct with size DYNAMIC_TAIL_ARRAY. This method was a legacy +// standard called "struct hack". +#if defined(_MSC_VER) || __STDC_VERSION__ >= 199901L // std >= c99 #define DYNAMIC_TAIL_ARRAY #else #define DYNAMIC_TAIL_ARRAY 0 @@ -218,7 +217,7 @@ typedef enum { // This will terminate compiler (because of 1/0 evaluvated) if ObjectType max // is not [count]. Use this to ensure every time switching ObjectType will // cover all object types. -#if DEBUG +#ifdef DEBUG #define CHECK_MISSING_OBJ_TYPE(count) (1/ ((int)(!(count ^ OBJ_USER))) ) #else #define CHECK_MISSING_OBJ_TYPE(count) do {} while (false) diff --git a/src/vm.c b/src/vm.c index 29e4a21..10539b3 100644 --- a/src/vm.c +++ b/src/vm.c @@ -25,7 +25,7 @@ static void* defaultRealloc(void* memory, size_t new_size, void* user_data); // till the next yield or return statement, and return result. static PkResult runFiber(PKVM* vm, Fiber* fiber); -PkConfiguration pkNewConfiguration() { +PkConfiguration pkNewConfiguration(void) { PkConfiguration config; config.realloc_fn = defaultRealloc; @@ -40,7 +40,7 @@ PkConfiguration pkNewConfiguration() { return config; } -PkCompileOptions pkNewCompilerOptions() { +PkCompileOptions pkNewCompilerOptions(void) { PkCompileOptions options; options.debug = false; // TODO: @@ -161,6 +161,39 @@ PkResult pkInterpretSource(PKVM* vm, PkStringPtr source, PkStringPtr path, return runFiber(vm, newFiber(vm, scr->body)); } +PkResult pkRunFiber(PKVM* vm, PkHandle* fiber, + int argc, PkHandle** argv) { + __ASSERT(fiber != NULL, "Handle fiber was NULL."); + Var fb = fiber->value; + __ASSERT(IS_OBJ_TYPE(fb, OBJ_FIBER), "Given handle is not a fiber."); + Fiber* _fiber = (Fiber*)AS_OBJ(fb); + + Var* args[MAX_ARGC]; + for (int i = 0; i < argc; i++) { + args[i] = &(argv[i]->value); + } + + if (!vmPrepareFiber(vm, _fiber, argc, args)) { + return PK_RESULT_RUNTIME_ERROR; + } + + ASSERT(_fiber->frame_count == 1, OOPS); + return runFiber(vm, _fiber); +} + +PkResult pkResumeFiber(PKVM* vm, PkHandle* fiber, PkVar value) { + __ASSERT(fiber != NULL, "Handle fiber was NULL."); + Var fb = fiber->value; + __ASSERT(IS_OBJ_TYPE(fb, OBJ_FIBER), "Given handle is not a fiber."); + Fiber* _fiber = (Fiber*)AS_OBJ(fb); + + if (!vmSwitchFiber(vm, _fiber, (Var*)value)) { + return PK_RESULT_RUNTIME_ERROR; + } + + return runFiber(vm, _fiber); +} + void pkSetRuntimeError(PKVM* vm, const char* message) { __ASSERT(vm->fiber != NULL, "This function can only be called at runtime."); vm->fiber->error = newString(vm, message); @@ -224,7 +257,7 @@ void vmCollectGarbage(PKVM* vm) { // Mark the core libs and builtin functions. grayObject(vm, &vm->core_libs->_super); - for (int i = 0; i < vm->builtins_count; i++) { + for (uint32_t i = 0; i < vm->builtins_count; i++) { grayObject(vm, &vm->builtins[i].fn->_super); } @@ -281,6 +314,96 @@ void vmCollectGarbage(PKVM* vm) { if (vm->next_gc < vm->min_heap_size) vm->next_gc = vm->min_heap_size; } +#define _ERR_FAIL(msg) \ + do { \ + if (vm->fiber != NULL) vm->fiber->error = msg; \ + return false; \ + } while (false) + +bool vmPrepareFiber(PKVM* vm, Fiber* fiber, int argc, Var** argv) { + ASSERT(fiber->func->arity >= -1, OOPS " (Forget to initialize arity.)"); + + if (argc != fiber->func->arity) { + char buff[STR_INT_BUFF_SIZE]; sprintf(buff, "%d", fiber->func->arity); + _ERR_FAIL(stringFormat(vm, "Expected excatly $ argument(s).", buff)); + } + + if (fiber->state != FIBER_NEW) { + switch (fiber->state) { + case FIBER_NEW: UNREACHABLE(); + case FIBER_RUNNING: + _ERR_FAIL(newString(vm, "The fiber has already been running.")); + case FIBER_YIELDED: + _ERR_FAIL(newString(vm, "Cannot run a fiber which is yielded, use " + "fiber_resume() instead.")); + case FIBER_DONE: + _ERR_FAIL(newString(vm, "The fiber has done running.")); + } + UNREACHABLE(); + } + + ASSERT(fiber->stack != NULL && fiber->sp == fiber->stack + 1, OOPS); + ASSERT(fiber->ret + 1 == fiber->sp, OOPS); + + // Pass the function arguments. + + // Assert we have the first frame (to push the arguments). And assert we have + // enought stack space for parameters. + ASSERT(fiber->frame_count == 1, OOPS); + ASSERT(fiber->frames[0].rbp == fiber->ret, OOPS); + ASSERT((fiber->stack + fiber->stack_size) - fiber->sp >= argc, OOPS); + + // ARG1 is fiber, function arguments are ARG(2), ARG(3), ... ARG(argc). + // And ret[0] is the return value, parameters starts at ret[1], ... + for (int i = 0; i < argc; i++) { + fiber->ret[1 + i] = *argv[i]; // +1: ret[0] is return value. + } + fiber->sp += argc; // Parameters. + + // Set the new fiber as the vm's fiber. + fiber->caller = vm->fiber; + vm->fiber = fiber; + + // On success return true. + return true; +} + +bool vmSwitchFiber(PKVM* vm, Fiber* fiber, Var* value) { + if (fiber->state != FIBER_YIELDED) { + switch (fiber->state) { + case FIBER_NEW: + _ERR_FAIL(newString(vm, "The fiber hasn't started. call fiber_run() " + "to start.")); + case FIBER_RUNNING: + _ERR_FAIL(newString(vm, "The fiber has already been running.")); + case FIBER_YIELDED: UNREACHABLE(); + case FIBER_DONE: + _ERR_FAIL(newString(vm, "The fiber has done running.")); + } + UNREACHABLE(); + } + + // Pass the resume argument if it has any. + + // Assert if we have a call frame and the stack size enough for the return + // value and the resumed value. + ASSERT(fiber->frame_count != 0, OOPS); + ASSERT((fiber->stack + fiber->stack_size) - fiber->sp >= 2, OOPS); + + // fb->ret will points to the return value of the 'yield()' call. + if (value == NULL) *fiber->ret = VAR_NULL; + else *fiber->ret = *value; + + // Switch fiber. + fiber->caller = vm->fiber; + vm->fiber = fiber; + + // On success return true. + return true; +} + +#undef _ERR_FAIL + void vmYieldFiber(PKVM* vm, Var* value) { Fiber* caller = vm->fiber->caller; @@ -454,7 +577,7 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) { // it from garbage collection and get the reference from native functions. vm->fiber = fiber; - ASSERT(fiber->state == FIBER_NEW, OOPS); + ASSERT(fiber->state == FIBER_NEW || fiber->state == FIBER_YIELDED, OOPS); fiber->state = FIBER_RUNNING; // The instruction pointer. @@ -471,12 +594,24 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) { #define READ_BYTE() (*ip++) #define READ_SHORT() (ip+=2, (uint16_t)((ip[-2] << 8) | ip[-1])) +// Switch back to the caller of the current fiber, will be called when we're +// done with the fiber or aborting it for runtime errors. +#define FIBER_SWITCH_BACK() \ + do { \ + Fiber* caller = vm->fiber->caller; \ + ASSERT(caller == NULL || caller->state == FIBER_RUNNING, OOPS); \ + vm->fiber->state = FIBER_DONE; \ + vm->fiber->caller = NULL; \ + vm->fiber = caller; \ + } while (false) + // Check if any runtime error exists and if so returns RESULT_RUNTIME_ERROR. #define CHECK_ERROR() \ do { \ if (HAS_ERROR()) { \ UPDATE_FRAME(); \ reportError(vm); \ + FIBER_SWITCH_BACK(); \ return PK_RESULT_RUNTIME_ERROR; \ } \ } while (false) @@ -487,6 +622,7 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) { vm->fiber->error = err_msg; \ UPDATE_FRAME(); \ reportError(vm); \ + FIBER_SWITCH_BACK(); \ return PK_RESULT_RUNTIME_ERROR; \ } while (false) @@ -522,8 +658,7 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) { #define OPCODE(code) case OP_##code #define DISPATCH() goto L_vm_main_loop - // TODO: remove the below push null and add it from the compiler. - PUSH(VAR_NULL); // Return value of the script body. + // Load the fiber's top call frame to the vm's execution variables. LOAD_FRAME(); L_vm_main_loop: @@ -689,14 +824,14 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) { OPCODE(IMPORT): { String* name = script->names.data[READ_SHORT()]; - Var script = importScript(vm, name); + Var scr = importScript(vm, name); // TODO: implement fiber bsed execution. //ASSERT(IS_OBJ_TYPE(script, OBJ_SCRIPT), OOPS); //Script* scr = (Script*)AS_OBJ(script); //if (!scr->initialized) vmRunScript(vm, scr); - PUSH(script); + PUSH(scr); CHECK_ERROR(); DISPATCH(); } @@ -914,21 +1049,20 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) { // Pop the last frame, and if no more call frames, we're done with the // current fiber. if (--vm->fiber->frame_count == 0) { - vm->fiber->state = FIBER_DONE; + // TODO: if we're evaluvating an expressoin we need to set it's + // value on the stack. + //vm->fiber->sp = vm->fiber->stack; ?? - // TODO: - //vm->fiber->sp = vm->fiber->stack;. + // Assert all the stack locals were popped. + ASSERT(vm->fiber->sp == vm->fiber->stack, OOPS); - if (vm->fiber->caller == NULL) { + FIBER_SWITCH_BACK(); + + if (vm->fiber == NULL) { return PK_RESULT_SUCCESS; } else { - Fiber* caller = vm->fiber->caller; - ASSERT(caller->state == FIBER_RUNNING, OOPS); - - vm->fiber->caller = NULL; - vm->fiber = caller; - *caller->ret = ret_value; + *vm->fiber->ret = ret_value; } } else { @@ -1186,27 +1320,14 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) { { if (vm->config.write_fn != NULL) { Var tmp = PEEK(-1); - vm->config.write_fn(vm, toRepr(vm, tmp)->data); - vm->config.write_fn(vm, "\n"); + if (!IS_NULL(tmp)) { + vm->config.write_fn(vm, toRepr(vm, tmp)->data); + vm->config.write_fn(vm, "\n"); + } } DISPATCH(); } - OPCODE(YIELD): - { - Fiber* call_fiber = vm->fiber; - - UPDATE_FRAME(); - vmYieldFiber(vm, NULL); - if (vm->fiber == NULL) return PK_RESULT_SUCCESS; - LOAD_FRAME(); - - // Pop function arguments except for the return value of the call fiber. - call_fiber->sp = call_fiber->ret + 1; - - DISPATCH(); - } - OPCODE(END): TODO; break; diff --git a/src/vm.h b/src/vm.h index 7627289..b844366 100644 --- a/src/vm.h +++ b/src/vm.h @@ -40,7 +40,7 @@ // entry of the array. typedef struct { const char* name; //< Name of the function. - int length; //< Length of the name. + uint32_t length; //< Length of the name. Function* fn; //< Native function pointer. } BuiltinFn; @@ -108,7 +108,7 @@ struct PKVM { // Array of all builtin functions. BuiltinFn builtins[BUILTIN_FN_CAPACITY]; - int builtins_count; + uint32_t builtins_count; // Current fiber. Fiber* fiber; @@ -180,6 +180,19 @@ void vmPopTempRef(PKVM* vm); // cache. If not found itll return NULL. Script* vmGetScript(PKVM* vm, String* path); +// ((Context switching - start)) +// Prepare a new fiber for execution with the given arguments. That can be used +// different fiber_run apis. Return true on success, otherwise it'll set the +// error to the vm's current fiber (if it has any). +bool vmPrepareFiber(PKVM* vm, Fiber* fiber, int argc, Var** argv); + +// ((Context switching - resume)) +// Switch the running fiber of the vm from the current fiber to the provided +// [fiber]. with an optional [value] (could be set to NULL). used in different +// fiber_resume apis. Return true on success, otherwise it'll set the error to +// the vm's current fiber (if it has any). +bool vmSwitchFiber(PKVM* vm, Fiber* fiber, Var* value); + // Yield from the current fiber. If the [value] isn't NULL it'll set it as the // yield value. void vmYieldFiber(PKVM* vm, Var* value);