diff --git a/scripts/build.bat b/scripts/build.bat index 99d5dba..e3c1706 100644 --- a/scripts/build.bat +++ b/scripts/build.bat @@ -97,13 +97,7 @@ set addnl_cdefines=/D_CRT_SECURE_NO_WARNINGS if "%debug_build%"=="false" ( - - :: Not sure why, but the release build are much slower with this script. - :: I might have to double check the compilation flags. - echo TODO: This Batch script doesn't support release builds for now - exit /b 1 - - set cflags=%cflags% -O2 -MD + set cflags=%cflags% -O2 -MD /DNDEBUG set target_dir=%pocket_root%build\Release\ ) else ( set cflags=%cflags% -MDd -ZI diff --git a/scripts/generate_native.py b/scripts/generate_native.py index cf2c1f3..78ea4cf 100644 --- a/scripts/generate_native.py +++ b/scripts/generate_native.py @@ -3,38 +3,45 @@ ## Copyright (c) 2021-2022 Pocketlang Contributors ## Distributed Under The MIT License -raise Exception("The paths of the files should be fixed " - "after this script is moved to scripts/ directory.") - import re, os from os.path import (join, exists, abspath, relpath, dirname) -## 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. -THIS_PATH = abspath(dirname(__file__)) +## Pocket lang root directory. All the listed paths bellow are relative to +## the root path. +ROOT_PATH = abspath(join(dirname(__file__), "../")) -POCKET_HEADER = join(THIS_PATH, "../src/include/pocketlang.h") -TARGET_NATIVE = join(THIS_PATH, "./modules/pknative.gen.c") -TARGET_DL = join(THIS_PATH, "./modules/std_dl_api.gen.h") +NATIVE_HEADER = "pknative.gen.h" +NATIVE_SOURCE = "nativeapi.gen.h" + +POCKET_HEADER = join(ROOT_PATH, f"src/include/pocketlang.h") +TARGET_HEADER = join(ROOT_PATH, f"src/include/{NATIVE_HEADER}") +TARGET_SOURCE = join(ROOT_PATH, f"src/core/{NATIVE_SOURCE}") +DL_IMPLEMENT = 'PK_DL_IMPLEMENT' PK_API = "pk_api" PK_API_TYPE = "PkNativeApi" +PK_API_INIT = 'pkInitApi' +PK_EXPORT_MODULE = 'pkExportModule' + API_DEF = f'''\ static {PK_API_TYPE} {PK_API}; -void pkInitApi({PK_API_TYPE}* api) {{%s +PK_EXPORT void {PK_API_INIT}({PK_API_TYPE}* api) {{%s }} ''' -SOURCE_GEN = f'''\ +## '%s' left for other includes. +HEADER = f'''\ /* * Copyright (c) 2020-2022 Thakee Nathees * Copyright (c) 2021-2022 Pocketlang Contributors * Distributed Under The MIT License */ -#include +#ifndef PK_AMALGAMATED +#include %s +#endif // !! THIS FILE IS GENERATED DO NOT EDIT !! @@ -77,6 +84,7 @@ def get_api_functions(): match = re.findall(r'^PK_PUBLIC [\S\n ]+?;', source, re.MULTILINE) for m in match: definition = flaten(m) + if '...' in definition: continue ## FIXME: VA ARGS api_functions.append(parse_definition(definition)) return api_functions @@ -114,36 +122,46 @@ def init_api(api_functions): return API_DEF % assign + '\n' def make_api(api_functions): - source = "#if defined(NATIVE_API_IMPLEMENT)\n\n" - source += f"{PK_API_TYPE} dlMakeApi() {{\n\n" + source = f"{PK_API_TYPE} pkMakeNativeAPI() {{\n\n" source += f" {PK_API_TYPE} api;\n\n" for fn, params, ret in api_functions: source += f" api.{fn}_ptr = {fn};\n" source += "\n" source += " return api;\n" source += "}\n\n" - source += "#endif // NATIVE_API_IMPLEMENT\n\n" return source def generate(): api_functions = get_api_functions() - ## Generate pocket native api. - with open(TARGET_NATIVE, 'w') as fp: - fp.write(SOURCE_GEN) + ## Generate pocket native header. + with open(TARGET_HEADER, 'w') as fp: + fp.write(HEADER % '') fp.write(fn_typedefs(api_functions)) fp.write(api_typedef(api_functions)) + fp.write(f'\n#if defined({DL_IMPLEMENT})\n\n') fp.write(init_api(api_functions)) fp.write(define_functions(api_functions)) + fp.write(f'\n#endif //{DL_IMPLEMENT}\n') + + ## Generate native api source. + with open(TARGET_SOURCE, 'w') as fp: + fp.write(HEADER % '') - ## Generate dl module api definition. - with open(TARGET_DL, 'w') as fp: - fp.write(SOURCE_GEN) fp.write(fn_typedefs(api_functions)) fp.write(api_typedef(api_functions)) + + fp.write(f'#define PK_API_INIT_FN_NAME "{PK_API_INIT}" \n') + fp.write(f'#define PK_EXPORT_FN_NAME "{PK_EXPORT_MODULE}" \n\n') + fp.write(f'typedef void (*{PK_API_INIT}Fn)({PK_API_TYPE}*);\n') + fp.write(f'typedef PkHandle* (*{PK_EXPORT_MODULE}Fn)(PKVM*);\n') + fp.write(f'\n') + + fp.write(f'#ifdef {DL_IMPLEMENT}\n\n') fp.write(make_api(api_functions)) + fp.write(f'#endif // {DL_IMPLEMENT}\n') if __name__ == "__main__": generate() - print("Generated:", relpath(TARGET_NATIVE, os.getcwd())) - print("Generated:", relpath(TARGET_DL, os.getcwd())) + print("Generated:", relpath(TARGET_HEADER, ROOT_PATH)) + print("Generated:", relpath(TARGET_SOURCE, ROOT_PATH)) diff --git a/scripts/vs2019.bat b/scripts/vs2019.bat new file mode 100644 index 0000000..59b973a --- /dev/null +++ b/scripts/vs2019.bat @@ -0,0 +1 @@ +@premake5 vs2019 diff --git a/src/core/public.c b/src/core/public.c index c969c10..8d259c9 100644 --- a/src/core/public.c +++ b/src/core/public.c @@ -129,6 +129,8 @@ PKVM* pkNewVM(PkConfiguration* config) { vm->heap_fill_percent = HEAP_FILL_PERCENT; vm->modules = newMap(vm); + vm->search_paths = newList(vm, 8); + vm->builtins_count = 0; // This is necessary to prevent garbage collection skip the entry in this @@ -201,6 +203,22 @@ void pkRegisterBuiltinFn(PKVM* vm, const char* name, pkNativeFn fn, vmPopTempRef(vm); // fptr. } +void pkAddSearchPath(PKVM* vm, const char* path) { + CHECK_ARG_NULL(path); + + size_t length = strlen(path); + ASSERT(length > 0, "Path size cannot be 0."); + + char last = path[length - 1]; + ASSERT(last == '/' || last == '\\', "Path should ends with " + "either '/' or '\\'."); + + String* spath = newStringLength(vm, path, (uint32_t) length); + vmPushTempRef(vm, &spath->_super); // spath. + listAppend(vm, vm->search_paths, VAR_OBJ(spath)); + vmPopTempRef(vm); // spath. +} + PkHandle* pkNewModule(PKVM* vm, const char* name) { CHECK_ARG_NULL(name); Module* module = newModuleInternal(vm, name); diff --git a/src/core/vm.c b/src/core/vm.c index 7ec8e6c..a946cc5 100644 --- a/src/core/vm.c +++ b/src/core/vm.c @@ -127,8 +127,9 @@ void vmCollectGarbage(PKVM* vm) { markObject(vm, &vm->builtin_classes[i]->_super); } - // Mark the modules. + // Mark the modules and search path. markObject(vm, &vm->modules->_super); + markObject(vm, &vm->search_paths->_super); // Mark temp references. for (int i = 0; i < vm->temp_reference_count; i++) { @@ -371,6 +372,37 @@ PkResult vmCallFunction(PKVM* vm, Closure* fn, int argc, Var* argv, Var* ret) { /* VM INTERNALS */ /*****************************************************************************/ +static Module* _importScript(PKVM* vm, String* resolved, String* name) { + char* source = vm->config.load_script_fn(vm, resolved->data); + if (source == NULL) { + VM_SET_ERROR(vm, stringFormat(vm, "Error loading module at \"@\"", + resolved)); + return NULL; + } + + // Make a new module, compile and cache it. + Module* module = newModule(vm); + module->path = resolved; + module->name = name; + + vmPushTempRef(vm, &module->_super); // module. + { + initializeModule(vm, module, false); + PkResult result = compile(vm, module, source, NULL); + pkRealloc(vm, source, 0); + if (result == PK_RESULT_SUCCESS) { + vmRegisterModule(vm, module, resolved); + } else { + VM_SET_ERROR(vm, stringFormat(vm, "Error compiling module at \"@\"", + resolved)); + module = NULL; //< set to null to indicate error. + } + } + vmPopTempRef(vm); // module. + + return module; +} + // Import and return the Module object with the [path] string. If the path // starts with with './' or '../' we'll only try relative imports, otherwise // we'll search native modules first and then at relative path. @@ -390,11 +422,24 @@ Var vmImportModule(PKVM* vm, String* from, String* path) { return entry; // We're done. } } + char* _resolved = NULL; - // If we reached here. It's not a native module (ie. module's absolute path - // is required to import and cache). - char* _resolved = vm->config.resolve_path_fn(vm, - (from) ? from->data : NULL, path->data); + const char* from_path = (from) ? from->data : NULL; + uint32_t search_path_idx = 0; + + do { + // If we reached here. It's not a native module (ie. module's absolute path + // is required to import and cache). + _resolved = vm->config.resolve_path_fn(vm, from_path, path->data); + if (_resolved) break; + + if (search_path_idx >= vm->search_paths->elements.count) break; + + Var sp = vm->search_paths->elements.data[search_path_idx++]; + ASSERT(IS_OBJ_TYPE(sp, OBJ_STRING), OOPS); + from_path = ((String*) AS_OBJ(sp))->data; + + } while (true); if (_resolved == NULL) { // Can't resolve a relative module. pkRealloc(vm, _resolved, 0); @@ -412,10 +457,9 @@ Var vmImportModule(PKVM* vm, String* from, String* path) { return entry; // We're done. } - // If we've reached here, the script is new. Import compile, and cache it. - // The script not exists in the VM, make sure we have the script loading // api function. + if (vm->config.load_script_fn == NULL) { VM_SET_ERROR(vm, newString(vm, "Cannot import. The hosting application " "haven't registered the module loading API")); @@ -425,45 +469,25 @@ Var vmImportModule(PKVM* vm, String* from, String* path) { Module* module = NULL; vmPushTempRef(vm, &resolved->_super); // resolved. - do { - - char* source = vm->config.load_script_fn(vm, resolved->data); - if (source == NULL) { - VM_SET_ERROR(vm, stringFormat(vm, "Error loading module at \"@\"", - resolved)); - break; - } - - // Make a new module, compile and cache it. - module = newModule(vm); - module->path = resolved; - + { // FIXME: - // __name__ will contain '/' instead of '.', To fix this I should use - // stringReplace with old = '/', new = '.' and the parameters should be - // string objects which should be allocated in VM statically for single - // char strings. + // stringReplace() function expect 2 strings old, and new to replace but + // we cannot afford to allocate new strings for single char, so we're + // replaceing and rehashing the string here. I should add some update + // string function that update a string after it's data was modified. // - // path here is the imported symbol not the actual path, - // Example: "foo/bar" which was imported as "foo.bar" - module->name = path; - - vmPushTempRef(vm, &module->_super); // module. - { - initializeModule(vm, module, false); - PkResult result = compile(vm, module, source, NULL); - pkRealloc(vm, source, 0); - if (result == PK_RESULT_SUCCESS) { - vmRegisterModule(vm, module, resolved); - } else { - VM_SET_ERROR(vm, stringFormat(vm, "Error compiling module at \"@\"", - resolved)); - module = NULL; //< set to null to indicate error. - } + // The path of the module contain '/' which was replacement of '.' in the + // import syntax, this is done so that path resolving can be done easily. + // However it needs to be '.' for the name of the module. + String* _name = newStringLength(vm, path->data, path->length); + for (char* c = _name->data; c < _name->data + _name->length; c++) { + if (*c == '/') *c = '.'; } - vmPopTempRef(vm); // module. - - } while (false); + _name->hash = utilHashString(_name->data); + vmPushTempRef(vm, &_name->_super); // _name. + module = _importScript(vm, resolved, _name); + vmPopTempRef(vm); // _name. + } vmPopTempRef(vm); // resolved. if (module == NULL) { diff --git a/src/core/vm.h b/src/core/vm.h index ca0ccdf..b51cea9 100644 --- a/src/core/vm.h +++ b/src/core/vm.h @@ -118,6 +118,9 @@ struct PKVM { // - otherwise path of the module. Map* modules; + // List of directories that used for search modules. + List* search_paths; + // Array of all builtin functions. Closure* builtins_funcs[BUILTIN_FN_CAPACITY]; int builtins_count; diff --git a/src/include/pocketlang.h b/src/include/pocketlang.h index 754f2b5..5efdc41 100644 --- a/src/include/pocketlang.h +++ b/src/include/pocketlang.h @@ -35,21 +35,21 @@ extern "C" { // pocketlang it self as a shared library. #ifdef _MSC_VER - #define _PK_EXPORT __declspec(dllexport) - #define _PK_IMPORT __declspec(dllimport) + #define PK_EXPORT __declspec(dllexport) + #define PK_IMPORT __declspec(dllimport) #elif defined(__GNUC__) - #define _PK_EXPORT __attribute__((visibility ("default"))) - #define _PK_IMPORT + #define PK_EXPORT __attribute__((visibility ("default"))) + #define PK_IMPORT #else - #define _PK_EXPORT - #define _PK_IMPORT + #define PK_EXPORT + #define PK_IMPORT #endif #ifdef PK_DLL #ifdef PK_COMPILE - #define PK_PUBLIC _PK_EXPORT + #define PK_PUBLIC PK_EXPORT #else - #define PK_PUBLIC _PK_IMPORT + #define PK_PUBLIC PK_IMPORT #endif #else #define PK_PUBLIC @@ -110,12 +110,17 @@ typedef void (*pkSignalFn) (void*); typedef char* (*pkLoadScriptFn) (PKVM* vm, const char* path); // A function callback to resolve the import statement path. [from] path can -// be either path to a script or NULL if [path] is relative to cwd. The return -// value should be a normalized absolute path of the [path]. Return NULL to -// indicate failure to resolve. Othrewise the string **must** be allocated with -// pkRealloc() and the VM will claim the ownership of the string. +// be either path to a script or a directory or NULL if [path] is relative to +// cwd. If the path is a directory it'll always ends with a path separator +// which could be either '/' or '\\' regardless of the system. Since pocketlang is +// un aware of the system, to indicate that the path is a directory. +// +// The return value should be a normalized absolute path of the [path]. Return +// NULL to indicate failure to resolve. Othrewise the string **must** be +// allocated with pkRealloc() and the VM will claim the ownership of the +// string. typedef char* (*pkResolvePathFn) (PKVM* vm, const char* from, - const char* path); + const char* path); // A function callback to allocate and return a new instance of the registered // class. Which will be called when the instance is constructed. The returned/ @@ -218,6 +223,11 @@ PK_PUBLIC void* pkGetUserData(const PKVM* vm); PK_PUBLIC void pkRegisterBuiltinFn(PKVM* vm, const char* name, pkNativeFn fn, int arity, const char* docstring); +// Adds a new search paht to the VM, the path will be appended to the list of +// search paths. Search path orders are the same as the registered order. +// the last character of the path **must** be a path seperator '/' or '\\'. +PK_PUBLIC void pkAddSearchPath(PKVM* vm, const char* path); + // Invoke pocketlang's allocator directly. This function should be called // when the host application want to send strings to the PKVM that are claimed // by the VM once the caller returned it. For other uses you **should** call diff --git a/src/libs/libs.h b/src/libs/libs.h index a00e7b1..9243e42 100644 --- a/src/libs/libs.h +++ b/src/libs/libs.h @@ -35,14 +35,9 @@ /* SHARED FUNCTIONS */ /*****************************************************************************/ -// These are "public" module functions that can be shared. Since some modules -// can be used for cli's internals we're defining such functions here and they -// will be imported in the cli. - -// The pocketlang's import statement path resolving function. This -// implementation is required by pockelang from it's hosting application -// inorder to use the import statements. -char* pathResolveImport(PKVM * vm, const char* from, const char* path); +// These are "public" module functions that can be shared. Since each source +// files in this modules doesn't have their own headers we're using this as +// a common header for every one. // Register all the the libraries to the PKVM. void registerLibs(PKVM* vm); @@ -51,4 +46,13 @@ void registerLibs(PKVM* vm); // with registerLibs() function. void cleanupLibs(PKVM* vm); +// The pocketlang's import statement path resolving function. This +// implementation is required by pockelang from it's hosting application +// inorder to use the import statements. +char* pathResolveImport(PKVM * vm, const char* from, const char* path); + +// Write the executable's path to the buffer and return true, if it failed +// it'll return false. +bool osGetExeFilePath(char* buff, int size); + #endif // LIBS_H diff --git a/src/libs/std_os.c b/src/libs/std_os.c index de08519..16e5144 100644 --- a/src/libs/std_os.c +++ b/src/libs/std_os.c @@ -65,6 +65,30 @@ // See: https://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html #define MAX_PATH_LEN 4096 +bool osGetExeFilePath(char* buff, int size) { + +#if defined(_PKOS_WIN_) + int bytes = GetModuleFileNameA(NULL, buff, size); + ASSERT(bytes > 0, "GetModuleFileName failed."); + return true; + +#elif defined(_PKOS_APPLE_) + unsigned sz = size; + _NSGetExecutablePath(buff, &sz); + return true; + +#elif defined(_PKOS_LINUX_) + char tmp[MAX_PATH_LEN]; + sprintf(tmp, "/proc/%d/exe", getpid()); + int len = readlink(tmp, buff, size); + buff[len] = '\0'; + return true; + +#else + return false; +#endif +} + // Yes both 'os' and 'path' have getcwd functions. DEF(_osGetCWD, "os.getcwd() -> String\n" @@ -186,15 +210,28 @@ DEF(_osGetenv, pkSetSlotString(vm, 0, value); } +DEF(_osExepath, + "os.exepath() -> String\n" + "Returns the path of the pocket interpreter executable.") { + + char buff[MAX_PATH_LEN]; + if (!osGetExeFilePath(buff, MAX_PATH_LEN)) { + pkSetRuntimeError(vm, "Cannot obtain ececutable path."); + return; + } + + pkSetSlotString(vm, 0, buff); +} + /*****************************************************************************/ /* MODULE REGISTER */ /*****************************************************************************/ void registerModuleOS(PKVM* vm) { - pkReserveSlots(vm, 2); PkHandle* os = pkNewModule(vm, "os"); + pkReserveSlots(vm, 2); pkSetSlotHandle(vm, 0, os); // slots[0] = os pkSetSlotString(vm, 1, OS_NAME); // slots[1] = "windows" pkSetAttribute(vm, 0, "NAME", 1); // os.NAME = "windows" @@ -208,6 +245,7 @@ void registerModuleOS(PKVM* vm) { pkModuleAddFunction(vm, os, "filesize", _osFileSize, 1); pkModuleAddFunction(vm, os, "system", _osSystem, 1); pkModuleAddFunction(vm, os, "getenv", _osGetenv, 1); + pkModuleAddFunction(vm, os, "exepath", _osExepath, 0); // TODO: // - Implement makedirs which recursively mkdir(). diff --git a/src/libs/std_path.c b/src/libs/std_path.c index b9adc83..dc8d6a5 100644 --- a/src/libs/std_path.c +++ b/src/libs/std_path.c @@ -54,7 +54,7 @@ // value as needed. #define MAX_JOIN_PATHS 8 -static inline size_t pathAbs(const char* path, char* buff, size_t buff_size); +static inline size_t pathAbs(const char* path, char* buff, size_t buffsz); static inline bool pathIsFile(const char* path); static inline bool pathIsDir(const char* path); @@ -93,18 +93,18 @@ static inline size_t checkImportExists(char* path, const char* ext, static char* tryImportPaths(PKVM* vm, char* path, char* buff) { size_t path_size = 0; - // FIXME: review this code. - do { - if ((path_size = checkImportExists(path, "", buff)) != 0) { - break; + static const char* EXT[] = { + "", + ".pk", + "/_init.pk", + NULL, // Sentinal to mark the array end. + }; - } else if ((path_size = checkImportExists(path, ".pk", buff)) != 0) { - break; - - } else if ((path_size = checkImportExists(path, "/_init.pk", buff)) != 0) { + for (const char** ext = EXT; *ext != NULL; ext++) { + if ((path_size = checkImportExists(path, *ext, buff)) != 0) { break; } - } while (false); + } char* ret = NULL; if (path_size != 0) { @@ -114,17 +114,6 @@ static char* tryImportPaths(PKVM* vm, char* path, char* buff) { return ret; } -// replace all the '\\' with '/' to make all the seperator in a path is the -// same this is only used in import path system to make the path of a module -// unique (otherwise same path with different seperator makes them different). -void pathFixWindowsSeperator(char* buff) { - ASSERT(buff, OOPS); - while (*buff != '\0') { - if (*buff == '\\') *buff = '/'; - buff++; - } -} - // Implementation of pocketlang import path resolving function. char* pathResolveImport(PKVM* vm, const char* from, const char* path) { @@ -138,7 +127,6 @@ char* pathResolveImport(PKVM* vm, const char* from, const char* path) { // buff1 = normalized path. +1 for null terminator. cwk_path_normalize(path, buff1, sizeof(buff1)); - pathFixWindowsSeperator(buff1); return tryImportPaths(vm, buff1, buff2); } @@ -150,36 +138,33 @@ char* pathResolveImport(PKVM* vm, const char* from, const char* path) { // buff2 = normalized path. +1 for null terminator. cwk_path_normalize(buff1, buff2, sizeof(buff2)); - pathFixWindowsSeperator(buff2); return tryImportPaths(vm, buff2, buff1); } - // Import statements doesn't support absolute paths. - ASSERT(cwk_path_is_absolute(from), "From path should be absolute."); + // Regardless of the platform both '/' and '\\' will be used by pocketlang + // to indicate its the path of a directory. + char last = from[strlen(from) - 1]; - // From is a path of a script. Try relative to that script. - { + // buff1 = absolute path of [from]. + pathAbs(from, buff1, sizeof(buff1)); + + // If the [from] path isn't a directory we use the dirname of the from + // script. + if (last != '/' && last != '\\') { size_t from_dir_length = 0; - cwk_path_get_dirname(from, &from_dir_length); + cwk_path_get_dirname(buff1, &from_dir_length); if (from_dir_length == 0) return NULL; - - // buff1 = from directory. - strncpy(buff1, from, sizeof(buff1)); buff1[from_dir_length] = '\0'; - - // buff2 = absolute joined path. - cwk_path_join(buff1, path, buff2, sizeof(buff2)); - - // buff1 = normalized absolute path. +1 for null terminator - cwk_path_normalize(buff2, buff1, sizeof(buff1)); - pathFixWindowsSeperator(buff1); - - return tryImportPaths(vm, buff1, buff2); } - // Cannot resolve the path. Return NULL to indicate failure. - return NULL; + // buff2 = absolute joined path. + cwk_path_join(buff1, path, buff2, sizeof(buff2)); + + // buff1 = normalized absolute path. +1 for null terminator + cwk_path_normalize(buff2, buff1, sizeof(buff1)); + + return tryImportPaths(vm, buff1, buff2); } /*****************************************************************************/ @@ -208,7 +193,7 @@ static inline bool pathIsExists(const char* path) { return access(path, F_OK) == 0; } -static inline size_t pathAbs(const char* path, char* buff, size_t buff_size) { +static inline size_t pathAbs(const char* path, char* buff, size_t buffsz) { char cwd[MAX_PATH_LEN]; @@ -216,7 +201,7 @@ static inline size_t pathAbs(const char* path, char* buff, size_t buff_size) { // TODO: handle error. } - return cwk_path_get_absolute(cwd, path, buff, buff_size); + return cwk_path_get_absolute(cwd, path, buff, buffsz); } /*****************************************************************************/ @@ -379,7 +364,39 @@ DEF(_pathListDir, "") { /* MODULE REGISTER */ /*****************************************************************************/ +// Add the executables path and exe_path + 'libs/' as a search path for +// the PKVM. +void _registerSearchPaths(PKVM* vm) { + char buff[MAX_PATH_LEN]; + if (!osGetExeFilePath(buff, MAX_PATH_LEN)) return; + size_t length; + cwk_path_get_dirname(buff, &length); + if (length == 0) return; + + enum cwk_path_style ps = cwk_path_get_style(); + + // Add path separator. Otherwise pkAddSearchPath will fail an assertion. + char last = buff[length - 1]; + if (last != '/' && last != '\\') { + last = (ps == CWK_STYLE_WINDOWS) ? '\\' : '/'; + buff[length++] = last; + } + + buff[length] = '\0'; + + pkAddSearchPath(vm, buff); + + // FIXME: the bellow code is hard coded. + if (length + strlen("libs/") < MAX_PATH_LEN) { + strcpy(buff + length, (ps == CWK_STYLE_WINDOWS) ? "libs\\" : "libs/"); + pkAddSearchPath(vm, buff); + } +} + void registerModulePath(PKVM* vm) { + + _registerSearchPaths(vm); + PkHandle* path = pkNewModule(vm, "path"); pkModuleAddFunction(vm, path, "getcwd", _pathGetCWD, 0); diff --git a/src/libs/thirdparty/cwalk/cwalk.h b/src/libs/thirdparty/cwalk/cwalk.h index 4b2d1bd..4d33a1c 100644 --- a/src/libs/thirdparty/cwalk/cwalk.h +++ b/src/libs/thirdparty/cwalk/cwalk.h @@ -157,7 +157,7 @@ extern "C" /** * @brief Determines the root of a path. * - * This function determines the root of a path by finding it's length. The + * This function determines the root of a path by finding its length. The * root always starts at the submitted path. If the path has no root, the * length will be set to zero. * @@ -220,7 +220,8 @@ extern "C" * * @param path The path which will be inspected. * @param basename The output of the basename pointer. - * @param length The output of the length of the basename. + * @param length The output of the length of the basename. This may be + * null if not required. */ CWK_PUBLIC void cwk_path_get_basename(const char* path, const char** basename, size_t* length); @@ -617,30 +618,44 @@ static void cwk_path_terminate_output(char* buffer, size_t buffer_size, } static bool cwk_path_is_string_equal(const char* first, const char* second, - size_t n) + size_t first_size, size_t second_size) { + bool are_both_separators; + + // The two strings are not equal if the sizes are not equal. + if (first_size != second_size) { + return false; + } + // If the path style is UNIX, we will compare case sensitively. This can be // done easily using strncmp. if (path_style == CWK_STYLE_UNIX) { - return strncmp(first, second, n) == 0; + return strncmp(first, second, first_size) == 0; } // However, if this is windows we will have to compare case insensitively. // Since there is no standard method to do that we will have to do it on our // own. - while (*first && *second && n > 0) { + while (*first && *second && first_size > 0) { // We can consider the string to be not equal if the two lowercase - // characters are not equal. - if (tolower(*first++) != tolower(*second++)) { + // characters are not equal. The two chars may also be separators, which + // means they would be equal. + are_both_separators = strchr(separators[path_style], *first) != NULL && + strchr(separators[path_style], *second) != NULL; + + if (tolower(*first) != tolower(*second) && !are_both_separators) { return false; } - --n; + first++; + second++; + + --first_size; } - // We can consider the string to be equal if we either reached n == 0 or both - // cursors point to a null character. - return n == 0 || (*first == '\0' && *second == '\0'); + // The string must be equal since they both have the same length and all the + // characters are the same. + return true; } static const char* cwk_path_find_next_stop(const char* c) @@ -932,9 +947,7 @@ cwk_path_segment_will_be_removed(const struct cwk_segment_joined* sj, // First we check whether this is a CWK_CURRENT or CWK_BACK segment, since // those will always be dropped. type = cwk_path_get_segment_type(&sj->segment); - if (type == CWK_CURRENT) { - return true; - } else if (type == CWK_BACK && absolute) { + if (type == CWK_CURRENT || (type == CWK_BACK && absolute)) { return true; } else if (type == CWK_BACK) { return cwk_path_segment_back_will_be_removed(&sjc); @@ -974,7 +987,7 @@ static void cwk_path_get_root_windows(const char* path, size_t* length) if (cwk_path_is_separator(c)) { ++c; - // Check whether the path starts with a single back slash, which means this + // Check whether the path starts with a single backslash, which means this // is not a network path - just a normal path starting with a backslash. if (!cwk_path_is_separator(c)) { // Okay, this is not a network path but we still use the backslash as a @@ -1062,6 +1075,29 @@ static bool cwk_path_is_root_absolute(const char* path, size_t length) return cwk_path_is_separator(&path[length - 1]); } +static void cwk_path_fix_root(char* buffer, size_t buffer_size, size_t length) +{ + size_t i; + + // This only affects windows. + if (path_style != CWK_STYLE_WINDOWS) { + return; + } + + // Make sure we are not writing further than we are actually allowed to. + if (length > buffer_size) { + length = buffer_size; + } + + // Replace all forward slashes with backwards slashes. Since this is windows + // we can't have any forward slashes in the root. + for (i = 0; i < length; ++i) { + if (cwk_path_is_separator(&buffer[i])) { + buffer[i] = *separators[CWK_STYLE_WINDOWS]; + } + } +} + static size_t cwk_path_join_and_normalize_multiple(const char** paths, char* buffer, size_t buffer_size) { @@ -1076,8 +1112,10 @@ static size_t cwk_path_join_and_normalize_multiple(const char** paths, // later on whether we can remove superfluous "../" or not. absolute = cwk_path_is_root_absolute(paths[0], pos); - // First copy the root to the output. We will not modify the root. + // First copy the root to the output. After copying, we will normalize the + // root. cwk_path_output_sized(buffer, buffer_size, 0, paths[0], pos); + cwk_path_fix_root(buffer, buffer_size, pos); // So we just grab the first segment. If there is no segment we will always // output a "/", since we currently only support absolute paths here. @@ -1196,7 +1234,7 @@ static void cwk_path_skip_segments_until_diverge(struct cwk_segment_joined* bsj, // Compare the content of both segments. We are done if they are not equal, // since they diverge. if (!cwk_path_is_string_equal(bsj->segment.begin, osj->segment.begin, - bsj->segment.size)) { + bsj->segment.size, osj->segment.size)) { break; } @@ -1224,7 +1262,8 @@ size_t cwk_path_get_relative(const char* base_directory, const char* path, cwk_path_get_root(base_directory, &base_root_length); cwk_path_get_root(path, &path_root_length); if (base_root_length != path_root_length || - !cwk_path_is_string_equal(base_directory, path, base_root_length)) { + !cwk_path_is_string_equal(base_directory, path, base_root_length, + path_root_length)) { cwk_path_terminate_output(buffer, buffer_size, pos); return pos; } @@ -1411,7 +1450,9 @@ void cwk_path_get_basename(const char* path, const char** basename, // to NULL and the length to 0. if (!cwk_path_get_last_segment(path, &segment)) { *basename = NULL; - *length = 0; + if (length) { + *length = 0; + } return; } @@ -1419,7 +1460,9 @@ void cwk_path_get_basename(const char* path, const char** basename, // There might be trailing separators after the basename, but the size does // not include those. *basename = segment.begin; - *length = segment.size; + if (length) { + *length = segment.size; + } } size_t cwk_path_change_basename(const char* path, const char* new_basename, @@ -1621,7 +1664,8 @@ size_t cwk_path_get_intersection(const char* path_base, const char* path_other) // absolute. cwk_path_get_root(path_base, &base_root_length); cwk_path_get_root(path_other, &other_root_length); - if (!cwk_path_is_string_equal(path_base, path_other, base_root_length)) { + if (!cwk_path_is_string_equal(path_base, path_other, base_root_length, + other_root_length)) { return 0; } @@ -1660,7 +1704,7 @@ size_t cwk_path_get_intersection(const char* path_base, const char* path_other) } if (!cwk_path_is_string_equal(base.segment.begin, other.segment.begin, - base.segment.size)) { + base.segment.size, other.segment.size)) { // So the content of those two segments are not equal. We will return the // size up to the beginning. return (size_t)(end - path_base);