Merge pull request #245 from ThakeeNathees/import-path

import system refactored
This commit is contained in:
Thakee Nathees 2022-05-30 17:34:55 +05:30 committed by GitHub
commit 3136eb4817
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 332 additions and 161 deletions

View File

@ -97,13 +97,7 @@ set addnl_cdefines=/D_CRT_SECURE_NO_WARNINGS
if "%debug_build%"=="false" ( if "%debug_build%"=="false" (
set cflags=%cflags% -O2 -MD /DNDEBUG
:: 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 target_dir=%pocket_root%build\Release\ set target_dir=%pocket_root%build\Release\
) else ( ) else (
set cflags=%cflags% -MDd -ZI set cflags=%cflags% -MDd -ZI

View File

@ -3,38 +3,45 @@
## Copyright (c) 2021-2022 Pocketlang Contributors ## Copyright (c) 2021-2022 Pocketlang Contributors
## Distributed Under The MIT License ## 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 import re, os
from os.path import (join, exists, abspath, from os.path import (join, exists, abspath,
relpath, dirname) relpath, dirname)
## The absolute path of this file, when run as a script. ## Pocket lang root directory. All the listed paths bellow are relative to
## This file is not intended to be included in other files at the moment. ## the root path.
THIS_PATH = abspath(dirname(__file__)) ROOT_PATH = abspath(join(dirname(__file__), "../"))
POCKET_HEADER = join(THIS_PATH, "../src/include/pocketlang.h") NATIVE_HEADER = "pknative.gen.h"
TARGET_NATIVE = join(THIS_PATH, "./modules/pknative.gen.c") NATIVE_SOURCE = "nativeapi.gen.h"
TARGET_DL = join(THIS_PATH, "./modules/std_dl_api.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 = "pk_api"
PK_API_TYPE = "PkNativeApi" PK_API_TYPE = "PkNativeApi"
PK_API_INIT = 'pkInitApi'
PK_EXPORT_MODULE = 'pkExportModule'
API_DEF = f'''\ API_DEF = f'''\
static {PK_API_TYPE} {PK_API}; 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) 2020-2022 Thakee Nathees
* Copyright (c) 2021-2022 Pocketlang Contributors * Copyright (c) 2021-2022 Pocketlang Contributors
* Distributed Under The MIT License * Distributed Under The MIT License
*/ */
#include <pocketlang.h> #ifndef PK_AMALGAMATED
#include <pocketlang.h>%s
#endif
// !! THIS FILE IS GENERATED DO NOT EDIT !! // !! 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) match = re.findall(r'^PK_PUBLIC [\S\n ]+?;', source, re.MULTILINE)
for m in match: for m in match:
definition = flaten(m) definition = flaten(m)
if '...' in definition: continue ## FIXME: VA ARGS
api_functions.append(parse_definition(definition)) api_functions.append(parse_definition(definition))
return api_functions return api_functions
@ -114,36 +122,46 @@ def init_api(api_functions):
return API_DEF % assign + '\n' return API_DEF % assign + '\n'
def make_api(api_functions): def make_api(api_functions):
source = "#if defined(NATIVE_API_IMPLEMENT)\n\n" source = f"{PK_API_TYPE} pkMakeNativeAPI() {{\n\n"
source += f"{PK_API_TYPE} dlMakeApi() {{\n\n"
source += f" {PK_API_TYPE} api;\n\n" source += f" {PK_API_TYPE} api;\n\n"
for fn, params, ret in api_functions: for fn, params, ret in api_functions:
source += f" api.{fn}_ptr = {fn};\n" source += f" api.{fn}_ptr = {fn};\n"
source += "\n" source += "\n"
source += " return api;\n" source += " return api;\n"
source += "}\n\n" source += "}\n\n"
source += "#endif // NATIVE_API_IMPLEMENT\n\n"
return source return source
def generate(): def generate():
api_functions = get_api_functions() api_functions = get_api_functions()
## Generate pocket native api. ## Generate pocket native header.
with open(TARGET_NATIVE, 'w') as fp: with open(TARGET_HEADER, 'w') as fp:
fp.write(SOURCE_GEN) fp.write(HEADER % '')
fp.write(fn_typedefs(api_functions)) fp.write(fn_typedefs(api_functions))
fp.write(api_typedef(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(init_api(api_functions))
fp.write(define_functions(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(fn_typedefs(api_functions))
fp.write(api_typedef(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(make_api(api_functions))
fp.write(f'#endif // {DL_IMPLEMENT}\n')
if __name__ == "__main__": if __name__ == "__main__":
generate() generate()
print("Generated:", relpath(TARGET_NATIVE, os.getcwd())) print("Generated:", relpath(TARGET_HEADER, ROOT_PATH))
print("Generated:", relpath(TARGET_DL, os.getcwd())) print("Generated:", relpath(TARGET_SOURCE, ROOT_PATH))

1
scripts/vs2019.bat Normal file
View File

@ -0,0 +1 @@
@premake5 vs2019

View File

@ -129,6 +129,8 @@ PKVM* pkNewVM(PkConfiguration* config) {
vm->heap_fill_percent = HEAP_FILL_PERCENT; vm->heap_fill_percent = HEAP_FILL_PERCENT;
vm->modules = newMap(vm); vm->modules = newMap(vm);
vm->search_paths = newList(vm, 8);
vm->builtins_count = 0; vm->builtins_count = 0;
// This is necessary to prevent garbage collection skip the entry in this // 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. 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) { PkHandle* pkNewModule(PKVM* vm, const char* name) {
CHECK_ARG_NULL(name); CHECK_ARG_NULL(name);
Module* module = newModuleInternal(vm, name); Module* module = newModuleInternal(vm, name);

View File

@ -127,8 +127,9 @@ void vmCollectGarbage(PKVM* vm) {
markObject(vm, &vm->builtin_classes[i]->_super); markObject(vm, &vm->builtin_classes[i]->_super);
} }
// Mark the modules. // Mark the modules and search path.
markObject(vm, &vm->modules->_super); markObject(vm, &vm->modules->_super);
markObject(vm, &vm->search_paths->_super);
// Mark temp references. // Mark temp references.
for (int i = 0; i < vm->temp_reference_count; i++) { 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 */ /* 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 // Import and return the Module object with the [path] string. If the path
// starts with with './' or '../' we'll only try relative imports, otherwise // starts with with './' or '../' we'll only try relative imports, otherwise
// we'll search native modules first and then at relative path. // 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. return entry; // We're done.
} }
} }
char* _resolved = NULL;
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 // If we reached here. It's not a native module (ie. module's absolute path
// is required to import and cache). // is required to import and cache).
char* _resolved = vm->config.resolve_path_fn(vm, _resolved = vm->config.resolve_path_fn(vm, from_path, path->data);
(from) ? from->data : NULL, 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. if (_resolved == NULL) { // Can't resolve a relative module.
pkRealloc(vm, _resolved, 0); pkRealloc(vm, _resolved, 0);
@ -412,10 +457,9 @@ Var vmImportModule(PKVM* vm, String* from, String* path) {
return entry; // We're done. 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 // The script not exists in the VM, make sure we have the script loading
// api function. // api function.
if (vm->config.load_script_fn == NULL) { if (vm->config.load_script_fn == NULL) {
VM_SET_ERROR(vm, newString(vm, "Cannot import. The hosting application " VM_SET_ERROR(vm, newString(vm, "Cannot import. The hosting application "
"haven't registered the module loading API")); "haven't registered the module loading API"));
@ -425,45 +469,25 @@ Var vmImportModule(PKVM* vm, String* from, String* path) {
Module* module = NULL; Module* module = NULL;
vmPushTempRef(vm, &resolved->_super); // resolved. 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.
//
// 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); // FIXME:
PkResult result = compile(vm, module, source, NULL); // stringReplace() function expect 2 strings old, and new to replace but
pkRealloc(vm, source, 0); // we cannot afford to allocate new strings for single char, so we're
if (result == PK_RESULT_SUCCESS) { // replaceing and rehashing the string here. I should add some update
vmRegisterModule(vm, module, resolved); // string function that update a string after it's data was modified.
} else { //
VM_SET_ERROR(vm, stringFormat(vm, "Error compiling module at \"@\"", // The path of the module contain '/' which was replacement of '.' in the
resolved)); // import syntax, this is done so that path resolving can be done easily.
module = NULL; //< set to null to indicate error. // 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 = '.';
} }
_name->hash = utilHashString(_name->data);
vmPushTempRef(vm, &_name->_super); // _name.
module = _importScript(vm, resolved, _name);
vmPopTempRef(vm); // _name.
} }
vmPopTempRef(vm); // module.
} while (false);
vmPopTempRef(vm); // resolved. vmPopTempRef(vm); // resolved.
if (module == NULL) { if (module == NULL) {

View File

@ -118,6 +118,9 @@ struct PKVM {
// - otherwise path of the module. // - otherwise path of the module.
Map* modules; Map* modules;
// List of directories that used for search modules.
List* search_paths;
// Array of all builtin functions. // Array of all builtin functions.
Closure* builtins_funcs[BUILTIN_FN_CAPACITY]; Closure* builtins_funcs[BUILTIN_FN_CAPACITY];
int builtins_count; int builtins_count;

View File

@ -35,21 +35,21 @@ extern "C" {
// pocketlang it self as a shared library. // pocketlang it self as a shared library.
#ifdef _MSC_VER #ifdef _MSC_VER
#define _PK_EXPORT __declspec(dllexport) #define PK_EXPORT __declspec(dllexport)
#define _PK_IMPORT __declspec(dllimport) #define PK_IMPORT __declspec(dllimport)
#elif defined(__GNUC__) #elif defined(__GNUC__)
#define _PK_EXPORT __attribute__((visibility ("default"))) #define PK_EXPORT __attribute__((visibility ("default")))
#define _PK_IMPORT #define PK_IMPORT
#else #else
#define _PK_EXPORT #define PK_EXPORT
#define _PK_IMPORT #define PK_IMPORT
#endif #endif
#ifdef PK_DLL #ifdef PK_DLL
#ifdef PK_COMPILE #ifdef PK_COMPILE
#define PK_PUBLIC _PK_EXPORT #define PK_PUBLIC PK_EXPORT
#else #else
#define PK_PUBLIC _PK_IMPORT #define PK_PUBLIC PK_IMPORT
#endif #endif
#else #else
#define PK_PUBLIC #define PK_PUBLIC
@ -110,10 +110,15 @@ typedef void (*pkSignalFn) (void*);
typedef char* (*pkLoadScriptFn) (PKVM* vm, const char* path); typedef char* (*pkLoadScriptFn) (PKVM* vm, const char* path);
// A function callback to resolve the import statement path. [from] path can // 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 // be either path to a script or a directory or NULL if [path] is relative to
// value should be a normalized absolute path of the [path]. Return NULL to // cwd. If the path is a directory it'll always ends with a path separator
// indicate failure to resolve. Othrewise the string **must** be allocated with // which could be either '/' or '\\' regardless of the system. Since pocketlang is
// pkRealloc() and the VM will claim the ownership of the string. // 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, typedef char* (*pkResolvePathFn) (PKVM* vm, const char* from,
const char* path); const char* path);
@ -218,6 +223,11 @@ PK_PUBLIC void* pkGetUserData(const PKVM* vm);
PK_PUBLIC void pkRegisterBuiltinFn(PKVM* vm, const char* name, pkNativeFn fn, PK_PUBLIC void pkRegisterBuiltinFn(PKVM* vm, const char* name, pkNativeFn fn,
int arity, const char* docstring); 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 // Invoke pocketlang's allocator directly. This function should be called
// when the host application want to send strings to the PKVM that are claimed // 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 // by the VM once the caller returned it. For other uses you **should** call

View File

@ -35,14 +35,9 @@
/* SHARED FUNCTIONS */ /* SHARED FUNCTIONS */
/*****************************************************************************/ /*****************************************************************************/
// These are "public" module functions that can be shared. Since some modules // These are "public" module functions that can be shared. Since each source
// can be used for cli's internals we're defining such functions here and they // files in this modules doesn't have their own headers we're using this as
// will be imported in the cli. // a common header for every one.
// 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);
// Register all the the libraries to the PKVM. // Register all the the libraries to the PKVM.
void registerLibs(PKVM* vm); void registerLibs(PKVM* vm);
@ -51,4 +46,13 @@ void registerLibs(PKVM* vm);
// with registerLibs() function. // with registerLibs() function.
void cleanupLibs(PKVM* vm); 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 #endif // LIBS_H

View File

@ -65,6 +65,30 @@
// See: https://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html // See: https://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html
#define MAX_PATH_LEN 4096 #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. // Yes both 'os' and 'path' have getcwd functions.
DEF(_osGetCWD, DEF(_osGetCWD,
"os.getcwd() -> String\n" "os.getcwd() -> String\n"
@ -186,15 +210,28 @@ DEF(_osGetenv,
pkSetSlotString(vm, 0, value); 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 */ /* MODULE REGISTER */
/*****************************************************************************/ /*****************************************************************************/
void registerModuleOS(PKVM* vm) { void registerModuleOS(PKVM* vm) {
pkReserveSlots(vm, 2);
PkHandle* os = pkNewModule(vm, "os"); PkHandle* os = pkNewModule(vm, "os");
pkReserveSlots(vm, 2);
pkSetSlotHandle(vm, 0, os); // slots[0] = os pkSetSlotHandle(vm, 0, os); // slots[0] = os
pkSetSlotString(vm, 1, OS_NAME); // slots[1] = "windows" pkSetSlotString(vm, 1, OS_NAME); // slots[1] = "windows"
pkSetAttribute(vm, 0, "NAME", 1); // os.NAME = "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, "filesize", _osFileSize, 1);
pkModuleAddFunction(vm, os, "system", _osSystem, 1); pkModuleAddFunction(vm, os, "system", _osSystem, 1);
pkModuleAddFunction(vm, os, "getenv", _osGetenv, 1); pkModuleAddFunction(vm, os, "getenv", _osGetenv, 1);
pkModuleAddFunction(vm, os, "exepath", _osExepath, 0);
// TODO: // TODO:
// - Implement makedirs which recursively mkdir(). // - Implement makedirs which recursively mkdir().

View File

@ -54,7 +54,7 @@
// value as needed. // value as needed.
#define MAX_JOIN_PATHS 8 #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 pathIsFile(const char* path);
static inline bool pathIsDir(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) { static char* tryImportPaths(PKVM* vm, char* path, char* buff) {
size_t path_size = 0; size_t path_size = 0;
// FIXME: review this code. static const char* EXT[] = {
do { "",
if ((path_size = checkImportExists(path, "", buff)) != 0) { ".pk",
break; "/_init.pk",
NULL, // Sentinal to mark the array end.
};
} else if ((path_size = checkImportExists(path, ".pk", buff)) != 0) { for (const char** ext = EXT; *ext != NULL; ext++) {
break; if ((path_size = checkImportExists(path, *ext, buff)) != 0) {
} else if ((path_size = checkImportExists(path, "/_init.pk", buff)) != 0) {
break; break;
} }
} while (false); }
char* ret = NULL; char* ret = NULL;
if (path_size != 0) { if (path_size != 0) {
@ -114,17 +114,6 @@ static char* tryImportPaths(PKVM* vm, char* path, char* buff) {
return ret; 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. // Implementation of pocketlang import path resolving function.
char* pathResolveImport(PKVM* vm, const char* from, const char* path) { 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. // buff1 = normalized path. +1 for null terminator.
cwk_path_normalize(path, buff1, sizeof(buff1)); cwk_path_normalize(path, buff1, sizeof(buff1));
pathFixWindowsSeperator(buff1);
return tryImportPaths(vm, buff1, buff2); 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. // buff2 = normalized path. +1 for null terminator.
cwk_path_normalize(buff1, buff2, sizeof(buff2)); cwk_path_normalize(buff1, buff2, sizeof(buff2));
pathFixWindowsSeperator(buff2);
return tryImportPaths(vm, buff2, buff1); return tryImportPaths(vm, buff2, buff1);
} }
// Import statements doesn't support absolute paths. // Regardless of the platform both '/' and '\\' will be used by pocketlang
ASSERT(cwk_path_is_absolute(from), "From path should be absolute."); // 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; 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; if (from_dir_length == 0) return NULL;
// buff1 = from directory.
strncpy(buff1, from, sizeof(buff1));
buff1[from_dir_length] = '\0'; buff1[from_dir_length] = '\0';
}
// buff2 = absolute joined path. // buff2 = absolute joined path.
cwk_path_join(buff1, path, buff2, sizeof(buff2)); cwk_path_join(buff1, path, buff2, sizeof(buff2));
// buff1 = normalized absolute path. +1 for null terminator // buff1 = normalized absolute path. +1 for null terminator
cwk_path_normalize(buff2, buff1, sizeof(buff1)); cwk_path_normalize(buff2, buff1, sizeof(buff1));
pathFixWindowsSeperator(buff1);
return tryImportPaths(vm, buff1, buff2); return tryImportPaths(vm, buff1, buff2);
}
// Cannot resolve the path. Return NULL to indicate failure.
return NULL;
} }
/*****************************************************************************/ /*****************************************************************************/
@ -208,7 +193,7 @@ static inline bool pathIsExists(const char* path) {
return access(path, F_OK) == 0; 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]; 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. // 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 */ /* 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) { void registerModulePath(PKVM* vm) {
_registerSearchPaths(vm);
PkHandle* path = pkNewModule(vm, "path"); PkHandle* path = pkNewModule(vm, "path");
pkModuleAddFunction(vm, path, "getcwd", _pathGetCWD, 0); pkModuleAddFunction(vm, path, "getcwd", _pathGetCWD, 0);

View File

@ -157,7 +157,7 @@ extern "C"
/** /**
* @brief Determines the root of a path. * @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 * root always starts at the submitted path. If the path has no root, the
* length will be set to zero. * length will be set to zero.
* *
@ -220,7 +220,8 @@ extern "C"
* *
* @param path The path which will be inspected. * @param path The path which will be inspected.
* @param basename The output of the basename pointer. * @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, CWK_PUBLIC void cwk_path_get_basename(const char* path, const char** basename,
size_t* length); 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, 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 // If the path style is UNIX, we will compare case sensitively. This can be
// done easily using strncmp. // done easily using strncmp.
if (path_style == CWK_STYLE_UNIX) { 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. // 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 // Since there is no standard method to do that we will have to do it on our
// own. // 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 // We can consider the string to be not equal if the two lowercase
// characters are not equal. // characters are not equal. The two chars may also be separators, which
if (tolower(*first++) != tolower(*second++)) { // 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; return false;
} }
--n; first++;
second++;
--first_size;
} }
// We can consider the string to be equal if we either reached n == 0 or both // The string must be equal since they both have the same length and all the
// cursors point to a null character. // characters are the same.
return n == 0 || (*first == '\0' && *second == '\0'); return true;
} }
static const char* cwk_path_find_next_stop(const char* c) 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 // First we check whether this is a CWK_CURRENT or CWK_BACK segment, since
// those will always be dropped. // those will always be dropped.
type = cwk_path_get_segment_type(&sj->segment); type = cwk_path_get_segment_type(&sj->segment);
if (type == CWK_CURRENT) { if (type == CWK_CURRENT || (type == CWK_BACK && absolute)) {
return true;
} else if (type == CWK_BACK && absolute) {
return true; return true;
} else if (type == CWK_BACK) { } else if (type == CWK_BACK) {
return cwk_path_segment_back_will_be_removed(&sjc); 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)) { if (cwk_path_is_separator(c)) {
++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. // is not a network path - just a normal path starting with a backslash.
if (!cwk_path_is_separator(c)) { if (!cwk_path_is_separator(c)) {
// Okay, this is not a network path but we still use the backslash as a // 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]); 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, static size_t cwk_path_join_and_normalize_multiple(const char** paths,
char* buffer, size_t buffer_size) 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. // later on whether we can remove superfluous "../" or not.
absolute = cwk_path_is_root_absolute(paths[0], pos); 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_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 // 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. // 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, // Compare the content of both segments. We are done if they are not equal,
// since they diverge. // since they diverge.
if (!cwk_path_is_string_equal(bsj->segment.begin, osj->segment.begin, if (!cwk_path_is_string_equal(bsj->segment.begin, osj->segment.begin,
bsj->segment.size)) { bsj->segment.size, osj->segment.size)) {
break; 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(base_directory, &base_root_length);
cwk_path_get_root(path, &path_root_length); cwk_path_get_root(path, &path_root_length);
if (base_root_length != 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); cwk_path_terminate_output(buffer, buffer_size, pos);
return 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. // to NULL and the length to 0.
if (!cwk_path_get_last_segment(path, &segment)) { if (!cwk_path_get_last_segment(path, &segment)) {
*basename = NULL; *basename = NULL;
if (length) {
*length = 0; *length = 0;
}
return; 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 // There might be trailing separators after the basename, but the size does
// not include those. // not include those.
*basename = segment.begin; *basename = segment.begin;
if (length) {
*length = segment.size; *length = segment.size;
}
} }
size_t cwk_path_change_basename(const char* path, const char* new_basename, 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. // absolute.
cwk_path_get_root(path_base, &base_root_length); cwk_path_get_root(path_base, &base_root_length);
cwk_path_get_root(path_other, &other_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; 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, 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 // So the content of those two segments are not equal. We will return the
// size up to the beginning. // size up to the beginning.
return (size_t)(end - path_base); return (size_t)(end - path_base);