From 600f9729279a4530f0b584da619d2f33b412c572 Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Mon, 21 Jun 2021 18:32:20 +0530 Subject: [PATCH] Native types & File object implementations. --- cli/all.c | 34 -- cli/common.h | 147 ++++--- cli/internal.h | 37 ++ cli/main.c | 17 +- cli/modules.c | 405 ++++++++++++++++++ cli/modules.h | 73 ++++ cli/modules/path.c | 248 ----------- cli/repl.c | 8 +- cli/thirdparty.c | 17 + cli/{modules => }/thirdparty/cwalk/LICENSE.md | 0 cli/{modules => }/thirdparty/cwalk/cwalk.c | 0 cli/{modules => }/thirdparty/cwalk/cwalk.h | 0 cli/{modules => }/thirdparty/dirent/LICENSE | 0 cli/{modules => }/thirdparty/dirent/dirent.h | 0 cli/utils.h | 2 +- src/include/pocketlang.h | 27 +- src/pk_buffers.h | 2 +- src/pk_common.h | 92 +--- src/pk_compiler.h | 2 +- src/pk_core.c | 70 ++- src/pk_core.h | 2 +- src/pk_debug.h | 2 +- src/pk_internal.h | 78 ++++ src/pk_utils.h | 2 +- src/pk_var.c | 67 ++- src/pk_var.h | 14 +- src/pk_vm.c | 3 + src/pk_vm.h | 2 +- tests/check.py | 41 +- 29 files changed, 932 insertions(+), 460 deletions(-) delete mode 100644 cli/all.c create mode 100644 cli/internal.h create mode 100644 cli/modules.c create mode 100644 cli/modules.h delete mode 100644 cli/modules/path.c create mode 100644 cli/thirdparty.c rename cli/{modules => }/thirdparty/cwalk/LICENSE.md (100%) rename cli/{modules => }/thirdparty/cwalk/cwalk.c (100%) rename cli/{modules => }/thirdparty/cwalk/cwalk.h (100%) rename cli/{modules => }/thirdparty/dirent/LICENSE (100%) rename cli/{modules => }/thirdparty/dirent/dirent.h (100%) create mode 100644 src/pk_internal.h diff --git a/cli/all.c b/cli/all.c deleted file mode 100644 index 6a60d8d..0000000 --- a/cli/all.c +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2020-2021 Thakee Nathees - * Distributed Under The MIT License - */ - -// This file will include all source files from the cli sub-directories here -// (thirdparty/modules) into a single file. That'll make it easy to compile -// with the command `gcc cli/*.c ...` instead of having to add every single -// sub-directory to the list of source directory. - -/*****************************************************************************/ -/* THIRD PARTY */ -/*****************************************************************************/ - -// Library : cwalk -// Source : https://github.com/likle/cwalk -// Doc : https://likle.github.io/cwalk/ -// About : Path library for C/C++. Cross-Platform for Windows, MacOS and -// Linux. Supports UNIX and Windows path styles on those platforms. -#include "modules/thirdparty/cwalk/cwalk.c" - -/*****************************************************************************/ -/* CLI MODULES */ -/*****************************************************************************/ - -#include "modules/path.c" - -/*****************************************************************************/ -/* MODULES REGISTER */ -/*****************************************************************************/ - -void registerModules(PKVM* vm) { - registerModulePath(vm); -} diff --git a/cli/common.h b/cli/common.h index 27bc420..a8bfdf9 100644 --- a/cli/common.h +++ b/cli/common.h @@ -3,46 +3,48 @@ * Distributed Under The MIT License */ -#ifndef COMMON_H -#define COMMON_H +// A collection of reusable macros that pocketlang use. This file doesn't have +// any dependencies, you can just drag and drop this file in your project if +// you want to use these macros. -#include -#include -#include -#include -#include +#ifndef PK_COMMON_H +#define PK_COMMON_H -#include +#ifndef __has_builtin + #define __has_builtin(x) 0 +#endif -#define CLI_NOTICE \ - "PocketLang " PK_VERSION_STRING " (https://github.com/ThakeeNathees/pocketlang/)\n" \ - "Copyright(c) 2020 - 2021 ThakeeNathees.\n" \ - "Free and open source software under the terms of the MIT license.\n" +#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" +#endif -// Note that the cli itself is not a part of the pocketlang compiler, instead -// it's a host application to run pocketlang from the command line. We're -// embedding the pocketlang VM and we can only use its public APIs, not any -// internals of it, including assertion macros. So we're re-defining those -// macros here (like if it's a new project). +#include //< Only needed here for ASSERT() macro and for release mode + //< TODO; macro use this to print a crash report. -/*****************************************************************************/ -/* COMMON MACROS */ -/*****************************************************************************/ +#define TOSTRING(x) #x +#define STRINGIFY(x) TOSTRING(x) -// These macros below are copied from pocketlang at "src/common.h". See above -// for not re-using these macros. +// CONCAT_LINE(X) will result evaluvated X<__LINE__>. +#define __CONCAT_LINE_INTERNAL_R(a, b) a ## b +#define __CONCAT_LINE_INTERNAL_F(a, b) __CONCAT_LINE_INTERNAL_R(a, b) +#define CONCAT_LINE(X) __CONCAT_LINE_INTERNAL_F(X, __LINE__) // The internal assertion macro, this will print error and break regardless of // the build target (debug or release). Use ASSERT() for debug assertion and -// use __ASSERT() for TODOs. -#define __ASSERT(condition, message) \ - do { \ - if (!(condition)) { \ - fprintf(stderr, "Assertion failed: %s\n\tat %s() (%s:%i)\n", \ - message, __func__, __FILE__, __LINE__); \ - DEBUG_BREAK(); \ - abort(); \ - } \ +// use __ASSERT() for TODOs and assertions in public methods (to indicate that +// the host application did something wrong). +#define __ASSERT(condition, message) \ + do { \ + if (!(condition)) { \ + fprintf(stderr, "Assertion failed: %s\n\tat %s() (%s:%i)\n", \ + message, __func__, __FILE__, __LINE__); \ + DEBUG_BREAK(); \ + abort(); \ + } \ } while (false) #define NO_OP do {} while (false) @@ -55,56 +57,97 @@ #define DEBUG_BREAK() __builtin_trap() #endif +// This will terminate the compilation if the [condition] is false, because of +// char _assertion_failure_<__LINE__>[-1] evaluated. +#define STATIC_ASSERT(condition) \ + static char CONCAT_LINE(_assertion_failure_)[2*!!(condition) - 1] + #define ASSERT(condition, message) __ASSERT(condition, message) #define ASSERT_INDEX(index, size) \ ASSERT(index >= 0 && index < size, "Index out of bounds.") -#define UNREACHABLE() \ - do { \ - fprintf(stderr, "Execution reached an unreachable path\n" \ - "\tat %s() (%s:%i)\n", __func__, __FILE__, __LINE__); \ - DEBUG_BREAK(); \ - abort(); \ +#define UNREACHABLE() \ + do { \ + fprintf(stderr, "Execution reached an unreachable path\n" \ + "\tat %s() (%s:%i)\n", __func__, __FILE__, __LINE__); \ + DEBUG_BREAK(); \ + abort(); \ } while (false) #else +#define STATIC_ASSERT(condition) NO_OP + #define DEBUG_BREAK() NO_OP #define ASSERT(condition, message) NO_OP #define ASSERT_INDEX(index, size) NO_OP -// Reference : https://github.com/wren-lang/ -#if defined( _MSC_VER ) +#if defined(_MSC_VER) #define UNREACHABLE() __assume(0) #elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) #define UNREACHABLE() __builtin_unreachable() +#elif defined(__EMSCRIPTEN__) || defined(__clang__) + #if __has_builtin(__builtin_unreachable) + #define UNREACHABLE() __builtin_unreachable() + #else + #define UNREACHABLE() NO_OP + #endif #else #define UNREACHABLE() NO_OP #endif #endif // DEBUG -#if defined( _MSC_VER ) +#if defined(_MSC_VER) #define forceinline __forceinline #else #define forceinline __attribute__((always_inline)) #endif -// Using __ASSERT() for make it to crash in release binary too. -#define TODO __ASSERT(false, "TODO: It hasn't been implemented yet.") +// Using __ASSERT() for make it crash in release binary too. +#define TODO __ASSERT(false, "TODO: It hasn't implemented yet.") #define OOPS "Oops a bug!! report please." -#define TOSTRING(x) #x -#define STRINGIFY(x) TOSTRING(x) +// The formated string to convert double to string. It'll be with the minimum +// length string representation of either a regular float or a scientific +// notation (at most 15 decimal points). +// Reference: https://www.cplusplus.com/reference/cstdio/printf/ +#define DOUBLE_FMT "%.16g" -/*****************************************************************************/ -/* CLI DEFINES */ -/*****************************************************************************/ +// Double number to string buffer size, used in sprintf with DOUBLE_FMT. +// A largest number : "-1.234567890123456e+308" +// + 1 fot sign '+' or '-' +// + 16 fot significant digits +// + 1 for decimal point '.' +// + 1 for exponent char 'e' +// + 1 for sign of exponent +// + 3 for the exponent digits +// + 1 for null byte '\0' +#define STR_DBL_BUFF_SIZE 24 - // FIXME: the vm user data of cli. -typedef struct { - bool repl_mode; -} VmUserData; +// Integer number to string buffer size, used in sprintf with format "%d". +// The minimum 32 bit integer = -2147483648 +// + 1 for sign '-' +// + 10 for digits +// + 1 for null byte '\0' +#define STR_INT_BUFF_SIZE 12 -#endif // COMMON_H +// Integer number (double) to hex string buffer size. +// The maximum value an unsigned 64 bit integer can get is +// 0xffffffffffffffff which is 16 characters. +// + 16 for hex digits +// + 1 for sign '-' +// + 2 for '0x' prefix +// + 1 for null byte '\0' +#define STR_HEX_BUFF_SIZE 20 + +// Integer number (double) to bin string buffer size. +// The maximum value an unsigned 64 bit integer can get is 0b11111... 64 1s. +// + 64 for bin digits +// + 1 for sign '-' +// + 2 for '0b' prefix +// + 1 for null byte '\0' +#define STR_BIN_BUFF_SIZE 68 + +#endif //PK_COMMON_H diff --git a/cli/internal.h b/cli/internal.h new file mode 100644 index 0000000..ab57167 --- /dev/null +++ b/cli/internal.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020-2021 Thakee Nathees + * Distributed Under The MIT License + */ + +#ifndef COMMON_H +#define COMMON_H + +#include + +#include +#include +#include +#include +#include +#include + +// Note that the cli itself is not a part of the pocketlang compiler, instead +// it's a host application to run pocketlang from the command line. We're +// embedding the pocketlang VM and we can only use its public APIs, not any +// internals of it, including assertion macros. So that we've copyied the +// "common.h" header. This can be moved to "src/include/common.h" and include +// as optional header, which is something I don't like because it makes +// pocketlang contain 2 headers (we'll always try to be minimal). +#include "common.h" + +#define CLI_NOTICE \ + "PocketLang " PK_VERSION_STRING " (https://github.com/ThakeeNathees/pocketlang/)\n" \ + "Copyright(c) 2020 - 2021 ThakeeNathees.\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 01f497d..8b6d575 100644 --- a/cli/main.c +++ b/cli/main.c @@ -3,22 +3,15 @@ * Distributed Under The MIT License */ -#include "common.h" +#include "internal.h" + +#include "modules.h" // FIXME: Everything below here is temporary and for testing. void repl(PKVM* vm, const PkCompileOptions* options); const char* read_line(uint32_t* length); -void registerModules(PKVM* vm); - -// Path public functions (TODO: should I add a header for that?) -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); - // --------------------------------------- void onResultDone(PKVM* vm, PkStringPtr result) { @@ -143,6 +136,10 @@ int main(int argc, char** argv) { config.error_fn = errorFunction; config.write_fn = writeFunction; config.read_fn = readFunction; + + config.free_inst_fn = freeObj; + config.inst_name_fn = getObjName; + config.load_script_fn = loadScript; config.resolve_path_fn = resolvePath; diff --git a/cli/modules.c b/cli/modules.c new file mode 100644 index 0000000..33c0b52 --- /dev/null +++ b/cli/modules.c @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2021 Thakee Nathees + * Distributed Under The MIT License + */ + +#include "modules.h" + +// 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) + +void freeObj(PKVM* vm, void* instance) { + + Obj* obj = (Obj*)instance; + // TODO: assert obj type is valid. + + // If the file isn't closed, close it to flush it's buffer. + if (obj->type == OBJ_FILE) { + File* file = (File*)obj; + if (!file->closed) { + if (fclose(file->fp) != 0) { /* TODO: error! */ } + file->closed = true; + } + } + + FREE_OBJ(obj); +} + +const char* getObjName(uint32_t id) { + switch ((ObjType)id) { + case OBJ_FILE: return "File"; + } + return NULL; +} + +/*****************************************************************************/ +/* PATH MODULE */ +/*****************************************************************************/ + +#include "thirdparty/cwalk/cwalk.h" + #if defined(_WIN32) && (defined(_MSC_VER) || defined(__TINYC__)) + #include "thirdparty/dirent/dirent.h" +#else + #include +#endif + +#if defined(_WIN32) + #include + #include + #define get_cwd _getcwd +#else + #include + #define get_cwd getcwd +#endif + +// The cstring pointer buffer size used in path.join(p1, p2, ...). Tune this +// value as needed. +#define MAX_JOIN_PATHS 8 + +/*****************************************************************************/ +/* PUBLIC FUNCTIONS */ +/*****************************************************************************/ + +bool pathIsAbsolute(const char* path) { + return cwk_path_is_absolute(path); +} + +void pathGetDirName(const char* path, size_t* length) { + cwk_path_get_dirname(path, length); +} + +size_t pathNormalize(const char* path, char* buff, size_t buff_size) { + return cwk_path_normalize(path, buff, buff_size); +} + +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); +} + +/*****************************************************************************/ +/* PATH INTERNAL FUNCTIONS */ +/*****************************************************************************/ + +static inline bool pathIsFileExists(const char* path) { + FILE* file = fopen(path, "r"); + if (file != NULL) { + fclose(file); + return true; + } + return false; +} + +// Reference: https://stackoverflow.com/a/12510903/10846399 +static inline bool pathIsDirectoryExists(const char* path) { + DIR* dir = opendir(path); + if (dir) { /* Directory exists. */ + closedir(dir); + return true; + } else if (errno == ENOENT) { /* Directory does not exist. */ + } else { /* opendir() failed for some other reason. */ + } + + return false; +} + +static inline bool pathIsExists(const char* path) { + return pathIsFileExists(path) || pathIsDirectoryExists(path); +} + +static inline size_t pathAbs(const char* path, char* buff, size_t buff_size) { + + char cwd[FILENAME_MAX]; + + if (get_cwd(cwd, sizeof(cwd)) == NULL) { + // TODO: handle error. + } + + return cwk_path_get_absolute(cwd, path, buff, buff_size); +} + +/*****************************************************************************/ +/* PATH MODULES FUNCTIONS */ +/*****************************************************************************/ + +static void _pathSetStyleUnix(PKVM* vm) { + bool value; + if (!pkGetArgBool(vm, 1, &value)) return; + cwk_path_set_style((value) ? CWK_STYLE_UNIX : CWK_STYLE_WINDOWS); +} + +static void _pathGetCWD(PKVM* vm) { + char cwd[FILENAME_MAX]; + if (get_cwd(cwd, sizeof(cwd)) == NULL) { + // TODO: Handle error. + } + pkReturnString(vm, cwd); +} + +static void _pathAbspath(PKVM* vm) { + const char* path; + if (!pkGetArgString(vm, 1, &path, NULL)) return; + + char abspath[FILENAME_MAX]; + size_t len = pathAbs(path, abspath, sizeof(abspath)); + pkReturnStringLength(vm, abspath, len); +} + +static void _pathRelpath(PKVM* vm) { + const char* from, * path; + if (!pkGetArgString(vm, 1, &from, NULL)) return; + if (!pkGetArgString(vm, 2, &path, NULL)) return; + + char abs_from[FILENAME_MAX]; + pathAbs(from, abs_from, sizeof(abs_from)); + + char abs_path[FILENAME_MAX]; + pathAbs(path, abs_path, sizeof(abs_path)); + + char result[FILENAME_MAX]; + size_t len = cwk_path_get_relative(abs_from, abs_path, + result, sizeof(result)); + pkReturnStringLength(vm, result, len); +} + +static void _pathJoin(PKVM* vm) { + const char* paths[MAX_JOIN_PATHS + 1]; // +1 for NULL. + int argc = pkGetArgc(vm); + + if (argc > MAX_JOIN_PATHS) { + pkSetRuntimeError(vm, "Cannot join more than " STRINGIFY(MAX_JOIN_PATHS) + "paths."); + return; + } + + for (int i = 0; i < argc; i++) { + pkGetArgString(vm, i + 1, &paths[i], NULL); + } + paths[argc] = NULL; + + char result[FILENAME_MAX]; + size_t len = cwk_path_join_multiple(paths, result, sizeof(result)); + pkReturnStringLength(vm, result, len); +} + +static void _pathNormalize(PKVM* vm) { + const char* path; + if (!pkGetArgString(vm, 1, &path, NULL)) return; + + char result[FILENAME_MAX]; + size_t len = cwk_path_normalize(path, result, sizeof(result)); + pkReturnStringLength(vm, result, len); +} + +static void _pathBaseName(PKVM* vm) { + const char* path; + if (!pkGetArgString(vm, 1, &path, NULL)) return; + + const char* base_name; + size_t length; + cwk_path_get_basename(path, &base_name, &length); + pkReturnString(vm, base_name); +} + +static void _pathDirName(PKVM* vm) { + const char* path; + if (!pkGetArgString(vm, 1, &path, NULL)) return; + + size_t length; + cwk_path_get_dirname(path, &length); + pkReturnStringLength(vm, path, length); +} + +static void _pathIsPathAbs(PKVM* vm) { + const char* path; + if (!pkGetArgString(vm, 1, &path, NULL)) return; + + pkReturnBool(vm, cwk_path_is_absolute(path)); +} + +static void _pathGetExtension(PKVM* vm) { + const char* path; + if (!pkGetArgString(vm, 1, &path, NULL)) return; + + const char* ext; + size_t length; + if (cwk_path_get_extension(path, &ext, &length)) { + pkReturnStringLength(vm, ext, length); + } else { + pkReturnStringLength(vm, NULL, 0); + } +} + +static void _pathExists(PKVM* vm) { + const char* path; + if (!pkGetArgString(vm, 1, &path, NULL)) return; + pkReturnBool(vm, pathIsExists(path)); +} + +static void _pathIsFile(PKVM* vm) { + const char* path; + if (!pkGetArgString(vm, 1, &path, NULL)) return; + pkReturnBool(vm, pathIsFileExists(path)); +} + +static void _pathIsDir(PKVM* vm) { + const char* path; + if (!pkGetArgString(vm, 1, &path, NULL)) return; + pkReturnBool(vm, pathIsDirectoryExists(path)); +} + +void registerModulePath(PKVM* vm) { + PkHandle* path = pkNewModule(vm, "path"); + + pkModuleAddFunction(vm, path, "setunix", _pathSetStyleUnix, 1); + pkModuleAddFunction(vm, path, "getcwd", _pathGetCWD, 0); + pkModuleAddFunction(vm, path, "abspath", _pathAbspath, 1); + pkModuleAddFunction(vm, path, "relpath", _pathRelpath, 2); + pkModuleAddFunction(vm, path, "join", _pathJoin, -1); + pkModuleAddFunction(vm, path, "normalize", _pathNormalize, 1); + pkModuleAddFunction(vm, path, "basename", _pathBaseName, 1); + pkModuleAddFunction(vm, path, "dirname", _pathDirName, 1); + pkModuleAddFunction(vm, path, "isabspath", _pathIsPathAbs, 1); + pkModuleAddFunction(vm, path, "getext", _pathGetExtension, 1); + pkModuleAddFunction(vm, path, "exists", _pathExists, 1); + pkModuleAddFunction(vm, path, "isfile", _pathIsFile, 1); + pkModuleAddFunction(vm, path, "isdir", _pathIsDir, 1); + + pkReleaseHandle(vm, path); +} + +/*****************************************************************************/ +/* FILE MODULE */ +/*****************************************************************************/ + +static void _fileOpen(PKVM* vm) { + + // TODO: handle arg range using pocketlang native api. + // 1 <= argc <= 2 + int argc = pkGetArgc(vm); + if (argc == 0) { + pkSetRuntimeError(vm, "Expected at least 1 argument"); + return; + } else if (argc > 2) { + pkSetRuntimeError(vm, "Expected at least 2 arguments"); + return; + } + + const char* path; + if (!pkGetArgString(vm, 1, &path, NULL)) return; + + const char* mode_str = "r"; + FileAccessMode mode = FMODE_READ; + + if (argc == 2) { + if (!pkGetArgString(vm, 2, &mode_str, NULL)) return; + + // Check if the mode string is valid, and update the mode value. + do { + if (strcmp(mode_str, "r") == 0) { mode = FMODE_READ; break; } + if (strcmp(mode_str, "w") == 0) { mode = FMODE_WRITE; break; } + if (strcmp(mode_str, "a") == 0) { mode = FMODE_APPEND; break; } + if (strcmp(mode_str, "r+") == 0) { mode = FMODE_READ_EXT; break; } + if (strcmp(mode_str, "w+") == 0) { mode = FMODE_WRITE_EXT; break; } + if (strcmp(mode_str, "a+") == 0) { mode = FMODE_APPEND_EXT; break; } + + // TODO: (fmt, ...) va_arg for runtime error public api. + // If we reached here, that means it's an invalid mode string. + pkSetRuntimeError(vm, "Invalid mode string"); + return; + } while (false); + } + + FILE* fp = fopen(path, mode_str); + + if (fp != NULL) { + File* file = NEW_OBJ(File); + file->fp = fp; + file->mode = mode; + file->closed = false; + + pkReturnInstNative(vm, (void*)file, OBJ_FILE); + + } else { + pkReturnNull(vm); + } +} + +static void _fileRead(PKVM* vm) { + File* file; + if (!pkGetArgInst(vm, 1, OBJ_FILE, (void**)&file)) return; + + if (file->closed) { + pkSetRuntimeError(vm, "Cannot read from a closed file."); + return; + } + + if ((file->mode != FMODE_READ) && ((_FMODE_EXT & file->mode) == 0)) { + pkSetRuntimeError(vm, "File is not readable."); + return; + } + + // TODO: this is temproary. + char buff[2048]; + fread((void*)buff, sizeof(char), sizeof(buff), file->fp); + pkReturnString(vm, (const char*)buff); +} + +static void _fileWrite(PKVM* vm) { + File* file; + const char* text; uint32_t length; + if (!pkGetArgInst(vm, 1, OBJ_FILE, (void**)&file)) return; + if (!pkGetArgString(vm, 2, &text, &length)) return; + + if (file->closed) { + pkSetRuntimeError(vm, "Cannot write to a closed file."); + return; + } + + if ((file->mode != FMODE_WRITE) && ((_FMODE_EXT & file->mode) == 0)) { + pkSetRuntimeError(vm, "File is not writable."); + return; + } + + fwrite(text, sizeof(char), (size_t)length, file->fp); +} + +static void _fileClose(PKVM* vm) { + File* file; + if (!pkGetArgInst(vm, 1, OBJ_FILE, (void**)&file)) return; + + if (file->closed) { + pkSetRuntimeError(vm, "File already closed."); + return; + } + + if (fclose(file->fp) != 0) { + pkSetRuntimeError(vm, "fclose() failed!\n" \ + " at " __FILE__ ":" STRINGIFY(__LINE__) "."); + } + file->closed = true; +} + +void registerModuleFile(PKVM* vm) { + PkHandle* file = pkNewModule(vm, "File"); + + pkModuleAddFunction(vm, file, "open", _fileOpen, -1); + pkModuleAddFunction(vm, file, "read", _fileRead, 1); + pkModuleAddFunction(vm, file, "write", _fileWrite, 2); + pkModuleAddFunction(vm, file, "close", _fileClose, 1); + + pkReleaseHandle(vm, file); +} + +/*****************************************************************************/ +/* REGISTER MODULES */ +/*****************************************************************************/ + +void registerModules(PKVM* vm) { + registerModuleFile(vm); + registerModulePath(vm); +} diff --git a/cli/modules.h b/cli/modules.h new file mode 100644 index 0000000..0795a15 --- /dev/null +++ b/cli/modules.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2021 Thakee Nathees + * Distributed Under The MIT License + */ + +#include "internal.h" + +/*****************************************************************************/ +/* MODULE OBJECTS */ +/*****************************************************************************/ + +// Type enums of cli module objects. +typedef enum { + OBJ_FILE = 1, +} ObjType; + +// The abstract type of the objects. +typedef struct { + ObjType type; +} Obj; + +// File access mode. +typedef enum { + + FMODE_READ = (1 << 0), + FMODE_WRITE = (1 << 1), + FMODE_APPEND = (1 << 2), + + _FMODE_EXT = (1 << 3), + FMODE_READ_EXT = (_FMODE_EXT | FMODE_READ), + FMODE_WRITE_EXT = (_FMODE_EXT | FMODE_WRITE), + FMODE_APPEND_EXT = (_FMODE_EXT | FMODE_APPEND), +} FileAccessMode; + +// Str | If already exists | If does not exist | +// -----+-------------------+-------------------| +// 'r' | read from start | failure to open | +// 'w' | destroy contents | create new | +// 'a' | write to end | create new | +// 'r+' | read from start | error | +// 'w+' | destroy contents | create new | +// 'a+' | write to end | create new | + +// A wrapper around the FILE* for the File module. +typedef struct { + Obj _super; + + FILE* fp; // C file poinnter. + FileAccessMode mode; // Access mode of the file. + bool closed; // True if the file isn't closed yet. +} File; + +/*****************************************************************************/ +/* MODULE PUBLIC FUNCTIONS */ +/*****************************************************************************/ + +// The free callback of the object, that'll called by pocketlang when a +// pocketlang native instance garbage collected. +void freeObj(PKVM* vm, void* instance); + +// The native instance get_name callback used to get the name of a native +// instance from pocketlang. Here the id we're using is the ObjType enum. +const char* getObjName(uint32_t id); + +// Registers all the cli modules. +void registerModules(PKVM* vm); + +// 'path' moudle public functions used at various cli functions. +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); diff --git a/cli/modules/path.c b/cli/modules/path.c deleted file mode 100644 index aa69e30..0000000 --- a/cli/modules/path.c +++ /dev/null @@ -1,248 +0,0 @@ -/* - * Copyright (c) 2021 Thakee Nathees - * Distributed Under The MIT License - */ - -#include -#include -#include /* defines FILENAME_MAX */ - -#include "thirdparty/cwalk/cwalk.h" -#if defined(_WIN32) && (defined(_MSC_VER) || defined(__TINYC__)) - #include "thirdparty/dirent/dirent.h" -#else - #include -#endif - -#if defined(_WIN32) - #include - #include - #define get_cwd _getcwd -#else - #include - #define get_cwd getcwd -#endif - -// TODO: this macros should be moved to a general place of in cli. -#define TOSTRING(x) #x -#define STRINGIFY(x) TOSTRING(x) - -// The cstring pointer buffer size used in path.join(p1, p2, ...). Tune this -// value as needed. -#define MAX_JOIN_PATHS 8 - -/*****************************************************************************/ -/* PUBLIC FUNCTIONS */ -/*****************************************************************************/ - -bool pathIsAbsolute(const char* path) { - return cwk_path_is_absolute(path); -} - -void pathGetDirName(const char* path, size_t* length) { - cwk_path_get_dirname(path, length); -} - -size_t pathNormalize(const char* path, char* buff, size_t buff_size) { - return cwk_path_normalize(path, buff, buff_size); -} - -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); -} - -/*****************************************************************************/ -/* INTERNAL FUNCTIONS */ -/*****************************************************************************/ - -static inline bool pathIsFileExists(const char* path) { - FILE* file = fopen(path, "r"); - if (file != NULL) { - fclose(file); - return true; - } - return false; -} - -// Reference: https://stackoverflow.com/a/12510903/10846399 -static inline bool pathIsDirectoryExists(const char* path) { - DIR* dir = opendir(path); - if (dir) { /* Directory exists. */ - closedir(dir); - return true; - } else if (errno == ENOENT) { /* Directory does not exist. */ - } else { /* opendir() failed for some other reason. */ - } - - return false; -} - -static inline bool pathIsExists(const char* path) { - return pathIsFileExists(path) || pathIsDirectoryExists(path); -} - -static inline size_t pathAbs(const char* path, char* buff, size_t buff_size) { - - char cwd[FILENAME_MAX]; - - if (get_cwd(cwd, sizeof(cwd)) == NULL) { - // TODO: handle error. - } - - return cwk_path_get_absolute(cwd, path, buff, buff_size); -} - -/*****************************************************************************/ -/* MODULE FUNCTIONS */ -/*****************************************************************************/ - -static void _pathSetStyleUnix(PKVM* vm) { - bool value; - if (!pkGetArgBool(vm, 1, &value)) return; - cwk_path_set_style((value)? CWK_STYLE_UNIX : CWK_STYLE_WINDOWS); -} - -static void _pathGetCWD(PKVM* vm) { - char cwd[FILENAME_MAX]; - if (get_cwd(cwd, sizeof(cwd)) == NULL) { - // TODO: Handle error. - } - pkReturnString(vm, cwd); -} - -static void _pathAbspath(PKVM* vm) { - const char* path; - if (!pkGetArgString(vm, 1, &path)) return; - - char abspath[FILENAME_MAX]; - size_t len = pathAbs(path, abspath, sizeof(abspath)); - pkReturnStringLength(vm, abspath, len); -} - -static void _pathRelpath(PKVM* vm) { - const char *from, *path; - if (!pkGetArgString(vm, 1, &from)) return; - if (!pkGetArgString(vm, 2, &path)) return; - - char abs_from[FILENAME_MAX]; - pathAbs(from, abs_from, sizeof(abs_from)); - - char abs_path[FILENAME_MAX]; - pathAbs(path, abs_path, sizeof(abs_path)); - - char result[FILENAME_MAX]; - size_t len = cwk_path_get_relative(abs_from, abs_path, - result, sizeof(result)); - pkReturnStringLength(vm, result, len); -} - -static void _pathJoin(PKVM* vm) { - const char* paths[MAX_JOIN_PATHS + 1]; // +1 for NULL. - int argc = pkGetArgc(vm); - - if (argc > MAX_JOIN_PATHS) { - pkSetRuntimeError(vm, "Cannot join more than " STRINGIFY(MAX_JOIN_PATHS) - "paths."); - return; - } - - for (int i = 0; i < argc; i++) { - pkGetArgString(vm, i+1, &paths[i]); - } - paths[argc] = NULL; - - char result[FILENAME_MAX]; - size_t len = cwk_path_join_multiple(paths, result, sizeof(result)); - pkReturnStringLength(vm, result, len); -} - -static void _pathNormalize(PKVM* vm) { - const char* path; - if (!pkGetArgString(vm, 1, &path)) return; - - char result[FILENAME_MAX]; - size_t len = cwk_path_normalize(path, result, sizeof(result)); - pkReturnStringLength(vm, result, len); -} - -static void _pathBaseName(PKVM* vm) { - const char* path; - if (!pkGetArgString(vm, 1, &path)) return; - - const char* base_name; - size_t length; - cwk_path_get_basename(path, &base_name, &length); - pkReturnString(vm, base_name); -} - -static void _pathDirName(PKVM* vm) { - const char* path; - if (!pkGetArgString(vm, 1, &path)) return; - - size_t length; - cwk_path_get_dirname(path, &length); - pkReturnStringLength(vm, path, length); -} - -static void _pathIsPathAbs(PKVM* vm) { - const char* path; - if (!pkGetArgString(vm, 1, &path)) return; - - pkReturnBool(vm, cwk_path_is_absolute(path)); -} - -static void _pathGetExtension(PKVM* vm) { - const char* path; - if (!pkGetArgString(vm, 1, &path)) return; - - const char* ext; - size_t length; - if (cwk_path_get_extension(path, &ext, &length)) { - pkReturnStringLength(vm, ext, length); - } else { - pkReturnStringLength(vm, NULL, 0); - } -} - -static void _pathExists(PKVM* vm) { - const char* path; - if (!pkGetArgString(vm, 1, &path)) return; - pkReturnBool(vm, pathIsExists(path)); -} - -static void _pathIsFile(PKVM* vm) { - const char* path; - if (!pkGetArgString(vm, 1, &path)) return; - pkReturnBool(vm, pathIsFileExists(path)); -} - -static void _pathIsDir(PKVM* vm) { - const char* path; - if (!pkGetArgString(vm, 1, &path)) return; - pkReturnBool(vm, pathIsDirectoryExists(path)); -} - -/*****************************************************************************/ -/* REGISTER MODULE */ -/*****************************************************************************/ - -void registerModulePath(PKVM* vm) { - PkHandle* path = pkNewModule(vm, "path"); - - pkModuleAddFunction(vm, path, "setunix", _pathSetStyleUnix, 1); - pkModuleAddFunction(vm, path, "getcwd", _pathGetCWD, 0); - pkModuleAddFunction(vm, path, "abspath", _pathAbspath, 1); - pkModuleAddFunction(vm, path, "relpath", _pathRelpath, 2); - pkModuleAddFunction(vm, path, "join", _pathJoin, -1); - pkModuleAddFunction(vm, path, "normalize", _pathNormalize, 1); - pkModuleAddFunction(vm, path, "basename", _pathBaseName, 1); - pkModuleAddFunction(vm, path, "dirname", _pathDirName, 1); - pkModuleAddFunction(vm, path, "isabspath", _pathIsPathAbs, 1); - pkModuleAddFunction(vm, path, "getext", _pathGetExtension, 1); - pkModuleAddFunction(vm, path, "exists", _pathExists, 1); - pkModuleAddFunction(vm, path, "isfile", _pathIsFile, 1); - pkModuleAddFunction(vm, path, "isdir", _pathIsDir, 1); - - pkReleaseHandle(vm, path); -} diff --git a/cli/repl.c b/cli/repl.c index 61a06a4..d21fe71 100644 --- a/cli/repl.c +++ b/cli/repl.c @@ -6,7 +6,7 @@ // The REPL (Read Evaluate Print Loop) implementation. // https://en.wikipedia.org/wiki/Read-eval-print_loop. -#include "common.h" +#include "internal.h" #include // isspace #include "utils.h" @@ -20,7 +20,9 @@ const char* read_line(uint32_t* length) { const int size = 1024; char* mem = (char*)malloc(size); - fgets(mem, size, stdin); + if (fgets(mem, size, stdin) == NULL) { + // TODO: handle error. + } size_t len = strlen(mem); // FIXME: handle \r\n, this is temp. @@ -61,7 +63,7 @@ int repl(PKVM* vm, const PkCompileOptions* options) { user_data->repl_mode = true; // Print the copyright and license notice. - printf("%s\n", CLI_NOTICE); + printf("%s", CLI_NOTICE); // The main module that'll be used to compile and execute the input source. PkHandle* module = pkNewModule(vm, "$(REPL)"); diff --git a/cli/thirdparty.c b/cli/thirdparty.c new file mode 100644 index 0000000..3a5e8c9 --- /dev/null +++ b/cli/thirdparty.c @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2020-2021 Thakee Nathees + * Distributed Under The MIT License + */ + +// This file will include all thirdparty source files from the thirdparty sub +// directories here into a single file. That'll make it easy to compile with +// the command `gcc cli/*.c ...` instead of having to add every single sub +// directory to the list of source directory. + +// Library : cwalk +// Source : https://github.com/likle/cwalk +// Doc : https://likle.github.io/cwalk/ +// About : Path library for C/C++. Cross-Platform for Windows, MacOS and +// Linux. Supports UNIX and Windows path styles on those platforms. +#include "thirdparty/cwalk/cwalk.c" + diff --git a/cli/modules/thirdparty/cwalk/LICENSE.md b/cli/thirdparty/cwalk/LICENSE.md similarity index 100% rename from cli/modules/thirdparty/cwalk/LICENSE.md rename to cli/thirdparty/cwalk/LICENSE.md diff --git a/cli/modules/thirdparty/cwalk/cwalk.c b/cli/thirdparty/cwalk/cwalk.c similarity index 100% rename from cli/modules/thirdparty/cwalk/cwalk.c rename to cli/thirdparty/cwalk/cwalk.c diff --git a/cli/modules/thirdparty/cwalk/cwalk.h b/cli/thirdparty/cwalk/cwalk.h similarity index 100% rename from cli/modules/thirdparty/cwalk/cwalk.h rename to cli/thirdparty/cwalk/cwalk.h diff --git a/cli/modules/thirdparty/dirent/LICENSE b/cli/thirdparty/dirent/LICENSE similarity index 100% rename from cli/modules/thirdparty/dirent/LICENSE rename to cli/thirdparty/dirent/LICENSE diff --git a/cli/modules/thirdparty/dirent/dirent.h b/cli/thirdparty/dirent/dirent.h similarity index 100% rename from cli/modules/thirdparty/dirent/dirent.h rename to cli/thirdparty/dirent/dirent.h diff --git a/cli/utils.h b/cli/utils.h index 99086e3..ad81bf4 100644 --- a/cli/utils.h +++ b/cli/utils.h @@ -3,7 +3,7 @@ * Distributed Under The MIT License */ -#include "common.h" +#include "internal.h" typedef struct { uint8_t* data; diff --git a/src/include/pocketlang.h b/src/include/pocketlang.h index 24fdfd2..ee13ef9 100644 --- a/src/include/pocketlang.h +++ b/src/include/pocketlang.h @@ -157,6 +157,17 @@ typedef void (*pkWriteFn) (PKVM* vm, const char* text); // contain a line ending (\n or \r\n). typedef PkStringPtr (*pkReadFn) (PKVM* vm); +// A function callback, that'll be called when a native instance (wrapper) is +// freed by by the garbage collector, to indicate that pocketlang is done with +// the native instance. +typedef void (*pkFreeInstFn) (PKVM* vm, void* instance); + +// A function callback to get the name of the native instance from pocketlang, +// using it's [id]. The returned string won't be copied by pocketlang so it's +// expected to be alived since the instance is alive and recomended to return +// a C literal string. +typedef const char* (*pkInstNameFn) (uint32_t id); + // A function callback symbol for clean/free the pkStringResult. typedef void (*pkResultDoneFn) (PKVM* vm, PkStringPtr result); @@ -278,6 +289,9 @@ struct PkConfiguration { pkWriteFn write_fn; pkReadFn read_fn; + pkFreeInstFn free_inst_fn; + pkInstNameFn inst_name_fn; + pkResolvePathFn resolve_path_fn; pkLoadScriptFn load_script_fn; @@ -337,7 +351,9 @@ PK_PUBLIC PkVar pkGetArg(const PKVM* vm, int arg); PK_PUBLIC bool pkGetArgBool(PKVM* vm, int arg, bool* value); PK_PUBLIC bool pkGetArgNumber(PKVM* vm, int arg, double* value); -PK_PUBLIC bool pkGetArgString(PKVM* vm, int arg, const char** value); +PK_PUBLIC bool pkGetArgString(PKVM* vm, int arg, + const char** value, uint32_t* length); +PK_PUBLIC bool pkGetArgInst(PKVM* vm, int arg, uint32_t id, void** value); PK_PUBLIC bool pkGetArgValue(PKVM* vm, int arg, PkVarType type, PkVar* value); // The functions follow are used to set the return value of the current native @@ -349,6 +365,9 @@ PK_PUBLIC void pkReturnNumber(PKVM* vm, double value); PK_PUBLIC void pkReturnString(PKVM* vm, const char* value); PK_PUBLIC void pkReturnStringLength(PKVM* vm, const char* value, size_t len); PK_PUBLIC void pkReturnValue(PKVM* vm, PkVar value); +PK_PUBLIC void pkReturnHandle(PKVM* vm, PkHandle* handle); + +PK_PUBLIC void pkReturnInstNative(PKVM* vm, void* data, uint32_t id); // Returns the cstring pointer of the given string. Make sure if the [value] is // a string before calling this function, otherwise it'll fail an assertion. @@ -382,6 +401,12 @@ PK_PUBLIC PkHandle* pkNewModule(PKVM* vm, const char* name); // Create and return a new fiber around the function [fn]. PK_PUBLIC PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn); +// Create and return a native instance around the [data]. The [id] is the +// unique id of the instance, this would be used to check if two instances are +// equal and used to get the name of the instance using NativeTypeNameFn +// callback. +PK_PUBLIC PkHandle* pkNewInstNative(PKVM* vm, void* data, uint32_t id); + // TODO: The functions below will push the primitive values on the stack // and return it's pointer as a PkVar. It's useful to convert your primitive // values as pocketlang variables. diff --git a/src/pk_buffers.h b/src/pk_buffers.h index ba1379f..dbde935 100644 --- a/src/pk_buffers.h +++ b/src/pk_buffers.h @@ -6,7 +6,7 @@ #ifndef BUFFERS_TEMPLATE_H #define BUFFERS_TEMPLATE_H -#include "pk_common.h" +#include "pk_internal.h" // The macro 'DECLARE_BUFFER()' emulate the C++ template to declare and define // different types of buffer objects. diff --git a/src/pk_common.h b/src/pk_common.h index 3bc3a17..a8bfdf9 100644 --- a/src/pk_common.h +++ b/src/pk_common.h @@ -3,79 +3,16 @@ * Distributed Under The MIT License */ +// A collection of reusable macros that pocketlang use. This file doesn't have +// any dependencies, you can just drag and drop this file in your project if +// you want to use these macros. + #ifndef PK_COMMON_H #define PK_COMMON_H -#include "include/pocketlang.h" - -// Commonly used c standard headers across the sources. Don't include any -// headers that are specific to a single source here, instead include them in -// their source files explicitly (can not be implicitly included by another -// header). And don't include any C standard headers in any of the pocketlang -// headers. -#include -#include -#include -#include -#include -#include -#include - -// __STDC_LIMIT_MACROS and __STDC_CONSTANT_MACROS are a workaround to -// allow C++ programs to use stdint.h macros specified in the C99 -// standard that aren't in the C++ standard. -#define __STDC_LIMIT_MACROS -#include - -/*****************************************************************************/ -/* INTERNAL CONFIGURATIONS */ -/*****************************************************************************/ - -// Set this to dump compiled opcodes of each functions. -#define DEBUG_DUMP_COMPILED_CODE 0 - -// Set this to dump stack frame before executing the next instruction. -#define DEBUG_DUMP_CALL_STACK 0 - -// Nan-Tagging could be disable for debugging/portability purposes. See "var.h" -// header for more information on Nan-tagging. -#define VAR_NAN_TAGGING 1 - -// The maximum number of argument a pocketlang function supported to call. This -// value is arbitrary and feel free to change it. (Just used this limit for an -// internal buffer to store values before calling a new fiber). -#define MAX_ARGC 32 - -// 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 - -/*****************************************************************************/ -/* ALLOCATION MACROS */ -/*****************************************************************************/ - -// Allocate object of [type] using the vmRealloc function. -#define ALLOCATE(vm, type) \ - ((type*)vmRealloc(vm, NULL, 0, sizeof(type))) - -// Allocate object of [type] which has a dynamic tail array of type [tail_type] -// with [count] entries. -#define ALLOCATE_DYNAMIC(vm, type, count, tail_type) \ - ((type*)vmRealloc(vm, NULL, 0, sizeof(type) + sizeof(tail_type) * (count))) - -// Allocate [count] amount of object of [type] array. -#define ALLOCATE_ARRAY(vm, type, count) \ - ((type*)vmRealloc(vm, NULL, 0, sizeof(type) * (count))) - -// Deallocate a pointer allocated by vmRealloc before. -#define DEALLOCATE(vm, pointer) \ - vmRealloc(vm, pointer, 0, 0) - -/*****************************************************************************/ -/* COMMON MACROS */ -/*****************************************************************************/ +#ifndef __has_builtin + #define __has_builtin(x) 0 +#endif #if defined(__GNUC__) #pragma GCC diagnostic ignored "-Wint-to-pointer-cast" @@ -146,20 +83,23 @@ #define ASSERT(condition, message) NO_OP #define ASSERT_INDEX(index, size) NO_OP -// Reference : https://github.com/wren-lang/ #if defined(_MSC_VER) #define UNREACHABLE() __assume(0) #elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) #define UNREACHABLE() __builtin_unreachable() -#elif defined(__EMSCRIPTEN__) - #define UNREACHABLE() __builtin_unreachable() +#elif defined(__EMSCRIPTEN__) || defined(__clang__) + #if __has_builtin(__builtin_unreachable) + #define UNREACHABLE() __builtin_unreachable() + #else + #define UNREACHABLE() NO_OP + #endif #else #define UNREACHABLE() NO_OP #endif #endif // DEBUG -#if defined( _MSC_VER ) +#if defined(_MSC_VER) #define forceinline __forceinline #else #define forceinline __attribute__((always_inline)) @@ -203,9 +143,7 @@ #define STR_HEX_BUFF_SIZE 20 // Integer number (double) to bin string buffer size. -// The maximum value an unsigned 64 bit integer can get is -// 0b1111111111111111111111111111111111111111111111111111111111111111 -// which is 64 characters. +// The maximum value an unsigned 64 bit integer can get is 0b11111... 64 1s. // + 64 for bin digits // + 1 for sign '-' // + 2 for '0b' prefix diff --git a/src/pk_compiler.h b/src/pk_compiler.h index fd821d7..9531e95 100644 --- a/src/pk_compiler.h +++ b/src/pk_compiler.h @@ -6,7 +6,7 @@ #ifndef COMPILER_H #define COMPILER_H -#include "pk_common.h" +#include "pk_internal.h" #include "pk_var.h" typedef enum { diff --git a/src/pk_core.c b/src/pk_core.c index 0673091..612caf1 100644 --- a/src/pk_core.c +++ b/src/pk_core.c @@ -113,21 +113,21 @@ PkHandle* pkGetFunction(PKVM* vm, PkHandle* module, } while(false) // Check for errors in before calling the get arg public api function. -#define CHECK_GET_ARG_API_ERRORS() \ - do { \ - __ASSERT(vm->fiber != NULL, \ - "This function can only be called at runtime."); \ - __ASSERT(arg > 0 || arg <= ARGC, "Invalid argument index."); \ - __ASSERT(value != NULL, "Argument [value] was NULL."); \ +#define CHECK_GET_ARG_API_ERRORS() \ + do { \ + __ASSERT(vm->fiber != NULL, \ + "This function can only be called at runtime."); \ + __ASSERT(arg > 0 || arg <= ARGC, "Invalid argument index."); \ + __ASSERT(value != NULL, "Argument [value] was NULL."); \ } while (false) -// Set error for incompatible type provided as an argument. -#define ERR_INVALID_ARG_TYPE(m_type) \ -do { \ - char buff[STR_INT_BUFF_SIZE]; \ - sprintf(buff, "%d", arg); \ - VM_SET_ERROR(vm, stringFormat(vm, "Expected a " m_type \ - " at argument $.", buff)); \ +// Set error for incompatible type provided as an argument. (TODO: got type). +#define ERR_INVALID_ARG_TYPE(m_type) \ +do { \ + char buff[STR_INT_BUFF_SIZE]; \ + sprintf(buff, "%d", arg); \ + VM_SET_ERROR(vm, stringFormat(vm, "Expected a '$' at argument $.", \ + m_type, buff)); \ } while (false) // pkGetArgc implementation (see pocketlang.h for description). @@ -173,12 +173,14 @@ bool pkGetArgNumber(PKVM* vm, int arg, double* value) { } // pkGetArgString implementation (see pocketlang.h for description). -bool pkGetArgString(PKVM* vm, int arg, const char** value) { +bool pkGetArgString(PKVM* vm, int arg, const char** value, uint32_t* length) { CHECK_GET_ARG_API_ERRORS(); Var val = ARG(arg); if (IS_OBJ_TYPE(val, OBJ_STRING)) { - *value = ((String*)AS_OBJ(val))->data; + String* str = (String*)AS_OBJ(val); + *value = str->data; + if (length) *length = str->length; } else { ERR_INVALID_ARG_TYPE("string"); @@ -188,6 +190,34 @@ bool pkGetArgString(PKVM* vm, int arg, const char** value) { return true; } +// pkGetArgInstance implementation (see pocketlang.h for description). +bool pkGetArgInst(PKVM* vm, int arg, uint32_t id, void** value) { + CHECK_GET_ARG_API_ERRORS(); + + Var val = ARG(arg); + bool is_native_instance = false; + + if (IS_OBJ_TYPE(val, OBJ_INST)) { + Instance* inst = ((Instance*)AS_OBJ(val)); + if (inst->is_native && inst->native_id == id) { + *value = inst->native; + is_native_instance = true; + } + } + + if (!is_native_instance) { + const char* ty_name = "$(?)"; + if (vm->config.inst_name_fn != NULL) { + ty_name = vm->config.inst_name_fn(id); + } + + ERR_INVALID_ARG_TYPE(ty_name); + return false; + } + + return true; +} + // pkGetArgValue implementation (see pocketlang.h for description). bool pkGetArgValue(PKVM* vm, int arg, PkVarType type, PkVar* value) { CHECK_GET_ARG_API_ERRORS(); @@ -234,6 +264,16 @@ void pkReturnValue(PKVM* vm, PkVar value) { RET(*(Var*)value); } +// pkReturnHandle implementation (see pocketlang.h for description). +void pkReturnHandle(PKVM* vm, PkHandle* handle) { + RET(handle->value); +} + +// pkReturnInstNative implementation (see pocketlang.h for description). +void pkReturnInstNative(PKVM* vm, void* data, uint32_t id) { + RET(VAR_OBJ(newInstanceNative(vm, data, id))); +} + const char* pkStringGetData(const PkVar value) { const Var str = (*(const Var*)value); __ASSERT(IS_OBJ_TYPE(str, OBJ_STRING), "Value should be of type string."); diff --git a/src/pk_core.h b/src/pk_core.h index 205ad37..03bfbb1 100644 --- a/src/pk_core.h +++ b/src/pk_core.h @@ -6,7 +6,7 @@ #ifndef CORE_H #define CORE_H -#include "pk_common.h" +#include "pk_internal.h" #include "pk_var.h" // Initialize core language, builtin function and core libs. diff --git a/src/pk_debug.h b/src/pk_debug.h index 56fd8ac..9f39049 100644 --- a/src/pk_debug.h +++ b/src/pk_debug.h @@ -6,7 +6,7 @@ #ifndef DEBUG_H #define DEBUG_H -#include "pk_common.h" +#include "pk_internal.h" #include "pk_var.h" // Dump the value of the [value] without a new line at the end to the buffer diff --git a/src/pk_internal.h b/src/pk_internal.h new file mode 100644 index 0000000..d928f33 --- /dev/null +++ b/src/pk_internal.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2020-2021 Thakee Nathees + * Distributed Under The MIT License + */ + +#ifndef PK_INTERNAL +#define PK_INTERNAL + +#include "include/pocketlang.h" + +#include "pk_common.h" + +// Commonly used c standard headers across the sources. Don't include any +// headers that are specific to a single source here, instead include them in +// their source files explicitly (can not be implicitly included by another +// header). And don't include any C standard headers in any of the pocketlang +// headers. +#include +#include +#include +#include +#include +#include +#include + +// __STDC_LIMIT_MACROS and __STDC_CONSTANT_MACROS are a workaround to +// allow C++ programs to use stdint.h macros specified in the C99 +// standard that aren't in the C++ standard. +#define __STDC_LIMIT_MACROS +#include + +/*****************************************************************************/ +/* INTERNAL CONFIGURATIONS */ +/*****************************************************************************/ + +// Set this to dump compiled opcodes of each functions. +#define DEBUG_DUMP_COMPILED_CODE 0 + +// Set this to dump stack frame before executing the next instruction. +#define DEBUG_DUMP_CALL_STACK 0 + +// Nan-Tagging could be disable for debugging/portability purposes. See "var.h" +// header for more information on Nan-tagging. +#define VAR_NAN_TAGGING 1 + +// The maximum number of argument a pocketlang function supported to call. This +// value is arbitrary and feel free to change it. (Just used this limit for an +// internal buffer to store values before calling a new fiber). +#define MAX_ARGC 32 + +// 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 + +/*****************************************************************************/ +/* ALLOCATION MACROS */ +/*****************************************************************************/ + +// Allocate object of [type] using the vmRealloc function. +#define ALLOCATE(vm, type) \ + ((type*)vmRealloc(vm, NULL, 0, sizeof(type))) + +// Allocate object of [type] which has a dynamic tail array of type [tail_type] +// with [count] entries. +#define ALLOCATE_DYNAMIC(vm, type, count, tail_type) \ + ((type*)vmRealloc(vm, NULL, 0, sizeof(type) + sizeof(tail_type) * (count))) + +// Allocate [count] amount of object of [type] array. +#define ALLOCATE_ARRAY(vm, type, count) \ + ((type*)vmRealloc(vm, NULL, 0, sizeof(type) * (count))) + +// Deallocate a pointer allocated by vmRealloc before. +#define DEALLOCATE(vm, pointer) \ + vmRealloc(vm, pointer, 0, 0) + +#endif // PK_INTERNAL diff --git a/src/pk_utils.h b/src/pk_utils.h index 3e61b1a..df091e8 100644 --- a/src/pk_utils.h +++ b/src/pk_utils.h @@ -6,7 +6,7 @@ #ifndef UTILS_H #define UTILS_H -#include "pk_common.h" +#include "pk_internal.h" // 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 diff --git a/src/pk_var.c b/src/pk_var.c index e0781ae..af64dc0 100644 --- a/src/pk_var.c +++ b/src/pk_var.c @@ -42,26 +42,53 @@ PkVarType pkGetValueType(const PkVar value) { } PkHandle* pkNewString(PKVM* vm, const char* value) { - return vmNewHandle(vm, VAR_OBJ(newString(vm, value))); + String* str = newString(vm, value); + vmPushTempRef(vm, &str->_super); // str + PkHandle* handle = vmNewHandle(vm, VAR_OBJ(str)); + vmPopTempRef(vm); // str + return handle; } PkHandle* pkNewStringLength(PKVM* vm, const char* value, size_t len) { - return vmNewHandle(vm, VAR_OBJ(newStringLength(vm, value, (uint32_t)len))); + String* str = newStringLength(vm, value, (uint32_t)len); + vmPushTempRef(vm, &str->_super); // str + PkHandle* handle = vmNewHandle(vm, VAR_OBJ(str)); + vmPopTempRef(vm); // str + return handle; } PkHandle* pkNewList(PKVM* vm) { - return vmNewHandle(vm, VAR_OBJ(newList(vm, MIN_CAPACITY))); + List* list = newList(vm, MIN_CAPACITY); + vmPushTempRef(vm, &list->_super); // list + PkHandle* handle = vmNewHandle(vm, VAR_OBJ(list)); + vmPopTempRef(vm); // list + return handle; } PkHandle* pkNewMap(PKVM* vm) { - return vmNewHandle(vm, VAR_OBJ(newMap(vm))); + Map* map = newMap(vm); + vmPushTempRef(vm, &map->_super); // map + PkHandle* handle = vmNewHandle(vm, VAR_OBJ(map)); + vmPopTempRef(vm); // map + return handle; } PkHandle* pkNewFiber(PKVM* vm, PkHandle* fn) { __ASSERT(IS_OBJ_TYPE(fn->value, OBJ_FUNC), "Fn should be of type function."); Fiber* fiber = newFiber(vm, (Function*)AS_OBJ(fn->value)); - return vmNewHandle(vm, VAR_OBJ(fiber)); + vmPushTempRef(vm, &fiber->_super); // fiber + PkHandle* handle = vmNewHandle(vm, VAR_OBJ(fiber)); + vmPopTempRef(vm); // fiber + return handle; +} + +PkHandle* pkNewInstNative(PKVM* vm, void* data, uint32_t id) { + Instance* inst = newInstanceNative(vm, data, id); + vmPushTempRef(vm, &inst->_super); // inst + PkHandle* handle = vmNewHandle(vm, VAR_OBJ(inst)); + vmPopTempRef(vm); // inst + return handle; } /*****************************************************************************/ @@ -511,6 +538,22 @@ Instance* newInstance(PKVM* vm, Class* ty, bool initialize) { return inst; } +Instance* newInstanceNative(PKVM* vm, void* data, uint32_t id) { + Instance* inst = ALLOCATE(vm, Instance); + varInitObject(&inst->_super, vm, OBJ_INST); + inst->is_native = true; + inst->native_id = id; + + if (vm->config.inst_name_fn != NULL) { + inst->name = vm->config.inst_name_fn(id); + } else { + inst->name = "$(?)"; + } + + inst->native = data; + return inst; +} + List* rangeAsList(PKVM* vm, Range* self) { List* list; if (self->from < self->to) { @@ -1006,7 +1049,10 @@ void freeObject(PKVM* vm, Object* self) { Instance* inst = (Instance*)self; if (inst->is_native) { - TODO; + if (vm->config.free_inst_fn != NULL) { + // TODO: Allow user to set error when freeing the object. + vm->config.free_inst_fn(vm, inst->native); + } } else { Inst* ins = inst->ins; @@ -1446,6 +1492,15 @@ static void _toStringInternal(PKVM* vm, const Var v, pkByteBuffer* buff, pkByteBufferWrite(buff, vm, '='); _toStringInternal(vm, ins->fields.data[i], buff, outer, repr); } + } else { + pkByteBufferWrite(buff, vm, ':'); + + char buff_addr[STR_HEX_BUFF_SIZE]; + char* ptr = (char*)buff_addr; + (*ptr++) = '0'; (*ptr++) = 'x'; + const int len = snprintf(ptr, sizeof(buff_addr) - 2, + "%08x", (unsigned int)(uintptr_t)inst->native); + pkByteBufferAddString(buff, vm, buff_addr, (uint32_t)len); } pkByteBufferWrite(buff, vm, ']'); diff --git a/src/pk_var.h b/src/pk_var.h index f6fe735..b12ee64 100644 --- a/src/pk_var.h +++ b/src/pk_var.h @@ -7,7 +7,7 @@ #define VAR_H #include "pk_buffers.h" -#include "pk_common.h" +#include "pk_internal.h" /** @file * A simple dynamic type system library for small dynamic typed languages using @@ -204,6 +204,7 @@ DECLARE_BUFFER(Class, Class*) void pkByteBufferAddString(pkByteBuffer* self, PKVM* vm, const char* str, uint32_t length); +// Type enums of the pocketlang heap allocated types. typedef enum { OBJ_STRING, OBJ_LIST, @@ -377,10 +378,13 @@ struct Instance { Object _super; const char* name; //< Name of the type it belongs to. + bool is_native; //< True if it's a native type instance. + uint32_t native_id; //< Unique ID of this native instance. + union { void* native; //< C struct pointer. // TODO: - Inst* ins; //< Module instance pointer. + Inst* ins; //< Module instance pointer. }; }; @@ -442,6 +446,12 @@ Class* newClass(PKVM* vm, Script* scr, const char* name, uint32_t length); // the buffer count = 0). Otherwise they'll be set to VAR_NULL. Instance* newInstance(PKVM* vm, Class* ty, bool initialize); +// Allocate new native instance and with [data] as the native type handle and +// return Instance*. The [id] is the unique id of the instance, this would be +// used to check if two instances are equal and used to get the name of the +// instance using NativeTypeNameFn callback. +Instance* newInstanceNative(PKVM* vm, void* data, uint32_t id); + /*****************************************************************************/ /* METHODS */ /*****************************************************************************/ diff --git a/src/pk_vm.c b/src/pk_vm.c index 51f93c9..4815361 100644 --- a/src/pk_vm.c +++ b/src/pk_vm.c @@ -30,6 +30,9 @@ PkConfiguration pkNewConfiguration(void) { config.write_fn = NULL; config.read_fn = NULL; + config.free_inst_fn = NULL; + config.inst_name_fn = NULL; + config.load_script_fn = NULL; config.resolve_path_fn = NULL; config.user_data = NULL; diff --git a/src/pk_vm.h b/src/pk_vm.h index a3d0a8e..b6ac6b9 100644 --- a/src/pk_vm.h +++ b/src/pk_vm.h @@ -6,8 +6,8 @@ #ifndef VM_H #define VM_H -#include "pk_common.h" #include "pk_compiler.h" +#include "pk_internal.h" #include "pk_var.h" // The maximum number of temporary object reference to protect them from being diff --git a/tests/check.py b/tests/check.py index e9414a2..ca843ab 100644 --- a/tests/check.py +++ b/tests/check.py @@ -6,8 +6,9 @@ ## uses of tabs and trailing white spaces, etc. import os, sys, re -from os.path import join, abspath, dirname from os import listdir +from os.path import ( + join, abspath, dirname, relpath) ## The absolute path of this file, when run as a script. ## This file is not intended to be included in other files at the moment. @@ -18,6 +19,11 @@ THIS_PATH = abspath(dirname(__file__)) def to_abs_paths(sources): return map(lambda s: os.path.join(THIS_PATH, s), sources) +## Converts the path from absolute path to relative path from the +## toplelve of the project. +def to_tolevel_path(path): + return relpath(path, join(THIS_PATH, '..')) + ## A list of source files, to check if the fnv1a hash values match it's ## corresponding cstring in the CASE_ATTRIB(name, hash) macro calls. HASH_CHECK_LIST = [ @@ -29,7 +35,14 @@ HASH_CHECK_LIST = [ C_SOURCE_DIRS = [ "../src/", "../cli/", - "../cli/modules/", +] + +## A list of common header that just copied in different projects. +## These common header cannot be re-used because we're trying to achieve +## minimalistic with the count of the sources in pocketlang. +COMMON_HEADERS = [ + "../src/pk_common.h", + "../cli/common.h", ] ## This global variable will be set to true if any check failed. @@ -38,6 +51,7 @@ checks_failed = False def main(): check_fnv1_hash(to_abs_paths(HASH_CHECK_LIST)) check_static(to_abs_paths(C_SOURCE_DIRS)) + check_common_header_match(to_abs_paths(COMMON_HEADERS)) if checks_failed: sys.exit(1) print("Static checks were passed.") @@ -58,7 +72,7 @@ def check_fnv1_hash(sources): if val == hash: continue ## Path of the file relative to top-level. - file_path = os.path.relpath(file, join(THIS_PATH, '..')) + file_path = to_tolevel_path(file) report_error(f"{location(file_path, line_no)} - hash mismatch. " f"hash('{name}') = {hash} not {val}") @@ -77,7 +91,7 @@ def check_static(dirs): fp = open(join(dir, file), 'r') ## Path of the file relative to top-level. - file_path = os.path.relpath(join(dir, file), join(THIS_PATH, '..')) + file_path = to_tolevel_path(join(dir, file)) ## This will be set to true if the last line is empty. is_last_empty = False; line_no = 0 @@ -108,7 +122,24 @@ def check_static(dirs): is_last_empty = False fp.close() - + +## Assert all the content of the headers list below are the same. +def check_common_header_match(headers): + headers = list(headers) + assert len(headers) >= 1 + + content = '' + with open(headers[0], 'r') as f: + content = f.read() + + for i in range(1, len(headers)): + with open(headers[i], 'r') as f: + if f.read() != content: + main_header = to_tolevel_path(headers[0]) + curr_header = to_tolevel_path(headers[i]) + report_error("File content mismatch: \"%s\" and \"%s\"\n" + " These files contants should be the same." + %(main_header, curr_header)) ## Returns a formated string of the error location. def location(file, line):