From c3041c74a973aaf4f53620c860ced834618f3af2 Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Fri, 14 May 2021 16:14:57 +0530 Subject: [PATCH] fixed: iterator (internal) variables popped twise. printing stack trace implemented --- cli/main.c | 8 ++++- docs/static/try_now.js | 39 ---------------------- docs/try/io_api.js | 14 ++++++-- docs/try/main.c | 6 ++-- src/compiler.c | 2 ++ src/core.c | 4 +++ src/debug.c | 24 +++++++------- src/include/pocketlang.h | 2 +- src/var.c | 3 +- src/var.h | 3 ++ src/vm.c | 70 +++++++++++++++++++++++----------------- test/examples/fib.pk | 17 ++++++++++ test/examples/prime.pk | 15 ++++++--- test/lang/locals.pk | 16 --------- test/run_tests.bat | 2 +- 15 files changed, 116 insertions(+), 109 deletions(-) delete mode 100644 docs/static/try_now.js create mode 100644 test/examples/fib.pk delete mode 100644 test/lang/locals.pk diff --git a/cli/main.c b/cli/main.c index 52459f3..05f9e68 100644 --- a/cli/main.c +++ b/cli/main.c @@ -10,7 +10,13 @@ void errorPrint(PKVM* vm, PKErrorType type, const char* file, int line, const char* message) { - fprintf(stderr, "Error: %s\n\tat %s:%i\n", message, file, line); + if (type == PK_ERROR_COMPILE) { + fprintf(stderr, "Error: %s\n\tat %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:%i] %s()\n", file, line, message); + } } void writeFunction(PKVM* vm, const char* text) { diff --git a/docs/static/try_now.js b/docs/static/try_now.js deleted file mode 100644 index 0810286..0000000 --- a/docs/static/try_now.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2021 Thakee Nathees - * Licensed under: MIT License - */ - -const _initial_snippet = `\ -# A recursive fibonacci function. -def fib(n) - if n < 2 then return n end - return fib(n-1) + fib(n-2) -end - -# Print all fibonacci from 0 to 5 exclusive. -for i in 0..5 - print(fib(i)) -end -` - -var highlight_fn = function(editor) { - // highlight.js does not trim old tags, - // let's do it by this hack. - editor.textContent = editor.textContent; - editor.innerHTML = Prism.highlight(editor.textContent, Prism.languages.ruby, 'ruby'); -} - -var runSource; -window.onload = function() { // called after index.html is loaded -> Module is defined. - runSource = Module.cwrap('runSource', 'number', ['string']); - document.getElementById("run-button").onclick = function() { - document.getElementById('output').innerText = ''; - const source = document.querySelector('.editor').textContent; - runSource(source); - } - - let editor = document.querySelector('.editor') - editor.textContent = _initial_snippet; - highlight_fn(editor); -} - diff --git a/docs/try/io_api.js b/docs/try/io_api.js index e471696..79a4828 100644 --- a/docs/try/io_api.js +++ b/docs/try/io_api.js @@ -10,9 +10,19 @@ mergeInto(LibraryManager.library, { /** js_func_name : function() {...} */ - js_errorPrint : function(message, line) { + js_errorPrint : function(type, line, message) { + var err_text = '' + const msg = AsciiToString(message); + if (type == 0 /*PK_ERROR_COMPILE*/) { + err_text = `[Error at:${line}] ${msg}`; + } else if (type == 1 /*PK_ERROR_RUNTIME*/) { + err_text = `Error: ${msg}`; + } else if (type == 2 /*PK_ERROR_STACKTRACE*/) { + err_text = ` [at:${line}] ${msg}`; + } + var out = document.getElementById("output"); - out.innerText += `[Error at:${line}]: ${AsciiToString(message)} \n`; + out.innerText += err_text + '\n'; }, js_writeFunction : function(message) { diff --git a/docs/try/main.c b/docs/try/main.c index 7c4a1c0..719872b 100644 --- a/docs/try/main.c +++ b/docs/try/main.c @@ -10,13 +10,15 @@ #include #include "pocketlang.h" -extern void js_errorPrint(const char* message, int line); +extern void js_errorPrint(int type, int line, const char* message); extern void js_writeFunction(const char* message); extern const char* js_loadScript(); void errorPrint(PKVM* vm, PKErrorType type, const char* file, int line, const char* message) { - js_errorPrint(message, line); + // No need to pass file (since there is only script that'll ever run on the + // browser. + js_errorPrint((int)type, line, message); } void writeFunction(PKVM* vm, const char* text) { diff --git a/src/compiler.c b/src/compiler.c index ebe87bb..11a4021 100644 --- a/src/compiler.c +++ b/src/compiler.c @@ -345,6 +345,8 @@ static void reportError(Parser* parser, const char* file, int line, int length = vsprintf(message, fmt, args); ASSERT(length < ERROR_MESSAGE_SIZE, "Error message buffer should not exceed " "the buffer"); + + if (vm->config.error_fn == NULL) return; vm->config.error_fn(vm, PK_ERROR_COMPILE, file, line, message); } diff --git a/src/core.c b/src/core.c index 56e1096..f602949 100644 --- a/src/core.c +++ b/src/core.c @@ -206,6 +206,10 @@ void coreToString(PKVM* vm) { } void corePrint(PKVM* vm) { + // If the host appliaction donesn't provide any write function, discard the + // output. + if (vm->config.write_fn == NULL) return; + String* str; //< Will be cleaned by garbage collector; for (int i = 1; i <= ARGC; i++) { diff --git a/src/debug.c b/src/debug.c index 756ae23..bd040e0 100644 --- a/src/debug.c +++ b/src/debug.c @@ -259,18 +259,18 @@ void dumpInstructions(PKVM* vm, Function* func) { } void reportStackTrace(PKVM* vm) { - Fiber* fiber = vm->fiber; - Script* script = fiber->func->owner; + if (vm->config.error_fn == NULL) return; - //vm->config.error_fn(vm, PK_ERROR_RUNTIME, NULL, -1, fiber->error ) - // - //// TODO: I'm not confident about this approach. - //if (script->path != NULL) { // User script. - // - // - //} else { // "std" script. - // - //} - + Fiber* fiber = vm->fiber; + + vm->config.error_fn(vm, PK_ERROR_RUNTIME, NULL, -1, fiber->error->data); + + for (int i = fiber->frame_count - 1; i >= 0; i--) { + CallFrame* frame = &fiber->frames[i]; + Function* fn = frame->fn; + ASSERT(!fn->is_native, OOPS); + int line = fn->fn->oplines.data[frame->ip - fn->fn->opcodes.data - 1]; + vm->config.error_fn(vm, PK_ERROR_STACKTRACE, fn->owner->name->data, line, fn->name); + } } diff --git a/src/include/pocketlang.h b/src/include/pocketlang.h index a588ccd..b4c476f 100644 --- a/src/include/pocketlang.h +++ b/src/include/pocketlang.h @@ -83,7 +83,7 @@ typedef void (*pkNativeFn)(PKVM* vm); typedef enum { // Compile time errors (syntax errors, unresolved fn, etc). - PK_ERROR_COMPILE, + PK_ERROR_COMPILE = 0, // Runtime error message. PK_ERROR_RUNTIME, diff --git a/src/var.c b/src/var.c index 4af54db..0ef0d0a 100644 --- a/src/var.c +++ b/src/var.c @@ -189,6 +189,7 @@ static void blackenObject(Object* obj, PKVM* vm) { } vm->bytes_allocated += sizeof(CallFrame) * fiber->frame_capacity; + grayObject(&fiber->caller->_super, vm); grayObject(&fiber->error->_super, vm); } break; @@ -286,7 +287,7 @@ Script* newScript(PKVM* vm, String* name) { stringBufferInit(&script->names); vmPushTempRef(vm, &script->_super); - const char* fn_name = "@(ScriptLevel)"; + const char* fn_name = "$(SourceBody)"; script->body = newFunction(vm, fn_name, (int)strlen(fn_name), script, false); vmPopTempRef(vm); diff --git a/src/var.h b/src/var.h index e988bc0..aeb2700 100644 --- a/src/var.h +++ b/src/var.h @@ -336,6 +336,9 @@ struct Fiber { // Number of frame entry in frames. int frame_count; + // Caller of this fiber if it has one, NULL otherwise. + Fiber* caller; + // Runtime error initially NULL, heap allocated. String* error; }; diff --git a/src/vm.c b/src/vm.c index 0845d68..87054ca 100644 --- a/src/vm.c +++ b/src/vm.c @@ -230,8 +230,9 @@ static Var importScript(PKVM* vm, String* name, bool is_core, *is_new_script = true; pkStringResult result = { false, NULL, NULL }; - if (vm->config.load_script_fn != NULL) + if (vm->config.load_script_fn != NULL) { result = vm->config.load_script_fn(vm, name->data); + } if (!result.success) { vmPopTempRef(vm); // name @@ -290,12 +291,15 @@ static inline void pushCallFrame(PKVM* vm, Function* fn) { } void pkSetRuntimeError(PKVM* vm, const char* message) { - vm->fiber->error = stringFormat(vm, "$", message); + vm->fiber->error = newString(vm, message); } void vmReportError(PKVM* vm) { ASSERT(HAS_ERROR(), "runtimeError() should be called after an error."); - TODO; // TODO: create debug.h + + // TODO: pass the error to the caller of the fiber. + + reportStackTrace(vm); } // FIXME: temp. @@ -327,16 +331,20 @@ PKInterpretResult pkInterpret(PKVM* vm, const char* file) { vmPushTempRef(vm, &name->_super); if (!resolveScriptPath(vm, &name)) { - vm->config.error_fn(vm, PK_ERROR_COMPILE, NULL, -1, - stringFormat(vm, "Failed to resolve path '$'.", file)->data); + if (vm->config.error_fn != NULL) { + vm->config.error_fn(vm, PK_ERROR_COMPILE, NULL, -1, + stringFormat(vm, "Failed to resolve path '$'.", file)->data); + } return PK_RESULT_COMPILE_ERROR; } // Load the script source. pkStringResult res = vm->config.load_script_fn(vm, name->data); if (!res.success) { - vm->config.error_fn(vm, PK_ERROR_COMPILE, NULL, -1, - stringFormat(vm, "Failed to load script '@'.", name)->data); + if (vm->config.error_fn != NULL) { + vm->config.error_fn(vm, PK_ERROR_COMPILE, NULL, -1, + stringFormat(vm, "Failed to load script '@'.", name)->data); + } return PK_RESULT_COMPILE_ERROR; } @@ -390,7 +398,10 @@ void _debugRuntime(PKVM* vm) { PKInterpretResult vmRunScript(PKVM* vm, Script* _script) { - register uint8_t* ip; //< Current instruction pointer. + // Reference to the instruction pointer in the call frame. + register uint8_t** ip; +#define IP (*ip) // Convinent macro to the instruction pointer. + register Var* rbp; //< Stack base pointer register. register CallFrame* frame; //< Current call frame. register Script* script; //< Currently executing script. @@ -420,8 +431,8 @@ PKInterpretResult vmRunScript(PKVM* vm, Script* _script) { #define POP() (*(--vm->fiber->sp)) #define DROP() (--vm->fiber->sp) #define PEEK() (*(vm->fiber->sp - 1)) -#define READ_BYTE() (*ip++) -#define READ_SHORT() (ip+=2, (uint16_t)((ip[-2] << 8) | ip[-1])) +#define READ_BYTE() (*IP++) +#define READ_SHORT() (IP+=2, (uint16_t)((IP[-2] << 8) | IP[-1])) // Check if any runtime error exists and if so returns RESULT_RUNTIME_ERROR. #define CHECK_ERROR() \ @@ -440,16 +451,12 @@ PKInterpretResult vmRunScript(PKVM* vm, Script* _script) { return PK_RESULT_RUNTIME_ERROR; \ } while (false) -// Store the current frame to vm's call frame before pushing a new frame. -// Frames rbp will set once it created and will never change. -#define STORE_FRAME() frame->ip = ip - -// Update the call frame and ip once vm's call frame pushed or popped. -// fuction call, return or done running imported script. +// Load the last call frame to vm's execution variables to resume/run the +// function. #define LOAD_FRAME() \ do { \ frame = &vm->fiber->frames[vm->fiber->frame_count-1]; \ - ip = frame->ip; \ + ip = &(frame->ip); \ rbp = frame->rbp; \ script = frame->fn->owner; \ } while (false) @@ -663,9 +670,8 @@ PKInterpretResult vmRunScript(PKVM* vm, Script* _script) { CHECK_ERROR(); } else { - STORE_FRAME(); pushCallFrame(vm, fn); - LOAD_FRAME(); + LOAD_FRAME(); //< Load the top frame to vm's execution variables. } } else { @@ -681,13 +687,10 @@ PKInterpretResult vmRunScript(PKVM* vm, Script* _script) { Var* container = (vm->fiber->sp - 3); uint16_t jump_offset = READ_SHORT(); - bool is_done = varIterate(vm, *container, iterator, iter_value); + bool iterated = varIterate(vm, *container, iterator, iter_value); CHECK_ERROR(); - if (!is_done) { - DROP(); //< Iter value. - DROP(); //< Iterator. - DROP(); //< Container. - ip += jump_offset; + if (!iterated) { + IP += jump_offset; } DISPATCH(); } @@ -695,14 +698,14 @@ PKInterpretResult vmRunScript(PKVM* vm, Script* _script) { OPCODE(JUMP): { uint16_t offset = READ_SHORT(); - ip += offset; + IP += offset; DISPATCH(); } OPCODE(LOOP): { uint16_t offset = READ_SHORT(); - ip -= offset; + IP -= offset; DISPATCH(); } @@ -712,7 +715,7 @@ PKInterpretResult vmRunScript(PKVM* vm, Script* _script) { Var cond = POP(); uint16_t offset = READ_SHORT(); if (toBool(cond)) { - ip += offset; + IP += offset; } DISPATCH(); } @@ -722,14 +725,18 @@ PKInterpretResult vmRunScript(PKVM* vm, Script* _script) { Var cond = POP(); uint16_t offset = READ_SHORT(); if (!toBool(cond)) { - ip += offset; + IP += offset; } DISPATCH(); } OPCODE(RETURN): { + // TODO: handle caller fiber. + Var ret = POP(); + + // Pop the last frame. vm->fiber->frame_count--; // If no more call frames. We're done. @@ -754,6 +761,7 @@ PKInterpretResult vmRunScript(PKVM* vm, Script* _script) { Var on = POP(); String* name = script->names.data[READ_SHORT()]; PUSH(varGetAttrib(vm, on, name)); + CHECK_ERROR(); DISPATCH(); } @@ -762,6 +770,7 @@ PKInterpretResult vmRunScript(PKVM* vm, Script* _script) { Var on = PEEK(); String* name = script->names.data[READ_SHORT()]; PUSH(varGetAttrib(vm, on, name)); + CHECK_ERROR(); DISPATCH(); } @@ -772,6 +781,7 @@ PKInterpretResult vmRunScript(PKVM* vm, Script* _script) { String* name = script->names.data[READ_SHORT()]; varSetAttrib(vm, on, name, value); PUSH(value); + CHECK_ERROR(); DISPATCH(); } @@ -864,7 +874,7 @@ PKInterpretResult vmRunScript(PKVM* vm, Script* _script) { OPCODE(MOD): { Var r = POP(), l = POP(); - PUSH(varModulo(vm, r, l)); + PUSH(varModulo(vm, l, r)); CHECK_ERROR(); DISPATCH(); } diff --git a/test/examples/fib.pk b/test/examples/fib.pk new file mode 100644 index 0000000..2c0e41c --- /dev/null +++ b/test/examples/fib.pk @@ -0,0 +1,17 @@ + +## Fib test. + +res = '' + +def fib(n) + a = 0; b = 1 + for _ in 0..n + res += to_string(a) + " " + temp = a; + a = b; + b += temp; + end +end + +fib(10) +assert(res == '0 1 1 2 3 5 8 13 21 34 ') \ No newline at end of file diff --git a/test/examples/prime.pk b/test/examples/prime.pk index 532baae..2e4a256 100644 --- a/test/examples/prime.pk +++ b/test/examples/prime.pk @@ -1,3 +1,8 @@ + +## Prime numbers. + +res = '' + def is_prime(n) if n < 2 then return false end for i in 2..n @@ -6,11 +11,13 @@ def is_prime(n) return true end -def print_all_primes(n) +def get_all_primes(n) for i in 0..n - if is_prime(i) then print(i) end + if is_prime(i) + res += to_string(i) + ' ' + end end end - -print_all_primes(10) \ No newline at end of file +get_all_primes(20) +assert(res == '2 3 5 7 11 13 17 19 ') \ No newline at end of file diff --git a/test/lang/locals.pk b/test/lang/locals.pk deleted file mode 100644 index 0b1b9e7..0000000 --- a/test/lang/locals.pk +++ /dev/null @@ -1,16 +0,0 @@ - -## Local variable test. - -seq = '' -def fib(n) - a = 0; b = 1 - for _ in 0..n - seq += to_string(a) + " " - temp = a; - a = b; - b += temp; - end -end - -fib(10) -assert(seq == '0 1 1 2 3 5 8 13 21 34 ') \ No newline at end of file diff --git a/test/run_tests.bat b/test/run_tests.bat index 4ef1ad3..b07bdf6 100644 --- a/test/run_tests.bat +++ b/test/run_tests.bat @@ -4,8 +4,8 @@ set files=( ^ lang\basics.pk ^ lang\import.pk ^ lang\if.pk ^ - lang\locals.pk ^ ^ + examples\fib.pk ^ examples\prime.pk ^ )