diff --git a/cli/internal.h b/cli/internal.h deleted file mode 100644 index 42ebcdb..0000000 --- a/cli/internal.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2020-2022 Thakee Nathees - * Copyright (c) 2021-2022 Pocketlang Contributors - * Distributed Under The MIT License - */ - -#ifndef COMMON_H -#define COMMON_H - -#include - -#include -#include -#include -#include -#include -#include - -#include "modules/common.h" - -#if defined(__GNUC__) - #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" - #pragma GCC diagnostic ignored "-Wunused-parameter" -#elif defined(__clang__) - #pragma clang diagnostic ignored "-Wint-to-pointer-cast" - #pragma clang diagnostic ignored "-Wunused-parameter" -#elif defined(_MSC_VER) - #pragma warning(disable:26812) -#endif - -#define CLI_NOTICE \ - "PocketLang " PK_VERSION_STRING " (https://github.com/ThakeeNathees/pocketlang/)\n" \ - "Copyright (c) 2020-2021 ThakeeNathees\n" \ - "Copyright (c) 2021-2022 Pocketlang Contributors\n" \ - "Free and open source software under the terms of the MIT license.\n" - - // FIXME: the vm user data of cli. -typedef struct { - bool repl_mode; -} VmUserData; - -#endif // COMMON_H diff --git a/cli/main.c b/cli/main.c index b877b9b..b982c6e 100644 --- a/cli/main.c +++ b/cli/main.c @@ -4,110 +4,23 @@ * Distributed Under The MIT License */ -#include "internal.h" -#include "modules/modules.h" -#include "thirdparty/argparse/argparse.h" +#include +#include // FIXME: Everything below here is temporary and for testing. -int repl(PKVM* vm, const PkCompileOptions* options); -const char* read_line(uint32_t* length); +#if defined(__GNUC__) + #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" + #pragma GCC diagnostic ignored "-Wunused-parameter" +#elif defined(__clang__) + #pragma clang diagnostic ignored "-Wint-to-pointer-cast" + #pragma clang diagnostic ignored "-Wunused-parameter" +#elif defined(_MSC_VER) + #pragma warning(disable:26812) +#endif -// --------------------------------------- - -void onResultDone(PKVM* vm, PkStringPtr result) { - if ((bool)result.user_data) { - free((void*)result.string); - } -} - -void stderrWrite(PKVM* vm, const char* text) { - fprintf(stderr, "%s", text); -} - -void stdoutWrite(PKVM* vm, const char* text) { - fprintf(stdout, "%s", text); -} - -PkStringPtr stdinRead(PKVM* vm) { - PkStringPtr result; - result.string = read_line(NULL); - result.on_done = onResultDone; - result.user_data = (void*)true; - return result; -} - -PkStringPtr resolvePath(PKVM* vm, const char* from, const char* path) { - PkStringPtr result; - result.on_done = onResultDone; - - size_t from_dir_len; - pathGetDirName(from, &from_dir_len); - - // FIXME: Should handle paths with size of more than FILENAME_MAX. - - if (from_dir_len == 0 || pathIsAbsolute(path)) { - size_t length = strlen(path); - - char* resolved = (char*)malloc(length + 1); - pathNormalize(path, resolved, length + 1); - - result.string = resolved; - result.user_data = (void*)true; - - } else { - char from_dir[FILENAME_MAX]; - strncpy(from_dir, from, from_dir_len); - from_dir[from_dir_len] = '\0'; - - char fullpath[FILENAME_MAX]; - size_t length = pathJoin(from_dir, path, fullpath, sizeof(fullpath)); - - char* resolved = (char*)malloc(length + 1); - pathNormalize(fullpath, resolved, length + 1); - - result.string = resolved; - result.user_data = (void*)true; - } - - return result; -} - -PkStringPtr loadScript(PKVM* vm, const char* path) { - - PkStringPtr result; - result.on_done = onResultDone; - - // Open the file. - FILE* file = fopen(path, "r"); - if (file == NULL) { - // Setting .string to NULL to indicate the failure of loading the script. - result.string = NULL; - return result; - } - - // Get the source length. - fseek(file, 0, SEEK_END); - long file_size = ftell(file); - fseek(file, 0, SEEK_SET); - - // Read source to buffer. - char* buff = (char*)malloc((size_t)(file_size) + 1); - - // Using read instead of file_size is because "\r\n" is read as '\n' in - // windows. - if (buff != NULL) { - size_t read = fread(buff, sizeof(char), file_size, file); - buff[read] = '\0'; - } - - fclose(file); - - result.string = buff; - result.user_data = (void*)true; - - return result; -} +#include "modules/modules.h" +#include "thirdparty/argparse/argparse.h" // FIXME: // Included for isatty(). This should be moved to somewhere. and I'm not sure @@ -115,20 +28,23 @@ PkStringPtr loadScript(PKVM* vm, const char* path) { #ifdef _WIN32 #include #include + #define isatty _isatty + #define fileno _fileno #else #include #endif +#define CLI_NOTICE \ + "PocketLang " PK_VERSION_STRING " (https://github.com/ThakeeNathees/pocketlang/)\n" \ + "Copyright (c) 2020-2021 ThakeeNathees\n" \ + "Copyright (c) 2021-2022 Pocketlang Contributors\n" \ + "Free and open source software under the terms of the MIT license.\n" + // Create new pocket VM and set it's configuration. static PKVM* intializePocketVM() { PkConfiguration config = pkNewConfiguration(); - config.stderr_write = stderrWrite; - config.stdout_write = stdoutWrite; - config.stdin_read = stdinRead; - - config.load_script_fn = loadScript; - config.resolve_path_fn = resolvePath; + config.resolve_path_fn = pathResolveImport; // FIXME: // Refactor and make it portable. Maybe custom is_tty() function?. @@ -195,21 +111,12 @@ int main(int argc, const char** argv) { // Create and initialize pocket VM. PKVM* vm = intializePocketVM(); - VmUserData user_data; - user_data.repl_mode = false; - pkSetUserData(vm, &user_data); REGISTER_ALL_MODULES(vm); - PkCompileOptions options = pkNewCompilerOptions(); - options.debug = debug; - if (cmd != NULL) { // pocket -c "print('foo')" - - PkStringPtr source = { cmd, NULL, NULL, 0, 0 }; - PkStringPtr path = { "@(Source)", NULL, NULL, 0, 0 }; - PkResult result = pkInterpretSource(vm, source, path, NULL); - exitcode = (int)result; + PkResult result = pkRunString(vm, cmd); + exitcode = (int) result; } else if (argc == 0) { // Run on REPL mode. @@ -218,22 +125,13 @@ int main(int argc, const char** argv) { printf("%s", CLI_NOTICE); } - options.repl_mode = true; - exitcode = repl(vm, &options); + exitcode = pkRunREPL(vm); } else { // pocket file.pk ... - PkStringPtr resolved = resolvePath(vm, ".", argv[0]); - PkStringPtr source = loadScript(vm, resolved.string); - - if (source.string != NULL) { - PkResult result = pkInterpretSource(vm, source, resolved, &options); - exitcode = (int)result; - } else { - fprintf(stderr, "Error: cannot open file at \"%s\"\n", resolved.string); - if (resolved.on_done != NULL) resolved.on_done(vm, resolved); - if (source.on_done != NULL) source.on_done(vm, source); - } + const char* file_path = argv[0]; + PkResult result = pkRunFile(vm, file_path); + exitcode = (int) result; } // Cleanup the VM and exit. diff --git a/cli/modules/modules.h b/cli/modules/modules.h index 1cb1ca8..37d74a3 100644 --- a/cli/modules/modules.h +++ b/cli/modules/modules.h @@ -57,13 +57,9 @@ void registerModuleMath(PKVM* vm); // can be used for cli's internals we're defining such functions here and they // will be imported in the cli. -bool pathIsAbsolute(const char* path); - -void pathGetDirName(const char* path, size_t* length); - -size_t pathNormalize(const char* path, char* buff, size_t buff_size); - -size_t pathJoin(const char* from, const char* path, char* buffer, - size_t buff_size); +// The pocketlang's import statement path resolving function. This +// implementation is required by pockelang from it's hosting application +// inorder to use the import statements. +char* pathResolveImport(PKVM * vm, const char* from, const char* path); #endif // MODULES_H diff --git a/cli/modules/std_path.c b/cli/modules/std_path.c index 2fa4111..795cb09 100644 --- a/cli/modules/std_path.c +++ b/cli/modules/std_path.c @@ -6,6 +6,11 @@ #include "modules.h" +// FIXME: +// In windows MinGw support most of the posix libraries. So we only need to +// check if it's MSVC (and tcc in windows) or not for posix headers and +// Refactor the bellow macro includes. + #include "thirdparty/cwalk/cwalk.h" #if defined(_WIN32) && (defined(_MSC_VER) || defined(__TINYC__)) #include "thirdparty/dirent/dirent.h" @@ -22,35 +27,89 @@ #define get_cwd getcwd #endif +// The maximum path size that pocketlang's default import system supports +// including the null terminator. To be able to support more characters +// override the functions from the host application. Since this is very much +// platform specific we're defining a more general limit. +// See: https://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html +#define MAX_PATH_LEN 4096 + // The cstring pointer buffer size used in path.join(p1, p2, ...). Tune this // value as needed. #define MAX_JOIN_PATHS 8 +static inline size_t pathAbs(const char* path, char* buff, size_t buff_size); +static inline bool pathIsFileExists(const char* path); + /*****************************************************************************/ /* PATH SHARED FUNCTIONS */ /*****************************************************************************/ -bool pathIsAbsolute(const char* path) { - return cwk_path_is_absolute(path); -} +// Implementation of pocketlang import path resolving function. +char* pathResolveImport(PKVM* vm, const char* from, const char* path) { -void pathGetDirName(const char* path, size_t* length) { - cwk_path_get_dirname(path, length); -} + // Buffers to store intermediate path results. + char buff1[FILENAME_MAX]; + char buff2[FILENAME_MAX]; -size_t pathNormalize(const char* path, char* buff, size_t buff_size) { - return cwk_path_normalize(path, buff, buff_size); -} + // If the path is absolute, Just normalize and return it. + if (cwk_path_is_absolute(path)) { + // buff1 = normalized path. +1 for null terminator. + size_t size = cwk_path_normalize(path, buff1, sizeof(buff1)) + 1; -size_t pathJoin(const char* path_a, const char* path_b, char* buffer, - size_t buff_size) { - return cwk_path_join(path_a, path_b, buffer, buff_size); + char* ret = pkAllocString(vm, size); + memcpy(ret, buff1, size); + return ret; + } + + if (from == NULL) { //< [path] is relative to cwd. + + // buff1 = absolute path of [path]. + pathAbs(path, buff1, sizeof(buff1)); + + // buff2 = normalized path. +1 for null terminator. + size_t size = cwk_path_normalize(buff1, buff2, sizeof(buff2)) + 1; + + char* ret = pkAllocString(vm, size); + memcpy(ret, buff2, size); + return ret; + } + + // Import statements doesn't support absolute paths. + ASSERT(cwk_path_is_absolute(from), "From path should be absolute."); + + // From is a path of a script. Try relative to that script. + { + size_t from_dir_length = 0; + cwk_path_get_dirname(from, &from_dir_length); + if (from_dir_length == 0) return NULL; + + // buff1 = from directory. + strncpy(buff1, from, sizeof(buff1)); + buff1[from_dir_length] = '\0'; + + // buff2 = absolute joined 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; + + if (pathIsFileExists(buff1)) { + char* ret = pkAllocString(vm, size); + memcpy(ret, buff1, size); + return ret; + } + } + + // Cannot resolve the path. Return NULL to indicate failure. + return NULL; } /*****************************************************************************/ /* PATH INTERNAL FUNCTIONS */ /*****************************************************************************/ +// FIXME: refactor (ref: https://stackoverflow.com/a/230068/10846399) static inline bool pathIsFileExists(const char* path) { FILE* file = fopen(path, "r"); if (file != NULL) { @@ -79,7 +138,7 @@ static inline bool pathIsExists(const char* path) { static inline size_t pathAbs(const char* path, char* buff, size_t buff_size) { - char cwd[FILENAME_MAX]; + char cwd[MAX_PATH_LEN]; if (get_cwd(cwd, sizeof(cwd)) == NULL) { // TODO: handle error. @@ -99,7 +158,7 @@ DEF(_pathSetStyleUnix, "") { } DEF(_pathGetCWD, "") { - char cwd[FILENAME_MAX]; + char cwd[MAX_PATH_LEN]; if (get_cwd(cwd, sizeof(cwd)) == NULL) { // TODO: Handle error. } @@ -110,7 +169,7 @@ DEF(_pathAbspath, "") { const char* path; if (!pkValidateSlotString(vm, 1, &path, NULL)) return; - char abspath[FILENAME_MAX]; + char abspath[MAX_PATH_LEN]; uint32_t len = (uint32_t) pathAbs(path, abspath, sizeof(abspath)); pkSetSlotStringLength(vm, 0, abspath, len); } @@ -120,13 +179,13 @@ DEF(_pathRelpath, "") { if (!pkValidateSlotString(vm, 1, &from, NULL)) return; if (!pkValidateSlotString(vm, 2, &path, NULL)) return; - char abs_from[FILENAME_MAX]; + char abs_from[MAX_PATH_LEN]; pathAbs(from, abs_from, sizeof(abs_from)); - char abs_path[FILENAME_MAX]; + char abs_path[MAX_PATH_LEN]; pathAbs(path, abs_path, sizeof(abs_path)); - char result[FILENAME_MAX]; + char result[MAX_PATH_LEN]; uint32_t len = (uint32_t) cwk_path_get_relative(abs_from, abs_path, result, sizeof(result)); pkSetSlotStringLength(vm, 0, result, len); @@ -147,7 +206,7 @@ DEF(_pathJoin, "") { } paths[argc] = NULL; - char result[FILENAME_MAX]; + char result[MAX_PATH_LEN]; uint32_t len = (uint32_t) cwk_path_join_multiple(paths, result, sizeof(result)); pkSetSlotStringLength(vm, 0, result, len); @@ -157,7 +216,7 @@ DEF(_pathNormalize, "") { const char* path; if (!pkValidateSlotString(vm, 1, &path, NULL)) return; - char result[FILENAME_MAX]; + char result[MAX_PATH_LEN]; uint32_t len = (uint32_t) cwk_path_normalize(path, result, sizeof(result)); pkSetSlotStringLength(vm, 0, result, len); } diff --git a/cli/repl.c b/cli/repl.c deleted file mode 100644 index 161a505..0000000 --- a/cli/repl.c +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright (c) 2020-2022 Thakee Nathees - * Copyright (c) 2021-2022 Pocketlang Contributors - * Distributed Under The MIT License - */ - -// The REPL (Read Evaluate Print Loop) implementation. -// https://en.wikipedia.org/wiki/Read-eval-print_loop. - -#include "internal.h" - -#include // isspace -#include "utils.h" - -// FIXME: use fgetc char by char till reach a new line. -// TODO: Will need refactoring to use a byte buffer. -// -// Read a line from stdin and returns it without the line ending. Accepting -// an optional argument [length] (could be NULL). Note that the returned string -// is heap allocated and should be cleaned by 'free()' function. -const char* read_line(uint32_t* length) { - const int size = 1024; - char* mem = (char*)malloc(size); - if (fgets(mem, size, stdin) == NULL) { - // TODO: handle error. - } - size_t len = strlen(mem); - - // FIXME: handle \r\n, this is temp. - mem[len - 1] = '\0'; - if (length != NULL) *length = (uint32_t)(len - 1); - - return mem; -} - -// Read a line from stdin and returns it without the line ending. -static void readLine(ByteBuffer* buff) { - char c; - - do { - c = (char)fgetc(stdin); - if (c == '\n') break; - - byteBufferWrite(buff, (uint8_t)c); - - } while (c != EOF); - - byteBufferWrite(buff, '\0'); -} - -// Returns true if the string is empty, used to check if the input line is -// empty to skip compilation of empty string. -static inline bool is_str_empty(const char* line) { - ASSERT(line != NULL, OOPS); - - for (const char* c = line; *c != '\0'; c++) { - if (!isspace(*c)) return false; - } - return true; -} - -// The main loop of the REPL. Will return the exit code. -int repl(PKVM* vm, const PkCompileOptions* options) { - - // Set repl_mode of the user_data. - VmUserData* user_data = (VmUserData*)pkGetUserData(vm); - user_data->repl_mode = true; - - // The main module that'll be used to compile and execute the input source. - PkHandle* module = pkNewModule(vm, "@(REPL)"); - - // A buffer to store lines read from stdin. - ByteBuffer lines; - byteBufferInit(&lines); - - // A buffer to store a line read from stdin. - ByteBuffer line; - byteBufferInit(&line); - - // Will be set to true if the compilation failed with unexpected EOF to add - // more lines to the [lines] buffer. - bool need_more_lines = false; - - bool done = false; - do { - - // Print the input listening line. - if (!need_more_lines) { - printf(">>> "); - } else { - printf("... "); - } - - // Read a line from stdin and add the line to the lines buffer. - readLine(&line); - bool is_empty = is_str_empty((const char*)line.data); - - // If the line contains EOF, REPL should be stopped. - if (line.count >= 2 && *((char*)(line.data) + line.count -2) == EOF) { - fprintf(stdout, "\n"); - break; - } - - // If the line is empty, we don't have to compile it. - if (is_empty && !need_more_lines) { - byteBufferClear(&line); - ASSERT(lines.count == 0, OOPS); - continue; - } - - if (lines.count != 0) byteBufferWrite(&lines, '\n'); - byteBufferAddString(&lines, (const char*)line.data, line.count - 1); - byteBufferClear(&line); - byteBufferWrite(&lines, '\0'); - - // Compile the buffer to the module. - PkStringPtr source_ptr = { (const char*)lines.data, NULL, NULL }; - PkResult result = pkCompileModule(vm, module, source_ptr, options); - - if (result == PK_RESULT_UNEXPECTED_EOF) { - ASSERT(lines.count > 0 && lines.data[lines.count - 1] == '\0', OOPS); - lines.count -= 1; // Remove the null byte to append a new string. - need_more_lines = true; - continue; - } - - // We're buffering the lines for unexpected EOF, if we reached here that - // means it's either successfully compiled or compilation error. Clean the - // buffer for the next iteration. - need_more_lines = false; - byteBufferClear(&lines); - - if (result != PK_RESULT_SUCCESS) continue; - - // Compiled source would be the "main" function of the module. Run it. - PkHandle* _main = pkModuleGetMainFunction(vm, module); - ASSERT(_main != NULL, OOPS); - result = pkRunFunction(vm, _main, 0, -1, -1); - pkReleaseHandle(vm, _main); - - } while (!done); - - byteBufferClear(&lines); - pkReleaseHandle(vm, module); - - return 0; -} - diff --git a/cli/utils.c b/cli/utils.c deleted file mode 100644 index 08be3fa..0000000 --- a/cli/utils.c +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2020-2022 Thakee Nathees - * Copyright (c) 2021-2022 Pocketlang Contributors - * Distributed Under The MIT License - */ - -#include "utils.h" - -// The factor by which a buffer will grow when it's capacity reached. -#define GROW_FACTOR 2 - -// The initial minimum capacity of a buffer to allocate. -#define MIN_CAPACITY 8 - -static inline int powerOf2Ceil(int n) { - n--; - n |= n >> 1; - n |= n >> 2; - n |= n >> 4; - n |= n >> 8; - n |= n >> 16; - n++; - return n; -} - -/*****************************************************************************/ -/* BYTE BUFFER IMPLEMENTATION */ -/*****************************************************************************/ - -void byteBufferInit(ByteBuffer* buffer) { - buffer->data = NULL; - buffer->count = 0; - buffer->capacity = 0; -} - -void byteBufferClear(ByteBuffer* buffer) { - free(buffer->data); - buffer->data = NULL; - buffer->count = 0; - buffer->capacity = 0; -} - -void byteBufferReserve(ByteBuffer* buffer, size_t size) { - if (buffer->capacity < size) { - int capacity = powerOf2Ceil((int)size); - if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY; - buffer->data = (uint8_t*)realloc(buffer->data, capacity * sizeof(uint8_t)); - buffer->capacity = capacity; - } -} - -void byteBufferFill(ByteBuffer* buffer, uint8_t data, int count) { - byteBufferReserve(buffer, buffer->count + count); - for (int i = 0; i < count; i++) { - buffer->data[buffer->count++] = data; - } -} - -void byteBufferWrite(ByteBuffer* buffer, uint8_t data) { - byteBufferFill(buffer, data, 1); -} - -void byteBufferAddString(ByteBuffer* buffer, const char* str, - uint32_t length) { - byteBufferReserve(buffer, buffer->count + length); - for (uint32_t i = 0; i < length; i++) { - buffer->data[buffer->count++] = *(str++); - } -} diff --git a/cli/utils.h b/cli/utils.h deleted file mode 100644 index 954bd71..0000000 --- a/cli/utils.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2020-2022 Thakee Nathees - * Copyright (c) 2021-2022 Pocketlang Contributors - * Distributed Under The MIT License - */ - -#include "internal.h" - -typedef struct { - uint8_t* data; - uint32_t count; - uint32_t capacity; -} ByteBuffer; - -/*****************************************************************************/ -/* BYTE BUFFER */ -/*****************************************************************************/ - -// Initialize a new buffer int instance. -void byteBufferInit(ByteBuffer* buffer); - -// Clears the allocated elements from the VM's realloc function. -void byteBufferClear(ByteBuffer* buffer); - -// Ensure the capacity is greater than [size], if not resize. -void byteBufferReserve(ByteBuffer* buffer, size_t size); - -// Fill the buffer at the end of it with provided data if the capacity -// isn't enough using VM's realloc function. -void byteBufferFill(ByteBuffer* buffer, uint8_t data, int count); - -// Write to the buffer with provided data at the end of the buffer. -void byteBufferWrite(ByteBuffer* buffer, uint8_t data); - -// Add all the characters to the buffer, byte buffer can also be used as a -// buffer to write string (like a string stream). Note that this will not -// add a null byte '\0' at the end. -void byteBufferAddString(ByteBuffer* buffer, const char* str, uint32_t length); diff --git a/docs/wasm/main.c b/docs/wasm/main.c index 79358c0..b10d0f2 100644 --- a/docs/wasm/main.c +++ b/docs/wasm/main.c @@ -31,32 +31,7 @@ int runSource(const char* source) { config.resolve_path_fn = NULL; PKVM* vm = pkNewVM(&config); - - // FIXME: - // The bellow blocks of code can be simplified with - // - // PkResult result = pkInterpretSource(vm, src, path, NULL); - // - // But the path is printed on the error messages as "@(TRY)" - // If we create a module and compile it, then it won't have a - // path and the error message is simplified. This behavior needs - // to be changed in the error report function. - - PkResult result; - - PkHandle* module = pkNewModule(vm, "@(TRY)"); - do { - PkStringPtr src = { .string = source }; - result = pkCompileModule(vm, module, src, NULL); - if (result != PK_RESULT_SUCCESS) break; - - PkHandle* _main = pkModuleGetMainFunction(vm, module); - result = pkRunFunction(vm, _main, 0, -1, -1); - pkReleaseHandle(vm, _main); - - } while (false); - pkReleaseHandle(vm, module); - + PkResult result = pkRunString(vm, source); pkFreeVM(vm); return (int)result; diff --git a/scripts/leak_detect.py b/scripts/leak_detect.py index b33281a..50090ef 100644 --- a/scripts/leak_detect.py +++ b/scripts/leak_detect.py @@ -12,31 +12,45 @@ import sys def detect_leak(): trace_log_id = "[memory trace]" - total_bytes = 0 ## Totally allocated bytes. mem = dict() ## key = address, value = bytes. + str_alloc_id = "[alloc string]" + strs = dict() ## string allocations. + trace_log_file_path = sys.argv[1] with open(trace_log_file_path, 'r') as f: for line in f.readlines(): - if line.startswith(trace_log_id): + if line.startswith(str_alloc_id): + line = line[len(trace_log_id) + 1:].strip() + type, data = split_and_strip(line, ':') + if type == "alloc": + addr, bytes = split_and_strip(data, '=') + bytes = int(bytes.replace(' bytes', '')) + strs[addr] = bytes + + else: ## "dealloc" + addr = data.strip() + strs.pop(addr) + + elif line.startswith(trace_log_id): line = line[len(trace_log_id) + 1:].strip() type, data = split_and_strip(line, ':') addr, bytes = split_and_strip(data, '=') bytes = bytes.replace(' bytes', '') - + if type == "malloc": bytes = int(bytes) assert(bytes >= 0); total_bytes += bytes mem[addr] = bytes - + elif type == "free": bytes = int(bytes) assert(bytes <= 0); total_bytes += bytes mem.pop(addr) - + elif type == "realloc": oldp, newp = split_and_strip(addr, '->') olds, news = split_and_strip(bytes, '->') @@ -50,13 +64,19 @@ def detect_leak(): if total_bytes != 0: print_err(f"Memory leak detected - {total_bytes} bytes were never freed.") success = False - + if len(mem) != 0: print_err("Memory leak detected - some addresses were never freed.") for addr in mem: print(f" {addr} : {mem[addr]} bytes") success = False - + + if len(strs) != 0: + print_err("Memory leak detected - string allocation(s) were never freed.") + for addr in strs: + print(f" {addr} : {strs[addr]} bytes") + success = False + if success: print("No leaks were detected.") else: @@ -70,6 +90,6 @@ def split_and_strip(string, delim): def print_err(msg): print("Error:", msg, file=sys.stderr) - + if __name__ == "__main__": detect_leak() diff --git a/src/include/pocketlang.h b/src/include/pocketlang.h index 08b4ff9..541a447 100644 --- a/src/include/pocketlang.h +++ b/src/include/pocketlang.h @@ -70,11 +70,8 @@ typedef struct PKVM PKVM; typedef struct PkHandle PkHandle; typedef enum PkVarType PkVarType; -typedef enum PkErrorType PkErrorType; typedef enum PkResult PkResult; -typedef struct PkStringPtr PkStringPtr; typedef struct PkConfiguration PkConfiguration; -typedef struct PkCompileOptions PkCompileOptions; // C function pointer which is callable from pocketLang by native module // functions. @@ -92,34 +89,28 @@ 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 -// 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); - // Function callback to write [text] to stdout or stderr. 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); - -// A function callback to resolve the import script name from the [from] path -// to an absolute (or relative to the cwd). This is required to solve same -// script imported with different relative path. Set the string attribute to -// NULL to indicate if it's failed to resolve. -typedef PkStringPtr (*pkResolvePathFn) (PKVM* vm, const char* from, - const char* path); +// 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 +// 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. Set the string attribute to NULL -// to indicate if it's failed to load the script. -typedef PkStringPtr (*pkLoadScriptFn) (PKVM* vm, const char* path); +// code and source for import statements. Return NULL to indicate failure to +// load. Otherwise the string **must** be allocated with pkAllocString() and +// the VM will claim the ownership of the string. +typedef char* (*pkLoadScriptFn) (PKVM* vm, const char* path); + +// A function callback to resolve the import statement path. [from] path can +// 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. +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/ @@ -158,31 +149,19 @@ enum PkVarType { enum PkResult { PK_RESULT_SUCCESS = 0, // Successfully finished the execution. + // Note that this result is internal and will not be returned to the host + // anymore. + // // 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 - // application to add another line to the last input. If REPL is not enabled, - // this will be PK_RESULT_COMPILE_ERROR. + // error that will ONLY be returned if we're compiling in REPL mode. We need + // this specific error to indicate the host application to add another line + // to the last input. If REPL is not enabled this will be compile error. PK_RESULT_UNEXPECTED_EOF, PK_RESULT_COMPILE_ERROR, // Compilation failed. PK_RESULT_RUNTIME_ERROR, // An error occurred at runtime. }; -// A string pointer wrapper to pass c string between host application and -// pocket VM. With a on_done() callback to clean it when the pocket VM is done -// with the string. -struct PkStringPtr { - const char* string; //< The string result. - pkResultDoneFn on_done; //< Called once vm done with the string. - void* user_data; //< User related data. - - // These values are provided by the pocket VM to the host application, you're - // not expected to set this when provideing string to the pocket VM. - uint32_t length; //< Length of the string. - uint32_t hash; //< Its 32 bit FNV-1a hash. -}; - struct PkConfiguration { // The callback used to allocate, reallocate, and free. If the function @@ -203,18 +182,6 @@ 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; - - // Set to true if compiling in REPL mode, This will print repr version of - // each evaluated non-null values. - bool repl_mode; -}; - /*****************************************************************************/ /* POCKETLANG PUBLIC API */ /*****************************************************************************/ @@ -224,11 +191,6 @@ struct PkCompileOptions { // 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); @@ -241,6 +203,16 @@ 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 +// 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); + // Release the handle and allow its value to be garbage collected. Always call // this for every handles before freeing the VM. PK_PUBLIC void pkReleaseHandle(PKVM* vm, PkHandle* handle); @@ -261,10 +233,6 @@ PK_PUBLIC void pkModuleAddFunction(PKVM* vm, PkHandle* module, const char* name, pkNativeFn fptr, int arity); -// Returns the main function of the [module]. When a module is compiled all of -// it's statements are wrapped around an implicit main function. -PK_PUBLIC PkHandle* pkModuleGetMainFunction(PKVM* vm, PkHandle* module); - // Create a new class on the [module] with the [name] and return it. // If the [base_class] is NULL by default it'll set to "Object" class. PK_PUBLIC PkHandle* pkNewClass(PKVM* vm, const char* name, @@ -278,28 +246,20 @@ PK_PUBLIC void pkClassAddMethod(PKVM* vm, PkHandle* cls, const char* name, pkNativeFn fptr, int arity); -// Compile the [module] with the provided [source]. Set the compiler options -// with the the [options] argument or set to NULL for default options. -PK_PUBLIC PkResult pkCompileModule(PKVM* vm, PkHandle* module, - PkStringPtr source, - const PkCompileOptions* options); +// Run the source string. The [source] is expected to be valid till this +// function returns. +PK_PUBLIC PkResult pkRunString(PKVM* vm, const char* source); -// 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. -// 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); +// Run the file at [path] relative to the current working directory. +PK_PUBLIC PkResult pkRunFile(PKVM* vm, const char* path); -// Run a function with [argc] arguments and their values are stored at the VM's -// slots starting at index [argv_slot] till the next [argc] consequent slots. -// If [argc] is 0 [argv_slot] value will be ignored. -// To store the return value set [ret_slot] to a valid slot index, if it's -// negative the return value will be ignored. -PK_PUBLIC PkResult pkRunFunction(PKVM* vm, PkHandle* fn, - int argc, int argv_slot, int ret_slot); +// FIXME: +// Currently exit function will terminate the process which should exit from +// the function and return to the caller. +// +// Run pocketlang REPL mode. If there isn't any stdin read function defined, +// or imput function ruturned NULL, it'll immediatly return a runtime error. +PK_PUBLIC PkResult pkRunREPL(PKVM* vm); /*****************************************************************************/ /* NATIVE / RUNTIME FUNCTION API */ diff --git a/src/pk_compiler.c b/src/pk_compiler.c index 8acd8fb..ba4ca5f 100644 --- a/src/pk_compiler.c +++ b/src/pk_compiler.c @@ -442,7 +442,7 @@ struct Compiler { // Compiler* next_compiler; - const PkCompileOptions* options; //< To configure the compilation. + const CompileOptions* options; //< To configure the compilation. Module* module; //< Current module that's being compiled. Loop* loop; //< Current loop the we're parsing. @@ -537,7 +537,7 @@ static void parserInit(Parser* parser, PKVM* vm, Compiler* compiler, } static void compilerInit(Compiler* compiler, PKVM* vm, const char* source, - Module* module, const PkCompileOptions* options) { + Module* module, const CompileOptions* options) { compiler->next_compiler = NULL; @@ -2800,27 +2800,30 @@ static Module* importFile(Compiler* compiler, const char* path) { PKVM* vm = compiler->parser.vm; - // Resolve the path. - PkStringPtr resolved = { path, NULL, NULL }; + // FIXME: REPL mode mdule doesn't have a path, So we're using NULL and the + // resolve path function will use cwd to resolve. + const char* from = NULL; + if (compiler->module->path != NULL) from = compiler->module->path->data; + + char* resolved = NULL; + if (vm->config.resolve_path_fn != NULL) { - resolved = vm->config.resolve_path_fn(vm, compiler->module->path->data, - path); + resolved = vm->config.resolve_path_fn(vm, from, path); } - if (resolved.string == NULL) { + if (resolved == NULL) { semanticError(compiler, compiler->parser.previous, - "Cannot resolve path '%s' from '%s'", path, - compiler->module->path->data); + "Cannot resolve path '%s' from '%s'", path, from); return NULL; } // Create new string for the resolved path. And free the resolved path. int index = 0; String* path_ = moduleAddString(compiler->module, compiler->parser.vm, - resolved.string, - (uint32_t)strlen(resolved.string), - &index); - if (resolved.on_done != NULL) resolved.on_done(vm, resolved); + resolved, + (uint32_t)strlen(resolved), + &index); + pkDeAllocString(vm, resolved); // Check if the script already compiled and cached in the PKVM. Var entry = mapGet(vm->modules, VAR_OBJ(path_)); @@ -2842,9 +2845,11 @@ static Module* importFile(Compiler* compiler, const char* path) { return NULL; } - // Load the script at the path. - PkStringPtr source = vm->config.load_script_fn(vm, path_->data); - if (source.string == NULL) { + // Load the script at the path. Note that if the source is not NULL, it's + // our responsibility to deallocate the string with the pkDeallocString() + // function. + char* source = vm->config.load_script_fn(vm, path_->data); + if (source == NULL) { semanticError(compiler, compiler->parser.previous, "Error loading script at \"%s\"", path_->data); return NULL; @@ -2854,18 +2859,8 @@ static Module* importFile(Compiler* compiler, const char* path) { Module* module = newModule(vm); module->path = path_; vmPushTempRef(vm, &module->_super); // module. - { - moduleAddMain(vm, module); - - // 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 (module->path->data[0] != SPECIAL_NAME_CHAR) { - moduleAddGlobal(vm, module, "__file__", 8, VAR_OBJ(module->path)); - } - // TODO: Add ARGV to the module's globals. - - vmRegisterModule(vm, module, path_); - } + initializeScript(vm, module); + vmRegisterModule(vm, module, path_); vmPopTempRef(vm); // module. // Push the compiled script on the stack. @@ -2874,13 +2869,13 @@ static Module* importFile(Compiler* compiler, const char* path) { // Even if we're running on repl mode the imported module cannot run on // repl mode. - PkCompileOptions options = pkNewCompilerOptions(); + CompileOptions options = newCompilerOptions(); if (compiler->options) options = *compiler->options; options.repl_mode = false; // Compile the source to the module and clean the source. - PkResult result = compile(vm, module, source.string, &options); - if (source.on_done != NULL) source.on_done(vm, source); + PkResult result = compile(vm, module, source, &options); + pkDeAllocString(vm, source); if (result != PK_RESULT_SUCCESS) { semanticError(compiler, compiler->parser.previous, @@ -3429,8 +3424,15 @@ static void compileTopLevelStatement(Compiler* compiler) { } +CompileOptions newCompilerOptions() { + CompileOptions options; + options.debug = false; + options.repl_mode = false; + return options; +} + PkResult compile(PKVM* vm, Module* module, const char* source, - const PkCompileOptions* options) { + const CompileOptions* options) { ASSERT(module != NULL, OOPS); diff --git a/src/pk_compiler.h b/src/pk_compiler.h index 286b991..ae62fc7 100644 --- a/src/pk_compiler.h +++ b/src/pk_compiler.h @@ -23,13 +23,32 @@ typedef enum { // compilers, we're restricted syntax-wise and from compile-time optimizations. typedef struct Compiler Compiler; +// The options to configure the compilation provided by the command line +// arguments (or other ways the host application provides). For now this +// struct is not publicily visible to the host for the sake of simplicity +// (like lua does) it needs to be a somehow addressed (TODO:). +typedef struct { + + // Compile debug version of the source. In release mode all the assertions + // and debug informations will be stripped (TODO:) and wll be optimized. + bool debug; + + // Set to true if compiling in REPL mode, This will print repr version of + // each evaluated non-null values. + bool repl_mode; + +} CompileOptions; + +// Create a new CompilerOptions with the default values and return it. +CompileOptions newCompilerOptions(); + // This will take source code as a cstring, compiles it to pocketlang bytecodes // and append them to the module's implicit main function. On a successfull // compilation it'll return PK_RESULT_SUCCESS, otherwise it'll return // PK_RESULT_COMPILE_ERROR but if repl_mode set in the [options], and we've // reached and unexpected EOF it'll return PK_RESULT_UNEXPECTED_EOF. PkResult compile(PKVM* vm, Module* module, const char* source, - const PkCompileOptions* options); + const CompileOptions* options); // Mark the heap allocated objects of the compiler at the garbage collection // called at the marking phase of vmCollectGarbage(). diff --git a/src/pk_core.c b/src/pk_core.c index 0871bb9..508dfbd 100644 --- a/src/pk_core.c +++ b/src/pk_core.c @@ -153,6 +153,16 @@ void initializeCore(PKVM* vm) { initializePrimitiveClasses(vm); } +void initializeScript(PKVM* vm, Module* module) { + ASSERT(module->path != NULL, OOPS); + ASSERT(module->path->data[0] != SPECIAL_NAME_CHAR, OOPS); + + // A script's path will always the absolute normalized path (the path + // resolving function would do take care of it) which is something that + // was added after python 3.9. + moduleAddGlobal(vm, module, "__file__", 8, VAR_OBJ(module->path)); +} + /*****************************************************************************/ /* INTERNAL FUNCTIONS */ /*****************************************************************************/ @@ -442,7 +452,7 @@ DEF(coreInput, "an optional argument [msg] and prints it before reading.") { int argc = ARGC; - if (argc != 1 && argc != 2) { + if (argc > 1) { // input() or input(str). RET_ERR(newString(vm, "Invalid argument count.")); } @@ -455,9 +465,13 @@ DEF(coreInput, vm->config.stdout_write(vm, str->data); } - PkStringPtr result = vm->config.stdin_read(vm); - String* line = newString(vm, result.string); - if (result.on_done) result.on_done(vm, result); + char* str = vm->config.stdin_read(vm); + if (str == NULL) { //< Input failed !? + RET_ERR(newString(vm, "Input function failed.")); + } + + String* line = newString(vm, str); + pkDeAllocString(vm, str); RET(VAR_OBJ(line)); } diff --git a/src/pk_core.h b/src/pk_core.h index 3c00a12..b85fdf4 100644 --- a/src/pk_core.h +++ b/src/pk_core.h @@ -42,6 +42,11 @@ // Initialize core language, builtin function and core libs. void initializeCore(PKVM* vm); +// Initialize a script module. At the moment it'll define __file__ global +// as an absolute path of the script. [path] should be the normalized absolute +// path of the script. +void initializeScript(PKVM* vm, Module* module); + // Create a new module with the given [name] and returns as a Module*. // This is function is a wrapper around `newModule()` function to create // native modules for pocket core and public native api. diff --git a/src/pk_public.c b/src/pk_public.c index e1d5fa2..a870140 100644 --- a/src/pk_public.c +++ b/src/pk_public.c @@ -9,6 +9,8 @@ #include "include/pocketlang.h" #include "pk_core.h" +#include "pk_compiler.h" +#include "pk_utils.h" #include "pk_value.h" #include "pk_vm.h" @@ -58,16 +60,41 @@ // configuration if the host doesn't provided any allocators for us. static void* defaultRealloc(void* memory, size_t new_size, void* _); +static void stderrWrite(PKVM* vm, const char* text); +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) { + 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; +#else + return (char*) vm->config.realloc_fn(NULL, 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); +} + PkConfiguration pkNewConfiguration() { PkConfiguration config; config.realloc_fn = defaultRealloc; - config.stdout_write = NULL; - config.stderr_write = NULL; - config.stdin_read = NULL; + config.stdout_write = stdoutWrite; + config.stderr_write = stderrWrite; + config.stdin_read = stdinRead; - config.load_script_fn = NULL; config.resolve_path_fn = NULL; + config.load_script_fn = loadScript; config.use_ansi_color = false; config.user_data = NULL; @@ -75,13 +102,6 @@ PkConfiguration pkNewConfiguration() { return config; } -PkCompileOptions pkNewCompilerOptions() { - PkCompileOptions options; - options.debug = false; - options.repl_mode = false; - return options; -} - PKVM* pkNewVM(PkConfiguration* config) { PkConfiguration default_config = pkNewConfiguration(); @@ -163,20 +183,6 @@ void pkModuleAddFunction(PKVM* vm, PkHandle* module, const char* name, NULL /*TODO: Public API for function docstring.*/); } -PkHandle* pkModuleGetMainFunction(PKVM* vm, PkHandle* module) { - CHECK_HANDLE_TYPE(module, OBJ_MODULE); - - Module* _module = (Module*)AS_OBJ(module->value); - - int main_index = moduleGetGlobalIndex(_module, IMPLICIT_MAIN_NAME, - (uint32_t)strlen(IMPLICIT_MAIN_NAME)); - if (main_index == -1) return NULL; - ASSERT_INDEX(main_index, (int)_module->globals.count); - Var main_fn = _module->globals.data[main_index]; - ASSERT(IS_OBJ_TYPE(main_fn, OBJ_CLOSURE), OOPS); - return vmNewHandle(vm, main_fn); -} - PkHandle* pkNewClass(PKVM* vm, const char* name, PkHandle* base_class, PkHandle* module, pkNewInstanceFn new_fn, @@ -250,80 +256,230 @@ void pkReleaseHandle(PKVM* vm, PkHandle* handle) { DEALLOCATE(vm, handle, PkHandle); } -PkResult pkCompileModule(PKVM* vm, PkHandle* module_handle, PkStringPtr source, - const PkCompileOptions* options) { - CHECK_ARG_NULL(source.string); - CHECK_HANDLE_TYPE(module_handle, OBJ_MODULE); +PkResult pkRunString(PKVM* vm, const char* source) { - Module* module = (Module*)AS_OBJ(module_handle->value); + PkResult result = PK_RESULT_SUCCESS; + + // Create a temproary module for the source. + Module* module = newModule(vm); + vmPushTempRef(vm, &module->_super); // module. + { + module->path = newString(vm, "@(String)"); + result = compile(vm, module, source, NULL); + if (result != PK_RESULT_SUCCESS) return result; + + // Module initialized needs to be set to true just before executing their + // main function to avoid cyclic inclusion crash the VM. + module->initialized = true; + + Fiber* fiber = newFiber(vm, module->body); + vmPushTempRef(vm, &fiber->_super); // fiber. + vmPrepareFiber(vm, fiber, 0, NULL); + vmPopTempRef(vm); // fiber. + result = vmRunFiber(vm, fiber); + } + vmPopTempRef(vm); // module. - PkResult result = compile(vm, module, source.string, options); - if (source.on_done) source.on_done(vm, source); return result; } -// 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) { +PkResult pkRunFile(PKVM* vm, const char* path) { - String* path_ = newString(vm, path.string); - if (path.on_done) path.on_done(vm, path); - vmPushTempRef(vm, &path_->_super); // path_ + // FIXME(_imp_): path resolve function should always expect a source file + // path (ends with .pk) to be consistance with the paths. - // FIXME: - // Should I clean the module if it already exists before compiling it? + // Note: The file may have been imported by some other script and cached in + // the VM's scripts cache. But we're not using that instead, we'll recompile + // the file and update the cache. - // Load a new module to the vm's modules cache. - Module* module = vmGetModule(vm, path_); - if (module == NULL) { - module = newModule(vm); - module->path = path_; - vmPushTempRef(vm, &module->_super); // module. - vmRegisterModule(vm, module, path_); - vmPopTempRef(vm); // module. + ASSERT(vm->config.load_script_fn != NULL, + "No script loading functions defined."); + + PkResult result = PK_RESULT_SUCCESS; + Module* module = NULL; + + // Resolve the path. + char* resolved_ = NULL; + if (vm->config.resolve_path_fn != NULL) { + resolved_ = vm->config.resolve_path_fn(vm, NULL, path); } - vmPopTempRef(vm); // path_ - // Compile the source. - PkResult result = compile(vm, module, source.string, options); - if (source.on_done) source.on_done(vm, source); + if (resolved_ == NULL) { + // FIXME: Error print should be moved and check for ascii color codes. + if (vm->config.stderr_write != NULL) { + vm->config.stderr_write(vm, "Error finding script at \""); + vm->config.stderr_write(vm, path); + vm->config.stderr_write(vm, "\"\n"); + } + return PK_RESULT_COMPILE_ERROR; + } + + module = newModule(vm); + vmPushTempRef(vm, &module->_super); // module. + { + // Set module path and and deallocate resolved. + String* script_path = newString(vm, resolved_); + vmPushTempRef(vm, &script_path->_super); // script_path. + pkDeAllocString(vm, resolved_); + module->path = script_path; + vmPopTempRef(vm); // script_path. + + initializeScript(vm, module); + + const char* _path = module->path->data; + char* source = vm->config.load_script_fn(vm, _path); + if (source == NULL) { + result = PK_RESULT_COMPILE_ERROR; + // FIXME: Error print should be moved and check for ascii color codes. + if (vm->config.stderr_write != NULL) { + vm->config.stderr_write(vm, "Error loading script at \""); + vm->config.stderr_write(vm, _path); + vm->config.stderr_write(vm, "\"\n"); + } + } else { + result = compile(vm, module, source, NULL); + pkDeAllocString(vm, source); + } + + if (result == PK_RESULT_SUCCESS) { + vmRegisterModule(vm, module, module->path); + } + } + vmPopTempRef(vm); // module. + if (result != PK_RESULT_SUCCESS) return result; - // Set module initialized to true before the execution ends to prevent cyclic - // inclusion cause a crash. + // Module initialized needs to be set to true just before executing their + // main function to avoid cyclic inclusion crash the VM. module->initialized = true; - Fiber* fiber = newFiber(vm, module->body); vmPushTempRef(vm, &fiber->_super); // fiber. - vmPrepareFiber(vm, fiber, 0, NULL /* TODO: get argv from user. */); + vmPrepareFiber(vm, fiber, 0, NULL); vmPopTempRef(vm); // fiber. - return vmRunFiber(vm, fiber); } -PK_PUBLIC PkResult pkRunFunction(PKVM* vm, PkHandle* fn, - int argc, int argv_slot, int ret_slot) { - CHECK_HANDLE_TYPE(fn, OBJ_CLOSURE); - Closure* closure = (Closure*)AS_OBJ(fn->value); +// FIXME: this should be moved to somewhere general. +// +// Returns true if the string is empty, used to check if the input line is +// empty to skip compilation of empty string in the bellow repl mode. +static inline bool isStringEmpty(const char* line) { + ASSERT(line != NULL, OOPS); - ASSERT(argc >= 0, "argc cannot be negative."); - Var* argv = NULL; + for (const char* c = line; *c != '\0'; c++) { + if (!utilIsSpace(*c)) return false; + } + return true; +} - if (argc != 0) { - for (int i = 0; i < argc; i++) { - VALIDATE_SLOT_INDEX(argv_slot + i); +// FIXME: +// this should be moved to somewhere general along with isStringEmpty(). +// +// This function will get the main function from the module to run it in the +// repl mode. +Closure* moduleGetMainFunction(PKVM* vm, Module* module) { + + int main_index = moduleGetGlobalIndex(module, IMPLICIT_MAIN_NAME, + (uint32_t) strlen(IMPLICIT_MAIN_NAME)); + if (main_index == -1) return NULL; + ASSERT_INDEX(main_index, (int) module->globals.count); + Var main_fn = module->globals.data[main_index]; + ASSERT(IS_OBJ_TYPE(main_fn, OBJ_CLOSURE), OOPS); + return (Closure*) AS_OBJ(main_fn); +} + +PkResult pkRunREPL(PKVM* vm) { + + pkWriteFn printfn = vm->config.stdout_write; + pkWriteFn printerrfn = vm->config.stderr_write; + pkReadFn inputfn = vm->config.stdin_read; + PkResult result = PK_RESULT_SUCCESS; + + CompileOptions options = newCompilerOptions(); + options.repl_mode = true; + + if (inputfn == NULL) { + if (printerrfn) printerrfn(vm, "REPL failed to input."); + return PK_RESULT_RUNTIME_ERROR; + } + + // The main module that'll be used to compile and execute the input source. + PkHandle* module = pkNewModule(vm, "@(REPL)"); + ASSERT(IS_OBJ_TYPE(module->value, OBJ_MODULE), OOPS); + Module* _module = (Module*)AS_OBJ(module->value); + + // A buffer to store multiple lines read from stdin. + pkByteBuffer lines; + pkByteBufferInit(&lines); + + // Will be set to true if the compilation failed with unexpected EOF to add + // more lines to the [lines] buffer. + bool need_more_lines = false; + + bool done = false; + do { + + const char* listening = (!need_more_lines) ? ">>> " : "... "; + printfn(vm, listening); + + // Read a line from stdin and add the line to the lines buffer. + char* line = inputfn(vm); + if (line == NULL) { + if (printerrfn) printerrfn(vm, "REPL failed to input."); + result = PK_RESULT_RUNTIME_ERROR; + break; } - argv = &SLOT(argv_slot); - } - Var* ret = NULL; - if (ret_slot >= 0) { - VALIDATE_SLOT_INDEX(ret_slot); - ret = &SLOT(ret_slot); - } + // If the line contains EOF, REPL should be stopped. + size_t line_length = strlen(line); + if (line_length >= 1 && *(line + line_length - 1) == EOF) { + printfn(vm, "\n"); + result = PK_RESULT_SUCCESS; + pkDeAllocString(vm, line); + break; + } - return vmRunFunction(vm, closure, argc, argv, ret); + // 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); + 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); + pkByteBufferWrite(&lines, vm, '\0'); + + // Compile the buffer to the module. + result = compile(vm, _module, (const char*) lines.data, &options); + + if (result == PK_RESULT_UNEXPECTED_EOF) { + ASSERT(lines.count > 0 && lines.data[lines.count - 1] == '\0', OOPS); + lines.count -= 1; // Remove the null byte to append a new string. + need_more_lines = true; + continue; + } + + // We're buffering the lines for unexpected EOF, if we reached here that + // means it's either successfully compiled or compilation error. Clean the + // buffer for the next iteration. + need_more_lines = false; + pkByteBufferClear(&lines, vm); + + if (result != PK_RESULT_SUCCESS) continue; + + // Compiled source would be the "main" function of the module. Run it. + Closure* _main = moduleGetMainFunction(vm, _module); + ASSERT(_main != NULL, OOPS); + result = vmRunFunction(vm, _main, 0, NULL, NULL); + + } while (!done); + + pkReleaseHandle(vm, module); + + return result; } /*****************************************************************************/ @@ -542,3 +698,53 @@ static void* defaultRealloc(void* memory, size_t new_size, void* _) { } return realloc(memory, new_size); } + +void stderrWrite(PKVM* vm, const char* text) { + fprintf(stderr, "%s", text); +} + +void stdoutWrite(PKVM* vm, const char* text) { + fprintf(stdout, "%s", text); +} + +static char* stdinRead(PKVM* vm) { + + pkByteBuffer buff; + pkByteBufferInit(&buff); + char c; + do { + c = (char)fgetc(stdin); + if (c == '\n') break; + pkByteBufferWrite(&buff, vm, (uint8_t)c); + } while (c != EOF); + pkByteBufferWrite(&buff, vm, '\0'); + + char* str = pkAllocString(vm, buff.count); + memcpy(str, buff.data, buff.count); + return str; +} + +static char* loadScript(PKVM* vm, const char* path) { + + FILE* file = fopen(path, "r"); + if (file == NULL) return NULL; + + // Get the source length. In windows the ftell will includes the cariage + // return when using ftell with fseek. But that's not an issue since + // we'll be allocating more memory than needed for fread(). + fseek(file, 0, SEEK_END); + size_t file_size = ftell(file); + fseek(file, 0, SEEK_SET); + + // Allocate string + 1 for the NULL terminator. + char* buff = pkAllocString(vm, file_size + 1); + ASSERT(buff != NULL, "pkAllocString failed."); + + clearerr(file); + size_t read = fread(buff, sizeof(char), file_size, file); + ASSERT(read <= file_size, "fread() failed."); + buff[read] = '\0'; + fclose(file); + + return buff; +} diff --git a/src/pk_utils.c b/src/pk_utils.c index 1e08c18..577292a 100644 --- a/src/pk_utils.c +++ b/src/pk_utils.c @@ -19,6 +19,10 @@ int utilPowerOf2Ceil(int n) { return n; } +bool utilIsSpace(char c) { + return (c == ' ' || c == '\t' || c == '\n' || c == '\v'); +} + // Function implementation, see utils.h for description. bool utilIsName(char c) { return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || (c == '_'); diff --git a/src/pk_utils.h b/src/pk_utils.h index c6970f8..2b47161 100644 --- a/src/pk_utils.h +++ b/src/pk_utils.h @@ -7,12 +7,16 @@ #ifndef PK_UTILS_H #define PK_UTILS_H -#include "pk_internal.h" +#include +#include // Returns the smallest power of two that is equal to or greater than [n]. // Source : https://github.com/wren-lang/wren/blob/main/src/vm/wren_utils.h#L119 int utilPowerOf2Ceil(int n); +// Returns true if c in [ ' ', '\t', '\n', '\v' ] +bool utilIsSpace(char c); + // Returns true if `c` is [A-Za-z_]. bool utilIsName(char c); @@ -34,15 +38,10 @@ uint32_t utilHashNumber(double num); // Generate a has code for [string]. uint32_t utilHashString(const char* string); -#endif // PK_UTILS_H - /**************************************************************************** * UTF8 * ****************************************************************************/ -#ifndef UTF8_H -#define UTF8_H - /** @file * A tiny UTF-8 utility library. * @@ -66,7 +65,6 @@ uint32_t utilHashString(const char* string); * is called the leading byte and the rest of the bytes of the character is * called continuation bytes. * - *
  * example:
  *                  v-- leading byte   v-- continuation byte => 2 bytes
  *             é =  11000011           10101001
@@ -74,16 +72,9 @@ uint32_t utilHashString(const char* string);
  *                  110 means 2 bytes  10 means continuation
  *
  * (note that the character é is 8 bit long with ANSI encoding)
- * 
* - * USAGE: - * // define implementation only a single *.c source file like this - * #define UTF8_IMPLEMENT - * #include "utf8.h" */ -#include - // Returns the number of bytes the the [value] would take to encode. returns 0 // if the value is invalid utf8 representation. // @@ -121,4 +112,4 @@ int utf8_encodeValue(int value, uint8_t* bytes); // value. int utf8_decodeBytes(uint8_t* bytes, int* value); -#endif // UTF8_H +#endif // PK_UTILS_H diff --git a/tests/native/README.md b/tests/native/README.md index 1ba5fc9..a23eb14 100644 --- a/tests/native/README.md +++ b/tests/native/README.md @@ -15,6 +15,11 @@ integrate pocket VM with your application ---- +#### `example0.c` - Contains how to run simple pocket script +``` +gcc example0.c -o example0 ../../src/*.c -I../../src/include -lm +``` + #### `example1.c` - Contains how to pass values between pocket VM and C ``` gcc example1.c -o example1 ../../src/*.c -I../../src/include -lm diff --git a/tests/native/example0.c b/tests/native/example0.c new file mode 100644 index 0000000..0fc0735 --- /dev/null +++ b/tests/native/example0.c @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020-2021 Thakee Nathees + * Distributed Under The MIT License + */ + +#include + +int main(int argc, char** argv) { + + // Create a new pocket VM. + PKVM* vm = pkNewVM(NULL); + + // Run a string. + pkRunString(vm, "print('hello world')"); + + // TODO: move path module to src/ or write a simple path resolving + // function to support pkRunFile() without requesting someone + // to provide path resolving function. + // + // Run a script from file. + //pkRunFile(vm, "script.pk"); + + // Free the VM. + pkFreeVM(vm); + + return 0; +} diff --git a/tests/native/example1.c b/tests/native/example1.c index e621aa7..4c5421f 100644 --- a/tests/native/example1.c +++ b/tests/native/example1.c @@ -6,7 +6,6 @@ // This is a minimal example on how to pass values between pocket VM and C. #include -#include // The pocket script we're using to test. static const char* code = @@ -32,26 +31,14 @@ static void cFunction(PKVM* vm) { pkSetSlotNumber(vm, 0, 3.14); } -/*****************************************************************************/ -/* POCKET VM CALLBACKS */ -/*****************************************************************************/ - -static void stdoutCallback(PKVM* vm, const char* text) { - fprintf(stdout, "%s", text); -} - /*****************************************************************************/ /* MAIN */ /*****************************************************************************/ int main(int argc, char** argv) { - // Pocket VM configuration. - PkConfiguration config = pkNewConfiguration(); - config.stdout_write = stdoutCallback; - // Create a new pocket VM. - PKVM* vm = pkNewVM(&config); + PKVM* vm = pkNewVM(NULL); // Registering a native module. PkHandle* my_module = pkNewModule(vm, "my_module"); @@ -59,15 +46,11 @@ int main(int argc, char** argv) { pkRegisterModule(vm, my_module); pkReleaseHandle(vm, my_module); - // The path and the source code. - PkStringPtr source = { .string = code }; - PkStringPtr path = { .string = "./some/path/" }; - // Run the code. - PkResult result = pkInterpretSource(vm, source, path, NULL/*options*/); + PkResult result = pkRunString(vm, code); // Free the VM. pkFreeVM(vm); - return (int)result; + return (int) result; } diff --git a/tests/native/example2.c b/tests/native/example2.c index 2082648..43f2eab 100644 --- a/tests/native/example2.c +++ b/tests/native/example2.c @@ -11,7 +11,6 @@ #include #include /* For malloc */ -#include /* For printf */ #include /* For strncmp */ #include /* For sqrt */ @@ -36,7 +35,7 @@ static const char* code = " print() \n" " \n" " v2 = Vec2(5, 6) \n" - " v3 = v1.add(v2) \n" + " v3 = v1 + v2 \n" " print('v3 = ${v3}') \n" " print('v3.x = ${v3.x}') \n" " print('v3.y = ${v3.y}') \n" @@ -64,8 +63,36 @@ void _deleteVec(void* vector) { free(vector); } -// Vec2 'add' method. -void _vec2Add(PKVM* vm) { +void _vecGetter(PKVM* vm) { + const char* name = pkGetSlotString(vm, 1, NULL); + Vector* self = (Vector*)pkGetSelf(vm); + if (strcmp("x", name) == 0) { + pkSetSlotNumber(vm, 0, self->x); + return; + } else if (strcmp("y", name) == 0) { + pkSetSlotNumber(vm, 0, self->y); + return; + } +} + +void _vecSetter(PKVM* vm) { + const char* name = pkGetSlotString(vm, 1, NULL); + Vector* self = (Vector*)pkGetSelf(vm); + if (strcmp("x", name) == 0) { + double x; + if (!pkValidateSlotNumber(vm, 2, &x)) return; + self->x = x; + return; + } else if (strcmp("y", name) == 0) { + double y; + if (!pkValidateSlotNumber(vm, 2, &y)) return; + self->y = y; + return; + } +} + +// Vec2 '+' operator method. +void _vecAdd(PKVM* vm) { Vector* self = (Vector*)pkGetSelf(vm); // FIXME: // Temproarly it's not possible to get vector from the args since the native @@ -78,7 +105,7 @@ void registerVector(PKVM* vm) { PkHandle* Vec2 = pkNewClass(vm, "Vec2", NULL /*Base Class*/, vector, _newVec, _deleteVec); - pkClassAddMethod(vm, Vec2, "add", _vec2Add, 1); + pkClassAddMethod(vm, Vec2, "+", _vecAdd, 1); pkReleaseHandle(vm, Vec2); pkRegisterModule(vm, vector); @@ -89,23 +116,12 @@ void registerVector(PKVM* vm) { /* POCKET VM CALLBACKS */ /*****************************************************************************/ -void stdoutCallback(PKVM* vm, const char* text) { - fprintf(stdout, "%s", text); -} - int main(int argc, char** argv) { - PkConfiguration config = pkNewConfiguration(); - config.stdout_write = stdoutCallback; - - PKVM* vm = pkNewVM(&config); + PKVM* vm = pkNewVM(NULL); registerVector(vm); - - PkStringPtr source = { .string = code }; - PkStringPtr path = { .string = "./some/path/" }; - - PkResult result = pkInterpretSource(vm, source, path, NULL/*options*/); + pkRunString(vm, code); pkFreeVM(vm); - return result; + return 0; }