diff --git a/docs/TODO.txt b/docs/TODO.txt index 0ffb2bd..90d82a2 100644 --- a/docs/TODO.txt +++ b/docs/TODO.txt @@ -4,23 +4,13 @@ Add '.title' attribute to string // To implement. -- refactor the build.bat script. - -- no __file__ for core modules. - -- def f(x) - f(x) ## not tco, unless return f(x) - end - if a function's last statement is a call, we can perform tco. +- make assert as a keyword (like python) and disable it on release build. - implement 'lang.getMaxCallDepth()' (default=1000 like python) and setMaxCallDepth(val) to change stack size at runtime. - change or add => to_string() to value.as_string and add as_repr, as_bool. - -- Make bool and num are incompatible (also it increase performance a bit). - 1 + true - make it not allowed. - literal function recursive call. fn = func(a, b) @@ -30,7 +20,6 @@ Add '.title' attribute to string - Implement utf8 support. - Implement gdb like debugger (add color print for readability). -- Initialize imported scripts (require fiber based vm). - 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). diff --git a/src/include/pocketlang.h b/src/include/pocketlang.h index eacbc18..7e747aa 100644 --- a/src/include/pocketlang.h +++ b/src/include/pocketlang.h @@ -77,7 +77,7 @@ typedef struct PkHandle PkHandle; typedef void* PkVar; // Type enum of the pocketlang variables, this can be used to get the type -// from a Var* in the method pkGetVarType(). +// from a PkVar in the method pkGetVarType(). typedef enum { PK_NULL, PK_BOOL, @@ -110,9 +110,6 @@ typedef enum { typedef enum { PK_RESULT_SUCCESS = 0, // Successfully finished the execution. - // This will be set to `true` if we're running REPL mode and reached an EOF - // unexpectedly, - // Unexpected EOF while compiling the source. This is another compile time // error that will ONLY be returned if we're compiling with the REPL mode set // in the compile options. We need this specific error to indicate the host @@ -216,7 +213,7 @@ PK_PUBLIC PkConfiguration pkNewConfiguration(void); // application. PK_PUBLIC PkCompileOptions pkNewCompilerOptions(void); -// Allocate, initialize and returns a new VM +// Allocate, initialize and returns a new VM. PK_PUBLIC PKVM* pkNewVM(PkConfiguration* config); // Clean the VM and dispose all the resources allocated by the VM. @@ -332,14 +329,6 @@ 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 evaluated non-null values. Note that if [repl_mode] is true the // [expression] should also be true otherwise it's incompatible, (will fail @@ -443,9 +432,11 @@ PK_PUBLIC PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn); // callback. PK_PUBLIC PkHandle* pkNewInstNative(PKVM* vm, void* data, uint32_t id); -// TODO: The functions below will push the primitive values on the stack -// and return it's pointer as a PkVar. It's useful to convert your primitive -// values as pocketlang variables. +// TODO: Create a primitive (non garbage collected) variable buffer (or a +// fixed size array) to store them and make the handle points to the variable +// in that buffer, this will prevent us from invoking an allocation call for +// each time we want to pass a primitive type. + //PK_PUBLIC PkVar pkPushNull(PKVM* vm); //PK_PUBLIC PkVar pkPushBool(PKVM* vm, bool value); //PK_PUBLIC PkVar pkPushNumber(PKVM* vm, double value); diff --git a/src/pk_compiler.c b/src/pk_compiler.c index 18ffdd3..ad1c2e8 100644 --- a/src/pk_compiler.c +++ b/src/pk_compiler.c @@ -2215,7 +2215,7 @@ static Script* importFile(Compiler* compiler, const char* path) { } // Make a new script and to compile it. - Script* scr = newScript(vm, path_name); + Script* scr = newScript(vm, path_name, false); vmPushTempRef(vm, &scr->_super); // scr. mapSet(vm, vm->scripts, VAR_OBJ(path_name), VAR_OBJ(scr)); vmPopTempRef(vm); // scr. diff --git a/src/pk_core.c b/src/pk_core.c index 903b6fd..97c4cf7 100644 --- a/src/pk_core.c +++ b/src/pk_core.c @@ -757,8 +757,7 @@ static Script* newModuleInternal(PKVM* vm, const char* name) { "A module named '$' already exists", name)->data); } - Script* scr = newScript(vm, _name); - scr->module = _name; + Script* scr = newScript(vm, _name, true); vmPopTempRef(vm); // _name // Add the script to core_libs. diff --git a/src/pk_opcodes.h b/src/pk_opcodes.h index 9f60995..7d2c2ec 100644 --- a/src/pk_opcodes.h +++ b/src/pk_opcodes.h @@ -108,9 +108,9 @@ OPCODE(PUSH_BUILTIN_FN, 1, 1) // Pop the stack top. OPCODE(POP, 0, -1) -// Pop the path from the stack, import the module at the path and push the -// script in the script. If the script is imported for the first time (not -// cached) the script's body will be executed. +// Push the pre-compiled module at the index (from opcode) on the stack, and +// initialize the module (ie. run the main function) if it's not initialized +// already. // params: 2 byte name index. OPCODE(IMPORT, 2, 1) diff --git a/src/pk_var.c b/src/pk_var.c index 486edaa..c4dc5db 100644 --- a/src/pk_var.c +++ b/src/pk_var.c @@ -367,13 +367,23 @@ Range* newRange(PKVM* vm, double from, double to) { return range; } -Script* newScript(PKVM* vm, String* path) { +Script* newScript(PKVM* vm, String* name, bool is_core) { Script* script = ALLOCATE(vm, Script); varInitObject(&script->_super, vm, OBJ_SCRIPT); - script->path = path; + ASSERT(name != NULL && name->length > 0, OOPS); + + script->path = name; script->module = NULL; script->initialized = false; + script->body = NULL; + + // Core modules has its name as the module name, and since they don't have a + // main function, they doesn't need an initialization. + if (is_core) { + script->module = name; + script->initialized = true; + } pkVarBufferInit(&script->globals); pkUintBufferInit(&script->global_names); @@ -382,20 +392,26 @@ Script* newScript(PKVM* vm, String* path) { pkClassBufferInit(&script->classes); pkStringBufferInit(&script->names); - vmPushTempRef(vm, &script->_super); - const char* fn_name = PK_IMPLICIT_MAIN_NAME; - script->body = newFunction(vm, fn_name, (int)strlen(fn_name), - script, false, NULL/*TODO*/); - script->body->arity = 0; // TODO: should it be 1 (ARGV)?. + // Add a implicit main function and the '__file__' global to the module, only + // if it's not a core module. + if (!is_core) { + vmPushTempRef(vm, &script->_super); + const char* fn_name = PK_IMPLICIT_MAIN_NAME; + script->body = newFunction(vm, fn_name, (int)strlen(fn_name), + script, false, NULL/*TODO*/); + script->body->arity = 0; - // Add '__file__' variable with it's path as value. If the path starts with - // '$' It's a special file ($(REPL) or $(TRY)) and don't define __file__. - if (script->path->data[0] != '$') { - scriptAddGlobal(vm, script, "__file__", 8, VAR_OBJ(script->path)); + // Add '__file__' variable with it's path as value. If the path starts with + // '$' It's a special file ($(REPL) or $(TRY)) and don't define __file__. + if (script->path->data[0] != '$') { + scriptAddGlobal(vm, script, "__file__", 8, VAR_OBJ(script->path)); + } + + // TODO: Add ARGV as a global. + + vmPopTempRef(vm); // script. } - vmPopTempRef(vm); // script. - return script; } diff --git a/src/pk_var.h b/src/pk_var.h index cf62f90..0b86074 100644 --- a/src/pk_var.h +++ b/src/pk_var.h @@ -437,8 +437,12 @@ Map* newMap(PKVM* vm); // Allocate new Range object and return Range*. Range* newRange(PKVM* vm, double from, double to); -// Allocate new Script object and return Script*. -Script* newScript(PKVM* vm, String* path); +// Allocate new Script object and return Script*, if the argument [is_core] is +// true the script will be used as a core module and the body of the script +// would be NULL and the [name] will be used as the module name. Otherwise the +// [name] will be used as the path of the module and a main function will be +// allocated for the module. +Script* newScript(PKVM* vm, String* name, bool is_core); // Allocate new Function object and return Function*. Parameter [name] should // be the name in the Script's nametable. If the [owner] is NULL the function diff --git a/src/pk_vm.c b/src/pk_vm.c index 9622f26..381a97d 100644 --- a/src/pk_vm.c +++ b/src/pk_vm.c @@ -143,7 +143,7 @@ PkResult pkInterpretSource(PKVM* vm, PkStringPtr source, PkStringPtr path, // Load a new script to the vm's scripts cache. Script* scr = vmGetScript(vm, path_name); if (scr == NULL) { - scr = newScript(vm, path_name); + scr = newScript(vm, path_name, false); vmPushTempRef(vm, &scr->_super); // scr. mapSet(vm, vm->scripts, VAR_OBJ(path_name), VAR_OBJ(scr)); vmPopTempRef(vm); // scr. @@ -548,7 +548,6 @@ static inline void pushCallFrame(PKVM* vm, const Function* fn, Var* rbp) { if (vm->fiber->stack_size <= needed) growStack(vm, needed); CallFrame* frame = vm->fiber->frames + vm->fiber->frame_count++; - *rbp = VAR_NULL; frame->rbp = rbp; frame->fn = fn; frame->ip = fn->fn->opcodes.data; @@ -703,7 +702,7 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) { #define DISPATCH() goto L_vm_main_loop // Trigger a break point here, if we're trying to debug the call stack. -#if DEBUG_DUMP_CALL_STACK +#if DEBUG_DUMP_CALL_STACK DEBUG_BREAK(); #endif @@ -908,13 +907,32 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) { String* name = script->names.data[READ_SHORT()]; Var scr = importScript(vm, name); - // TODO: implement fiber based execution. - //ASSERT(IS_OBJ_TYPE(script, OBJ_SCRIPT), OOPS); - //Script* scr = (Script*)AS_OBJ(script); - //if (!scr->initialized) vmRunScript(vm, scr); - + ASSERT(IS_OBJ_TYPE(scr, OBJ_SCRIPT), OOPS); + Script* module = (Script*)AS_OBJ(scr); PUSH(scr); - CHECK_ERROR(); + + // TODO: If the body doesn't have any statements (just the functions). + // This initialization call is un-necessary. + + if (!module->initialized) { + module->initialized = true; + + ASSERT(module->body != NULL, OOPS); + + // Note that we're setting the main function's return address to the + // module itself (for every other function we'll push a null at the rbp + // before calling them and it'll be returned without modified if the + // function doesn't returned anything). Also We can't return from the + // body of the script, so the main function will return what's at the + // rbp without modifying it. So at the end of the main function the + // stack top would be the module itself. + Var* module_ret = vm->fiber->sp - 1; + + UPDATE_FRAME(); //< Update the current frame's ip. + pushCallFrame(vm, module->body, module_ret); + LOAD_FRAME(); //< Load the top frame to vm's execution variables. + } + DISPATCH(); } @@ -953,6 +971,10 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) { RUNTIME_ERROR(msg); } + // Next call frame starts here. (including return value). + call_fiber->ret = callable; + *(call_fiber->ret) = VAR_NULL; //< Set the return value to null. + if (fn->is_native) { if (fn->native == NULL) { @@ -963,10 +985,6 @@ static PkResult runFiber(PKVM* vm, Fiber* fiber) { // Update the current frame's ip. UPDATE_FRAME(); - // Next call frame starts here. (including return value). - call_fiber->ret = callable; - *(call_fiber->ret) = VAR_NULL; //< Set the return value to null. - fn->native(vm); //< Call the native function. // Calling yield() will change vm->fiber to it's caller fiber, which diff --git a/tests/lang/import.pk b/tests/lang/import.pk index be57a4f..a784eab 100644 --- a/tests/lang/import.pk +++ b/tests/lang/import.pk @@ -35,6 +35,15 @@ assert(all_f3() == 'f3') import 'import/all_import.pk' as all_import assert(all_import.all_f1 == all_f1) +## Test if the imported globals were initialized +import 'import/globals.pk' +assert(g_import != null) +assert(g_import.g_var_1 == 3) +assert(g_import.g_var_2 == g_import.get_a_value()) + +import 'import/globals2.pk' +assert(g_val_1 == 100) +assert(g_val_2 == get_a_value()) # If we got here, that means all test were passed. print('All TESTS PASSED') diff --git a/tests/lang/import/globals.pk b/tests/lang/import/globals.pk new file mode 100644 index 0000000..06c0405 --- /dev/null +++ b/tests/lang/import/globals.pk @@ -0,0 +1,9 @@ + +module g_import + +g_var_1 = 1 + 2 +g_var_2 = get_a_value() + +def get_a_value() + return "foobar" +end diff --git a/tests/lang/import/globals2.pk b/tests/lang/import/globals2.pk new file mode 100644 index 0000000..bc1b73c --- /dev/null +++ b/tests/lang/import/globals2.pk @@ -0,0 +1,11 @@ + +g_val_1 = 0 +for i in 0..100 + g_val_1 += 1 +end + +g_val_2 = get_a_value() + +def get_a_value() + return "baz" +end