mirror of
https://github.com/zekexiao/pocketlang.git
synced 2025-03-04 05:05:57 +08:00
import system refactored
- using normalized windows path in windows for import search.
- search path implemented, executable's path, exe/libs/ directories
added to the search path.
- cwalk library updated after buf fix:08e7520d33
- dl library support added and will be implemented.
- generate_native.py refactored.
This commit is contained in:
parent
b30dfc8e4f
commit
1255db4bc8
@ -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
|
||||
|
@ -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 <pocketlang.h>
|
||||
#ifndef PK_AMALGAMATED
|
||||
#include <pocketlang.h>%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))
|
||||
|
1
scripts/vs2019.bat
Normal file
1
scripts/vs2019.bat
Normal file
@ -0,0 +1 @@
|
||||
@premake5 vs2019
|
@ -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);
|
||||
|
106
src/core/vm.c
106
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;
|
||||
|
||||
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).
|
||||
char* _resolved = vm->config.resolve_path_fn(vm,
|
||||
(from) ? from->data : NULL, path->data);
|
||||
_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.
|
||||
//
|
||||
// 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.
|
||||
// FIXME:
|
||||
// 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.
|
||||
//
|
||||
// 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 = '.';
|
||||
}
|
||||
_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.
|
||||
|
||||
if (module == NULL) {
|
||||
|
@ -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;
|
||||
|
@ -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,10 +110,15 @@ 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);
|
||||
|
||||
@ -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
|
||||
|
@ -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
|
||||
|
@ -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().
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
/*****************************************************************************/
|
||||
@ -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);
|
||||
|
84
src/libs/thirdparty/cwalk/cwalk.h
vendored
84
src/libs/thirdparty/cwalk/cwalk.h
vendored
@ -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;
|
||||
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;
|
||||
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);
|
||||
|
Loading…
Reference in New Issue
Block a user