From 67badc3e4808e8eff89d7b1d32587e15c32c26b5 Mon Sep 17 00:00:00 2001 From: John Alanbrook Date: Tue, 25 Nov 2025 09:56:44 -0600 Subject: [PATCH] remove physfs; cellfs now is at parity --- benchmarks/nota.ce | 2 +- docs/tutorial.md | 2 +- meson.build | 12 +- scripts/build.ce | 2 +- scripts/cellfs.cm | 168 +++++++++++++++- scripts/clean.ce | 2 +- scripts/config.ce | 2 +- scripts/engine.cm | 1 - scripts/graphics.cm | 2 +- scripts/init.ce | 2 +- scripts/json.cm | 5 + scripts/list.ce | 2 +- scripts/patch.ce | 2 +- scripts/resources.cm | 2 +- scripts/vendor.ce | 2 +- source/cell.c | 3 - source/jsffi.c | 5 +- source/qjs_fd.c | 43 ++++ source/qjs_io.c | 429 ---------------------------------------- source/qjs_io.h | 8 - source/qjs_wildstar.c | 37 ++++ source/qjs_wildstar.h | 8 + subprojects/physfs.wrap | 13 -- tests/cellfs.ce | 192 ------------------ tests/chunkread.ce | 2 +- tests/http.ce | 2 +- tests/modules.ce | 2 +- tests/qr.ce | 2 +- tests/qr_drag.ce | 2 +- 29 files changed, 279 insertions(+), 677 deletions(-) delete mode 100644 source/qjs_io.c delete mode 100644 source/qjs_io.h create mode 100644 source/qjs_wildstar.c create mode 100644 source/qjs_wildstar.h delete mode 100644 subprojects/physfs.wrap delete mode 100644 tests/cellfs.ce diff --git a/benchmarks/nota.ce b/benchmarks/nota.ce index fff973dd..e75ecce3 100644 --- a/benchmarks/nota.ce +++ b/benchmarks/nota.ce @@ -1,6 +1,6 @@ var nota = use('nota') var os = use('os') -var io = use('io') +var io = use('cellfs') var ll = io.slurp('benchmarks/nota.json') diff --git a/docs/tutorial.md b/docs/tutorial.md index c6657043..9c1b49d2 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -29,7 +29,7 @@ The ```config.js``` module must return a single object that describes your game. | **url** | `"https://github.com/johnbrethauer/prosperon"` | string | URL link associated with the project, such as a repository or official homepage. | -With the engine configured, prosperon starts ```main.js``` as the first **actor** of the game. Actors are created from files that **do not** return a value. An actor executes the statements in its script. Initialization should happen here. An actor can pull in other chunks of code by importing modules. Modules are imported with the ```use``` statement. ```use``` returns the value the module returned, and it can be assigned to any variable. To use the internal ```io``` module, for example, you might say ```var io = use('io')```. +With the engine configured, prosperon starts ```main.js``` as the first **actor** of the game. Actors are created from files that **do not** return a value. An actor executes the statements in its script. Initialization should happen here. An actor can pull in other chunks of code by importing modules. Modules are imported with the ```use``` statement. ```use``` returns the value the module returned, and it can be assigned to any variable. To use the internal ```io``` module, for example, you might say ```var io = use('cellfs')```. Actors exist until they are explicitly killed, by invoking their ```kill``` function. Because the actor created from ```main.js``` is the root actor for the entire game, when it is killed, the program exits. In an actor script file, ```this``` is set to the actor being spawned from the script. diff --git a/meson.build b/meson.build index 4d37c11a..536abca8 100644 --- a/meson.build +++ b/meson.build @@ -175,18 +175,8 @@ else deps += miniz_dep endif -# Try to find system-installed physfs first -physfs_dep = dependency('physfs', static: true, required: false) -if not physfs_dep.found() - message('⚙ System physfs not found, building subproject...') - deps += dependency('physfs', static:true) -else - deps += physfs_dep -endif - deps += dependency('threads') - # Try to find system-installed chipmunk first chipmunk_dep = dependency('chipmunk', static: true, required: false) if not chipmunk_dep.found() @@ -276,7 +266,7 @@ src += [ 'jsffi.c', 'cell.c', 'wildmatch.c', - 'qjs_io.c', + 'qjs_wildstar.c', 'qjs_fd.c', 'qjs_qop.c', 'qjs_os.c', diff --git a/scripts/build.ce b/scripts/build.ce index d5d6276a..1a6345e1 100644 --- a/scripts/build.ce +++ b/scripts/build.ce @@ -1,6 +1,6 @@ // cell build – Compile all .cm and .ce files to bytecode -var io = use('io') +var io = use('cellfs') var js = use('js') var time = use('time') diff --git a/scripts/cellfs.cm b/scripts/cellfs.cm index bce009da..17899872 100644 --- a/scripts/cellfs.cm +++ b/scripts/cellfs.cm @@ -5,6 +5,7 @@ var cellfs = this var fd = use('fd') var miniz = use('miniz') +var wildstar = use('wildstar') // Internal state var mounts = [] // Array of {source, type, handle, name} @@ -54,7 +55,28 @@ function mount_exists(mount, path) { var full_path = join_paths(mount.source, path) try { var st = fd.stat(full_path) - return st.isFile + return st.isFile || st.isDirectory + } catch (e) { + return false + } + } +} + +// Check if a path refers to a directory in a specific mount +function is_directory(path) { + var res = resolve(path) + var mount = res.mount + if (mount.type == 'zip') { + try { + return mount.handle.is_directory(path); + } catch (e) { + return false; + } + } else { // fs + var full_path = join_paths(mount.source, path) + try { + var st = fd.stat(full_path) + return st.isDirectory } catch (e) { return false } @@ -230,6 +252,137 @@ function mount_package(name) { mount(dir, name) } +// New functions for qjs_io compatibility + +function match(str, pattern) { + return wildstar.match(pattern, str, wildstar.WM_PATHNAME | wildstar.WM_PERIOD | wildstar.WM_WILDSTAR) +} + +function rm(path) { + var res = resolve(path, true) + if (res.mount.type != 'fs') throw new Error("Cannot delete from non-fs mount") + + var full_path = join_paths(res.mount.source, res.path) + var st = fd.stat(full_path) + if (st.isDirectory) fd.rmdir(full_path) + else fd.unlink(full_path) +} + +function mkdir(path) { + var full = join_paths(writepath, path) + fd.mkdir(full) +} + +function set_writepath(path) { + writepath = path +} + +function basedir() { + return fd.getcwd() +} + +function prefdir(org, app) { + return "./" +} + +function realdir(path) { + var res = resolve(path, false) + if (!res) return null + return join_paths(res.mount.source, res.path) +} + +function enumerate(path, recurse) { + if (path == null) path = "" + + var res = resolve(path, true) + var results = [] + + function visit(curr_full, rel_prefix) { + var list = fd.readdir(curr_full) + if (!list) return + + for (var item of list) { + var item_rel = rel_prefix ? rel_prefix + "/" + item : item + results.push(item_rel) + + if (recurse) { + var st = fd.stat(join_paths(curr_full, item)) + if (st.isDirectory) { + visit(join_paths(curr_full, item), item_rel) + } + } + } + } + + if (res.mount.type == 'fs') { + var full = join_paths(res.mount.source, res.path) + var st = fd.stat(full) + if (st && st.isDirectory) { + visit(full, "") + } + } + + return results +} + +function globfs(globs, dir) { + if (dir == null) dir = "" + var res = resolve(dir, true) + var results = [] + + function check_neg(path) { + for (var g of globs) { + if (g.startsWith("!") && wildstar.match(g.substring(1), path, wildstar.WM_WILDSTAR)) return true; + } + return false; + } + + function check_pos(path) { + for (var g of globs) { + if (!g.startsWith("!") && wildstar.match(g, path, wildstar.WM_WILDSTAR)) return true; + } + return false; + } + + function visit(curr_full, rel_prefix) { + if (rel_prefix && check_neg(rel_prefix)) return + + var list = fd.readdir(curr_full) + if (!list) return + + for (var item of list) { + var item_rel = rel_prefix ? rel_prefix + "/" + item : item + + var child_full = join_paths(curr_full, item) + var st = fd.stat(child_full) + + if (st.isDirectory) { + if (!check_neg(item_rel)) { + visit(child_full, item_rel) + } + } else { + if (!check_neg(item_rel) && check_pos(item_rel)) { + results.push(item_rel) + } + } + } + } + + if (res.mount.type == 'fs') { + var full = join_paths(res.mount.source, res.path) + var st = fd.stat(full) + if (st && st.isDirectory) { + visit(full, "") + } + } + + return results +} + +function slurpbytes(path) { + return slurp(path) +} + // Exports cellfs.mount = mount cellfs.mount_package = mount_package @@ -237,7 +390,20 @@ cellfs.unmount = unmount cellfs.slurp = slurp cellfs.slurpwrite = slurpwrite cellfs.exists = exists +cellfs.is_directory = is_directory cellfs.stat = stat cellfs.searchpath = searchpath +cellfs.match = match +cellfs.enumerate = enumerate +cellfs.globfs = globfs +cellfs.rm = rm +cellfs.mkdir = mkdir +cellfs.writepath = set_writepath +cellfs.basedir = basedir +cellfs.prefdir = prefdir +cellfs.realdir = realdir +cellfs.slurpbytes = slurpbytes + +cellfs.mount('.') return cellfs diff --git a/scripts/clean.ce b/scripts/clean.ce index 23c49592..d9bd0e34 100644 --- a/scripts/clean.ce +++ b/scripts/clean.ce @@ -1,6 +1,6 @@ // cell clean - Remove build artifacts from modules/ -var io = use('io') +var io = use('cellfs') log.console(io.searchpath()) diff --git a/scripts/config.ce b/scripts/config.ce index 0acef3b6..fc0f0442 100644 --- a/scripts/config.ce +++ b/scripts/config.ce @@ -1,6 +1,6 @@ // cell config - Manage system and actor configurations -var io = use('io') +var io = use('cellfs') var toml = use('toml') var shop = use('shop') var text = use('text') diff --git a/scripts/engine.cm b/scripts/engine.cm index b8e2eb38..cfb2c65e 100644 --- a/scripts/engine.cm +++ b/scripts/engine.cm @@ -150,7 +150,6 @@ function disrupt(err) actor_mod.on_exception(disrupt) var js = use_embed('js') -var io = use_embed('io') var use_cache = {} diff --git a/scripts/graphics.cm b/scripts/graphics.cm index 49fa58bf..4ff7a118 100644 --- a/scripts/graphics.cm +++ b/scripts/graphics.cm @@ -1,6 +1,6 @@ var graphics = this -var io = use('io') +var io = use('cellfs') var time = use('time') var res = use('resources') var json = use('json') diff --git a/scripts/init.ce b/scripts/init.ce index 6aabc220..76ff044b 100644 --- a/scripts/init.ce +++ b/scripts/init.ce @@ -1,6 +1,6 @@ // cell init - Initialize a new .cell program shop -var io = use('io') +var io = use('cellfs') var shop = use('shop') // Initialize the .cell directory structure diff --git a/scripts/json.cm b/scripts/json.cm index 7c0b0b21..9750bd44 100644 --- a/scripts/json.cm +++ b/scripts/json.cm @@ -1,4 +1,5 @@ var json = {} +var utf8 = use('utf8') json.encode = function encode(val,replacer,space = 1,whitelist) { @@ -13,6 +14,10 @@ If the space input is true, then line breaks and extra whitespace will be includ json.decode = function decode(text,reviver) { + if (typeof text != 'string') { + text = utf8.decode(text) + if (!text) throw new Error("couldn't parse text: not a string") + } return JSON.parse(text,reviver) } diff --git a/scripts/list.ce b/scripts/list.ce index 649344b8..ecfa4631 100644 --- a/scripts/list.ce +++ b/scripts/list.ce @@ -1,6 +1,6 @@ var shop = use('shop') -var io = use('io') +var io = use('cellfs') // Initialize shop if needed if (!shop.init()) { diff --git a/scripts/patch.ce b/scripts/patch.ce index 47ebf8c4..8ecc030f 100644 --- a/scripts/patch.ce +++ b/scripts/patch.ce @@ -1,6 +1,6 @@ // cell patch - Create a patch for a module -var io = use('io') +var io = use('cellfs') var shop = use('shop') if (args.length < 1) { diff --git a/scripts/resources.cm b/scripts/resources.cm index 80eaa962..65916c08 100644 --- a/scripts/resources.cm +++ b/scripts/resources.cm @@ -1,4 +1,4 @@ -var io = use('io') +var io = use('cellfs') Object.defineProperty(Function.prototype, "hashify", { value: function () { diff --git a/scripts/vendor.ce b/scripts/vendor.ce index 28be9772..54b734a4 100644 --- a/scripts/vendor.ce +++ b/scripts/vendor.ce @@ -1,6 +1,6 @@ // cell vendor - Copy all dependencies into modules/ for hermetic builds -var io = use('io') +var io = use('cellfs') var shop = use('shop') if (!io.exists('.cell/shop.toml')) { diff --git a/source/cell.c b/source/cell.c index be1a81fb..2a71ad4a 100644 --- a/source/cell.c +++ b/source/cell.c @@ -991,8 +991,6 @@ int main(int argc, char **argv) tracy_profiling_enabled = profile_enabled; #endif - PHYSFS_init(argv[0]); - /* Load QOP package attached to executable - this is now mandatory! */ int mounted = prosperon_mount_core(); if (!mounted) { @@ -1014,7 +1012,6 @@ int main(int argc, char **argv) for (int i = 1; i < actor_argc; i++) wota_write_text(&startwota, actor_argv[i]); - /* Initialize synchronization primitives */ ready_sem = SDL_CreateSemaphore(0); main_sem = SDL_CreateSemaphore(0); diff --git a/source/jsffi.c b/source/jsffi.c index 0c4d055d..bb241838 100644 --- a/source/jsffi.c +++ b/source/jsffi.c @@ -35,7 +35,6 @@ #include "qjs_geometry.h" #include "qjs_transform.h" #include "qjs_sprite.h" -#include "qjs_io.h" #include "qjs_sdl_gpu.h" #include "qjs_os.h" #include "qjs_actor.h" @@ -49,7 +48,7 @@ #include "qjs_fit.h" #include "qjs_text.h" #include "qjs_staef.h" -#include "qjs_io.h" +#include "qjs_wildstar.h" #include "qjs_fd.h" #include "qjs_qop.h" @@ -1120,7 +1119,7 @@ void ffi_load(JSContext *js) arrput(rt->module_registry, MISTLINE(blob)); // extra - arrput(rt->module_registry, ((ModuleEntry){"io", js_io_use})); + arrput(rt->module_registry, MISTLINE(wildstar)); arrput(rt->module_registry, ((ModuleEntry){"fd", js_fd_use})); arrput(rt->module_registry, ((ModuleEntry){"qop", js_qop_use})); arrput(rt->module_registry, ((ModuleEntry){"os", js_os_use})); diff --git a/source/qjs_fd.c b/source/qjs_fd.c index 5d2a7731..ebda1b60 100644 --- a/source/qjs_fd.c +++ b/source/qjs_fd.c @@ -15,6 +15,7 @@ #include #define mkdir(path, mode) _mkdir(path) #define rmdir _rmdir +#define unlink _unlink #define getcwd _getcwd #define PATH_MAX _MAX_PATH #define fsync(fd) _commit(fd) @@ -206,6 +207,11 @@ JSC_SCALL(fd_mkdir, ret = JS_ThrowReferenceError(js, "could not make directory %s: %s", str, strerror(errno)); ) +JSC_SCALL(fd_unlink, + if (unlink(str) != 0) + ret = JS_ThrowReferenceError(js, "could not remove file %s: %s", str, strerror(errno)); +) + JSC_CCALL(fd_fsync, int fd = js2fd(js, argv[0]); if (fd < 0) return JS_EXCEPTION; @@ -304,6 +310,41 @@ JSC_SCALL(fd_stat, return obj; ) +JSC_SCALL(fd_readdir, +#ifdef _WIN32 + WIN32_FIND_DATA ffd; + char path[PATH_MAX]; + snprintf(path, sizeof(path), "%s\\*", str); + HANDLE hFind = FindFirstFile(path, &ffd); + if (hFind == INVALID_HANDLE_VALUE) { + ret = JS_ThrowReferenceError(js, "FindFirstFile failed for %s", path); + } else { + ret = JS_NewArray(js); + int i = 0; + do { + if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue; + JS_SetPropertyUint32(js, ret, i++, JS_NewString(js, ffd.cFileName)); + } while (FindNextFile(hFind, &ffd) != 0); + FindClose(hFind); + } +#else + DIR *d; + struct dirent *dir; + d = opendir(str); + if (d) { + ret = JS_NewArray(js); + int i = 0; + while ((dir = readdir(d)) != NULL) { + if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue; + JS_SetPropertyUint32(js, ret, i++, JS_NewString(js, dir->d_name)); + } + closedir(d); + } else { + ret = JS_ThrowReferenceError(js, "opendir failed for %s: %s", str, strerror(errno)); + } +#endif +) + static const JSCFunctionListEntry js_fd_funcs[] = { MIST_FUNC_DEF(fd, open, 2), MIST_FUNC_DEF(fd, write, 2), @@ -312,11 +353,13 @@ static const JSCFunctionListEntry js_fd_funcs[] = { MIST_FUNC_DEF(fd, lseek, 3), MIST_FUNC_DEF(fd, getcwd, 0), MIST_FUNC_DEF(fd, rmdir, 1), + MIST_FUNC_DEF(fd, unlink, 1), MIST_FUNC_DEF(fd, mkdir, 1), MIST_FUNC_DEF(fd, fsync, 1), MIST_FUNC_DEF(fd, close, 1), MIST_FUNC_DEF(fd, stat, 1), MIST_FUNC_DEF(fd, fstat, 1), + MIST_FUNC_DEF(fd, readdir, 1), }; JSValue js_fd_use(JSContext *js) { diff --git a/source/qjs_io.c b/source/qjs_io.c deleted file mode 100644 index 7aed0b56..00000000 --- a/source/qjs_io.c +++ /dev/null @@ -1,429 +0,0 @@ -#include "qjs_io.h" -#include "jsffi.h" -#include "qjs_macros.h" -#include "cell.h" - -#include "qjs_blob.h" - -#include -#include -#include -#include "wildmatch.h" - -// Helper function for array length using QuickJS -// JS_ArrayLength removed - use JS_ArrayLength directly - -// PHYSFS_File free function -static void PHYSFS_File_free(JSRuntime *rt, PHYSFS_File *f) -{ - PHYSFS_close(f); -} - -// Class definition for PHYSFS_File -QJSCLASS(PHYSFS_File,) - -// Helper function for writing to PHYSFS -static size_t js_physfs_write(JSContext *js, PHYSFS_File *f, JSValue val) -{ - size_t len; - size_t wrote; - if (JS_IsString(val)) { - const char *data = JS_ToCStringLen(js,&len,val); - wrote = PHYSFS_writeBytes(f,data,len); - JS_FreeCString(js,data); - } else { - unsigned char *data = js_get_blob_data(js,&len,val); - wrote = PHYSFS_writeBytes(f,data,len); - } - - if (wrote < len) wrote = -1; - return wrote; -} - -// Glob data structure for filesystem operations -struct globdata { - JSContext *js; - JSValue arr; - char **globs; - char *glob; - int idx; - int recurse; -}; - -// Callback for enumerate -static int enumerate_cb(void *udata, const char *dir, const char *fname) -{ - struct globdata *data = (struct globdata*)udata; - char *path; - int needfree = 0; - - if (dir[0] == 0) path = (char*)fname; - else { - size_t dlen = strlen(dir); - int ends_slash = (dlen && dir[dlen - 1] == '/'); - path = malloc(dlen + strlen(fname) + (ends_slash ? 1 : 2)); - if (ends_slash) sprintf(path, "%s%s", dir, fname); else sprintf(path, "%s/%s", dir, fname); - needfree = 1; - } - - PHYSFS_Stat st; - if (!PHYSFS_stat(path, &st)) { - if (needfree) free(path); - return 1; - } - - /* If it's a directory and we're recursing, enumerate it further. */ - if (st.filetype == PHYSFS_FILETYPE_DIRECTORY && data->recurse) - PHYSFS_enumerate(path, enumerate_cb, data); - - /* Add this item (file or directory) to the JS array. */ - JS_SetPropertyUint32(data->js, data->arr, data->idx++, JS_NewString(data->js, path)); - - if (needfree) free(path); - return 1; /* continue enumerating */ -} - -// I/O FUNCTIONS - -JSC_SCALL(io_rm, - if (!PHYSFS_delete(str)) ret = JS_ThrowReferenceError(js,"could not remove %s. %s", str, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); -) - -JSC_SCALL(io_mkdir, - if (!PHYSFS_mkdir(str)) ret = JS_ThrowReferenceError(js,"could not make directory %s. %s", str, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); -) - -JSC_SCALL(io_exists, ret = JS_NewBool(js,PHYSFS_exists(str)); ) - -JSC_SCALL(io_stat, - PHYSFS_Stat stat; - if (!PHYSFS_stat(str, &stat)) - return JS_ThrowReferenceError(js, "%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - - ret = JS_NewObject(js); - JS_SetPropertyStr(js,ret,"filesize", number2js(js,stat.filesize)); - JS_SetPropertyStr(js,ret,"modtime", number2js(js,stat.modtime)); - JS_SetPropertyStr(js,ret,"createtime", number2js(js,stat.createtime)); - JS_SetPropertyStr(js,ret,"accesstime", number2js(js,stat.accesstime)); -) - -JSC_SCALL(io_slurpbytes, - PHYSFS_File *f = PHYSFS_openRead(str); - if (!f) { - ret = JS_ThrowReferenceError(js,"physfs error when slurping %s: %s", str, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - goto END; - } - PHYSFS_Stat stat; - PHYSFS_stat(str,&stat); - void *data = malloc(stat.filesize); - PHYSFS_readBytes(f,data,stat.filesize); - PHYSFS_close(f); - ret = js_new_blob_stoned_copy(js,data,stat.filesize); - - END: -) - -JSC_SCALL(io_slurp, - PHYSFS_File *f = PHYSFS_openRead(str); - if (!f) { - ret = JS_ThrowReferenceError(js,"physfs error when slurping %s: %s", str, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - goto END; - } - PHYSFS_Stat stat; - PHYSFS_stat(str,&stat); - void *data = malloc(stat.filesize); - PHYSFS_readBytes(f,data,stat.filesize); - PHYSFS_close(f); - ret = JS_NewStringLen(js,data, stat.filesize); - - free(data); - - END: -) - -JSC_SCALL(io_slurpwrite, - PHYSFS_File *f = PHYSFS_openWrite(str); - if (!f) { - ret = JS_ThrowReferenceError(js,"could not write to %s: %s", str, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - goto END; - } - size_t wrote = js_physfs_write(js,f,argv[1]); - - PHYSFS_close(f); - if (wrote == -1) - ret = JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - - END: -) - -JSC_CCALL(io_mount, - const char *src = JS_ToCString(js, argv[0]); - const char *mountpoint; - if (JS_IsNull(argv[1])) - mountpoint = NULL; - else - mountpoint = JS_ToCString(js, argv[1]); - - int prepend = 0; - - if (argc > 2 && !JS_IsNull(argv[2])) - prepend = JS_ToBool(js, argv[2]); - - if (!PHYSFS_mount(src, mountpoint, prepend)) - ret = JS_ThrowReferenceError(js,"Unable to mount %s at %s: %s", src, mountpoint, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - - JS_FreeCString(js, src); - if (mountpoint) - JS_FreeCString(js, mountpoint); -) - -JSC_SCALL(io_unmount, - if (!PHYSFS_unmount(str)) ret = JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); -) - -JSC_SCALL(io_writepath, - if (!PHYSFS_setWriteDir(str)) ret = JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); -) - -JSC_SSCALL(io_match, - if (wildmatch(str, str2, WM_PATHNAME | WM_PERIOD | WM_WILDSTAR) == WM_MATCH) - ret = JS_NewBool(js,1); - else - ret = JS_NewBool(js,0); -) - -// Callback for globfs, with support for negative patterns (e.g. "!**/.git") -static int globfs_cb(struct globdata *data, char *dir, char *file) -{ - int needfree = 0; - char *path; - PHYSFS_Stat stat; - char **glob; - - if (dir[0] == 0) - path = file; - else { - path = malloc(strlen(dir) + strlen(file) + 2); - path[0] = 0; - strcat(path, dir); - strcat(path, "/"); - strcat(path, file); - needfree = 1; - } - - // Grab filetype now - PHYSFS_stat(path, &stat); - - if (stat.filetype == PHYSFS_FILETYPE_DIRECTORY) { - // Check negative patterns first: if directory matches a "!…" pattern, skip it - for (glob = data->globs; *glob != NULL; glob++) { - if ((*glob)[0] == '!') { - const char *neg_pattern = (*glob) + 1; - if (wildmatch(neg_pattern, path, WM_WILDSTAR) == WM_MATCH) - goto END; - } - } - // Not excluded: recurse into this directory - PHYSFS_enumerate(path, globfs_cb, data); - goto END; - } - - // It's a file—first see if it matches any negative pattern - for (glob = data->globs; *glob != NULL; glob++) { - if ((*glob)[0] == '!') { - const char *neg_pattern = (*glob) + 1; - if (wildmatch(neg_pattern, path, WM_WILDSTAR) == WM_MATCH) - goto END; - } - } - - // Now check positive patterns ("**/*.cm", "**/*.ce", etc.) - for (glob = data->globs; *glob != NULL; glob++) { - if ((*glob)[0] == '!') - continue; - if (wildmatch(*glob, path, WM_WILDSTAR) == WM_MATCH) { - JS_SetPropertyUint32(data->js, - data->arr, - data->idx++, - JS_NewString(data->js, path)); - break; - } - } - - END: - if (needfree) - free(path); - return 1; -} - -// arg0: glob patterns (array of strings) -// arg1: directory (string) -JSC_CCALL(io_globfs, - ret = JS_NewArray(js); - struct globdata data; - data.js = js; - data.arr = ret; - data.idx = 0; - int globs_len = JS_ArrayLength(js,argv[0]); - const char *globs[globs_len+1]; - for (int i = 0; i < globs_len; i++) { - JSValue g = JS_GetPropertyUint32(js,argv[0],i); - globs[i] = JS_ToCString(js,g); - JS_FreeValue(js,g); - } - - globs[globs_len] = NULL; - data.globs = globs; - - const char *path = NULL; - if (!JS_IsNull(argv[1])) path = JS_ToCString(js,argv[1]); - PHYSFS_enumerate(path, globfs_cb, &data); - - for (int i = 0; i < globs_len; i++) - JS_FreeCString(js,globs[i]); - - ret = data.arr; - JS_FreeCString(js,path); -) - -JSC_SCALL(io_enumerate, - /* First argument: str (directory name) */ - /* Second argument: boolean => recurse or not */ - ret = JS_NewArray(js); - - struct globdata data; - data.js = js; - data.arr = ret; - data.idx = 0; - data.glob = NULL; /* not used here */ - data.globs = NULL; /* not used here */ - data.recurse = 0; - - if (!JS_IsNull(argv[1])) /* parse second arg if provided */ - data.recurse = JS_ToBool(js, argv[1]); - - /* Enumerate the directory given by 'str'. */ - PHYSFS_enumerate(str, enumerate_cb, &data); - - /* Return the JS array we filled. */ - ret = data.arr; -) - -JSC_CCALL(io_basedir, return JS_NewString(js,PHYSFS_getBaseDir())) - -JSC_SSCALL(io_prefdir, return JS_NewString(js,PHYSFS_getPrefDir(str, str2))) - -JSC_SCALL(io_open, - PHYSFS_File *f = PHYSFS_openWrite(str); - if (!f) - ret = JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - else - ret = PHYSFS_File2js(js,f); -) - -JSC_SCALL(io_realdir, - const char *real = PHYSFS_getRealDir(str); - if (!real) - ret = JS_NULL; - else - ret = JS_NewString(js,real); -) - -JSC_CCALL(io_searchpath, - ret = JS_NewArray(js); - char **paths = PHYSFS_getSearchPath(); - for (int i = 0; paths[i] != NULL; i++) - JS_SetPropertyUint32(js,ret,i,JS_NewString(js,paths[i])); - PHYSFS_freeList(paths); -) - -JSC_SCALL(io_is_directory, - PHYSFS_Stat stat; - int good = PHYSFS_stat(str, &stat); - if (!good) - ret = JS_NewBool(js, 0); - else - ret = JS_NewBool(js, stat.filetype == PHYSFS_FILETYPE_DIRECTORY); -) - -// FILE FUNCTIONS - -JSC_CCALL(file_close, - PHYSFS_File *f = js2PHYSFS_File(js,self); - PHYSFS_close(f); -) - -JSC_CCALL(file_write, - PHYSFS_File *f = js2PHYSFS_File(js,self); - size_t wrote = js_physfs_write(js,f,argv[0]); - if (wrote == -1) - return JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); -) - -JSC_CCALL(file_buffer, - PHYSFS_File *f = js2PHYSFS_File(js,self); - size_t size = js2number(js,argv[0]); - if (!PHYSFS_setBuffer(f,size)) - return JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); -) - -JSC_CCALL(file_tell, - PHYSFS_File *f = js2PHYSFS_File(js,self); - size_t tell = PHYSFS_tell(f); - if (tell == -1) - return JS_ThrowReferenceError(js,"%s", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode())); - - return number2js(js,tell); -) - -JSC_CCALL(file_eof, - PHYSFS_File *f = js2PHYSFS_File(js,self); - return JS_NewBool(js, PHYSFS_eof(f)); -) - -JSC_CCALL(io_mountpoint, - const char *path = JS_ToCString(js,argv[0]); - const char *point = PHYSFS_getMountPoint(path); - if (point) - return JS_NewString(js,point); -) - -static const JSCFunctionListEntry js_io_funcs[] = { - MIST_FUNC_DEF(io, rm, 1), - MIST_FUNC_DEF(io, mkdir, 1), - MIST_FUNC_DEF(io,stat,1), - MIST_FUNC_DEF(io, globfs, 2), - MIST_FUNC_DEF(io, match, 2), - MIST_FUNC_DEF(io, exists, 1), - MIST_FUNC_DEF(io, mount, 3), - MIST_FUNC_DEF(io,unmount,1), - MIST_FUNC_DEF(io,slurp,1), - MIST_FUNC_DEF(io,slurpbytes,1), - MIST_FUNC_DEF(io,slurpwrite,2), - MIST_FUNC_DEF(io,writepath, 1), - MIST_FUNC_DEF(io,basedir, 0), - MIST_FUNC_DEF(io, prefdir, 2), - MIST_FUNC_DEF(io, realdir, 1), - MIST_FUNC_DEF(io, open, 1), - MIST_FUNC_DEF(io, searchpath, 0), - MIST_FUNC_DEF(io, enumerate, 2), - MIST_FUNC_DEF(io, is_directory, 1), - MIST_FUNC_DEF(io, mountpoint, 1), -}; - -static const JSCFunctionListEntry js_PHYSFS_File_funcs[] = { - MIST_FUNC_DEF(file, close, 0), - MIST_FUNC_DEF(file, write, 1), - MIST_FUNC_DEF(file, buffer, 1), - MIST_FUNC_DEF(file, tell, 0), - MIST_FUNC_DEF(file, eof, 0), -}; - -JSValue js_io_use(JSContext *js) { - QJSCLASSPREP_FUNCS(PHYSFS_File); - - JSValue mod = JS_NewObject(js); - JS_SetPropertyFunctionList(js,mod,js_io_funcs,countof(js_io_funcs)); - - return mod; -} diff --git a/source/qjs_io.h b/source/qjs_io.h deleted file mode 100644 index 6d0ed5c8..00000000 --- a/source/qjs_io.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef QJS_IO_H -#define QJS_IO_H - -#include "cell.h" - -JSValue js_io_use(JSContext *ctx); - -#endif /* QJS_IO_H */ diff --git a/source/qjs_wildstar.c b/source/qjs_wildstar.c new file mode 100644 index 00000000..7a6420a2 --- /dev/null +++ b/source/qjs_wildstar.c @@ -0,0 +1,37 @@ +#include "qjs_wildstar.h" +#include "wildmatch.h" +#include "jsffi.h" +#include "qjs_macros.h" + +JSC_CCALL(wildstar_match, + const char *pattern = JS_ToCString(js, argv[0]); + const char *string = JS_ToCString(js, argv[1]); + int flags = 0; + if (argc > 2) + flags = js2number(js, argv[2]); + + int result = wildmatch(pattern, string, flags); + + JS_FreeCString(js, pattern); + JS_FreeCString(js, string); + + return JS_NewBool(js, result == WM_MATCH); +) + +static const JSCFunctionListEntry js_wildstar_funcs[] = { + MIST_FUNC_DEF(wildstar, match, 3), + JS_PROP_INT32_DEF("WM_MATCH", WM_MATCH, JS_PROP_CONFIGURABLE), + JS_PROP_INT32_DEF("WM_NOMATCH", WM_NOMATCH, JS_PROP_CONFIGURABLE), + JS_PROP_INT32_DEF("WM_NOESCAPE", WM_NOESCAPE, JS_PROP_CONFIGURABLE), + JS_PROP_INT32_DEF("WM_PATHNAME", WM_PATHNAME, JS_PROP_CONFIGURABLE), + JS_PROP_INT32_DEF("WM_PERIOD", WM_PERIOD, JS_PROP_CONFIGURABLE), + JS_PROP_INT32_DEF("WM_LEADING_DIR", WM_LEADING_DIR, JS_PROP_CONFIGURABLE), + JS_PROP_INT32_DEF("WM_CASEFOLD", WM_CASEFOLD, JS_PROP_CONFIGURABLE), + JS_PROP_INT32_DEF("WM_WILDSTAR", WM_WILDSTAR, JS_PROP_CONFIGURABLE), +}; + +JSValue js_wildstar_use(JSContext *js) { + JSValue mod = JS_NewObject(js); + JS_SetPropertyFunctionList(js, mod, js_wildstar_funcs, countof(js_wildstar_funcs)); + return mod; +} diff --git a/source/qjs_wildstar.h b/source/qjs_wildstar.h new file mode 100644 index 00000000..5949d058 --- /dev/null +++ b/source/qjs_wildstar.h @@ -0,0 +1,8 @@ +#ifndef QJS_WILDSTAR_H +#define QJS_WILDSTAR_H + +#include "quickjs.h" + +JSValue js_wildstar_use(JSContext *js); + +#endif diff --git a/subprojects/physfs.wrap b/subprojects/physfs.wrap deleted file mode 100644 index 5497c87d..00000000 --- a/subprojects/physfs.wrap +++ /dev/null @@ -1,13 +0,0 @@ -[wrap-file] -directory = physfs-release-3.2.0 -source_url = https://github.com/icculus/physfs/archive/refs/tags/release-3.2.0.zip -source_filename = physfs-release-3.2.0.zip -source_hash = 18adad9a2d5e165a709588d1d942d73e3ffcb0495244b108cfe402690489990c -patch_filename = physfs_3.2.0-2_patch.zip -patch_url = https://wrapdb.mesonbuild.com/v2/physfs_3.2.0-2/get_patch -patch_hash = 7c51a672081f2a82bb789f40a68a9b7c1750659843a74c6853646d0d546718f7 -source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/physfs_3.2.0-2/physfs-release-3.2.0.zip -wrapdb_version = 3.2.0-2 - -[provide] -physfs = physfs_dep diff --git a/tests/cellfs.ce b/tests/cellfs.ce deleted file mode 100644 index 3505bec7..00000000 --- a/tests/cellfs.ce +++ /dev/null @@ -1,192 +0,0 @@ -// CellFS vs IO Performance Test -// Compares the speed of cellfs (miniz + fd) vs physfs-based io - -var cellfs = use('cellfs') -var io = use('io') -var time = use('time') -var json = use('json') -var blob = use('blob') -var utf8 = use('utf8') - -log.console("CellFS vs IO Performance Test") -log.console("=================================") - -// Test file operations -var test_file = "test.txt" -var test_content = "Hello, World! This is a test file for performance comparison.\n" - -// Create test data -log.console("Creating test file...") -io.writepath('.') - -io.mount('.') - -var io_paths = io.searchpath() -log.console(io_paths) - -for (var i = 0; i < io_paths.length; i++) { - var path = io_paths[i] - try { - - cellfs.mount(path) - } catch (e) { - // Some paths might not be mountable, skip them - } -} - -io.slurpwrite(test_file, test_content) - -// Verify both systems have the same search paths -log.console(`IO search paths: ${json.encode(io.searchpath())}`) -log.console(`CellFS search paths: ${json.encode(cellfs.searchpath())}`) - -log.console("Testing read operations...") - -// Test io.slurpbytes -var start_time = time.number() -for (var i = 0; i < 100; i++) { - var content = io.slurpbytes(test_file) -} -var io_time = time.number() - start_time -log.console(`IO slurpbytes (100 iterations): ${io_time}ms`) - -// Test cellfs.slurpbytes -start_time = time.number() -for (var i = 0; i < 100; i++) { - var content = cellfs.slurp(test_file) -} -var cellfs_time = time.number() - start_time -log.console(`CellFS slurpbytes (100 iterations): ${cellfs_time}ms`) - -// Compare results -var speedup = io_time / cellfs_time -log.console(`CellFS is ${speedup.toFixed(2)}x ${speedup > 1 ? "faster" : "slower"} than IO for reading`) - -// Test stat operations -log.console("\nTesting stat operations...") - -start_time = time.number() -for (var i = 0; i < 1000; i++) { - var stats = io.stat(test_file) -} -io_time = time.number() - start_time -log.console(`IO stat (1000 iterations): ${io_time}ms`) - -start_time = time.number() -for (var i = 0; i < 1000; i++) { - var stats = cellfs.stat(test_file) -} -cellfs_time = time.number() - start_time -log.console(`CellFS stat (1000 iterations): ${cellfs_time}ms`) - -speedup = io_time / cellfs_time -log.console(`CellFS is ${speedup.toFixed(2)}x ${speedup > 1 ? "faster" : "slower"} than IO for stat`) - -// Test exists operations -log.console("\nTesting exists operations...") - -start_time = time.number() -for (var i = 0; i < 1000; i++) { - var exists = io.exists(test_file) -} -io_time = time.number() - start_time -log.console(`IO exists (1000 iterations): ${io_time}ms`) - -start_time = time.number() -for (var i = 0; i < 1000; i++) { - var exists = cellfs.exists(test_file) -} -cellfs_time = time.number() - start_time -log.console(`CellFS exists (1000 iterations): ${cellfs_time}ms`) - -speedup = io_time / cellfs_time -log.console(`CellFS is ${speedup.toFixed(2)}x ${speedup > 1 ? "faster" : "slower"} than IO for exists`) - -// Test ZIP archive operations -log.console("\nTesting ZIP archive operations...") - -// Create a test ZIP file using miniz -var miniz = use('miniz') -var zip_writer = miniz.write("test_archive.zip") -// Use io.slurpbytes to get content as ArrayBuffer for miniz -var content_bytes = io.slurpbytes(test_file) -zip_writer.add_file("test2.txt", content_bytes) -zip_writer = null // Close it - -// Mount the ZIP with io -io.mount("test_archive.zip") - -// Mount the ZIP with cellfs -cellfs.mount("test_archive.zip") - -log.console("Testing ZIP file reading...") - -start_time = time.number() -for (var i = 0; i < 100; i++) { - var content = io.slurp("test2.txt") -} -io_time = time.number() - start_time -log.console(`IO ZIP read (100 iterations): ${io_time}ms`) - -start_time = time.number() -for (var i = 0; i < 100; i++) { - var content = cellfs.slurp("test2.txt") -} - -cellfs_time = time.number() - start_time -log.console(`CellFS ZIP read (100 iterations): ${cellfs_time}ms`) - -speedup = io_time / cellfs_time -log.console(`CellFS is ${speedup.toFixed(2)}x ${speedup > 1 ? "faster" : "slower"} than IO for ZIP reading`) - -// Test large file operations -log.console("\nTesting large file operations...") - -var large_file = "large_test.bin" -var size_mb = 10 -var size_bits = size_mb * 1024 * 1024 * 8 -log.console(`Creating ${size_mb}MB large file...`) - -var blob = new blob(size_bits, function() { return Math.floor(Math.random() * (1 << 52)) }) -stone(blob) -io.slurpwrite(large_file, blob) - -log.console("Testing large file reading...") - -// Test io.slurpbytes -start_time = time.number() -for (var i = 0; i < 10; i++) { - var large_content = io.slurpbytes(large_file) -} -io_time = time.number() - start_time -log.console(`IO large file read (10 iterations): ${io_time}ms`) - -start_time = time.number() -for (var i = 0; i < 10; i++) { - var large_content = cellfs.slurp(large_file) -} -cellfs_time = time.number() - start_time -log.console(`CellFS large file read (10 iterations): ${cellfs_time}ms`) - -speedup = io_time / cellfs_time -log.console(`CellFS is ${speedup.toFixed(2)}x ${speedup > 1 ? "faster" : "slower"} than IO for large file reading`) - -// Cleanup -io.rm(test_file) -io.rm("test_archive.zip") -io.rm(large_file) -//io.unmount("/test_zip") -//cellfs.unmount("/test_zip") - -// Unmount all the paths we mounted in cellfs -for (var path of io_paths) { - try { - cellfs.unmount(path) - } catch (e) { - // Ignore unmount errors - } -} - -log.console("\nTest completed!") - -$_.stop() \ No newline at end of file diff --git a/tests/chunkread.ce b/tests/chunkread.ce index 8047191f..812c9963 100644 --- a/tests/chunkread.ce +++ b/tests/chunkread.ce @@ -2,7 +2,7 @@ var fd = use('fd') var time = use('time') var blob = use('blob') -var io = use('io') +var io = use('cellfs') var data = new blob var st = time.number() diff --git a/tests/http.ce b/tests/http.ce index a8fe6d37..958c3182 100644 --- a/tests/http.ce +++ b/tests/http.ce @@ -1,7 +1,7 @@ var http = use('http') var text = use('text') var time = use('time') -var io = use('io') +var io = use('cellfs') try { var st = time.number() diff --git a/tests/modules.ce b/tests/modules.ce index b950cc35..cdbccd37 100644 --- a/tests/modules.ce +++ b/tests/modules.ce @@ -1,6 +1,6 @@ // Test script for the module system -var io = use('io') +var io = use('cellfs') var shop = use('shop') var time = use('time') diff --git a/tests/qr.ce b/tests/qr.ce index c67c935f..5a6d7224 100644 --- a/tests/qr.ce +++ b/tests/qr.ce @@ -1,6 +1,6 @@ var qr = use('qr') var os = use('os') -var io = use('io') +var io = use('cellfs') var myqr = qr.encode("HELLO WORLD", { version:14, diff --git a/tests/qr_drag.ce b/tests/qr_drag.ce index ee8643da..8a7ab1e2 100644 --- a/tests/qr_drag.ce +++ b/tests/qr_drag.ce @@ -35,7 +35,7 @@ var ioguy = { __ACTORDATA__: { id: actor.ioactor() } } -var io = use('io') +var io = use('cellfs') io.mount('/') var miniz = use('miniz')