From 6a22653263cb712537f7690f4cb22fd9e47ad5b4 Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Fri, 20 May 2022 20:00:24 +0530 Subject: [PATCH] Minor code enhancements (read bellow) - Warnings were fixed - Libraries are registered internally when PKVM created and cleanedup when PKVM freed (if PK_NO_LIBS not defined) - Lang.clock() moved to time module and sleep, epoch time were added. - Support both upper case and lower case hex literals - Support hex excaped characters inside strings (ex: "\x41") - Native api for import modules added `pkImportModule(...)` - pkAllocString, pkDeallocString are changed to pkRealloc. - NewInstance, DeleteInstance functions now take PKVM however delete function should not allocate any memory since it's invoked at the GC execution. --- README.md | 2 +- cli/main.c | 1 - docs/GettingStarted/LanguageManual.md | 2 +- docs/wasm/compile.py | 11 +-- scripts/amalgamate.py | 4 +- scripts/leak_detect.py | 3 + src/core/compiler.c | 81 +++++++++++++----- src/core/core.c | 30 +++---- src/core/public.c | 114 +++++++++++++++----------- src/core/value.c | 7 +- src/core/vm.c | 42 ++++------ src/core/vm.h | 6 ++ src/include/pocketlang.h | 41 ++++----- src/libs/libs.c | 16 ++-- src/libs/libs.h | 14 ++-- src/libs/std_dummy.c | 12 +-- src/libs/std_io.c | 13 +-- src/libs/std_path.c | 10 +-- src/libs/std_time.c | 60 ++++++++++++++ tests/benchmarks/factors/factors.pk | 2 +- tests/benchmarks/fib/fib.pk | 2 +- tests/benchmarks/list/list.pk | 2 +- tests/benchmarks/loop/loop.pk | 2 +- tests/benchmarks/primes/primes.pk | 2 +- tests/benchmarks/tco/tco.pk | 2 +- tests/lang/import.pk | 2 +- 26 files changed, 299 insertions(+), 184 deletions(-) create mode 100644 src/libs/std_time.c diff --git a/README.md b/README.md index 10408bb..1ebc3fb 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ were used as a reference to write this language. ```ruby # Python like import statement. -from lang import clock as now +from time import clock as now # A recursive fibonacci function. def fib(n) diff --git a/cli/main.c b/cli/main.c index ceda01d..8ed9476 100644 --- a/cli/main.c +++ b/cli/main.c @@ -60,7 +60,6 @@ static PKVM* intializePocketVM() { } PKVM* vm = pkNewVM(&config); - pkRegisterLibs(vm); return vm; } diff --git a/docs/GettingStarted/LanguageManual.md b/docs/GettingStarted/LanguageManual.md index f3fb937..ef29dd9 100644 --- a/docs/GettingStarted/LanguageManual.md +++ b/docs/GettingStarted/LanguageManual.md @@ -384,7 +384,7 @@ from lang import clock as now import foo.bar # Import module bar from foo directory import baz # If baz is a directory it'll import baz/_init.pk -# I'll only search for foo relatievly. +# It'll only search for foo relatievly. import .foo # ./foo.pk or ./foo/_init.pk or ./foo.dll, ... # ^ meaning parent directory relative to this script. diff --git a/docs/wasm/compile.py b/docs/wasm/compile.py index e7f7bc9..ab7e431 100644 --- a/docs/wasm/compile.py +++ b/docs/wasm/compile.py @@ -6,7 +6,7 @@ from shutil import which ## This file is not intended to be included in other files at the moment. THIS_PATH = abspath(dirname(__file__)) -POCKET_SOURCE_DIR = join(THIS_PATH, "../../../pocketlang/src/") +POCKET_ROOT = join(THIS_PATH, "../../../pocketlang/src/") JS_API_PATH = join(THIS_PATH, "io_api.js") MAIN_C = join(THIS_PATH, "main.c") TARGET_DIR = join(THIS_PATH, "../static/wasm/") @@ -27,7 +27,7 @@ def main(): os.mkdir(TARGET_DIR) sources = ' '.join(collect_source_files()) - include = '-I' + join(POCKET_SOURCE_DIR, 'include/') + include = '-I' + join(POCKET_ROOT, 'include/') output = join(TARGET_DIR, TARGET_NAME) exports = "\"EXPORTED_RUNTIME_METHODS=['ccall','cwrap']\"" js_api = JS_API_PATH @@ -39,9 +39,10 @@ def main(): def collect_source_files(): sources = [] - for file in os.listdir(POCKET_SOURCE_DIR): - if isdir(file): continue - if file.endswith('.c'): sources.append(join(POCKET_SOURCE_DIR, file)) + for dir in ('core/', 'libs/'): + for file in os.listdir(join(POCKET_ROOT, dir)): + if isdir(file): continue + if file.endswith('.c'): sources.append(join(POCKET_ROOT, dir, file)) return sources if __name__ == "__main__": diff --git a/scripts/amalgamate.py b/scripts/amalgamate.py index b06b7d9..591ecff 100644 --- a/scripts/amalgamate.py +++ b/scripts/amalgamate.py @@ -96,10 +96,10 @@ def generate(): '\n' gen += parse(header) + '\n' - gen += '#ifdef PK_IMPL\n\n' + gen += '#ifdef PK_IMPLEMENT\n\n' for source in SOURCES: gen += parse(source) - gen += '#endif // PK_IMPL\n' + gen += '#endif // PK_IMPLEMENT\n' return gen diff --git a/scripts/leak_detect.py b/scripts/leak_detect.py index 50090ef..bafe4b6 100644 --- a/scripts/leak_detect.py +++ b/scripts/leak_detect.py @@ -8,6 +8,9 @@ ## To get the trace report redefine TRACE_MEMORY as 1 at the ## pk_internal.h and compile pocketlang. +raise "This script Need refactor after removing pkAllocString " + \ + "and adding pkRealloc" + import sys def detect_leak(): diff --git a/src/core/compiler.c b/src/core/compiler.c index b465460..1699739 100644 --- a/src/core/compiler.c +++ b/src/core/compiler.c @@ -664,6 +664,28 @@ static void setNextValueToken(Parser* parser, _TokenType type, Var value); static void setNextToken(Parser* parser, _TokenType type); static bool matchChar(Parser* parser, char c); +#define _BETWEEN(a, c, b) (((a) <= (c)) && ((c) <= (b))) +static inline bool _isCharHex(char c) { + return (_BETWEEN('0', c, '9') + || _BETWEEN('a', c, 'z') + || _BETWEEN('A', c, 'Z')); +} + +static inline uint8_t _charHexVal(char c) { + ASSERT(_isCharHex(c), OOPS); + + if (_BETWEEN('0', c, '9')) { + return c - '0'; + } else if (_BETWEEN('a', c, 'z')) { + return c - 'a' + 10; + } else if (_BETWEEN('A', c, 'Z')) { + return c - 'A' + 10; + } + UNREACHABLE(); + return 0; +} +#undef _BETWEEN + static void eatString(Compiler* compiler, bool single_quote) { Parser* parser = &compiler->parser; @@ -693,15 +715,15 @@ static void eatString(Compiler* compiler, bool single_quote) { if (parser->si_depth < MAX_STR_INTERP_DEPTH) { tk_type = TK_STRING_INTERP; - char c = peekChar(parser); - if (c == '{') { // Expression interpolation (ie. "${expr}"). + char c2 = peekChar(parser); + if (c2 == '{') { // Expression interpolation (ie. "${expr}"). eatChar(parser); parser->si_depth++; parser->si_quote[parser->si_depth - 1] = quote; parser->si_open_brace[parser->si_depth - 1] = 0; } else { // Name Interpolation. - if (!utilIsName(c)) { + if (!utilIsName(c2)) { syntaxError(compiler, makeErrToken(parser), "Expected '{' or identifier after '$'."); return; @@ -740,9 +762,35 @@ static void eatString(Compiler* compiler, bool single_quote) { // '$' In pocketlang string is used for interpolation. case '$': pkByteBufferWrite(&buff, parser->vm, '$'); break; + // Hex literal in string should match `\x[0-9a-zA-Z][0-9a-zA-Z]` + case 'x': { + uint8_t val = 0; + + c = eatChar(parser); + if (!_isCharHex(c)) { + semanticError(compiler, makeErrToken(parser), + "Invalid hex escape."); + break; + } + + val = _charHexVal(c); + + c = eatChar(parser); + if (!_isCharHex(c)) { + semanticError(compiler, makeErrToken(parser), + "Invalid hex escape."); + break; + } + + val = (val << 4) | _charHexVal(c); + + pkByteBufferWrite(&buff, parser->vm, val); + + } break; + default: - syntaxError(compiler, makeErrToken(parser), - "Invalid escape character."); + semanticError(compiler, makeErrToken(parser), + "Invalid escape character."); return; } } else { @@ -807,10 +855,6 @@ static void eatName(Parser* parser) { static void eatNumber(Compiler* compiler) { Parser* parser = &compiler->parser; -#define IS_HEX_CHAR(c) \ - (('0' <= (c) && (c) <= '9') || \ - ('a' <= (c) && (c) <= 'f')) - #define IS_BIN_CHAR(c) (((c) == '0') || ((c) == '1')) Var value = VAR_NULL; // The number value. @@ -855,8 +899,8 @@ static void eatNumber(Compiler* compiler) { uint64_t hex = 0; c = peekChar(parser); - // The first digit should be either hex digit. - if (!IS_HEX_CHAR(c)) { + // The first digit should be hex digit. + if (!_isCharHex(c)) { syntaxError(compiler, makeErrToken(parser), "Invalid hex literal."); return; @@ -864,7 +908,7 @@ static void eatNumber(Compiler* compiler) { do { // Consume the next digit. c = peekChar(parser); - if (!IS_HEX_CHAR(c)) break; + if (!_isCharHex(c)) break; eatChar(parser); // Check the length of the binary literal. @@ -876,10 +920,7 @@ static void eatNumber(Compiler* compiler) { } // "Append" the next digit at the end. - uint8_t append_val = ('0' <= c && c <= '9') - ? (uint8_t)(c - '0') - : (uint8_t)((c - 'a') + 10); - hex = (hex << 4) | append_val; + hex = (hex << 4) | _charHexVal(c); } while (true); @@ -930,7 +971,6 @@ static void eatNumber(Compiler* compiler) { setNextValueToken(parser, TK_NUMBER, value); #undef IS_BIN_CHAR -#undef IS_HEX_CHAR } // Read and ignore chars till it reach new line or EOF. @@ -1337,7 +1377,6 @@ static int findBuiltinFunction(const PKVM* vm, static int findBuiltinClass(const PKVM* vm, const char* name, uint32_t length) { for (int i = 0; i < PK_INSTANCE; i++) { - uint32_t bfn_length = vm->builtin_classes[i]->name->length; if (IS_CSTR_EQ(vm->builtin_classes[i]->name, name, length)) { return i; } @@ -2344,12 +2383,10 @@ static void compilerAddForward(Compiler* compiler, int instruction, Fn* fn, // Add a literal constant to module literals and return it's index. static int compilerAddConstant(Compiler* compiler, Var value) { - pkVarBuffer* constants = &compiler->module->constants; - uint32_t index = moduleAddConstant(compiler->parser.vm, compiler->module, value); checkMaxConstantsReached(compiler, index); - return (int)index; + return (int) index; } // Enters inside a block. @@ -2899,7 +2936,7 @@ static Token compileImportPath(Compiler* compiler) { // Create constant pool entry for the path string. int index = 0; moduleAddString(compiler->module, compiler->parser.vm, - buff.data, buff.count - 1, &index); + (const char*) buff.data, buff.count - 1, &index); pkByteBufferClear(&buff, vm); diff --git a/src/core/core.c b/src/core/core.c index 5e20a3c..51e1520 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -7,7 +7,6 @@ #include #include #include -#include #ifndef PK_AMALGAMATED #include "core.h" @@ -40,9 +39,6 @@ /* VALIDATORS */ /*****************************************************************************/ -// Evaluated to true of the [num] is in byte range. -#define IS_NUM_BYTE(num) ((CHAR_MIN <= (num)) && ((num) <= CHAR_MAX)) - // Check if [var] is a numeric value (bool/number) and set [value]. static inline bool isNumeric(Var var, double* value) { if (IS_NUM(var)) { @@ -396,11 +392,11 @@ DEF(coreChr, int64_t num; if (!validateInteger(vm, ARG(1), &num, "Argument 1")) return; - if (!IS_NUM_BYTE(num)) { - RET_ERR(newString(vm, "The number is not in a byte range.")); + if (!(0 <= num && num <= 0xff)) { + RET_ERR(newString(vm, "The number should be in range 0x00 to 0xff.")); } - char c = (char)num; + char c = (char) num; RET(VAR_OBJ(newStringLength(vm, &c, 1))); } @@ -462,7 +458,7 @@ DEF(coreInput, } String* line = newString(vm, str); - pkDeAllocString(vm, str); + pkRealloc(vm, str, 0); RET(VAR_OBJ(line)); } @@ -655,14 +651,6 @@ void moduleAddFunctionInternal(PKVM* vm, Module* module, } // 'lang' library methods. -// ----------------------- - -DEF(stdLangClock, - "clock() -> num\n" - "Returns the number of seconds since the application started") { - - RET(VAR_NUM((double)clock() / CLOCKS_PER_SEC)); -} DEF(stdLangGC, "gc() -> num\n" @@ -734,7 +722,6 @@ static void initializeCoreModules(PKVM* vm) { vmPopTempRef(vm) /* module */ NEW_MODULE(lang, "lang"); - MODULE_ADD_FN(lang, "clock", stdLangClock, 0); MODULE_ADD_FN(lang, "gc", stdLangGC, 0); MODULE_ADD_FN(lang, "disas", stdLangDisas, 1); MODULE_ADD_FN(lang, "write", stdLangWrite, -1); @@ -760,13 +747,13 @@ static void _ctorBool(PKVM* vm) { static void _ctorNumber(PKVM* vm) { double value; + if (isNumeric(ARG(1), &value)) { RET(VAR_NUM(value)); } if (IS_OBJ_TYPE(ARG(1), OBJ_STRING)) { String* str = (String*) AS_OBJ(ARG(1)); - double value; const char* err = utilToNumber(str->data, &value); if (err == NULL) RET(VAR_NUM(value)); VM_SET_ERROR(vm, newString(vm, err)); @@ -919,6 +906,7 @@ static void initializePrimitiveClasses(PKVM* vm) { ADD_CTOR(PK_BOOL, "@ctorBool", _ctorBool, 1); ADD_CTOR(PK_NUMBER, "@ctorNumber", _ctorNumber, 1); ADD_CTOR(PK_STRING, "@ctorString", _ctorString, -1); + ADD_CTOR(PK_RANGE, "@ctorRange", _ctorRange, 2); ADD_CTOR(PK_LIST, "@ctorList", _ctorList, -1); ADD_CTOR(PK_MAP, "@ctorMap", _ctorMap, 0); ADD_CTOR(PK_FIBER, "@ctorFiber", _ctorFiber, 1); @@ -945,8 +933,6 @@ static void initializePrimitiveClasses(PKVM* vm) { #undef ADD_METHOD } -#undef IS_NUM_BYTE - /*****************************************************************************/ /* OPERATORS */ /*****************************************************************************/ @@ -1176,6 +1162,8 @@ Var varAdd(PKVM* vm, Var v1, Var v2, bool inplace) { } } } break; + + default: break; } } CHECK_INST_BINARY_OP("+"); @@ -1353,6 +1341,8 @@ bool varContains(PKVM* vm, Var elem, Var container) { Map* map = (Map*)AS_OBJ(container); return !IS_UNDEF(mapGet(map, elem)); } break; + + default: break; } #define v1 container diff --git a/src/core/public.c b/src/core/public.c index ad35a86..2636714 100644 --- a/src/core/public.c +++ b/src/core/public.c @@ -15,6 +15,23 @@ #include "vm.h" #endif +// TODO: Document this or Find a better way. +// +// Pocketlang core doesn't implement path resolving funcionality. Rather it +// should be provided the host application. By default we're using an +// implementation from the path library. However pocket core cannot be depend +// on its libs, otherwise it'll breaks the encapsulation. +// +// As a workaround we declare the default path resolver here and use it. +// But if someone wants to compile just the core pocketlang without libs +// they have to define PK_NO_LIBS to prevent the compiler from not be able +// to find functions when linking. +#ifndef PK_NO_LIBS + void registerLibs(PKVM* vm); + void cleanupLibs(PKVM* vm); + char* pathResolveImport(PKVM* vm, const char* from, const char* path); +#endif + #define CHECK_ARG_NULL(name) \ ASSERT((name) != NULL, "Argument " #name " was NULL."); @@ -66,41 +83,17 @@ static void stdoutWrite(PKVM* vm, const char* text); static char* stdinRead(PKVM* vm); static char* loadScript(PKVM* vm, const char* path); -char* pkAllocString(PKVM* vm, size_t size) { +PK_PUBLIC void* pkRealloc(PKVM* vm, void* ptr, size_t size) { ASSERT(vm->config.realloc_fn != NULL, "PKVM's allocator was NULL."); - #if TRACE_MEMORY - void* ptr = vm->config.realloc_fn(NULL, size, vm->config.user_data); - printf("[alloc string] alloc : %p = %+li bytes\n", ptr, (long) size); - return (char*) ptr; + void* newptr = vm->config.realloc_fn(ptr, size, vm->config.user_data); + printf("[pkRealloc] %p -> %p %+li bytes\n", ptr, newptr, (long) size); + return ptr; #else - return (char*) vm->config.realloc_fn(NULL, size, vm->config.user_data); + return vm->config.realloc_fn(ptr, size, vm->config.user_data); #endif } -void pkDeAllocString(PKVM* vm, char* str) { - ASSERT(vm->config.realloc_fn != NULL, "PKVM's allocator was NULL."); -#if TRACE_MEMORY - printf("[alloc string] dealloc : %p\n", str); -#endif - vm->config.realloc_fn(str, 0, vm->config.user_data); -} - -// TODO: Document this or Find a better way. -// -// Pocketlang core doesn't implement path resolving funcionality. Rather it -// should be provided the host application. By default we're using an -// implementation from the path library. However pocket core cannot be depend -// on its libs, otherwise it'll breaks the encapsulation. -// -// As a workaround we declare the default path resolver here and use it. -// But if someone wants to compile just the core pocketlang without libs -// they have to define PK_NO_LIBS to prevent the compiler from not be able -// to find functions when linking. -#if !defined(PK_NO_LIBS) - char* pathResolveImport(PKVM* vm, const char* from, const char* path); -#endif - PkConfiguration pkNewConfiguration() { PkConfiguration config; memset(&config, 0, sizeof(config)); @@ -110,7 +103,7 @@ PkConfiguration pkNewConfiguration() { config.stdout_write = stdoutWrite; config.stderr_write = stderrWrite; config.stdin_read = stdinRead; -#if !defined(PK_NO_LIBS) +#ifndef PK_NO_LIBS config.resolve_path_fn = pathResolveImport; #endif config.load_script_fn = loadScript; @@ -146,11 +139,20 @@ PKVM* pkNewVM(PkConfiguration* config) { } initializeCore(vm); + +#ifndef PK_NO_LIBS + registerLibs(vm); +#endif + return vm; } void pkFreeVM(PKVM* vm) { +#ifndef PK_NO_LIBS + cleanupLibs(vm); +#endif + Object* obj = vm->first; while (obj != NULL) { Object* next = obj->next; @@ -334,7 +336,7 @@ PkResult pkRunFile(PKVM* vm, const char* path) { // Set module path and and deallocate resolved. String* script_path = newString(vm, resolved_); vmPushTempRef(vm, &script_path->_super); // script_path. - pkDeAllocString(vm, resolved_); + pkRealloc(vm, resolved_, 0); module->path = script_path; vmPopTempRef(vm); // script_path. @@ -352,7 +354,7 @@ PkResult pkRunFile(PKVM* vm, const char* path) { } } else { result = compile(vm, module, source, NULL); - pkDeAllocString(vm, source); + pkRealloc(vm, source, 0); } if (result == PK_RESULT_SUCCESS) { @@ -449,21 +451,21 @@ PkResult pkRunREPL(PKVM* vm) { if (line_length >= 1 && *(line + line_length - 1) == EOF) { printfn(vm, "\n"); result = PK_RESULT_SUCCESS; - pkDeAllocString(vm, line); + pkRealloc(vm, line, 0); break; } // If the line is empty, we don't have to compile it. if (isStringEmpty(line)) { if (need_more_lines) ASSERT(lines.count != 0, OOPS); - pkDeAllocString(vm, line); + pkRealloc(vm, line, 0); continue; } // Add the line to the lines buffer. if (lines.count != 0) pkByteBufferWrite(&lines, vm, '\n'); pkByteBufferAddString(&lines, vm, line, (uint32_t) line_length); - pkDeAllocString(vm, line); + pkRealloc(vm, line, 0); pkByteBufferWrite(&lines, vm, '\0'); // Compile the buffer to the module. @@ -546,12 +548,12 @@ bool pkCheckArgcRange(PKVM* vm, int argc, int min, int max) { } // Set error for incompatible type provided as an argument. (TODO: got type). -#define ERR_INVALID_ARG_TYPE(ty_name) \ - do { \ - char buff[STR_INT_BUFF_SIZE]; \ - sprintf(buff, "%d", arg); \ - VM_SET_ERROR(vm, stringFormat(vm, "Expected a '$' at argument $.", \ - ty_name, buff)); \ +#define ERR_INVALID_SLOT_TYPE(ty_name) \ + do { \ + char buff[STR_INT_BUFF_SIZE]; \ + sprintf(buff, "%d", arg); \ + VM_SET_ERROR(vm, stringFormat(vm, "Expected a '$' at slot $.", \ + ty_name, buff)); \ } while (false) // FIXME: If the user needs just the boolean value of the object, they should @@ -562,7 +564,7 @@ PK_PUBLIC bool pkValidateSlotBool(PKVM* vm, int arg, bool* value) { Var val = ARG(arg); if (!IS_BOOL(val)) { - ERR_INVALID_ARG_TYPE("Boolean"); + ERR_INVALID_SLOT_TYPE("Boolean"); return false; } @@ -576,7 +578,7 @@ PK_PUBLIC bool pkValidateSlotNumber(PKVM* vm, int arg, double* value) { Var val = ARG(arg); if (!IS_NUM(val)) { - ERR_INVALID_ARG_TYPE("Number"); + ERR_INVALID_SLOT_TYPE("Number"); return false; } @@ -591,7 +593,7 @@ PK_PUBLIC bool pkValidateSlotString(PKVM* vm, int arg, const char** value, Var val = ARG(arg); if (!IS_OBJ_TYPE(val, OBJ_STRING)) { - ERR_INVALID_ARG_TYPE("String"); + ERR_INVALID_SLOT_TYPE("String"); return false; } String* str = (String*)AS_OBJ(val); @@ -604,7 +606,7 @@ bool pkValidateSlotType(PKVM* vm, int arg, PkVarType type) { CHECK_FIBER_EXISTS(vm); VALIDATE_ARGC(arg); if (getVarType(ARG(arg)) != type) { - ERR_INVALID_ARG_TYPE(getPkVarTypeName(type)); + ERR_INVALID_SLOT_TYPE(getPkVarTypeName(type)); return false; } @@ -620,7 +622,7 @@ PK_PUBLIC bool pkValidateSlotInstanceOf(PKVM* vm, int arg, int cls) { if (!varIsType(vm, instance, class_)) { // If [class_] is not a valid class, it's already an error. if (VM_HAS_ERROR(vm)) return false; - ERR_INVALID_ARG_TYPE(((Class*)AS_OBJ(class_))->name->data); + ERR_INVALID_SLOT_TYPE(((Class*)AS_OBJ(class_))->name->data); return false; } @@ -632,7 +634,6 @@ bool pkIsSlotInstanceOf(PKVM* vm, int inst, int cls, bool* val) { VALIDATE_SLOT_INDEX(inst); VALIDATE_SLOT_INDEX(cls); - Var instance = SLOT(inst), class_ = SLOT(cls); *val = varIsType(vm, inst, cls); return !VM_HAS_ERROR(vm); } @@ -876,6 +877,19 @@ void pkPlaceSelf(PKVM* vm, int index) { SET_SLOT(index, vm->fiber->self); } +bool pkImportModule(PKVM* vm, const char* path, int index) { + CHECK_FIBER_EXISTS(vm); + VALIDATE_SLOT_INDEX(index); + + String* path_ = newString(vm, path); + vmPushTempRef(vm, &path_->_super); // path_ + Var module = vmImportModule(vm, NULL, path_); + vmPopTempRef(vm); // path_ + + SET_SLOT(index, module); + return !VM_HAS_ERROR(vm); +} + void pkGetClass(PKVM* vm, int instance, int index) { CHECK_FIBER_EXISTS(vm); VALIDATE_SLOT_INDEX(instance); @@ -928,7 +942,7 @@ static char* stdinRead(PKVM* vm) { } while (c != EOF); pkByteBufferWrite(&buff, vm, '\0'); - char* str = pkAllocString(vm, buff.count); + char* str = pkRealloc(vm, NULL, buff.count); memcpy(str, buff.data, buff.count); return str; } @@ -946,8 +960,8 @@ static char* loadScript(PKVM* vm, const char* path) { fseek(file, 0, SEEK_SET); // Allocate string + 1 for the NULL terminator. - char* buff = pkAllocString(vm, file_size + 1); - ASSERT(buff != NULL, "pkAllocString failed."); + char* buff = pkRealloc(vm, NULL, file_size + 1); + ASSERT(buff != NULL, "pkRealloc failed."); clearerr(file); size_t read = fread(buff, sizeof(char), file_size, file); diff --git a/src/core/value.c b/src/core/value.c index e90cace..b7a81b8 100644 --- a/src/core/value.c +++ b/src/core/value.c @@ -508,7 +508,7 @@ Instance* newInstance(PKVM* vm, Class* cls) { inst->attribs = newMap(vm); if (cls->new_fn != NULL) { - inst->native = cls->new_fn(); + inst->native = cls->new_fn(vm); } else { inst->native = NULL; } @@ -753,6 +753,8 @@ static uint32_t _hashObject(Object* obj) { Range* range = (Range*)obj; return utilHashNumber(range->from) ^ utilHashNumber(range->to); } + + default: break; } UNREACHABLE(); @@ -1020,7 +1022,7 @@ void freeObject(PKVM* vm, Object* self) { case OBJ_INST: { Instance* inst = (Instance*)self; if (inst->cls->delete_fn != NULL) { - inst->cls->delete_fn(inst->native); + inst->cls->delete_fn(vm, inst->native); } DEALLOCATE(vm, inst, Instance); return; @@ -1506,7 +1508,6 @@ static void _toStringInternal(PKVM* vm, const Var v, pkByteBuffer* buff, } case OBJ_UPVALUE: { - const Upvalue* upvalue = (const Upvalue*)obj; pkByteBufferAddString(buff, vm, "[Upvalue]", 9); return; } diff --git a/src/core/vm.c b/src/core/vm.c index 285c929..5460bda 100644 --- a/src/core/vm.c +++ b/src/core/vm.c @@ -339,7 +339,7 @@ PkResult vmCallFunction(PKVM* vm, Closure* fn, int argc, Var* argv, Var* ret) { // Import and return the Module object with the [path] string. If the path // starts with with './' or '../' we'll only try relative imports, otherwise // we'll search native modules first and then at relative path. -static inline Var importModule(PKVM* vm, String* from, String* path) { +Var vmImportModule(PKVM* vm, String* from, String* path) { ASSERT((path != NULL) && (path->length > 0), OOPS); bool is_relative = path->data[0] == '.'; @@ -362,19 +362,13 @@ static inline Var importModule(PKVM* vm, String* from, String* path) { (from) ? from->data : NULL, path->data); if (_resolved == NULL) { // Can't resolve a relative module. - pkDeAllocString(vm, _resolved); - if (from) { - VM_SET_ERROR(vm, stringFormat(vm, "Cannot resolve a relative import " - "path \"@\" from \"@\".", path, from)); - } else { - VM_SET_ERROR(vm, stringFormat(vm, "Cannot resolve a relative import " - "path \"@\"", path)); - } + pkRealloc(vm, _resolved, 0); + VM_SET_ERROR(vm, stringFormat(vm, "Cannot import module '@'", path)); return VAR_NULL; } String* resolved = newString(vm, _resolved); - pkDeAllocString(vm, _resolved); + pkRealloc(vm, _resolved, 0); // If the script already imported and cached, return it. Var entry = mapGet(vm->modules, VAR_OBJ(resolved)); @@ -405,14 +399,14 @@ static inline Var importModule(PKVM* vm, String* from, String* path) { break; } - // Make a new module and to compile it. + // Make a new module, compile and cache it. module = newModule(vm); module->path = resolved; vmPushTempRef(vm, &module->_super); // module. { initializeScript(vm, module); PkResult result = compile(vm, module, source, NULL); - pkDeAllocString(vm, source); + pkRealloc(vm, source, 0); if (result == PK_RESULT_SUCCESS) { vmRegisterModule(vm, module, resolved); } else { @@ -948,14 +942,14 @@ L_vm_main_loop: // Capture the vaupes. for (int i = 0; i < fn->upvalue_count; i++) { uint8_t is_immediate = READ_BYTE(); - uint8_t index = READ_BYTE(); + uint8_t idx = READ_BYTE(); if (is_immediate) { // rbp[0] is the return value, rbp + 1 is the first local and so on. - closure->upvalues[i] = captureUpvalue(vm, fiber, (rbp + 1 + index)); + closure->upvalues[i] = captureUpvalue(vm, fiber, (rbp + 1 + idx)); } else { // The upvalue is already captured by the current function, reuse it. - closure->upvalues[i] = frame->closure->upvalues[index]; + closure->upvalues[i] = frame->closure->upvalues[idx]; } } @@ -1025,7 +1019,7 @@ L_vm_main_loop: String* name = moduleGetStringAt(module, (int)index); ASSERT(name != NULL, OOPS); - Var _imported = importModule(vm, module->path, name); + Var _imported = vmImportModule(vm, module->path, name); CHECK_ERROR(); ASSERT(IS_OBJ_TYPE(_imported, OBJ_MODULE), OOPS); @@ -1485,8 +1479,8 @@ L_do_call: OPCODE(POSITIVE): { // Don't pop yet, we need the reference for gc. - Var self = PEEK(-1); - Var result = varPositive(vm, self); + Var self_ = PEEK(-1); + Var result = varPositive(vm, self_); DROP(); // self PUSH(result); @@ -1497,8 +1491,8 @@ L_do_call: OPCODE(NEGATIVE): { // Don't pop yet, we need the reference for gc. - Var self = PEEK(-1); - Var result = varNegative(vm, self); + Var v = PEEK(-1); + Var result = varNegative(vm, v); DROP(); // self PUSH(result); @@ -1509,8 +1503,8 @@ L_do_call: OPCODE(NOT): { // Don't pop yet, we need the reference for gc. - Var self = PEEK(-1); - Var result = varNot(vm, self); + Var v = PEEK(-1); + Var result = varNot(vm, v); DROP(); // self PUSH(result); @@ -1521,8 +1515,8 @@ L_do_call: OPCODE(BIT_NOT): { // Don't pop yet, we need the reference for gc. - Var self = PEEK(-1); - Var result = varBitNot(vm, self); + Var v = PEEK(-1); + Var result = varBitNot(vm, v); DROP(); // self PUSH(result); diff --git a/src/core/vm.h b/src/core/vm.h index 93632b0..bc29d87 100644 --- a/src/core/vm.h +++ b/src/core/vm.h @@ -235,4 +235,10 @@ PkResult vmCallFunction(PKVM* vm, Closure* fn, int argc, Var* argv, Var* ret); PkResult vmCallMethod(PKVM* vm, Var self, Closure* fn, int argc, Var* argv, Var* ret); +// Import a module with the [path] and return it. The path sepearation should +// be '/' example: to import module "a.b" the [path] should be "a/b". +// If the [from] is not NULL, it'll be used for relative path search. +// On failure, it'll set an error and return VAR_NULL. +Var vmImportModule(PKVM* vm, String* from, String* path); + #endif // PK_VM_H diff --git a/src/include/pocketlang.h b/src/include/pocketlang.h index ef8b0ca..31c13e7 100644 --- a/src/include/pocketlang.h +++ b/src/include/pocketlang.h @@ -94,13 +94,13 @@ 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). The returned string **must** be -// allocated with pkAllocString() and the VM will claim the ownership of the +// allocated with pkRealloc() and the VM will claim the ownership of the // string. typedef char* (*pkReadFn) (PKVM* vm); // Load and return the script. Called by the compiler to fetch initial source // code and source for import statements. Return NULL to indicate failure to -// load. Otherwise the string **must** be allocated with pkAllocString() and +// load. Otherwise the string **must** be allocated with pkRealloc() and // the VM will claim the ownership of the string. typedef char* (*pkLoadScriptFn) (PKVM* vm, const char* path); @@ -108,18 +108,20 @@ typedef char* (*pkLoadScriptFn) (PKVM* vm, const char* path); // be either path to a script or NULL if [path] is relative to cwd. The return // value should be a normalized absolute path of the [path]. Return NULL to // indicate failure to resolve. Othrewise the string **must** be allocated with -// pkAllocString() and the VM will claim the ownership of the string. +// pkRealloc() and the VM will claim the ownership of the string. typedef char* (*pkResolvePathFn) (PKVM* vm, const char* from, const char* path); // A function callback to allocate and return a new instance of the registered // class. Which will be called when the instance is constructed. The returned/ // data is expected to be alive till the delete callback occurs. -typedef void* (*pkNewInstanceFn) (); +typedef void* (*pkNewInstanceFn) (PKVM* vm); -// A function callback to de-allocate the aloocated native instance of the -// registered class. -typedef void (*pkDeleteInstanceFn) (void*); +// A function callback to de-allocate the allocated native instance of the +// registered class. This function is invoked at the GC execution. No object +// allocations are allowed during it, so **NEVER** allocate any objects +// inside them. +typedef void (*pkDeleteInstanceFn) (PKVM* vm, void*); /*****************************************************************************/ /* POCKETLANG TYPES */ @@ -197,26 +199,20 @@ PK_PUBLIC PKVM* pkNewVM(PkConfiguration* config); // Clean the VM and dispose all the resources allocated by the VM. PK_PUBLIC void pkFreeVM(PKVM* vm); -// This will register all the standard libraries of pocketlang to the VM. The -// libraries are not part of the core implementation, and one can use just the -// bare bone of the language without any libraries if they don't call this. -PK_PUBLIC void pkRegisterLibs(PKVM* vm); - // Update the user data of the vm. PK_PUBLIC void pkSetUserData(PKVM* vm, void* user_data); // Returns the associated user data. PK_PUBLIC void* pkGetUserData(const PKVM* vm); -// Allocate memory with [size] and return it. This function should be called +// Invoke pocketlang's allocator directly. This function should be called // when the host application want to send strings to the PKVM that are claimed -// by the VM once the caller returned it. -PK_PUBLIC char* pkAllocString(PKVM* vm, size_t size); - -// Complementary function to pkAllocString. This should not be called if the -// string is returned to the VM. Since PKVM will claim the ownership and -// deallocate the string itself. -PK_PUBLIC void pkDeAllocString(PKVM* vm, char* ptr); +// by the VM once the caller returned it. For other uses you **should** call +// pkRealloc with [size] 0 to cleanup, otherwise there will be a memory leak. +// +// Internally it'll call `pkReallocFn` function that was provided in the +// configuration. +PK_PUBLIC void* pkRealloc(PKVM* vm, void* ptr, size_t size); // Release the handle and allow its value to be garbage collected. Always call // this for every handles before freeing the VM. @@ -391,6 +387,11 @@ PK_PUBLIC bool pkGetAttribute(PKVM* vm, int instance, const char* name, // Place the [self] instance at the [index] slot. PK_PUBLIC void pkPlaceSelf(PKVM* vm, int index); +// Import a module with the [path] and place it at [index] slot. The path +// sepearation should be '/'. Example: to import module "foo.bar" the [path] +// should be "foo/bar". On failure, it'll set an error and return false. +PK_PUBLIC bool pkImportModule(PKVM* vm, const char* path, int index); + // Set the [index] slot's value as the class of the [instance]. PK_PUBLIC void pkGetClass(PKVM* vm, int instance, int index); diff --git a/src/libs/libs.c b/src/libs/libs.c index 014b2af..e6290e1 100644 --- a/src/libs/libs.c +++ b/src/libs/libs.c @@ -8,14 +8,20 @@ #include "libs.h" #endif -void registerModuleDummy(PKVM* vm); +void registerModuleMath(PKVM* vm); +void registerModuleTime(PKVM* vm); void registerModuleIO(PKVM* vm); void registerModulePath(PKVM* vm); -void registerModuleMath(PKVM* vm); +void registerModuleDummy(PKVM* vm); -void pkRegisterLibs(PKVM* vm) { - registerModuleDummy(vm); +void registerLibs(PKVM* vm) { + registerModuleMath(vm); + registerModuleTime(vm); registerModuleIO(vm); registerModulePath(vm); - registerModuleMath(vm); + registerModuleDummy(vm); +} + +void cleanupLibs(PKVM* vm) { + } diff --git a/src/libs/libs.h b/src/libs/libs.h index 0f8842c..9ac858a 100644 --- a/src/libs/libs.h +++ b/src/libs/libs.h @@ -113,13 +113,6 @@ #endif // PK_AMALGAMATED -// Allocate a new module object of type [Ty]. -#define NEW_OBJ(Ty) (Ty*) malloc(sizeof(Ty)) - -// Dellocate module object, allocated by NEW_OBJ(). Called by the freeObj -// callback. -#define FREE_OBJ(ptr) free(ptr) - /*****************************************************************************/ /* SHARED FUNCTIONS */ /*****************************************************************************/ @@ -133,4 +126,11 @@ // inorder to use the import statements. char* pathResolveImport(PKVM * vm, const char* from, const char* path); +// Register all the the libraries to the PKVM. +void registerLibs(PKVM* vm); + +// Cleanup registered libraries call this only if the libraries were registered +// with registerLibs() function. +void cleanupLibs(PKVM* vm); + #endif // LIBS_H diff --git a/src/libs/std_dummy.c b/src/libs/std_dummy.c index 40f8b01..4cf165c 100644 --- a/src/libs/std_dummy.c +++ b/src/libs/std_dummy.c @@ -14,16 +14,15 @@ typedef struct { double val; } Dummy; -void* _newDummy() { - Dummy* dummy = NEW_OBJ(Dummy); - ASSERT(dummy != NULL, "malloc failed."); +void* _newDummy(PKVM* vm) { + Dummy* dummy = pkRealloc(vm, NULL, sizeof(Dummy)); + ASSERT(dummy != NULL, "pkRealloc failed."); dummy->val = 0; return dummy; } -void _deleteDummy(void* ptr) { - Dummy* dummy = (Dummy*)ptr; - FREE_OBJ(dummy); +void _deleteDummy(PKVM* vm, void* ptr) { + pkRealloc(vm, ptr, 0); } DEF(_dummyInit, "") { @@ -146,6 +145,7 @@ DEF(_dummyCallMethod, } void registerModuleDummy(PKVM* vm) { + PkHandle* dummy = pkNewModule(vm, "dummy"); pkModuleAddFunction(vm, dummy, "afunc", _dummyFunction, 2); diff --git a/src/libs/std_io.c b/src/libs/std_io.c index 3001186..f0a410f 100644 --- a/src/libs/std_io.c +++ b/src/libs/std_io.c @@ -37,21 +37,22 @@ typedef struct { bool closed; // True if the file isn't closed yet. } File; -void* _newFile() { - File* file = NEW_OBJ(File); +void* _newFile(PKVM* vm) { + File* file = pkRealloc(vm, NULL, sizeof(File)); + ASSERT(file != NULL, "pkRealloc failed."); file->closed = true; file->mode = FMODE_NONE; file->fp = NULL; return file; } -void _deleteFile(void* ptr) { +void _deleteFile(PKVM* vm, void* ptr) { File* file = (File*)ptr; if (!file->closed) { if (fclose(file->fp) != 0) { /* TODO: error! */ } file->closed = true; } - FREE_OBJ(file); + pkRealloc(vm, file, 0); } /*****************************************************************************/ @@ -123,8 +124,10 @@ DEF(_fileRead, "") { } // TODO: this is temporary. + char buff[2048]; - fread((void*)buff, sizeof(char), sizeof(buff), file->fp); + size_t read = fread((void*)buff, sizeof(char), sizeof(buff), file->fp); + (void) read; pkSetSlotString(vm, 0, (const char*)buff); } diff --git a/src/libs/std_path.c b/src/libs/std_path.c index 4988a53..52847b4 100644 --- a/src/libs/std_path.c +++ b/src/libs/std_path.c @@ -20,7 +20,7 @@ #include #include -#if defined(_WIN32) +#ifdef _WIN32 #include #endif @@ -109,7 +109,7 @@ static char* tryImportPaths(PKVM* vm, char* path, char* buff) { char* ret = NULL; if (path_size != 0) { - ret = pkAllocString(vm, path_size + 1); + ret = pkRealloc(vm, NULL, path_size + 1); memcpy(ret, buff, path_size + 1); } return ret; @@ -138,7 +138,7 @@ char* pathResolveImport(PKVM* vm, const char* from, const char* path) { if (cwk_path_is_absolute(path)) { // buff1 = normalized path. +1 for null terminator. - size_t size = cwk_path_normalize(path, buff1, sizeof(buff1)) + 1; + cwk_path_normalize(path, buff1, sizeof(buff1)); pathFixWindowsSeperator(buff1); return tryImportPaths(vm, buff1, buff2); @@ -150,7 +150,7 @@ char* pathResolveImport(PKVM* vm, const char* from, const char* path) { pathAbs(path, buff1, sizeof(buff1)); // buff2 = normalized path. +1 for null terminator. - size_t size = cwk_path_normalize(buff1, buff2, sizeof(buff2)) + 1; + cwk_path_normalize(buff1, buff2, sizeof(buff2)); pathFixWindowsSeperator(buff2); return tryImportPaths(vm, buff2, buff1); @@ -173,7 +173,7 @@ char* pathResolveImport(PKVM* vm, const char* from, const char* path) { cwk_path_join(buff1, path, buff2, sizeof(buff2)); // buff1 = normalized absolute path. +1 for null terminator - size_t size = cwk_path_normalize(buff2, buff1, sizeof(buff1)) + 1; + cwk_path_normalize(buff2, buff1, sizeof(buff1)); pathFixWindowsSeperator(buff1); return tryImportPaths(vm, buff1, buff2); diff --git a/src/libs/std_time.c b/src/libs/std_time.c new file mode 100644 index 0000000..00eeb22 --- /dev/null +++ b/src/libs/std_time.c @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2020-2022 Thakee Nathees + * Copyright (c) 2021-2022 Pocketlang Contributors + * Distributed Under The MIT License + */ + +#include + +#ifndef PK_AMALGAMATED +#include "libs.h" +#endif + +#ifdef _WIN32 + #include +#endif + +#if !defined(_MSC_VER) && !(defined(_WIN32) && defined(__TINYC__)) + #include // usleep +#endif + +DEF(_timeEpoch, + "time() -> num\n" + "Returns the number of seconds since the Epoch, 1970-01-01 " + "00:00:00 +0000 (UTC).") { + pkSetSlotNumber(vm, 0, (double) time(NULL)); +} + +DEF(_timeClock, + "clock() -> num\n" + "Returns the number of clocks passed divied by CLOCKS_PER_SEC.") { + pkSetSlotNumber(vm, 0, (double) clock() / CLOCKS_PER_SEC); +} + +DEF(_timeSleep, + "sleep(t:num) -> num\n" + "Sleep for [t] milliseconds.") { + + double t; + pkValidateSlotNumber(vm, 1, &t); + +#if defined(_MSC_VER) || (defined(_WIN32) && defined(__TINYC__)) + // Sleep(milli seconds) + Sleep((DWORD) t); + +#else // usleep(micro seconds) + usleep(((useconds_t) (t)) * 1000); +#endif +} + +void registerModuleTime(PKVM* vm) { + PkHandle* time = pkNewModule(vm, "time"); + + pkModuleAddFunction(vm, time, "epoch", _timeEpoch, 0); + pkModuleAddFunction(vm, time, "sleep", _timeSleep, 1); + pkModuleAddFunction(vm, time, "clock", _timeClock, 0); + + pkRegisterModule(vm, time); + pkReleaseHandle(vm, time); +} + diff --git a/tests/benchmarks/factors/factors.pk b/tests/benchmarks/factors/factors.pk index 3e0782b..b4b5d48 100644 --- a/tests/benchmarks/factors/factors.pk +++ b/tests/benchmarks/factors/factors.pk @@ -1,4 +1,4 @@ -from lang import clock +from time import clock start = clock() N = 50000000; factors = [] diff --git a/tests/benchmarks/fib/fib.pk b/tests/benchmarks/fib/fib.pk index 76417b4..6700509 100644 --- a/tests/benchmarks/fib/fib.pk +++ b/tests/benchmarks/fib/fib.pk @@ -1,5 +1,5 @@ -from lang import clock +from time import clock def fib(n) if n < 2 then return n end diff --git a/tests/benchmarks/list/list.pk b/tests/benchmarks/list/list.pk index 49d8ede..4a13b44 100644 --- a/tests/benchmarks/list/list.pk +++ b/tests/benchmarks/list/list.pk @@ -1,4 +1,4 @@ -from lang import clock +from time import clock from math import floor def reverse_list(list) diff --git a/tests/benchmarks/loop/loop.pk b/tests/benchmarks/loop/loop.pk index 46909ce..c6124ed 100644 --- a/tests/benchmarks/loop/loop.pk +++ b/tests/benchmarks/loop/loop.pk @@ -1,4 +1,4 @@ -from lang import clock +from time import clock start = clock() l = [] diff --git a/tests/benchmarks/primes/primes.pk b/tests/benchmarks/primes/primes.pk index f2b75fd..bec02f2 100644 --- a/tests/benchmarks/primes/primes.pk +++ b/tests/benchmarks/primes/primes.pk @@ -1,4 +1,4 @@ -from lang import clock +from time import clock def is_prime(n) if n < 2 then return false end diff --git a/tests/benchmarks/tco/tco.pk b/tests/benchmarks/tco/tco.pk index 9bd59b2..57d331b 100644 --- a/tests/benchmarks/tco/tco.pk +++ b/tests/benchmarks/tco/tco.pk @@ -2,7 +2,7 @@ ## Run the script in pocketlang with ## toc enabled VS toc disabled -from lang import clock +from time import clock N = 20000 diff --git a/tests/lang/import.pk b/tests/lang/import.pk index baa36c0..18a27ed 100644 --- a/tests/lang/import.pk +++ b/tests/lang/import.pk @@ -4,7 +4,7 @@ import lang import lang, path import lang as o, path as p from lang import write -from lang import clock as c +from time import sleep as s import basics import controlflow as if_test