From fdf685731d754eb4316bf1eeb834b43bbb681299 Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Mon, 7 Jun 2021 11:24:06 +0530 Subject: [PATCH] refactor for repl support [2/3] --- build/build.bat | 3 +- cli/TODO.txt | 10 +++ cli/main.c | 51 +++++++++--- src/common.h | 5 -- src/compiler.c | 140 +++++++++++++++++++++++---------- src/compiler.h | 5 +- src/core.c | 114 ++++++++++++++++++--------- src/core.h | 6 +- src/debug.c | 3 + src/include/pocketlang.h | 90 +++++++++++++++++---- src/opcodes.h | 7 ++ src/var.c | 70 +++++++++++++---- src/var.h | 16 ++-- src/vm.c | 163 ++++++++++++++++++++++++++------------- src/vm.h | 10 ++- 15 files changed, 502 insertions(+), 191 deletions(-) diff --git a/build/build.bat b/build/build.bat index b652bd1..2c84d01 100644 --- a/build/build.bat +++ b/build/build.bat @@ -44,7 +44,7 @@ if not defined INCLUDE goto :MSVC_INIT goto :START :MSVC_INIT -call :ColorText 0f "Not running on MSVM prompt, searching for one..." +call :ColorText 0f "Not running on MSVC prompt, searching for one..." echo. :: Find vswhere @@ -170,6 +170,7 @@ goto :END :FAIL call :ColorText 0c "Build failed. See the error messages." echo. +exit /b 1 goto :END :END diff --git a/cli/TODO.txt b/cli/TODO.txt index c048358..c601f6b 100644 --- a/cli/TODO.txt +++ b/cli/TODO.txt @@ -1,5 +1,15 @@ // To implement. + +- refactor build.bat batch script to powershell + refactor makefile and setup a github action. + +- change or add => to_string() to value.as_string + and add as_repr, as_bool. + str_lower("UPPER") to "UPPER".lower + +- support new line just after '=' (and other places) + - Implement argparse. - -v --version - emit opcodes diff --git a/cli/main.c b/cli/main.c index c1089c3..b179c06 100644 --- a/cli/main.c +++ b/cli/main.c @@ -9,7 +9,7 @@ #include - // FIXME: everything below here is temproary and for testing. +// FIXME: everything below here is temproary and for testing. void registerModules(PKVM* vm); @@ -23,7 +23,14 @@ size_t pathJoin(const char* from, const char* path, char* buffer, // --------------------------------------- -void errorPrint(PKVM* vm, PkErrorType type, const char* file, int line, +void onResultDone(PKVM* vm, PkStringPtr result) { + + if ((bool)result.user_data) { + free((void*)result.string); + } +} + +void errorFunction(PKVM* vm, PkErrorType type, const char* file, int line, const char* message) { if (type == PK_ERROR_COMPILE) { fprintf(stderr, "Error: %s\n at \"%s\":%i\n", message, file, line); @@ -40,11 +47,24 @@ void writeFunction(PKVM* vm, const char* text) { fprintf(stdout, "%s", text); } -void onResultDone(PKVM* vm, PkStringPtr result) { +static const char* read_line() { + // FIXME: use fgetc char by char till reach a new line. + const int size = 1024; + char* mem = (char*) malloc(size); + fgets(mem, size, stdin); + size_t len = strlen(mem); - if ((bool)result.user_data) { - free((void*)result.string); - } + // FIXME: handle \r\n, this is temp. + mem[len - 1] = '\0'; + return mem; +} + +PkStringPtr readFunction(PKVM* vm) { + PkStringPtr result; + result.string = read_line(); + result.on_done = onResultDone; + result.user_data = (void*)true; + return result; } PkStringPtr resolvePath(PKVM* vm, const char* from, const char* path) { @@ -135,22 +155,31 @@ int main(int argc, char** argv) { pathInit(); PkConfiguration config = pkNewConfiguration(); - config.error_fn = errorPrint; + config.error_fn = errorFunction; config.write_fn = writeFunction; + config.read_fn = readFunction; config.load_script_fn = loadScript; config.resolve_path_fn = resolvePath; + PkCompileOptions options = pkNewCompilerOptions(); + options.debug = true; // TODO: update this with cli args. + PKVM* vm = pkNewVM(&config); registerModules(vm); - PkInterpretResult result; + PkResult result; // FIXME: this is temp till arg parse implemented. - if (argc >= 3 && strcmp(argv[1], "-c") == 0) { + + if (argc == 1) { + // TODO: + //PkHandle* module = pkNewModule(vm, "$(REPL)"); + + } if (argc >= 3 && strcmp(argv[1], "-c") == 0) { PkStringPtr source = { argv[2], NULL, NULL }; PkStringPtr path = { "$(Source)", NULL, NULL }; - result = pkInterpretSource(vm, source, path); + result = pkInterpretSource(vm, source, path, NULL); pkFreeVM(vm); return result; } @@ -159,7 +188,7 @@ int main(int argc, char** argv) { PkStringPtr source = loadScript(vm, resolved.string); if (source.string != NULL) { - result = pkInterpretSource(vm, source, resolved); + result = pkInterpretSource(vm, source, resolved, &options); } else { result = PK_RESULT_COMPILE_ERROR; diff --git a/src/common.h b/src/common.h index 2838cc9..a9cb1e3 100644 --- a/src/common.h +++ b/src/common.h @@ -164,9 +164,4 @@ // + 1 for null byte '\0' #define STR_INT_BUFF_SIZE 12 -/*****************************************************************************/ -/* INTERNAL TYPE DEFINES */ -/*****************************************************************************/ - - #endif //PK_COMMON_H diff --git a/src/compiler.c b/src/compiler.c index bfe52f0..607de49 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -40,6 +40,9 @@ // available in C++98. #define ERROR_MESSAGE_SIZE 256 +// The name of a literal function. +#define LITERAL_FN_NAME "$(LiteralFn)" + /***************************************************************************** * TOKENS * *****************************************************************************/ @@ -328,6 +331,8 @@ struct Compiler { Token previous, current, next; //< Currently parsed tokens. bool has_errors; //< True if any syntex error occured at. + const PkCompileOptions* options; //< To configure the compilation. + // Current depth the compiler in (-1 means top level) 0 means function // level and > 0 is inner scope. int scope_depth; @@ -479,7 +484,7 @@ static void eatString(Compiler* compiler, bool single_quote) { // '\0' will be added by varNewSring(); Var string = VAR_OBJ(newStringLength(compiler->vm, (const char*)buff.data, - (uint32_t)buff.count)); + (uint32_t)buff.count)); byteBufferClear(&buff, compiler->vm); @@ -879,7 +884,7 @@ static NameSearchResult compilerSearchName(Compiler* compiler, int index; // For storing the search result below. // Search through globals. - index = scriptSearchGlobals(compiler->script, name, length); + index = scriptGetGlobals(compiler->script, name, length); if (index != -1) { result.type = NAME_GLOBAL_VAR; result.index = index; @@ -887,7 +892,7 @@ static NameSearchResult compilerSearchName(Compiler* compiler, } // Search through functions. - index = scriptSearchFunc(compiler->script, name, length); + index = scriptGetFunc(compiler->script, name, length); if (index != -1) { result.type = NAME_FUNCTION; result.index = index; @@ -1453,7 +1458,7 @@ static void parsePrecedence(Compiler* compiler, Precedence precedence) { *****************************************************************************/ static void compilerInit(Compiler* compiler, PKVM* vm, const char* source, - Script* script) { + Script* script, const PkCompileOptions* options) { compiler->vm = vm; compiler->next_compiler = NULL; @@ -1462,6 +1467,7 @@ static void compilerInit(Compiler* compiler, PKVM* vm, const char* source, compiler->script = script; compiler->token_start = source; compiler->has_errors = false; + compiler->options = options; compiler->current_char = source; compiler->current_line = 1; @@ -1689,7 +1695,7 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) { } } else { - name = "$(LiteralFn)"; + name = LITERAL_FN_NAME; name_length = (int)strlen(name); } @@ -1860,8 +1866,14 @@ static Script* importFile(Compiler* compiler, const char* path) { emitOpcode(compiler, OP_IMPORT); emitShort(compiler, index); + // Option for the compilation, even if we're running on repl mode the + // imported script cannot run on repl mode. + PkCompileOptions options = pkNewCompilerOptions(); + if (compiler->options) options = *compiler->options; + options.repl_mode = false; + // Compile the source to the script and clean the source. - bool success = compile(vm, scr, source.string); + bool success = compile(vm, scr, source.string, &options); if (source.on_done != NULL) source.on_done(vm, source); if (!success) parseError(compiler, "Compilation of imported script " @@ -2247,6 +2259,11 @@ static void compileForStatement(Compiler* compiler) { // variable declaration, which will be handled. 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. + bool is_temproary = false; + if (match(compiler, TK_BREAK)) { if (compiler->loop == NULL) { parseError(compiler, "Cannot use 'break' outside a loop."); @@ -2304,22 +2321,55 @@ static void compileStatement(Compiler* compiler) { compiler->new_local = false; compileExpression(compiler); consumeEndStatement(compiler); - if (!compiler->new_local) { - // Pop the temp. - emitOpcode(compiler, OP_POP); - } + 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 (compiler->options && compiler->options->repl_mode && + is_temproary /*&& 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 +// as import statement, function define, and if we're running REPL mode top +// level expression's evaluvated value will be printed. +static void compileTopLevelStatement(Compiler* compiler) { + if (match(compiler, TK_NATIVE)) { + compileFunction(compiler, FN_NATIVE); + + } else if (match(compiler, TK_DEF)) { + compileFunction(compiler, FN_SCRIPT); + + } else if (match(compiler, TK_FROM)) { + compileFromImport(compiler); + + } else if (match(compiler, TK_IMPORT)) { + compileRegularImport(compiler); + + } else if (match(compiler, TK_MODULE)) { + parseError(compiler, "Module name must be the first statement " + "of the script."); + + } else { + compileStatement(compiler); + } } -bool compile(PKVM* vm, Script* script, const char* source) { +bool compile(PKVM* vm, Script* script, const char* source, + const PkCompileOptions* options) { // Skip utf8 BOM if there is any. if (strncmp(source, "\xEF\xBB\xBF", 3) == 0) source += 3; Compiler _compiler; Compiler* compiler = &_compiler; //< Compiler pointer for quick access. - compilerInit(compiler, vm, source, script); + compilerInit(compiler, vm, source, script, options); // If compiling for an imported script the vm->compiler would be the compiler // of the script that imported this script. Add the all the compilers into a @@ -2327,6 +2377,12 @@ 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; + Func curr_fn; curr_fn.depth = DEPTH_SCRIPT; curr_fn.ptr = script->body; @@ -2356,40 +2412,25 @@ bool compile(PKVM* vm, Script* script, const char* source) { } while (!match(compiler, TK_EOF)) { - - if (match(compiler, TK_NATIVE)) { - compileFunction(compiler, FN_NATIVE); - - } else if (match(compiler, TK_DEF)) { - compileFunction(compiler, FN_SCRIPT); - - } else if (match(compiler, TK_FROM)) { - compileFromImport(compiler); - - } else if (match(compiler, TK_IMPORT)) { - compileRegularImport(compiler); - - } else if (match(compiler, TK_MODULE)) { - parseError(compiler, "Module name must be the first statement " - "of the script."); - - } else { - compileStatement(compiler); - } - + compileTopLevelStatement(compiler); skipNewLines(compiler); } - emitOpcode(compiler, OP_PUSH_NULL); - emitOpcode(compiler, OP_RETURN); - emitOpcode(compiler, OP_END); + 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); + } // Resolve forward names (function names that are used before defined). for (int i = 0; i < compiler->forwards_count; i++) { ForwardName* forward = &compiler->forwards[i]; const char* name = forward->name; int length = forward->length; - int index = scriptSearchFunc(script, name, (uint32_t)length); + int index = scriptGetFunc(script, name, (uint32_t)length); if (index != -1) { patchForward(compiler, forward->func, forward->instruction, index); } else { @@ -2404,8 +2445,29 @@ bool compile(PKVM* vm, Script* script, const char* source) { dumpFunctionCode(vm, script->body); #endif - // Return true if success. - return !(compiler->has_errors); + // 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; +} + +PkResult pkCompileModule(PKVM* vm, PkHandle* module, PkStringPtr source, + 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"); + Script* script = (Script*)AS_OBJ(scr); + + bool success = compile(vm, script, source.string, options); + if (source.on_done) source.on_done(vm, source); + + if (!success) return PK_RESULT_COMPILE_ERROR; + return PK_RESULT_SUCCESS; } void compilerMarkObjects(PKVM* vm, Compiler* compiler) { diff --git a/src/compiler.h b/src/compiler.h index 521231a..f4ea4fc 100644 --- a/src/compiler.h +++ b/src/compiler.h @@ -25,8 +25,9 @@ typedef enum { typedef struct Compiler Compiler; // This will take source code as a cstring, compiles it to pocketlang bytecodes -// and append them to the script's implicit main (the $SourceBody function). -bool compile(PKVM* vm, Script* script, const char* source); +// and append them to the script's implicit main function ("$(SourceBody)"). +bool compile(PKVM* vm, Script* script, const char* source, + const PkCompileOptions* options); // Mark the heap allocated ojbects of the compiler at the garbage collection // called at the marking phase of vmCollectGarbage(). diff --git a/src/core.c b/src/core.c index 0da81c9..b83dda2 100644 --- a/src/core.c +++ b/src/core.c @@ -14,7 +14,7 @@ #include "vm.h" /*****************************************************************************/ -/* PUBLIC API */ +/* CORE PUBLIC API */ /*****************************************************************************/ // Create a new module with the given [name] and returns as a Script* for @@ -36,15 +36,44 @@ PkHandle* pkNewModule(PKVM* vm, const char* name) { void pkModuleAddFunction(PKVM* vm, PkHandle* module, const char* name, pkNativeFn fptr, int arity) { __ASSERT(module != NULL, "Argument module was NULL."); - Var scr = module->value; __ASSERT(IS_OBJ_TYPE(scr, OBJ_SCRIPT), "Given handle is not a module"); - moduleAddFunctionInternal(vm, (Script*)AS_OBJ(scr), name, fptr, arity); } +PkHandle* pkGetFunction(PKVM* vm, PkHandle* module, + const char* name) { + __ASSERT(module != NULL, "Argument module was NULL."); + Var scr = module->value; + __ASSERT(IS_OBJ_TYPE(scr, OBJ_SCRIPT), "Given handle is not a module"); + Script* script = (Script*)AS_OBJ(scr); + + // TODO: Currently it's O(n) and could be optimized to O(log(n)) but does it + // worth it? + // + // 'function_names' buffer is un-necessary since the function itself has the + // reference to the function name and it can be refactored into a index buffer + // in an "increasing-name" order which can be used to binary search. Similer + // for 'global_names' refactor them from VarBuffer to GlobalVarBuffer where + // GlobalVar is struct { const char* name, Var value }; + // + // "nicreasing-name" order index buffer: + // A buffer of int where each is an index in the function buffer and each + // points to different functions in an "increasing-name" (could be hash + // value) order. If we have more than some threshold number of function + // use binary search. (remember to skip literal functions). + for (uint32_t i = 0; i < script->functions.count; i++) { + const char* fn_name = script->functions.data[i]->name; + if (strcmp(name, fn_name) == 0) { + return vmNewHandle(vm, VAR_OBJ(script->functions.data[i])); + } + } + + return NULL; +} + // A convinent macro to get the nth (1 based) argument of the current function. -#define ARG(n) vm->fiber->ret[n] +#define ARG(n) (vm->fiber->ret[n]) // Convinent macros to get the 1st, 2nd, 3rd arguments. #define ARG1 ARG(1) @@ -260,7 +289,7 @@ static inline bool validateIndex(PKVM* vm, int32_t index, int32_t size, VALIDATE_ARG_OBJ(Fiber, OBJ_FIBER, "fiber") /*****************************************************************************/ -/* BUILTIN FUNCTIONS API */ +/* SHARED FUNCTIONS */ /*****************************************************************************/ // findBuiltinFunction implementation (see core.h for description). @@ -299,12 +328,12 @@ Script* getCoreLib(const PKVM* vm, String* name) { /*****************************************************************************/ #define FN_IS_PRIMITE_TYPE(name, check) \ - void coreIs##name(PKVM* vm) { \ + static void coreIs##name(PKVM* vm) { \ RET(VAR_BOOL(check(ARG1))); \ } #define FN_IS_OBJ_TYPE(name, _enum) \ - void coreIs##name(PKVM* vm) { \ + static void coreIs##name(PKVM* vm) { \ Var arg1 = ARG1; \ if (IS_OBJ_TYPE(arg1, _enum)) { \ RET(VAR_TRUE); \ @@ -370,20 +399,7 @@ PK_DOC(coreYield, RET_ERR(newString(vm, "Invalid argument count.")); } - Fiber* caller = vm->fiber->caller; - - // Return the yield value to the caller fiber. - if (caller != NULL) { - if (argc == 0) *caller->ret = VAR_NULL; - else *caller->ret = ARG1; - } - - // Can be resumed by another caller fiber. - vm->fiber->caller = NULL; - vm->fiber->state = FIBER_YIELDED; - vm->fiber = caller; - - return; + vmYieldFiber(vm, (argc == 1) ? &ARG1 : NULL); } PK_DOC(coreToString, @@ -400,24 +416,36 @@ PK_DOC(corePrint, // output. if (vm->config.write_fn == NULL) return; - String* str; //< Will be cleaned by garbage collector; - for (int i = 1; i <= ARGC; i++) { - Var arg = ARG(i); - // If it's already a string don't allocate a new string instead use it. - if (IS_OBJ_TYPE(arg, OBJ_STRING)) { - str = (String*)AS_OBJ(arg); - } else { - str = toString(vm, arg); - } - if (i != 1) vm->config.write_fn(vm, " "); - vm->config.write_fn(vm, str->data); + vm->config.write_fn(vm, toString(vm, ARG(i))->data); } vm->config.write_fn(vm, "\n"); } +PK_DOC(coreInput, + "input([msg:var]) -> string\n" + "Read a line from stdin and returns it without the line ending. Accepting " + "an optional argument [msg] and prints it before reading.") { + int argc = ARGC; + if (argc != 1 && argc != 2) { + RET_ERR(newString(vm, "Invalid argument count.")); + } + + // If the host appliaction donesn't provide any write function, return. + if (vm->config.read_fn == NULL) return; + + if (argc == 1) { + vm->config.write_fn(vm, toString(vm, ARG1)->data); + } + + PkStringPtr result = vm->config.read_fn(vm); + String* line = newString(vm, result.string); + if (result.on_done) result.on_done(vm, result); + RET(VAR_OBJ(line)); +} + // String functions. // ----------------- PK_DOC(coreStrLower, @@ -558,6 +586,8 @@ PK_DOC(coreFiberRun, Fiber* fb; if (!validateArgFiber(vm, 1, &fb)) return; + ASSERT(fb->func->arity >= -1 , OOPS " (Forget to initialize arity.)"); + 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)); @@ -691,13 +721,13 @@ static void moduleAddFunctionInternal(PKVM* vm, Script* script, int arity) { // Check if function with the same name already exists. - if (scriptSearchFunc(script, name, (uint32_t)strlen(name)) != -1) { + if (scriptGetFunc(script, name, (uint32_t)strlen(name)) != -1) { __ASSERT(false, stringFormat(vm, "A function named '$' already esists " "on module '@'", name, script->moudle)->data); } // Check if a global variable with the same name already exists. - if (scriptSearchGlobals(script, name, (uint32_t)strlen(name)) != -1) { + if (scriptGetGlobals(script, name, (uint32_t)strlen(name)) != -1) { __ASSERT(false, stringFormat(vm, "A global variable named '$' already " "esists on module '@'", name, script->moudle)->data); } @@ -707,6 +737,8 @@ static void moduleAddFunctionInternal(PKVM* vm, Script* script, fn->arity = arity; } +// TODO: make the below module functions as PK_DOC(name, doc); + // 'lang' library methods. // ----------------------- @@ -835,6 +867,11 @@ void initializeCore(PKVM* vm) { INITALIZE_BUILTIN_FN("type_name", coreTypeName, 1); // TOOD: (maybe remove is_*() functions) suspend by type_name. + // and add is keyword with modules for builtin types + // ex: val is Num; val is null; val is List; val is Range + // List.append(l, e) # List is implicitly imported core module. + // String.lower(s) + INITALIZE_BUILTIN_FN("is_null", coreIsNull, 1); INITALIZE_BUILTIN_FN("is_bool", coreIsBool, 1); INITALIZE_BUILTIN_FN("is_num", coreIsNum, 1); @@ -851,6 +888,7 @@ void initializeCore(PKVM* vm) { INITALIZE_BUILTIN_FN("yield", coreYield, -1); INITALIZE_BUILTIN_FN("to_string", coreToString, 1); INITALIZE_BUILTIN_FN("print", corePrint, -1); + INITALIZE_BUILTIN_FN("input", coreInput, -1); // String functions. INITALIZE_BUILTIN_FN("str_lower", coreStrLower, 1); @@ -1101,14 +1139,14 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) { Script* scr = (Script*)obj; // Search in functions. - uint32_t index = scriptSearchFunc(scr, attrib->data, attrib->length); + uint32_t index = scriptGetFunc(scr, attrib->data, attrib->length); if (index != -1) { ASSERT_INDEX(index, scr->functions.count); return VAR_OBJ(scr->functions.data[index]); } // Search in globals. - index = scriptSearchGlobals(scr, attrib->data, attrib->length); + index = scriptGetGlobals(scr, attrib->data, attrib->length); if (index != -1) { ASSERT_INDEX(index, scr->globals.count); return scr->globals.data[index]; @@ -1173,7 +1211,7 @@ do { \ Script* scr = (Script*)obj; // Check globals. - uint32_t index = scriptSearchGlobals(scr, attrib->data, attrib->length); + uint32_t index = scriptGetGlobals(scr, attrib->data, attrib->length); if (index != -1) { ASSERT_INDEX(index, scr->globals.count); scr->globals.data[index] = value; @@ -1181,7 +1219,7 @@ do { \ } // Check function (Functions are immutable). - index = scriptSearchFunc(scr, attrib->data, attrib->length); + index = scriptGetFunc(scr, attrib->data, attrib->length); if (index != -1) { ASSERT_INDEX(index, scr->functions.count); ATTRIB_IMMUTABLE(scr->functions.data[index]->name); diff --git a/src/core.h b/src/core.h index 551d71f..a4ffaac 100644 --- a/src/core.h +++ b/src/core.h @@ -33,11 +33,11 @@ Script* getCoreLib(const PKVM* vm, String* name); Var varAdd(PKVM* vm, Var v1, Var v2); // Returns v1 + v2. Var varSubtract(PKVM* vm, Var v1, Var v2); // Returns v1 - v2. Var varMultiply(PKVM* vm, Var v1, Var v2); // Returns v1 * v2. -Var varDivide(PKVM* vm, Var v1, Var v2); // Returns v1 / v2. -Var varModulo(PKVM* vm, Var v1, Var v2); // Returns v1 % v2. +Var varDivide(PKVM* vm, Var v1, Var v2); // Returns v1 / v2. +Var varModulo(PKVM* vm, Var v1, Var v2); // Returns v1 % v2. bool varGreater(Var v1, Var v2); // Returns v1 > v2. -bool varLesser(Var v1, Var v2); // Returns v1 < v2. +bool varLesser(Var v1, Var v2); // Returns v1 < v2. // Returns the attribute named [attrib] on the variable [on]. Var varGetAttrib(PKVM* vm, Var on, String* attrib); diff --git a/src/debug.c b/src/debug.c index 44264b6..ef256d9 100644 --- a/src/debug.c +++ b/src/debug.c @@ -7,6 +7,7 @@ #include #include "core.h" +#include "var.h" #include "vm.h" // To limit maximum elements to be dumpin in a map or a list. @@ -313,6 +314,8 @@ void dumpFunctionCode(PKVM* vm, Function* func) { case OP_GTEQ: 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 d09c6bf..3ad55f2 100644 --- a/src/include/pocketlang.h +++ b/src/include/pocketlang.h @@ -63,7 +63,11 @@ extern "C" { // } // #define PK_DOC(func, doc) \ - /* TODO: static char __pkdoc__##func[] = doc;*/ void func(PKVM* vm) + /* TODO: static char __pkdoc__##func[] = doc;*/ static void func(PKVM* vm) + +// 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)" /*****************************************************************************/ /* POCKETLANG TYPES */ @@ -100,6 +104,7 @@ typedef enum { typedef struct PkStringPtr PkStringPtr; typedef struct PkConfiguration PkConfiguration; +typedef struct PkCompileOptions PkCompileOptions; // Type of the error message that pocketlang will provide with the pkErrorFn // callback. @@ -109,13 +114,13 @@ typedef enum { PK_ERROR_STACKTRACE, // One entry of a runtime error stack. } PkErrorType; -// Result that pocketlang will return after running a script or a function -// or evaluvating an expression. +// Result that pocketlang will return after a compilation or running a script +// or a function or evaluvating an expression. typedef enum { PK_RESULT_SUCCESS = 0, // Successfully finished the execution. PK_RESULT_COMPILE_ERROR, // Compilation failed. PK_RESULT_RUNTIME_ERROR, // An error occured at runtime. -} PkInterpretResult; +} PkResult; /*****************************************************************************/ /* POCKETLANG FUNCTION POINTERS & CALLBACKS */ @@ -136,15 +141,20 @@ typedef void (*pkNativeFn)(PKVM* vm); // function will return NULL. typedef void* (*pkReallocFn)(void* memory, size_t new_size, void* user_data); -// Error callback function pointer. for runtime error it'll call first with +// Error callback function pointer. For runtime error it'll call first with // PK_ERROR_RUNTIME followed by multiple callbacks with PK_ERROR_STACKTRACE. +// The error messages should be written to stderr. typedef void (*pkErrorFn) (PKVM* vm, PkErrorType type, const char* file, int line, const char* message); -// A function callback used by `print()` statement. +// A function callback to write [text] to stdout. typedef void (*pkWriteFn) (PKVM* vm, const char* text); +// A function callback to read a line from stdin. The returned string shouldn't +// contain a line ending (\n or \r\n). +typedef PkStringPtr (*pkReadFn) (PKVM* vm); + // A function callback symbol for clean/free the pkStringResult. typedef void (*pkResultDoneFn) (PKVM* vm, PkStringPtr result); @@ -169,6 +179,11 @@ typedef PkStringPtr (*pkLoadScriptFn) (PKVM* vm, const char* path); // application. PK_PUBLIC PkConfiguration pkNewConfiguration(); +// 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(); + // Allocate initialize and returns a new VM PK_PUBLIC PKVM* pkNewVM(PkConfiguration* config); @@ -195,21 +210,36 @@ PK_PUBLIC PkVar pkGetHandleValue(const PkHandle* handle); // this for every handles before freeing the VM. PK_PUBLIC void pkReleaseHandle(PKVM* vm, PkHandle* handle); -// Add a new module named [name] to the [vm]. Note that the module shouldn't -// already existed, otherwise an assertion will fail to indicate that. -PK_PUBLIC PkHandle* pkNewModule(PKVM* vm, const char* name); - // Add a native function to the given script. If [arity] is -1 that means // The function has variadic parameters and use pkGetArgc() to get the argc. PK_PUBLIC void pkModuleAddFunction(PKVM* vm, PkHandle* module, const char* name, pkNativeFn fptr, int arity); +// Returns the function from the [module] as a handle, if not found it'll +// return NULL. +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. +PK_PUBLIC PkResult pkCompileModule(PKVM* vm, PkHandle* module, + PkStringPtr source, + const PkCompileOptions* options); + // Interpret the source and return the result. Once It's done with the source // and path 'on_done' will be called to clean the string if it's not NULL. -PK_PUBLIC PkInterpretResult pkInterpretSource(PKVM* vm, - PkStringPtr source, - PkStringPtr path); +// Set the compiler options with the the [options] argument or it can be set to +// NULL for default options. +PK_PUBLIC PkResult pkInterpretSource(PKVM* vm, + PkStringPtr source, + PkStringPtr path, + const PkCompileOptions* options); + + +//PK_PUBLIC PkResult pkRunFiber(PKVM* vm, PkHandle* fiber, +// int argc, PkHandle** argv); /*****************************************************************************/ /* POCKETLANG PUBLIC TYPE DEFINES */ @@ -232,6 +262,7 @@ struct PkConfiguration { pkErrorFn error_fn; pkWriteFn write_fn; + pkReadFn read_fn; pkResolvePathFn resolve_path_fn; pkLoadScriptFn load_script_fn; @@ -240,6 +271,29 @@ struct PkConfiguration { void* user_data; }; +// The options to configure the compilation provided by the command line +// arguments (or other ways the host application provides). +struct PkCompileOptions { + + // Compile debug version of the source. + bool debug; + + // TODO: don't use FILE* pointer or any of functions here. + // instead add a stream option to vm.config.write_fn callback. + // + // Dump the compiled opcodes to the given [dump_stream] FILE* could be stdio, + // stderr, or a file pointer. + //bool dump_opcodes; + //FILE* dump_stream; + + // Set to true if compiling in REPL mode, This will print repr version of + // each evaluvated non-null values. Note that if [repl_mode] is true the + // [expression] should also be true otherwise it's incompatible, (will fail + // an assertion). + bool repl_mode; + +}; + /*****************************************************************************/ /* NATIVE FUNCTION API */ /*****************************************************************************/ @@ -257,7 +311,7 @@ PK_PUBLIC int pkGetArgc(const PKVM* vm); // Return the [arg] th argument as a PkVar. This pointer will only be // valid till the current function ends, because it points to the var at the -// stack and it'll popped when the current call frame ended. Use handlers to +// stack and it'll popped when the current call frame ended. Use handles to // keep the var alive even after that. PK_PUBLIC PkVar pkGetArg(const PKVM* vm, int arg); @@ -294,8 +348,14 @@ PK_PUBLIC PkHandle* pkNewStringLength(PKVM* vm, const char* value, size_t len); PK_PUBLIC PkHandle* pkNewList(PKVM* vm); PK_PUBLIC PkHandle* pkNewMap(PKVM* vm); -PK_PUBLIC const char* pkStringGetData(const PkVar value); +// Add a new module named [name] to the [vm]. Note that the module shouldn't +// already existed, otherwise an assertion will fail to indicate that. +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 diff --git a/src/opcodes.h b/src/opcodes.h index 514d2ae..7423d3d 100644 --- a/src/opcodes.h +++ b/src/opcodes.h @@ -188,6 +188,13 @@ OPCODE(GTEQ, 0, -1) OPCODE(RANGE, 0, -1) //< Pop 2 integer make range push. OPCODE(IN, 0, -1) +// Print the repr string of the value at the stack top, used in REPL mode. +// 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/var.c b/src/var.c index d799b2b..271f5ee 100644 --- a/src/var.c +++ b/src/var.c @@ -10,7 +10,7 @@ #include "vm.h" /*****************************************************************************/ -/* PUBLIC API */ +/* VAR PUBLIC API */ /*****************************************************************************/ PkVarType pkGetValueType(const PkVar value) { @@ -55,6 +55,13 @@ PkHandle* pkNewMap(PKVM* vm) { return vmNewHandle(vm, VAR_OBJ(newMap(vm))); } +PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn) { + __ASSERT(IS_OBJ_TYPE(fn->value, OBJ_FUNC), "Fn should be of type function."); + + Fiber* fiber = newFiber(vm, (Function*)AS_OBJ(fn->value)); + 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."); @@ -137,7 +144,7 @@ void grayVarBuffer(PKVM* vm, VarBuffer* self) { GRAY_OBJ_BUFFER(String) GRAY_OBJ_BUFFER(Function) -static void blackenObject(Object* obj, PKVM* vm) { +static void _blackenObject(Object* obj, PKVM* vm) { // TODO: trace here. switch (obj->type) { @@ -242,12 +249,11 @@ static void blackenObject(Object* obj, PKVM* vm) { } } - void blackenObjects(PKVM* vm) { while (vm->gray_list_count > 0) { // Pop the gray object from the list. Object* gray = vm->gray_list[--vm->gray_list_count]; - blackenObject(gray, vm); + _blackenObject(gray, vm); } } @@ -334,8 +340,9 @@ Script* newScript(PKVM* vm, String* path) { stringBufferInit(&script->names); vmPushTempRef(vm, &script->_super); - const char* fn_name = "$(SourceBody)"; + const char* fn_name = PK_BODY_FN_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); return script; @@ -847,8 +854,10 @@ struct OuterSequence { }; typedef struct OuterSequence OuterSequence; -static void toStringInternal(PKVM* vm, Var v, ByteBuffer* buff, - OuterSequence* outer) { +static void _toStringInternal(PKVM* vm, const Var v, ByteBuffer* buff, + OuterSequence* outer, bool repr) { + ASSERT(outer == NULL || repr, OOPS); + if (IS_NULL(v)) { byteBufferAddString(buff, vm, "null", 4); return; @@ -887,9 +896,27 @@ static void toStringInternal(PKVM* vm, Var v, ByteBuffer* buff, case OBJ_STRING: { const String* str = (const String*)obj; - if (outer == NULL) { + if (outer == NULL && !repr) { byteBufferAddString(buff, vm, str->data, str->length); return; + } else { + // If recursive return with quotes (ex: [42, "hello", 0..10]). + byteBufferWrite(buff, vm, '"'); + for (const char* c = str->data; *c != '\0'; c++) { + switch (*c) { + case '"': byteBufferAddString(buff, vm, "\\\"", 2); break; + case '\\': byteBufferAddString(buff, vm, "\\\\", 2); break; + case '\n': byteBufferAddString(buff, vm, "\\n", 2); break; + case '\r': byteBufferAddString(buff, vm, "\\r", 2); break; + case '\t': byteBufferAddString(buff, vm, "\\t", 2); break; + + default: + byteBufferWrite(buff, vm, *c); + break; + } + } + byteBufferWrite(buff, vm, '"'); + return; } // If recursive return with quotes (ex: [42, "hello", 0..10]). @@ -924,7 +951,7 @@ static void toStringInternal(PKVM* vm, Var v, ByteBuffer* buff, byteBufferWrite(buff, vm, '['); for (uint32_t i = 0; i < list->elements.count; i++) { if (i != 0) byteBufferAddString(buff, vm, ", ", 2); - toStringInternal(vm, list->elements.data[i], buff, &seq_list); + _toStringInternal(vm, list->elements.data[i], buff, &seq_list, true); } byteBufferWrite(buff, vm, ']'); return; @@ -971,9 +998,9 @@ static void toStringInternal(PKVM* vm, Var v, ByteBuffer* buff, byteBufferAddString(buff, vm, ", ", 2); _first = false; } - toStringInternal(vm, map->entries[i].key, buff, &seq_map); + _toStringInternal(vm, map->entries[i].key, buff, &seq_map, true); byteBufferWrite(buff, vm, ':'); - toStringInternal(vm, map->entries[i].value, buff, &seq_map); + _toStringInternal(vm, map->entries[i].value, buff, &seq_map, true); i++; } while (i < map->capacity); @@ -1044,10 +1071,23 @@ static void toStringInternal(PKVM* vm, Var v, ByteBuffer* buff, return; } -String* toString(PKVM* vm, Var v) { +String* toString(PKVM* vm, const Var value) { + + // If it's already a string don't allocate a new string. + if (IS_OBJ_TYPE(value, OBJ_STRING)) { + return (String*)AS_OBJ(value); + } + ByteBuffer buff; byteBufferInit(&buff); - toStringInternal(vm, v, &buff, NULL); + _toStringInternal(vm, value, &buff, NULL, false); + return newStringLength(vm, (const char*)buff.data, buff.count); +} + +String* toRepr(PKVM* vm, const Var value) { + ByteBuffer buff; + byteBufferInit(&buff); + _toStringInternal(vm, value, &buff, NULL, true); return newStringLength(vm, (const char*)buff.data, buff.count); } @@ -1168,7 +1208,7 @@ uint32_t scriptAddName(Script* self, PKVM* vm, const char* name, return self->names.count - 1; } -int scriptSearchFunc(Script* script, const char* name, uint32_t length) { +int scriptGetFunc(Script* script, const char* name, uint32_t length) { for (uint32_t i = 0; i < script->function_names.count; i++) { uint32_t name_index = script->function_names.data[i]; String* fn_name = script->names.data[name_index]; @@ -1180,7 +1220,7 @@ int scriptSearchFunc(Script* script, const char* name, uint32_t length) { return -1; } -int scriptSearchGlobals(Script* script, const char* name, uint32_t length) { +int scriptGetGlobals(Script* script, const char* name, uint32_t length) { for (uint32_t i = 0; i < script->global_names.count; i++) { uint32_t name_index = script->global_names.data[i]; String* g_name = script->names.data[name_index]; diff --git a/src/var.h b/src/var.h index 8d380e9..355ef31 100644 --- a/src/var.h +++ b/src/var.h @@ -138,7 +138,7 @@ #define IS_INT(value) ((value & _MASK_INTEGER) == _MASK_INTEGER) #define IS_NUM(value) ((value & _MASK_QNAN) != _MASK_QNAN) #define IS_OBJ(value) ((value & _MASK_OBJECT) == _MASK_OBJECT) -#define IS_OBJ_TYPE(obj, obj_type) IS_OBJ(obj) && AS_OBJ(obj)->type == obj_type +#define IS_OBJ_TYPE(var, obj_type) IS_OBJ(var) && AS_OBJ(var)->type == obj_type // Decode types. #define AS_BOOL(value) ((value) == VAR_TRUE) @@ -408,7 +408,7 @@ Script* newScript(PKVM* vm, String* path); // would be builtin function. For builtin function arity and the native // function pointer would be initialized after calling this function. Function* newFunction(PKVM* vm, const char* name, int length, Script* owner, - bool is_native); + bool is_native); // Allocate new Fiber object around the function [fn] and return Fiber*. Fiber* newFiber(PKVM* vm, Function* fn); @@ -500,8 +500,12 @@ uint32_t varHashValue(Var v); // Return true if the object type is hashable. bool isObjectHashable(ObjectType type); -// Returns the string version of the value. -String* toString(PKVM* vm, Var v); +// Returns the string version of the [value]. +String* toString(PKVM* vm, const Var value); + +// Returns the representation version of the [value], similer of python's +// __repr__() method. +String * toRepr(PKVM * vm, const Var value); // Returns the truthy value of the var. bool toBool(Var v); @@ -523,10 +527,10 @@ uint32_t scriptAddName(Script* self, PKVM* vm, const char* name, // Search for the function name in the script and return it's index in it's // [functions] buffer. If not found returns -1. -int scriptSearchFunc(Script* script, const char* name, uint32_t length); +int scriptGetFunc(Script* script, const char* name, uint32_t length); // Search for the global variable name in the script and return it's index in // it's [globals] buffer. If not found returns -1. -int scriptSearchGlobals(Script* script, const char* name, uint32_t length); +int scriptGetGlobals(Script* script, const char* name, uint32_t length); #endif // VAR_H diff --git a/src/vm.c b/src/vm.c index a60ed3d..29e4a21 100644 --- a/src/vm.c +++ b/src/vm.c @@ -21,12 +21,17 @@ // if the host doesn't provided any allocators for us. static void* defaultRealloc(void* memory, size_t new_size, void* user_data); +// Runs the [fiber] if it's at yielded state, this will resume the execution +// till the next yield or return statement, and return result. +static PkResult runFiber(PKVM* vm, Fiber* fiber); + PkConfiguration pkNewConfiguration() { PkConfiguration config; config.realloc_fn = defaultRealloc; config.error_fn = NULL; config.write_fn = NULL; + config.read_fn = NULL; config.load_script_fn = NULL; config.resolve_path_fn = NULL; @@ -35,6 +40,16 @@ PkConfiguration pkNewConfiguration() { return config; } +PkCompileOptions pkNewCompilerOptions() { + PkCompileOptions options; + options.debug = false; + // TODO: + //options.dump_opcodes = false; + //options.dump_stream = stdout; + options.repl_mode = false; + return options; +} + PKVM* pkNewVM(PkConfiguration* config) { PkConfiguration default_config = pkNewConfiguration(); @@ -112,6 +127,49 @@ void pkReleaseHandle(PKVM* vm, PkHandle* handle) { DEALLOCATE(vm, handle); } +// This function is responsible to call on_done function if it's done with the +// provided string pointers. +PkResult pkInterpretSource(PKVM* vm, PkStringPtr source, PkStringPtr path, + const PkCompileOptions* options) { + + String* path_name = newString(vm, path.string); + if (path.on_done) path.on_done(vm, path); + vmPushTempRef(vm, &path_name->_super); // path_name. + + // TODO: Should I clean the script if it already exists before compiling it? + + // Load a new script to the vm's scripts cache. + Script* scr = vmGetScript(vm, path_name); + if (scr == NULL) { + scr = newScript(vm, path_name); + vmPushTempRef(vm, &scr->_super); // scr. + mapSet(vm, vm->scripts, VAR_OBJ(path_name), VAR_OBJ(scr)); + vmPopTempRef(vm); // scr. + } + vmPopTempRef(vm); // path_name. + + // Compile the source. + bool success = compile(vm, scr, source.string, options); + if (source.on_done) source.on_done(vm, source); + + if (!success) return PK_RESULT_COMPILE_ERROR; + + // Set script initialized to true before the execution ends to prevent cyclic + // inclusion cause a crash. + scr->initialized = true; + + return runFiber(vm, newFiber(vm, scr->body)); +} + +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); +} + +/*****************************************************************************/ +/* SHARED FUNCTIONS */ +/*****************************************************************************/ + PkHandle* vmNewHandle(PKVM* vm, Var value) { PkHandle* handle = (PkHandle*)ALLOCATE(vm, PkHandle); handle->value = value; @@ -122,7 +180,6 @@ PkHandle* vmNewHandle(PKVM* vm, Var value) { return handle; } - void* vmRealloc(PKVM* vm, void* memory, size_t old_size, size_t new_size) { // TODO: Debug trace allocations here. @@ -152,6 +209,13 @@ void vmPopTempRef(PKVM* vm) { vm->temp_reference_count--; } +Script* vmGetScript(PKVM* vm, String* path) { + Var scr = mapGet(vm->scripts, VAR_OBJ(path)); + if (IS_UNDEF(scr)) return NULL; + ASSERT(AS_OBJ(scr)->type == OBJ_SCRIPT, OOPS); + return (Script*)AS_OBJ(scr); +} + void vmCollectGarbage(PKVM* vm) { // Reset VM's bytes_allocated value and count it again so that we don't @@ -217,6 +281,22 @@ void vmCollectGarbage(PKVM* vm) { if (vm->next_gc < vm->min_heap_size) vm->next_gc = vm->min_heap_size; } +void vmYieldFiber(PKVM* vm, Var* value) { + + Fiber* caller = vm->fiber->caller; + + // Return the yield value to the caller fiber. + if (caller != NULL) { + if (value == NULL) *caller->ret = VAR_NULL; + else *caller->ret = *value; + } + + // Can be resumed by another caller fiber. + vm->fiber->caller = NULL; + vm->fiber->state = FIBER_YIELDED; + vm->fiber = caller; +} + /*****************************************************************************/ /* VM INTERNALS */ /*****************************************************************************/ @@ -231,13 +311,6 @@ static void* defaultRealloc(void* memory, size_t new_size, void* user_data) { return realloc(memory, new_size); } -static inline Script* getScript(PKVM* vm, String* path) { - Var scr = mapGet(vm->scripts, VAR_OBJ(path)); - if (IS_UNDEF(scr)) return NULL; - ASSERT(AS_OBJ(scr)->type == OBJ_SCRIPT, OOPS); - return (Script*)AS_OBJ(scr); -} - // If failed to resolve it'll return false. Parameter [result] should be points // to the string which is the path that has to be resolved and once it resolved // the provided result's string's on_done() will be called and, it's string @@ -353,12 +426,7 @@ static inline void pushCallFrame(PKVM* vm, const Function* fn) { frame->ip = fn->fn->opcodes.data; } -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); -} - -void vmReportError(PKVM* vm) { +static void reportError(PKVM* vm) { ASSERT(HAS_ERROR(), "runtimeError() should be called after an error."); // TODO: pass the error to the caller of the fiber. @@ -376,44 +444,11 @@ void vmReportError(PKVM* vm) { } } -// This function is responsible to call on_done function if it's done with the -// provided string pointers. -PkInterpretResult pkInterpretSource(PKVM* vm, PkStringPtr source, - PkStringPtr path) { - String* path_name = newString(vm, path.string); - if (path.on_done) path.on_done(vm, path); - vmPushTempRef(vm, &path_name->_super); // path_name. - - // TODO: Should I clean the script if it already exists before compiling it? - - // Load a new script to the vm's scripts cache. - Script* scr = getScript(vm, path_name); - if (scr == NULL) { - scr = newScript(vm, path_name); - vmPushTempRef(vm, &scr->_super); // scr. - mapSet(vm, vm->scripts, VAR_OBJ(path_name), VAR_OBJ(scr)); - vmPopTempRef(vm); // scr. - } - vmPopTempRef(vm); // path_name. - - // Compile the source. - bool success = compile(vm, scr, source.string); - if (source.on_done) source.on_done(vm, source); - - if (!success) return PK_RESULT_COMPILE_ERROR; - - // Set script initialized to true before the execution ends to prevent cyclic - // inclusion cause a crash. - scr->initialized = true; - - return vmRunFiber(vm, newFiber(vm, scr->body)); -} - /****************************************************************************** * RUNTIME * *****************************************************************************/ -PkInterpretResult vmRunFiber(PKVM* vm, Fiber* fiber) { +static PkResult runFiber(PKVM* vm, Fiber* fiber) { // Set the fiber as the vm's current fiber (another root object) to prevent // it from garbage collection and get the reference from native functions. @@ -423,9 +458,6 @@ PkInterpretResult vmRunFiber(PKVM* vm, Fiber* fiber) { fiber->state = FIBER_RUNNING; // The instruction pointer. - // Note: sing 'uint8_t** ip' as reference to the instruction pointer in the - // call frame seems a bit slower because of the dereferencing (~0.1 sec for - // 100 million calls). register const uint8_t* ip; register Var* rbp; //< Stack base pointer register. @@ -444,7 +476,7 @@ PkInterpretResult vmRunFiber(PKVM* vm, Fiber* fiber) { do { \ if (HAS_ERROR()) { \ UPDATE_FRAME(); \ - vmReportError(vm); \ + reportError(vm); \ return PK_RESULT_RUNTIME_ERROR; \ } \ } while (false) @@ -454,7 +486,7 @@ PkInterpretResult vmRunFiber(PKVM* vm, Fiber* fiber) { do { \ vm->fiber->error = err_msg; \ UPDATE_FRAME(); \ - vmReportError(vm); \ + reportError(vm); \ return PK_RESULT_RUNTIME_ERROR; \ } while (false) @@ -1150,6 +1182,31 @@ PkInterpretResult vmRunFiber(PKVM* vm, Fiber* fiber) { // TODO: Implement bool varContaines(vm, on, value); TODO; + OPCODE(REPL_PRINT): + { + 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"); + } + 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 f31c7a4..7627289 100644 --- a/src/vm.h +++ b/src/vm.h @@ -176,8 +176,12 @@ void vmPushTempRef(PKVM* vm, Object* obj); // Pop the top most object from temporary reference stack. void vmPopTempRef(PKVM* vm); -// Runs the [fiber] if it's at yielded state, this will resume the execution -// till the next yield or return statement, and return result. -PkInterpretResult vmRunFiber(PKVM* vm, Fiber* fiber); +// Returns the scrpt with the resolved [path] (also the key) in the vm's script +// cache. If not found itll return NULL. +Script* vmGetScript(PKVM* vm, String* path); + +// 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); #endif // VM_H