From 8738ccfe64aae0765bfc8911c03f57a12bfad20d Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Sun, 23 May 2021 02:29:32 +0530 Subject: [PATCH] Native function api implemented --- cli/cli_modules.c | 43 ++++++ cli/main.c | 25 ++-- src/common.h | 21 ++- src/core.c | 239 ++++++++++++++++++++++++--------- src/include/pocketlang.h | 278 +++++++++++++++++++++++++++------------ src/var.c | 93 +++++++++---- src/var.h | 13 +- src/vm.c | 56 +++++++- src/vm.h | 17 ++- test/lang/import.pk | 2 +- 10 files changed, 590 insertions(+), 197 deletions(-) create mode 100644 cli/cli_modules.c diff --git a/cli/cli_modules.c b/cli/cli_modules.c new file mode 100644 index 0000000..5aee559 --- /dev/null +++ b/cli/cli_modules.c @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2021 Thakee Nathees + * Licensed under: MIT License + */ + +#include "pocketlang.h" + +// FIXME: everything below here is temproary and for testing. + +#include + +void stdPathAbspath(PKVM* vm) { + PkVar path; + if (!pkGetArgValue(vm, 1, PK_STRING, &path)) return; + + const char* data = pkStringGetData(path); + + pkReturnNull(vm); +} + +void stdPathCurdir(PKVM* vm) { + pkReturnNull(vm); +} + +void testAdd(PKVM* vm) { + double v1, v2; + if (!pkGetArgNumber(vm, 1, &v1)) return; + if (!pkGetArgNumber(vm, 2, &v2)) return; + + double total = v1 + v2; + + pkReturnNumber(vm, total); +} + +void register_cli_modules(PKVM* vm) { + PkHandle* path = pkNewModule(vm, "path"); + pkModuleAddFunction(vm, path, "abspath", stdPathAbspath, 1); + pkModuleAddFunction(vm, path, "curdir", stdPathCurdir, 0); + + PkHandle* test = pkNewModule(vm, "test"); + pkModuleAddFunction(vm, test, "add", testAdd, 2); + +} \ No newline at end of file diff --git a/cli/main.c b/cli/main.c index 0cbba04..0fe44c4 100644 --- a/cli/main.c +++ b/cli/main.c @@ -9,7 +9,15 @@ #include "pocketlang.h" -void errorPrint(PKVM* vm, PKErrorType type, const char* file, int line, + // FIXME: everything below here is temproary and for testing. + +// TODO: include this. +void register_cli_modules(PKVM* vm); + + +// --------------------------------------- + +void errorPrint(PKVM* vm, PkErrorType type, const char* file, int line, const char* message) { if (type == PK_ERROR_COMPILE) { fprintf(stderr, "Error: %s\n at %s:%i\n", message, file, line); @@ -101,16 +109,15 @@ int main(int argc, char** argv) { // FIXME: this is temp till arg parse implemented. if (argc >= 3 && strcmp(argv[1], "-c") == 0) { PKVM* vm = pkNewVM(&config); - PKInterpretResult result = pkInterpretSource(vm, argv[2], "$(Source)"); - pkFreeVM(vm); - return result; - - } else { - PKVM* vm = pkNewVM(&config); - PKInterpretResult result = pkInterpret(vm, argv[1]); + PkInterpretResult result = pkInterpretSource(vm, argv[2], "$(Source)"); pkFreeVM(vm); return result; } - return 0; + PKVM* vm = pkNewVM(&config); + register_cli_modules(vm); + + PkInterpretResult result = pkInterpret(vm, argv[1]); + pkFreeVM(vm); + return result; } diff --git a/src/common.h b/src/common.h index 5282553..6d0b450 100644 --- a/src/common.h +++ b/src/common.h @@ -23,7 +23,11 @@ #include //< Only needed for ASSERT() macro and for release mode //< TODO; macro use this to print a crash report. -// The internal assertion macro, do not use this. Use ASSERT() instead. + +// 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 and assetion's in public methods (to indicate that +// the host application did something wrong). #define __ASSERT(condition, message) \ do { \ if (!(condition)) { \ @@ -103,19 +107,22 @@ #define DEALLOCATE(vm, pointer) \ vmRealloc(vm, pointer, 0, 0) +// Unique number to identify for various cases. +typedef uint32_t ID; + +#if VAR_NAN_TAGGING + typedef uint64_t Var; +#else + typedef struct Var Var; +#endif + typedef struct Object Object; typedef struct String String; typedef struct List List; typedef struct Map Map; typedef struct Range Range; - typedef struct Script Script; typedef struct Function Function; - -// Unique number to identify for various cases. -typedef uint32_t ID; - -// VM's fiber type. typedef struct Fiber Fiber; #endif //PK_COMMON_H diff --git a/src/core.c b/src/core.c index 8c921fb..6c5a1c8 100644 --- a/src/core.c +++ b/src/core.c @@ -13,6 +13,137 @@ #include "var.h" #include "vm.h" +/*****************************************************************************/ +/* PUBLIC API */ +/*****************************************************************************/ + +// Declare internal functions of public api. +static Script* newModuleInternal(PKVM* vm, const char* name); +static void moduleAddFunctionInternal(PKVM* vm, Script* script, + const char* name, pkNativeFn fptr, int arity); + +PkHandle* pkNewModule(PKVM* vm, const char* name) { + Script* module = newModuleInternal(vm, name); + return vmNewHandle(vm, VAR_OBJ(&module->_super)); +} + +void pkModuleAddFunction(PKVM* vm, PkHandle* module, const char* name, + pkNativeFn fptr, int arity) { + __ASSERT(module != NULL, "Argument module was NULL."); + + Var scr = module->value; + __ASSERT(IS_OBJ(scr) && AS_OBJ(scr)->type == OBJ_SCRIPT, + "Given handle is not a module"); + + moduleAddFunctionInternal(vm, (Script*)AS_OBJ(scr), name, fptr, arity); +} + +// Argument getter (1 based). +#define ARG(n) vm->fiber->ret[n] + +// Convinent macros. +#define ARG1 ARG(1) +#define ARG2 ARG(2) +#define ARG3 ARG(3) + +// Argument count used in variadic functions. +#define ARGC ((int)(vm->fiber->sp - vm->fiber->ret) - 1) + +// Set return value. +#define RET(value) \ + do { \ + *(vm->fiber->ret) = value; \ + return; \ + } while (false) + +// Check for errors in before calling the get arg public api function. +#define CHECK_GET_ARG_API_ERRORS() \ + __ASSERT(vm->fiber != NULL, "This function can only be called at runtime."); \ + __ASSERT(arg > 0 || arg < ARGC, "Invalid argument index."); \ + __ASSERT(value != NULL, "Parameter [value] was NULL."); \ + ((void*)0) + +#define ERR_INVALID_ARG_TYPE(m_type) \ +do { \ + /* 12 chars is enought for a 4 byte integer string.*/ \ + char buff[12]; \ + sprintf(buff, "%d", arg); \ + vm->fiber->error = stringFormat(vm, "Expected a " m_type \ + " at argument $.", buff); \ +} while (false) + +int pkGetArgc(PKVM* vm) { + __ASSERT(vm->fiber != NULL, "This function can only be called at runtime."); + return ARGC; +} + +PkVar pkGetArg(PKVM* vm, int arg) { + __ASSERT(vm->fiber != NULL, "This function can only be called at runtime."); + __ASSERT(arg > 0 || arg < ARGC, "Invalid argument index."); + + return &(ARG(arg)); +} + +bool pkGetArgNumber(PKVM* vm, int arg, double* value) { + CHECK_GET_ARG_API_ERRORS(); + + Var val = ARG(arg); + if (IS_NUM(val)) { + *value = AS_NUM(val); + + } else if (IS_BOOL(val)) { + *value = AS_BOOL(val) ? 1 : 0; + + } else { + ERR_INVALID_ARG_TYPE("number"); + return false; + } + + return true; +} + +bool pkGetArgBool(PKVM* vm, int arg, bool* value) { + CHECK_GET_ARG_API_ERRORS(); + + Var val = ARG(arg); + *value = toBool(val); + return true; +} + +bool pkGetArgValue(PKVM* vm, int arg, PkVarType type, PkVar* value) { + CHECK_GET_ARG_API_ERRORS(); + + Var val = ARG(arg); + if (pkGetValueType((PkVar)&val) != type) { + char buff[12]; sprintf(buff, "%d", arg); + vm->fiber->error = stringFormat(vm, + "Expected a $ at argument $.", getPkVarTypeName(type), buff); + return false; + } + + *value = (PkVar)&val; + return true; +} + +void pkReturnNull(PKVM* vm) { + RET(VAR_NULL); +} + +void pkReturnBool(PKVM* vm, bool value) { + RET(VAR_BOOL(value)); +} + +void pkReturnNumber(PKVM* vm, double value) { + RET(VAR_NUM(value)); +} + +void pkReturnValue(PKVM* vm, PkVar value) { + RET(*(Var*)value); +} + +// ---------------------------------------------------------------------------- + + // Convert number var as int32_t. Check if it's number before using it. #define _AS_INTEGER(var) (int32_t)trunc(AS_NUM(var)) @@ -104,23 +235,6 @@ static bool validateArgString(PKVM* vm, Var var, String** value, int arg_ind) { /* BUILTIN FUNCTIONS API */ /*****************************************************************************/ -// Argument getter (1 based). -#define ARG(n) vm->fiber->ret[n] - -// Convinent macros. -#define ARG1 ARG(1) -#define ARG2 ARG(2) -#define ARG3 ARG(3) - -// Argument count used in variadic functions. -#define ARGC ((int)(vm->fiber->sp - vm->fiber->ret) - 1) - -// Set return value. -#define RET(value) \ - do { \ - *(vm->fiber->ret) = value; \ - return; \ - } while (false) Function* getBuiltinFunction(PKVM* vm, int index) { ASSERT_INDEX(index, vm->builtins_count); @@ -276,25 +390,54 @@ void coreStrStrip(PKVM* vm) { } /*****************************************************************************/ -/* CORE LIBRARY METHODS */ +/* CORE MODULE METHODS */ /*****************************************************************************/ -// 'path' library methods. -// ----------------------- +// Create a module and add it to the vm's core modules, returns the script. +static Script* newModuleInternal(PKVM* vm, const char* name) { -// TODO: path library should be added by the cli (or the hosting application). -void stdPathAbspath(PKVM* vm) { - Var relpath = ARG1; - if (!IS_OBJ(relpath) || AS_OBJ(relpath)->type != OBJ_STRING) { - vm->fiber->error = newString(vm, "Expected a string at argument 1."); + // Create a new Script for the module. + String* _name = newString(vm, name); + vmPushTempRef(vm, &_name->_super); + + // Check if any module with the same name already exists and assert to the + // hosting application. + if (!IS_UNDEF(mapGet(vm->core_libs, VAR_OBJ(&_name->_super)))) { + vmPopTempRef(vm); // _name + __ASSERT(false, stringFormat(vm, "A module named '$' already exists", + name)->data); } - // TODO: abspath. - RET(VAR_OBJ(newString(vm, "TODO: abspath"))); + Script* scr = newScript(vm, _name); + scr->moudle = _name; + vmPopTempRef(vm); // _name + + // Add the script to core_libs. + vmPushTempRef(vm, &scr->_super); + mapSet(vm, vm->core_libs, VAR_OBJ(&_name->_super), VAR_OBJ(&scr->_super)); + vmPopTempRef(vm); + + return scr; } -void stdPathCurdir(PKVM* vm) { - RET(VAR_OBJ(newString(vm, "TODO: curdir"))); +static void moduleAddFunctionInternal(PKVM* vm, Script* script, + const char* name, pkNativeFn fptr, int arity) { + + // Check if function with the same name already exists. + if (scriptSearchFunc(script, name, (uint32_t)strlen(name)) != -1) { + __ASSERT(false, stringFormat(vm, "A function named '$' already esists " + "on module '@'", name, script->moudle)->data); + } + + // Check if a global variable with the same name already exists. + if (scriptSearchGlobals(script, name, (uint32_t)strlen(name)) != -1) { + __ASSERT(false, stringFormat(vm, "A global variable named '$' already " + "esists on module '@'", name, script->moudle)->data); + } + + Function* fn = newFunction(vm, name, (int)strlen(name), script, true); + fn->native = fptr; + fn->arity = arity; } // 'lang' library methods. @@ -367,40 +510,12 @@ void initializeCore(PKVM* vm) { INITALIZE_BUILTIN_FN("str_upper", coreStrUpper, 1); INITALIZE_BUILTIN_FN("str_strip", coreStrStrip, 1); - // Make STD scripts. - Script* std; // A temporary pointer to the current std script. - Function* fn; // A temporary pointer to the allocated function function. -#define STD_NEW_SCRIPT(_name) \ - do { \ - /* Create a new Script. */ \ - String* name = newString(vm, _name); \ - vmPushTempRef(vm, &name->_super); \ - std = newScript(vm, name); \ - std->moudle = name; /* Core libs's path and the module are the same. */ \ - vmPopTempRef(vm); \ - /* Add the script to core_libs. */ \ - vmPushTempRef(vm, &std->_super); \ - mapSet(vm, vm->core_libs, VAR_OBJ(&name->_super), VAR_OBJ(&std->_super)); \ - vmPopTempRef(vm); \ - } while (false) + // Core Modules ///////////////////////////////////////////////////////////// -#define STD_ADD_FUNCTION(_name, fptr, _arity) \ - do { \ - fn = newFunction(vm, _name, (int)strlen(_name), std, true); \ - fn->native = fptr; \ - fn->arity = _arity; \ - } while (false) - - // path - STD_NEW_SCRIPT("path"); - STD_ADD_FUNCTION("abspath", stdPathAbspath, 1); - STD_ADD_FUNCTION("curdir", stdPathCurdir, 0); - - // lang - STD_NEW_SCRIPT("lang"); - STD_ADD_FUNCTION("clock", stdLangClock, 0); - STD_ADD_FUNCTION("gc", stdLangGC, 0); - STD_ADD_FUNCTION("write", stdLangWrite, -1); + Script* lang = newModuleInternal(vm, "lang"); + moduleAddFunctionInternal(vm, lang, "clock", stdLangClock, 0); + moduleAddFunctionInternal(vm, lang, "gc", stdLangGC, 0); + moduleAddFunctionInternal(vm, lang, "write", stdLangWrite, -1); } /*****************************************************************************/ diff --git a/src/include/pocketlang.h b/src/include/pocketlang.h index 77b36a8..96cb98a 100644 --- a/src/include/pocketlang.h +++ b/src/include/pocketlang.h @@ -3,8 +3,8 @@ * Licensed under: MIT License */ -#ifndef MINISCRIPT_H -#define MINISCRIPT_H +#ifndef POCKETLANG_H +#define POCKETLANG_H #ifdef __cplusplus extern "C" { @@ -14,7 +14,14 @@ extern "C" { #include #include +/*****************************************************************************/ +/* POCKETLANG DEFINES */ +/*****************************************************************************/ + // The version number macros. +// Major Version - Increment when changes break compatibility. +// Minor Version - Increment when new functionality added to public api. +// Patch Version - Increment when bug fixed or minor changes between releases. #define PK_VERSION_MAJOR 0 #define PK_VERSION_MINOR 1 #define PK_VERSION_PATCH 0 @@ -22,8 +29,9 @@ extern "C" { // String representation of the value. #define PK_VERSION_STRING "0.1.0" -// miniscript visibility macros. define PK_DLL for using miniscript as a -// shared library and define PK_COMPILE to export symbols. +// Pocketlang visibility macros. define PK_DLL for using pocketlang as a +// shared library and define PK_COMPILE to export symbols when compiling the +// pocketlang it self as a shared library. #ifdef _MSC_VER #define _PK_EXPORT __declspec(dllexport) @@ -46,24 +54,72 @@ extern "C" { #define PK_PUBLIC #endif -// MiniScript Virtual Machine. -// it'll contain the state of the execution, stack, heap, and manage memory -// allocations. -typedef struct PKVM PKVM; +/*****************************************************************************/ +/* POCKETLANG TYPES */ +/*****************************************************************************/ // Nan-Tagging could be disable for debugging/portability purposes only when -// compiling the compiler. Do not change this if using the miniscript library +// compiling the compiler. Do not change this if using the pocketlang library // for embedding. To disable when compiling the compiler, define // `VAR_NAN_TAGGING 0`, otherwise it defaults to Nan-Tagging. #ifndef VAR_NAN_TAGGING #define VAR_NAN_TAGGING 1 #endif -#if VAR_NAN_TAGGING - typedef uint64_t Var; -#else - typedef struct Var Var; -#endif +// PocketLang Virtual Machine. It'll contain the state of the execution, stack, +// heap, and manage memory allocations. +typedef struct PKVM PKVM; + +// A handle to the pocketlang variables. It'll hold the reference to the +// variable and ensure that the variable it holds won't be garbage collected +// till it released with pkReleaseHandle(). +typedef struct PkHandle PkHandle; + +// A temproary pointer to the pocketlang variable. This pointer is aquired +// from the pocketlang's current stack frame and the pointer will become +// dangling once after the stack frame is popped. +typedef void* PkVar; + +// Type enum of the pocketlang varaibles, this can be used to get the type +// from a Var* in the method pkGetVarType(). +typedef enum { + PK_NULL, + PK_BOOL, + PK_NUMBER, + PK_STRING, + PK_LIST, + PK_MAP, + PK_RANGE, + PK_SCRIPT, + PK_FUNCTION, + PK_FIBER, +} PkVarType; + +typedef struct pkStringPtr pkStringPtr; +typedef struct pkConfiguration pkConfiguration; + +// Type of the error message that pocketlang will provide with the pkErrorFn +// callback. +typedef enum { + PK_ERROR_COMPILE = 0, // Compile time errors. + PK_ERROR_RUNTIME, // Runtime error message. + PK_ERROR_STACKTRACE, // One entry of a runtime error stack. +} PkErrorType; + +// Result that pocketlang will return after running a script or a function +// or evaluvating an expression. +typedef enum { + PK_RESULT_SUCCESS = 0, // Successfully finished the execution. + PK_RESULT_COMPILE_ERROR, // Compilation failed. + PK_RESULT_RUNTIME_ERROR, // An error occured at runtime. +} PkInterpretResult; + +/*****************************************************************************/ +/* POCKETLANG FUNCTION POINTERS & CALLBACKS */ +/*****************************************************************************/ + +// C function pointer which is callable from PocketLang. +typedef void (*pkNativeFn)(PKVM* vm); // A function that'll be called for all the allocation calls by PKVM. // @@ -77,43 +133,18 @@ typedef struct PKVM PKVM; // function will return NULL. typedef void* (*pkReallocFn)(void* memory, size_t new_size, void* user_data); -// C function pointer which is callable from MiniScript. -typedef void (*pkNativeFn)(PKVM* vm); - -typedef enum { - - // Compile time errors (syntax errors, unresolved fn, etc). - PK_ERROR_COMPILE = 0, - - // Runtime error message. - PK_ERROR_RUNTIME, - - // One entry of a runtime error stack. - PK_ERROR_STACKTRACE, -} PKErrorType; - // Error callback function pointer. for runtime error it'll call first with // PK_ERROR_RUNTIME followed by multiple callbacks with PK_ERROR_STACKTRACE. -typedef void (*pkErrorFn) (PKVM* vm, PKErrorType type, +typedef void (*pkErrorFn) (PKVM* vm, PkErrorType type, const char* file, int line, const char* message); // A function callback used by `print()` statement. typedef void (*pkWriteFn) (PKVM* vm, const char* text); -typedef struct pkStringPtr pkStringPtr; - // A function callback symbol for clean/free the pkStringResult. typedef void (*pkResultDoneFn) (PKVM* vm, pkStringPtr result); -// A string pointer wrapper to pass cstring around with a on_done() callback -// to clean it when the user of the string 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. -}; - // 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 @@ -126,11 +157,73 @@ typedef pkStringPtr(*pkResolvePathFn) (PKVM* vm, const char* from, // to indicate if it's failed to load the script. typedef pkStringPtr(*pkLoadScriptFn) (PKVM* vm, const char* path); -// This function will be called once it done with the loaded script only if -// it's corresponding MSLoadScriptResult is succeeded (ie. is_failed = false). -//typedef void (*pkLoadDoneFn) (PKVM* vm, pkStringResult result); +/*****************************************************************************/ +/* POCKETLANG PUBLIC API */ +/*****************************************************************************/ -typedef struct { +// Create a new pkConfiguraition with the default values and return it. +// Override those default configuration to adopt to another hosting +// application. +PK_PUBLIC pkConfiguration pkNewConfiguration(); + +// Allocate initialize and returns a new VM +PK_PUBLIC PKVM* pkNewVM(pkConfiguration* config); + +// Clean the VM and dispose all the resources allocated by the VM. +PK_PUBLIC void pkFreeVM(PKVM* vm); + +// Update the user data of the vm. +PK_PUBLIC void pkSetUserData(PKVM* vm, void* user_data); + +// Returns the associated user data. +PK_PUBLIC void* pkGetUserData(PKVM* vm); + +// Create a new handle for the [value]. This is usefull to keep the [value] +// alive once it aquired from the stack. Do not use the [value] once creating +// a new handle for it instead get the value from the handle by using +// pkGetHandleValue() function. +PK_PUBLIC PkHandle* pkNewHandle(PKVM* vm, PkVar value); + +// Return the PkVar pointer in the handle, the returned pointer will be valid +// till the handle is released. +PK_PUBLIC PkVar pkGetHandleValue(PkHandle* handle); + +// Release the handle and allow it's value to be garbage collected. Always call +// this for every handles before freeing the VM. +PK_PUBLIC void pkReleaseHandle(PKVM* vm, PkHandle* handle); + +// Add a new module named [name] to the [vm]. Note that the module shouldn't +// already existed, otherwise an assertion will fail to indicate that. +PK_PUBLIC PkHandle* pkNewModule(PKVM* vm, const char* name); + +// Add a native function to the given script. If [arity] is -1 that means +// The function has variadic parameters and use pkGetArgc() to get the argc. +PK_PUBLIC void pkModuleAddFunction(PKVM* vm, PkHandle* module, + const char* name, + pkNativeFn fptr, int arity); + +// Interpret the source and return the result. +PK_PUBLIC PkInterpretResult pkInterpretSource(PKVM* vm, + const char* source, + const char* path); + +// Compile and execut file at given path. +PK_PUBLIC PkInterpretResult pkInterpret(PKVM* vm, const char* path); + +/*****************************************************************************/ +/* POCKETLANG PUBLIC TYPE DEFINES */ +/*****************************************************************************/ + +// A string pointer wrapper to pass cstring from host application to pocketlang +// vm, with a on_done() callback to clean it when the pocketlang vm 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. +}; + +struct pkConfiguration { // The callback used to allocate, reallocate, and free. If the function // pointer is NULL it defaults to the VM's realloc(), free() wrappers. @@ -144,57 +237,72 @@ typedef struct { // User defined data associated with VM. void* user_data; +}; -} pkConfiguration; - -// Create a new pkConfiguraition with the default values and return it. -// Override those default configuration to adopt to another hosting -// application. -PK_PUBLIC pkConfiguration pkNewConfiguration(); - -typedef enum { - PK_RESULT_SUCCESS = 0, - PK_RESULT_COMPILE_ERROR, - PK_RESULT_RUNTIME_ERROR, -} PKInterpretResult; - -// Allocate initialize and returns a new VM -PK_PUBLIC PKVM* pkNewVM(pkConfiguration* config); - -// Clean the VM and dispose all the resources allocated by the VM. -PK_PUBLIC void pkFreeVM(PKVM* vm); - -// Interpret the source and return the result. -PK_PUBLIC PKInterpretResult pkInterpretSource(PKVM* vm, - const char* source, - const char* path); - -// Compile and execut file at given path. -PK_PUBLIC PKInterpretResult pkInterpret(PKVM* vm, const char* path); +/*****************************************************************************/ +/* NATIVE FUNCTION API */ +/*****************************************************************************/ // Set a runtime error to vm. PK_PUBLIC void pkSetRuntimeError(PKVM* vm, const char* message); -// Returns the associated user data. -PK_PUBLIC void* pkGetUserData(PKVM* vm); +// Return the type of the [value] this will help to get the type of the +// variable that was extracted from pkGetArg() earlier. +PK_PUBLIC PkVarType pkGetValueType(PkVar value); -// Update the user data of the vm. -PK_PUBLIC void pkSetUserData(PKVM* vm, void* user_data); +// Return the current functions argument count. This is needed for functions +// registered with -1 argument count (which means variadic arguments). +PK_PUBLIC int pkGetArgc(PKVM* vm); -// Encode types to var. -// TODO: user need to use vmPushTempRoot() for strings. -PK_PUBLIC Var pkVarBool(PKVM* vm, bool value); -PK_PUBLIC Var pkVarNumber(PKVM* vm, double value); -PK_PUBLIC Var pkVarString(PKVM* vm, const char* value); +// Return the [arg] th argument as a PkVar. This pointer will only be +// valid till the current function ends, because it points to the var at the +// stack and it'll popped when the current call frame ended. Use handlers to +// keep the var alive even after that. +PK_PUBLIC PkVar pkGetArg(PKVM* vm, int arg); + +// The below functions are used to extract the function arguments from the +// stack as a type. They will first validate the argument's type and set a +// runtime error if it's not and return false. Otherwise it'll set the [value] +// with the extracted value. Note that the arguments are 1 based (to get the +// first argument use 1 not 0). + +PK_PUBLIC bool pkGetArgBool(PKVM* vm, int arg, bool* vlaue); +PK_PUBLIC bool pkGetArgNumber(PKVM* vm, int arg, double* value); +PK_PUBLIC bool pkGetArgValue(PKVM* vm, int arg, PkVarType type, PkVar* value); + +// The below functions are used to set the return value of the current native +// function's. Don't use it outside a registered native function. + +PK_PUBLIC void pkReturnNull(PKVM* vm); +PK_PUBLIC void pkReturnBool(PKVM* vm, bool value); +PK_PUBLIC void pkReturnNumber(PKVM* vm, double value); +PK_PUBLIC void pkReturnValue(PKVM* vm, PkVar value); + +/*****************************************************************************/ +/* POCKETLANG TYPE FUNCTIONS */ +/*****************************************************************************/ + +// The below functions will allocate a new object and return's it's value +// wrapped around a handler. + +PK_PUBLIC PkHandle* pkNewString(PKVM* vm, const char* value); +PK_PUBLIC PkHandle* pkNewList(PKVM* vm); +PK_PUBLIC PkHandle* pkNewMap(PKVM* vm); + +PK_PUBLIC const char* pkStringGetData(PkVar value); + + +// TODO: +// The below functions will push the primitive values on the stack and return +// it's pointer as a PkVar it's usefull to convert your primitive values as +// pocketlang variables. +//PK_PUBLIC PkVar pkPushNull(PKVM* vm); +//PK_PUBLIC PkVar pkPushBool(PKVM* vm, bool value); +//PK_PUBLIC PkVar pkPushNumber(PKVM* vm, double value); -// Decode var types. -// TODO: const char* should be copied otherwise it'll become dangling pointer. -PK_PUBLIC bool pkAsBool(PKVM* vm, Var value); -PK_PUBLIC double pkAsNumber(PKVM* vm, Var value); -PK_PUBLIC const char* pkAsString(PKVM* vm, Var value); #ifdef __cplusplus } // extern "C" #endif -#endif // MINISCRIPT_H +#endif // POCKETLANG_H diff --git a/src/var.c b/src/var.c index 6f2dd98..5195d4b 100644 --- a/src/var.c +++ b/src/var.c @@ -9,32 +9,57 @@ #include "var.h" #include "vm.h" -// Public Api ///////////////////////////////////////////////////////////////// -Var pkVarBool(PKVM* vm, bool value) { - return VAR_BOOL(value); + /*****************************************************************************/ + /* PUBLIC API */ + /*****************************************************************************/ + +PkVarType pkGetValueType(PkVar value) { + __ASSERT(value != NULL, "Given value was NULL."); + + if (IS_NULL(*(Var*)(value))) return PK_NULL; + if (IS_BOOL(*(Var*)(value))) return PK_BOOL; + if (IS_NUM(*(Var*)(value))) return PK_NUMBER; + + __ASSERT(IS_OBJ(*(Var*)(value)), + "Invalid var pointer. Might be a dangling pointer"); + + Object* obj = AS_OBJ(*(Var*)(value)); + switch (obj->type) { + case OBJ_STRING: return PK_STRING; + case OBJ_LIST: return PK_LIST; + case OBJ_MAP: return PK_MAP; + case OBJ_RANGE: return PK_RANGE; + case OBJ_SCRIPT: return PK_SCRIPT; + case OBJ_FUNC: return PK_FUNCTION; + case OBJ_FIBER: return PK_FIBER; + } + + UNREACHABLE(); + return (PkVarType)0; } -Var pkVarNumber(PKVM* vm, double value) { - return VAR_NUM(value); +PkHandle* pkNewString(PKVM* vm, const char* value) { + return vmNewHandle(vm, VAR_OBJ(&newString(vm, value)->_super)); } -Var pkVarString(PKVM* vm, const char* value) { - return VAR_OBJ(newStringLength(vm, value, (uint32_t)strlen(value))); +PkHandle* pkNewList(PKVM* vm) { + return vmNewHandle(vm, VAR_OBJ(&newList(vm, MIN_CAPACITY)->_super)); } -bool pkAsBool(PKVM* vm, Var value) { - return AS_BOOL(value); +PkHandle* pkNewMap(PKVM* vm) { + return vmNewHandle(vm, VAR_OBJ(&newMap(vm)->_super)); } -double pkAsNumber(PKVM* vm, Var value) { - return AS_NUM(value); +const char* pkStringGetData(PkVar value) { + Var str = (*(Var*)value); + __ASSERT(IS_OBJ(str) && AS_OBJ(str)->type == OBJ_STRING, + "Value should be of type string."); + return ((String*)AS_OBJ(str))->data; } -const char* pkAsString(PKVM* vm, Var value) { - return AS_STRING(value)->data; -} - -/////////////////////////////////////////////////////////////////////////////// +/*****************************************************************************/ +/* VAR INTERNALS */ +/*****************************************************************************/ // Number of maximum digits for to_string buffer. #define TO_STRING_BUFF_SIZE 128 @@ -644,14 +669,26 @@ void freeObject(PKVM* vm, Object* self) { // Utility functions ////////////////////////////////////////////////////////// -const char* varTypeName(Var v) { - if (IS_NULL(v)) return "null"; - if (IS_BOOL(v)) return "bool"; - if (IS_NUM(v)) return "number"; +const char* getPkVarTypeName(PkVarType type) { + switch (type) { + case PK_NULL: return "null"; + case PK_BOOL: return "bool"; + case PK_NUMBER: return "number"; + case PK_STRING: return "String"; + case PK_LIST: return "List"; + case PK_MAP: return "Map"; + case PK_RANGE: return "Range"; + case PK_SCRIPT: return "Script"; + case PK_FUNCTION: return "Function"; + case PK_FIBER: return "Fiber"; + } - ASSERT(IS_OBJ(v), OOPS); - Object* obj = AS_OBJ(v); - switch (obj->type) { + UNREACHABLE(); + return NULL; +} + +const char* getObjectTypeName(ObjectType type) { + switch (type) { case OBJ_STRING: return "String"; case OBJ_LIST: return "List"; case OBJ_MAP: return "Map"; @@ -665,6 +702,16 @@ const char* varTypeName(Var v) { } } +const char* varTypeName(Var v) { + if (IS_NULL(v)) return "null"; + if (IS_BOOL(v)) return "bool"; + if (IS_NUM(v)) return "number"; + + ASSERT(IS_OBJ(v), OOPS); + Object* obj = AS_OBJ(v); + return getObjectTypeName(obj->type); +} + bool isValuesSame(Var v1, Var v2) { #if VAR_NAN_TAGGING // Bit representation of each values are unique so just compare the bits. diff --git a/src/var.h b/src/var.h index f3c9d2f..c3ab4a3 100644 --- a/src/var.h +++ b/src/var.h @@ -180,18 +180,15 @@ typedef struct { #endif // VAR_NAN_TAGGING -typedef enum /* ObjectType */ { +typedef enum { OBJ_STRING, OBJ_LIST, OBJ_MAP, OBJ_RANGE, - OBJ_SCRIPT, OBJ_FUNC, - OBJ_FIBER, - - // TODO: remove OBJ_USER and implement handlers for that. + // TODO: OBJ_USER, } ObjectType; @@ -433,6 +430,12 @@ void freeObject(PKVM* vm, Object* self); // Utility functions ////////////////////////////////////////////////////////// +// Returns the type name of the PkVarType enum value. +const char* getPkVarTypeName(PkVarType type); + +// Returns the type name of the ObjectType enum value. +const char* getObjectTypeName(ObjectType type); + // Returns the type name of the var [v]. const char* varTypeName(Var v); diff --git a/src/vm.c b/src/vm.c index e3d851c..82d1ef6 100644 --- a/src/vm.c +++ b/src/vm.c @@ -107,9 +107,41 @@ void pkFreeVM(PKVM* self) { self->gray_list = (Object**)self->config.realloc_fn( self->gray_list, 0, self->config.user_data); + // Tell the host application that it forget to release all of it's handles + // before freeing the VM. + __ASSERT(self->handles != NULL, "Not all handles were released."); + DEALLOCATE(self, self); } +PkHandle* pkNewHandle(PKVM* vm, PkVar value) { + return vmNewHandle(vm, *((Var*)value)); +} + +PkVar pkGetHandleValue(PkHandle* handle) { + return (PkVar)&handle->value; +} + +void pkReleaseHandle(PKVM* vm, PkHandle* handle) { + __ASSERT(handle != NULL, "Given handle was NULL."); + + // If the handle is the head of the vm's handle chain set it to the next one. + if (handle == vm->handles) { + vm->handles = handle->next; + } + + // Remove the handle from the chain by connecting the both ends together. + if (handle->next) handle->next->prev = handle->prev; + if (handle->prev) handle->prev->next = handle->next; + + // Free the handle. + DEALLOCATE(vm, handle); +} + +/*****************************************************************************/ +/* VM INTERNALS */ +/*****************************************************************************/ + void vmPushTempRef(PKVM* self, Object* obj) { ASSERT(obj != NULL, "Cannot reference to NULL."); ASSERT(self->temp_reference_count < MAX_TEMP_REFERENCE, @@ -122,6 +154,16 @@ void vmPopTempRef(PKVM* self) { self->temp_reference_count--; } +PkHandle* vmNewHandle(PKVM* self, Var value) { + PkHandle* handle = (PkHandle*)ALLOCATE(self, PkHandle); + handle->value = value; + handle->prev = NULL; + handle->next = self->handles; + if (handle->next != NULL) handle->next->prev = handle; + self->handles = handle; + return handle; +} + void vmCollectGarbage(PKVM* self) { // Reset VM's bytes_allocated value and count it again so that we don't @@ -142,6 +184,11 @@ void vmCollectGarbage(PKVM* self) { grayObject(self, self->temp_reference[i]); } + // Mark the handles. + for (PkHandle* handle = self->handles; handle != NULL; handle = handle->next) { + grayValue(self, handle->value); + } + // Garbage collection triggered at the middle of a compilation. if (self->compiler != NULL) { compilerMarkObjects(self, self->compiler); @@ -326,6 +373,7 @@ static inline void pushCallFrame(PKVM* vm, Function* fn) { } void pkSetRuntimeError(PKVM* vm, const char* message) { + __ASSERT(vm->fiber != NULL, "This function can only be called at runtime."); vm->fiber->error = newString(vm, message); } @@ -349,7 +397,7 @@ void vmReportError(PKVM* vm) { // This function is responsible to call on_done function if it's done with the // provided string pointers. -static PKInterpretResult interpretSource(PKVM* vm, pkStringPtr source, +static PkInterpretResult interpretSource(PKVM* vm, pkStringPtr source, pkStringPtr path) { String* path_name = newString(vm, path.string); if (path.on_done) path.on_done(vm, path); @@ -376,7 +424,7 @@ static PKInterpretResult interpretSource(PKVM* vm, pkStringPtr source, return vmRunScript(vm, scr); } -PKInterpretResult pkInterpretSource(PKVM* vm, const char* source, +PkInterpretResult pkInterpretSource(PKVM* vm, const char* source, const char* path) { // Call the internal interpretSource implementation. pkStringPtr source_ptr = { source, NULL, NULL }; @@ -384,7 +432,7 @@ PKInterpretResult pkInterpretSource(PKVM* vm, const char* source, return interpretSource(vm, source_ptr, path_ptr); } -PKInterpretResult pkInterpret(PKVM* vm, const char* path) { +PkInterpretResult pkInterpret(PKVM* vm, const char* path) { pkStringPtr resolved; resolved.string = path; @@ -413,7 +461,7 @@ PKInterpretResult pkInterpret(PKVM* vm, const char* path) { return interpretSource(vm, source, resolved); } -PKInterpretResult vmRunScript(PKVM* vm, Script* _script) { +PkInterpretResult vmRunScript(PKVM* vm, Script* _script) { // Reference to the instruction pointer in the call frame. register uint8_t** ip; diff --git a/src/vm.h b/src/vm.h index 883f238..b7da103 100644 --- a/src/vm.h +++ b/src/vm.h @@ -32,6 +32,14 @@ typedef struct { Function* fn; //< Native function pointer. } BuiltinFn; +// A doubly link list of vars that have reference in the host application. +struct PkHandle { + Var value; + + PkHandle* prev; + PkHandle* next; +}; + struct PKVM { // The first object in the link list of all heap allocated objects. @@ -61,6 +69,9 @@ struct PKVM { Object* temp_reference[MAX_TEMP_REFERENCE]; int temp_reference_count; + // Pointer to the first handle in the doubly linked list of handles. + PkHandle* handles; + // VM's configurations. pkConfiguration config; @@ -109,10 +120,14 @@ void vmPushTempRef(PKVM* self, Object* obj); // Pop the top most object from temporary reference stack. void vmPopTempRef(PKVM* self); +// Create and return a new handle for the [value]. +PkHandle* vmNewHandle(PKVM* self, Var value); + // Trigger garbage collection manually. void vmCollectGarbage(PKVM* self); + // Runs the script and return result. -PKInterpretResult vmRunScript(PKVM* vm, Script* script); +PkInterpretResult vmRunScript(PKVM* vm, Script* script); #endif // VM_H diff --git a/test/lang/import.pk b/test/lang/import.pk index 99b4030..4b38fff 100644 --- a/test/lang/import.pk +++ b/test/lang/import.pk @@ -13,7 +13,7 @@ from path import * import "basics.pk" ## will import all import "if.pk" as if_test -from "chain_call.pk" import fn1, fn2 as f2, fn3 +from "functions.pk" import fn1, fn2 as f2, fn3 ## If it has a module name it'll bind to that name. import 'import/module.pk'