qopconv; now uses cell's own qopconv for building
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -27,3 +27,4 @@ discord_social_sdk/
|
||||
discord_partner_sdk/
|
||||
steam_api64.dll
|
||||
subprojects/.wraplock
|
||||
.gemini
|
||||
|
||||
@@ -364,7 +364,7 @@ cell_dep = declare_dependency(
|
||||
# Create core.zip from scripts folder
|
||||
qop_target = custom_target('core.qop',
|
||||
output: 'core.qop',
|
||||
command: ['sh', '-c', 'qopconv -d ' + meson.project_source_root() / 'scripts . core.qop'],
|
||||
command: ['sh', '-c', ' mkdir -p .cell && cell qopconv -d ' + meson.project_source_root() / 'scripts . core.qop'],
|
||||
build_by_default: true,
|
||||
build_always_stale: true
|
||||
)
|
||||
|
||||
@@ -3,42 +3,61 @@
|
||||
var io = use('cellfs')
|
||||
var js = use('js')
|
||||
var time = use('time')
|
||||
var qop = use('qop')
|
||||
|
||||
var build_root = '.cell/build'
|
||||
|
||||
log.console("Building scripts...")
|
||||
|
||||
var now = time.number()
|
||||
|
||||
// Find and compile all .cm and .ce files from root
|
||||
var files = io.globfs(['**/*.cm', '**/*.ce', '!**/*.git', '!**/*.cell', '!**/subprojects'], '')
|
||||
|
||||
var compiled_count = 0
|
||||
var error_count = 0
|
||||
|
||||
for (var file of files) {
|
||||
var src = io.slurp(file)
|
||||
var fullpath = io.realdir(file) + "/" + file
|
||||
var outpath = build_root + fullpath
|
||||
io.mkdir(outpath.substring(0, outpath.lastIndexOf('/')))
|
||||
var mod_name = file
|
||||
function compile_file(src_path, dest_path, is_core) {
|
||||
try {
|
||||
var src_content
|
||||
if (is_core) {
|
||||
return
|
||||
} else {
|
||||
src_content = io.slurp(src_path)
|
||||
}
|
||||
|
||||
io.mkdir(dest_path.substring(0, dest_path.lastIndexOf('/')))
|
||||
|
||||
var mod_name = src_path
|
||||
.replace(/\.(cm|ce)$/, '')
|
||||
.replace(/[\/\-.]/g, '_')
|
||||
|
||||
var src_content = io.slurp(file)
|
||||
|
||||
var wrapped = '(function ' + mod_name + '(arg){' + src_content + '})'
|
||||
try {
|
||||
var compiled = js.compile(file, wrapped)
|
||||
|
||||
var compiled = js.compile(src_path, wrapped)
|
||||
var blob = js.compile_blob(compiled)
|
||||
io.slurpwrite(outpath, blob)
|
||||
io.slurpwrite(dest_path, blob)
|
||||
compiled_count++
|
||||
// log.console("Compiled " + src_path + " -> " + dest_path)
|
||||
} catch(e) {
|
||||
log.error(e)
|
||||
log.console("GHELO")
|
||||
log.console(`Failed to compile ${src_path}: ${e.message}`);
|
||||
error_count++
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Local files
|
||||
var local_files = io.globfs(['**/*.cm', '**/*.ce', '!**/*.git', '!**/*.cell', '!**/subprojects'], '')
|
||||
for (var file of local_files) {
|
||||
var dest = build_root + '/local/' + file + '.o'
|
||||
compile_file(file, dest, false)
|
||||
}
|
||||
|
||||
// 2. Modules
|
||||
var module_files = io.globfs(['**/*.cm', '**/*.ce'], '.cell/modules')
|
||||
for (var file of module_files) {
|
||||
// file is relative to .cell/modules, e.g. "prosperon/draw2d.cm"
|
||||
var dest = build_root + '/modules/' + file + '.o'
|
||||
var src = '.cell/modules/' + file
|
||||
compile_file(src, dest, false)
|
||||
}
|
||||
|
||||
log.console("Build complete: " + compiled_count + " files compiled in " + (time.number()-now) + " seconds")
|
||||
if (error_count > 0) {
|
||||
log.console(" " + error_count + " errors")
|
||||
|
||||
@@ -5,6 +5,7 @@ var cellfs = this
|
||||
|
||||
var fd = use('fd')
|
||||
var miniz = use('miniz')
|
||||
var qop = use('qop')
|
||||
var wildstar = use('wildstar')
|
||||
|
||||
// Internal state
|
||||
@@ -51,6 +52,12 @@ function mount_exists(mount, path) {
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
} else if (mount.type == 'qop') {
|
||||
try {
|
||||
return mount.handle.stat(path) != null
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
} else { // fs
|
||||
var full_path = join_paths(mount.source, path)
|
||||
try {
|
||||
@@ -72,6 +79,12 @@ function is_directory(path) {
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
} else if (mount.type == 'qop') {
|
||||
try {
|
||||
return mount.handle.is_directory(path);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
} else { // fs
|
||||
var full_path = join_paths(mount.source, path)
|
||||
try {
|
||||
@@ -146,16 +159,29 @@ function mount(source, name) {
|
||||
if (st.isDirectory) {
|
||||
mount_info.type = 'fs'
|
||||
} else if (st.isFile) {
|
||||
var zip_data = fd.slurp(source)
|
||||
var blob = fd.slurp(source)
|
||||
|
||||
var zip = miniz.read(zip_data)
|
||||
// Try QOP first (it's likely faster to fail?) or Zip?
|
||||
// QOP open checks magic.
|
||||
var qop_archive = null
|
||||
try {
|
||||
qop_archive = qop.open(blob)
|
||||
} catch(e) {}
|
||||
|
||||
if (qop_archive) {
|
||||
mount_info.type = 'qop'
|
||||
mount_info.handle = qop_archive
|
||||
mount_info.zip_blob = blob // keep blob alive
|
||||
} else {
|
||||
var zip = miniz.read(blob)
|
||||
if (!zip || typeof zip.count != 'function') {
|
||||
throw new Error("Invalid ZIP file: " + source)
|
||||
throw new Error("Invalid archive file (not zip or qop): " + source)
|
||||
}
|
||||
|
||||
mount_info.type = 'zip'
|
||||
mount_info.handle = zip
|
||||
mount_info.zip_blob = zip_data // keep blob alive for lifetime of mount
|
||||
mount_info.zip_blob = blob // keep blob alive
|
||||
}
|
||||
} else {
|
||||
throw new Error("Unsupported mount source type: " + source)
|
||||
}
|
||||
@@ -183,6 +209,10 @@ function slurp(path) {
|
||||
|
||||
if (res.mount.type == 'zip') {
|
||||
return res.mount.handle.slurp(res.path)
|
||||
} else if (res.mount.type == 'qop') {
|
||||
var data = res.mount.handle.read(res.path)
|
||||
if (!data) throw new Error("File not found in qop: " + path)
|
||||
return data
|
||||
} else {
|
||||
var full_path = join_paths(res.mount.source, res.path)
|
||||
return fd.slurp(full_path)
|
||||
@@ -219,6 +249,14 @@ function stat(path) {
|
||||
modtime: mod * 1000,
|
||||
isDirectory: false
|
||||
}
|
||||
} else if (res.mount.type == 'qop') {
|
||||
var s = res.mount.handle.stat(res.path)
|
||||
if (!s) throw new Error("File not found in qop: " + path)
|
||||
return {
|
||||
filesize: s.size,
|
||||
modtime: s.modtime,
|
||||
isDirectory: s.isDirectory
|
||||
}
|
||||
} else {
|
||||
var full_path = join_paths(res.mount.source, res.path)
|
||||
var s = fd.stat(full_path)
|
||||
@@ -320,6 +358,32 @@ function enumerate(path, recurse) {
|
||||
if (st && st.isDirectory) {
|
||||
visit(full, "")
|
||||
}
|
||||
} else if (res.mount.type == 'qop') {
|
||||
var all = res.mount.handle.list()
|
||||
var prefix = res.path ? res.path + "/" : ""
|
||||
var prefix_len = prefix.length
|
||||
|
||||
// Use a set to avoid duplicates if we are simulating directories
|
||||
var seen = {}
|
||||
|
||||
for (var p of all) {
|
||||
if (p.startsWith(prefix)) {
|
||||
var rel = p.substring(prefix_len)
|
||||
if (rel.length == 0) continue
|
||||
|
||||
if (!recurse) {
|
||||
var slash = rel.indexOf('/')
|
||||
if (slash != -1) {
|
||||
rel = rel.substring(0, slash)
|
||||
}
|
||||
}
|
||||
|
||||
if (!seen[rel]) {
|
||||
seen[rel] = true
|
||||
results.push(rel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
@@ -374,6 +438,21 @@ function globfs(globs, dir) {
|
||||
if (st && st.isDirectory) {
|
||||
visit(full, "")
|
||||
}
|
||||
} else if (res.mount.type == 'qop') {
|
||||
var all = res.mount.handle.list()
|
||||
var prefix = res.path ? res.path + "/" : ""
|
||||
var prefix_len = prefix.length
|
||||
|
||||
for (var p of all) {
|
||||
if (p.startsWith(prefix)) {
|
||||
var rel = p.substring(prefix_len)
|
||||
if (rel.length == 0) continue
|
||||
|
||||
if (!check_neg(rel) && check_pos(rel)) {
|
||||
results.push(rel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
// cell clean - Remove build artifacts from modules/
|
||||
|
||||
var io = use('cellfs')
|
||||
var fd = use('fd')
|
||||
|
||||
log.console(io.searchpath())
|
||||
|
||||
if (!io.exists('.cell/build')) {
|
||||
if (!fd.stat('.cell/build').isDirectory) {
|
||||
log.console("No build directory found")
|
||||
$_.stop()
|
||||
return
|
||||
@@ -14,10 +12,10 @@ log.console("Cleaning build artifacts...")
|
||||
|
||||
// Remove the build directory
|
||||
try {
|
||||
io.rmdir('.cell/build')
|
||||
fd.rm('.cell/build')
|
||||
log.console("Build directory removed")
|
||||
} catch (e) {
|
||||
log.error("Failed during cleanup: " + e)
|
||||
log.error(e)
|
||||
}
|
||||
|
||||
log.console("Clean complete!")
|
||||
|
||||
@@ -52,7 +52,7 @@ log.error = function(msg = new Error())
|
||||
var caller = caller_data(1)
|
||||
|
||||
if (msg instanceof Error)
|
||||
msg = msg + "\n" + msg.stack
|
||||
msg = msg.name + ": " + msg.message + "\n" + msg.stack
|
||||
|
||||
console_mod.print(console_rec(caller.line,caller.file,msg))
|
||||
}
|
||||
@@ -87,7 +87,7 @@ if (!fd.stat('.cell').isDirectory) {
|
||||
function is_file(path) {
|
||||
try {
|
||||
var st = fd.stat(path)
|
||||
return st.isFile
|
||||
return !!(st && st.isFile)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
@@ -101,9 +101,12 @@ function write_file(path, blob) {
|
||||
|
||||
function mkdir_p(dir) {
|
||||
if (dir == '' || dir == '.') return
|
||||
try { fd.stat(dir) } catch {
|
||||
var st = null
|
||||
try { st = fd.stat(dir) } catch {}
|
||||
|
||||
if (!st || !st.isDirectory) {
|
||||
mkdir_p(dir.substring(0, dir.lastIndexOf('/')))
|
||||
fd.mkdir(dir)
|
||||
try { fd.mkdir(dir) } catch {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,168 +186,105 @@ function get_package_from_path(path) {
|
||||
// This will be set after shop.load_config() is called
|
||||
var config = null
|
||||
|
||||
// Resolve actor program path with package awareness
|
||||
// Resolution order:
|
||||
// 1. Current package (root project when pkg_context is null)
|
||||
// 2. Declared dependencies (from cell.toml)
|
||||
// 3. core_qop (standard library)
|
||||
function resolve_actor_path(requested, pkg_context) {
|
||||
// Unified path resolution function
|
||||
function resolve_path(requested, pkg_context, ext) {
|
||||
var dependencies = (config && config.dependencies) ? config.dependencies : {}
|
||||
|
||||
// Helper to check file existence and return result object
|
||||
function check(path, pkg, isCore) {
|
||||
if (isCore) {
|
||||
try {
|
||||
core_qop.read(path)
|
||||
return { path: path, package_name: null, isCore: true }
|
||||
} catch (e) { return null }
|
||||
}
|
||||
if (is_file(path)) {
|
||||
return { path: path, package_name: pkg, isCore: false }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Step 1: current package
|
||||
if (pkg_context) {
|
||||
var pkg_actor_path = '.cell/modules/' + pkg_context + '/' + requested + ACTOR_EXT
|
||||
if (is_file(pkg_actor_path)) {
|
||||
return { path: pkg_actor_path, package_name: pkg_context, isCore: false }
|
||||
}
|
||||
var pkg_path = '.cell/modules/' + pkg_context + '/' + requested + ext
|
||||
var res = check(pkg_path, pkg_context, false)
|
||||
if (res) return res
|
||||
|
||||
// Check if package is locally replaced
|
||||
if (config && config.replace && config.replace[pkg_context]) {
|
||||
var replace_path = config.replace[pkg_context]
|
||||
var full_path = replace_path + '/' + requested + ACTOR_EXT
|
||||
if (is_file(full_path)) {
|
||||
return { path: full_path, package_name: pkg_context, isCore: false }
|
||||
}
|
||||
var full_path = replace_path + '/' + requested + ext
|
||||
res = check(full_path, pkg_context, false)
|
||||
if (res) return res
|
||||
}
|
||||
} else {
|
||||
var project_actor_path = requested + ACTOR_EXT
|
||||
if (is_file(project_actor_path)) {
|
||||
return { path: project_actor_path, package_name: null, isCore: false }
|
||||
}
|
||||
var project_path = requested + ext
|
||||
var res = check(project_path, null, false)
|
||||
if (res) return res
|
||||
}
|
||||
|
||||
// Step 2: dependencies (explicit alias first) and replace directives
|
||||
if (requested.includes('/')) {
|
||||
var actor_parts = requested.split('/')
|
||||
var actor_pkg_alias = actor_parts[0]
|
||||
var actor_sub_path = actor_parts.slice(1).join('/')
|
||||
var parts = requested.split('/')
|
||||
var pkg_alias = parts[0]
|
||||
var sub_path = parts.slice(1).join('/')
|
||||
|
||||
// Check for replace directive first
|
||||
if (config && config.replace && config.replace[actor_pkg_alias]) {
|
||||
var replace_path = config.replace[actor_pkg_alias]
|
||||
var full_path = replace_path + '/' + (actor_sub_path || actor_pkg_alias) + ACTOR_EXT
|
||||
if (is_file(full_path)) {
|
||||
return { path: full_path, package_name: actor_pkg_alias, isCore: false }
|
||||
}
|
||||
} else if (dependencies[actor_pkg_alias]) {
|
||||
var dep_actor_path = '.cell/modules/' + actor_pkg_alias + '/' + actor_sub_path + ACTOR_EXT
|
||||
if (is_file(dep_actor_path)) {
|
||||
return { path: dep_actor_path, package_name: actor_pkg_alias, isCore: false }
|
||||
}
|
||||
if (config && config.replace && config.replace[pkg_alias]) {
|
||||
var replace_path = config.replace[pkg_alias]
|
||||
var full_path = replace_path + '/' + (sub_path || pkg_alias) + ext
|
||||
var res = check(full_path, pkg_alias, false)
|
||||
if (res) return res
|
||||
} else if (dependencies[pkg_alias]) {
|
||||
var dep_path = '.cell/modules/' + pkg_alias + '/' + sub_path + ext
|
||||
var res = check(dep_path, pkg_alias, false)
|
||||
if (res) return res
|
||||
}
|
||||
} else {
|
||||
// Check replace directives for simple actor names
|
||||
// Check replace directives for simple names
|
||||
if (config && config.replace && config.replace[requested]) {
|
||||
var replace_path = config.replace[requested]
|
||||
var full_path = replace_path + '/' + requested + ACTOR_EXT
|
||||
if (is_file(full_path)) {
|
||||
return { path: full_path, package_name: requested, isCore: false }
|
||||
}
|
||||
}
|
||||
// Check dependencies for simple actor names
|
||||
for (var actor_alias in dependencies) {
|
||||
var dep_actor_simple = '.cell/modules/' + actor_alias + '/' + requested + ACTOR_EXT
|
||||
if (is_file(dep_actor_simple)) {
|
||||
return { path: dep_actor_simple, package_name: actor_alias, isCore: false }
|
||||
var full_path = replace_path + '/' + requested + ext
|
||||
var res = check(full_path, requested, false)
|
||||
if (res) return res
|
||||
}
|
||||
// Check dependencies for simple names
|
||||
for (var alias in dependencies) {
|
||||
var dep_simple = '.cell/modules/' + alias + '/' + requested + ext
|
||||
var res = check(dep_simple, alias, false)
|
||||
if (res) return res
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: core
|
||||
try {
|
||||
core_qop.read(requested + ACTOR_EXT)
|
||||
return { path: requested + ACTOR_EXT, package_name: null, isCore: true }
|
||||
} catch (e) {
|
||||
// Not in core
|
||||
}
|
||||
|
||||
return null
|
||||
return check(requested + ext, null, true)
|
||||
}
|
||||
|
||||
// Resolve module path with package awareness
|
||||
// Resolution order:
|
||||
// 1. Current package (root project when pkg_context is null)
|
||||
// 2. Declared dependencies (from cell.toml)
|
||||
// 3. core_qop (standard library)
|
||||
function resolve_module_path(requested, pkg_context) {
|
||||
var dependencies = (config && config.dependencies) ? config.dependencies : {}
|
||||
|
||||
// Step 1: current package
|
||||
if (pkg_context) {
|
||||
var pkg_module_path = '.cell/modules/' + pkg_context + '/' + requested + MOD_EXT
|
||||
if (is_file(pkg_module_path)) {
|
||||
return { path: pkg_module_path, package_name: pkg_context, isCore: false }
|
||||
function get_compiled_path(resolved) {
|
||||
var build_base = '.cell/build/'
|
||||
if (resolved.isCore) {
|
||||
return build_base + 'core/' + resolved.path + '.o'
|
||||
} else if (resolved.package_name) {
|
||||
// If it's in .cell/modules/<pkg>/...
|
||||
var prefix = '.cell/modules/' + resolved.package_name + '/'
|
||||
if (resolved.path.startsWith(prefix)) {
|
||||
return build_base + 'modules/' + resolved.package_name + '/' + resolved.path.substring(prefix.length) + '.o'
|
||||
}
|
||||
|
||||
// Check if package is locally replaced
|
||||
if (config && config.replace && config.replace[pkg_context]) {
|
||||
var replace_path = config.replace[pkg_context]
|
||||
var full_path = replace_path + '/' + requested + MOD_EXT
|
||||
if (is_file(full_path)) {
|
||||
return { path: full_path, package_name: pkg_context, isCore: false }
|
||||
}
|
||||
}
|
||||
// This seems correct for the "modules" case.
|
||||
log.console(json.encode(resolved))
|
||||
return build_base + 'modules/' + resolved.package_name + '/' + resolved.path.substring(resolved.path.lastIndexOf('/') + 1) + '.o'
|
||||
} else {
|
||||
var project_module_path = requested + MOD_EXT
|
||||
if (is_file(project_module_path)) {
|
||||
return { path: project_module_path, package_name: null, isCore: false }
|
||||
// Local project file
|
||||
return build_base + 'local/' + resolved.path + '.o'
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: dependencies (explicit alias first) and replace directives
|
||||
if (requested.includes('/')) {
|
||||
var module_parts = requested.split('/')
|
||||
var module_pkg_alias = module_parts[0]
|
||||
var module_sub = module_parts.slice(1).join('/')
|
||||
|
||||
// Check for replace directive first
|
||||
if (config && config.replace && config.replace[module_pkg_alias]) {
|
||||
var replace_path = config.replace[module_pkg_alias]
|
||||
var full_path = replace_path + '/' + (module_sub || module_pkg_alias) + MOD_EXT
|
||||
if (is_file(full_path)) {
|
||||
return { path: full_path, package_name: module_pkg_alias, isCore: false }
|
||||
}
|
||||
} else if (dependencies[module_pkg_alias]) {
|
||||
var dep_module_path = '.cell/modules/' + module_pkg_alias + '/' + module_sub + MOD_EXT
|
||||
if (is_file(dep_module_path)) {
|
||||
return { path: dep_module_path, package_name: module_pkg_alias, isCore: false }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Check replace directives for simple module names
|
||||
if (config && config.replace && config.replace[requested]) {
|
||||
var replace_path = config.replace[requested]
|
||||
var full_path = replace_path + '/' + requested + MOD_EXT
|
||||
if (is_file(full_path)) {
|
||||
return { path: full_path, package_name: requested, isCore: false }
|
||||
}
|
||||
}
|
||||
// Check dependencies for simple module names
|
||||
for (var module_alias in dependencies) {
|
||||
var dep_module_simple = '.cell/modules/' + module_alias + '/' + requested + MOD_EXT
|
||||
if (is_file(dep_module_simple)) {
|
||||
return { path: dep_module_simple, package_name: module_alias, isCore: false }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 3: core
|
||||
try {
|
||||
core_qop.read(requested + MOD_EXT)
|
||||
return { path: requested + MOD_EXT, package_name: null, isCore: true }
|
||||
} catch (e) {
|
||||
// Not in core
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
globalThis.use = function use(file, ...args) {
|
||||
/* Package-aware module resolution:
|
||||
1. If in a package context, check within that package first
|
||||
2. Check local project files
|
||||
3. Check declared dependencies (from cell.toml [dependencies])
|
||||
4. Check core_qop (standard library)
|
||||
1. Check local package (cwd, ie '.')
|
||||
2. Check declared dependencies (from cell.toml [dependencies])
|
||||
3. Check core_qop (standard library)
|
||||
|
||||
There's also the possibility of native C code;
|
||||
there may be, in a package, a .so/.dll/.dylib
|
||||
@@ -361,7 +301,7 @@ globalThis.use = function use(file, ...args) {
|
||||
var embed_mod = use_embed(requested)
|
||||
|
||||
// Resolve the module path with package awareness
|
||||
var resolved = resolve_module_path(requested, current_package)
|
||||
var resolved = resolve_path(requested, current_package, MOD_EXT)
|
||||
|
||||
// Generate cache key based on resolution
|
||||
var cache_key = resolved
|
||||
@@ -370,6 +310,8 @@ globalThis.use = function use(file, ...args) {
|
||||
|
||||
if (use_cache[cache_key]) return use_cache[cache_key]
|
||||
|
||||
log.console(`cache miss: ${cache_key}`)
|
||||
|
||||
if (!resolved && !embed_mod)
|
||||
throw new Error(`Module ${file} could not be found (package context: ${current_package || 'none'})`)
|
||||
|
||||
@@ -387,15 +329,38 @@ globalThis.use = function use(file, ...args) {
|
||||
if (isCore) {
|
||||
var ret = null
|
||||
try {
|
||||
// Try to load compiled version first
|
||||
var compiledPath = get_compiled_path(resolved)
|
||||
var useCompiled = false
|
||||
|
||||
if (is_file(compiledPath)) {
|
||||
useCompiled = true
|
||||
}
|
||||
|
||||
var fn
|
||||
if (useCompiled) {
|
||||
var compiledBlob = fd.slurp(compiledPath)
|
||||
fn = js.compile_unblob(compiledBlob)
|
||||
fn = js.eval_compile(fn)
|
||||
log.console("use: using compiled core version " + compiledPath)
|
||||
} else {
|
||||
var script = utf8.decode(core_qop.read(path))
|
||||
var mod_script = `(function setup_${requested.replace(/[^a-zA-Z0-9_]/g, '_')}_module(arg, $_){${script};})`
|
||||
var fn = js.compile(path, mod_script)
|
||||
fn = js.compile(path, mod_script)
|
||||
|
||||
// Save compiled version
|
||||
mkdir_p(compiledPath.substring(0, compiledPath.lastIndexOf('/')))
|
||||
var compiled = js.compile_blob(fn)
|
||||
write_file(compiledPath, compiled)
|
||||
|
||||
fn = js.eval_compile(fn)
|
||||
}
|
||||
|
||||
var context = embed_mod ? embed_mod : {}
|
||||
ret = fn.call(context, args, $_)
|
||||
} catch (e) {
|
||||
// Script component doesn't exist, fall back to embedded module
|
||||
// log.console("use: core module " + path + " has no script component, using embedded module")
|
||||
// Script component doesn't exist or failed, fall back to embedded module
|
||||
// log.console("use: core module " + path + " failed to load script (using embedded if avail)")
|
||||
}
|
||||
|
||||
if (!ret && embed_mod) {
|
||||
@@ -429,8 +394,7 @@ globalThis.use = function use(file, ...args) {
|
||||
current_package = module_package
|
||||
|
||||
// Determine the compiled file path in .cell directory
|
||||
var cleanPath = path.replace(/[:\\]/g, '/').replace(/\/+/g, '/')
|
||||
var compiledPath = ".cell/build/" + cleanPath + '.o'
|
||||
var compiledPath = get_compiled_path(resolved)
|
||||
|
||||
mkdir_p(compiledPath.substring(0, compiledPath.lastIndexOf('/')))
|
||||
|
||||
@@ -438,9 +402,10 @@ globalThis.use = function use(file, ...args) {
|
||||
var useCompiled = false
|
||||
var srcStat = fd.stat(path)
|
||||
var compiledStat = fd.stat(compiledPath)
|
||||
// if (srcStat && compiledStat && compiledStat.mtime > srcStat.mtime) {
|
||||
// useCompiled = true
|
||||
// }
|
||||
|
||||
if (srcStat && srcStat.isFile && compiledStat && compiledStat.isFile && compiledStat.mtime > srcStat.mtime) {
|
||||
useCompiled = true
|
||||
}
|
||||
|
||||
var fn
|
||||
var mod_name = path.substring(path.lastIndexOf('/') + 1, path.lastIndexOf('.'))
|
||||
@@ -449,14 +414,15 @@ globalThis.use = function use(file, ...args) {
|
||||
var compiledBlob = fd.slurp(compiledPath)
|
||||
fn = js.compile_unblob(compiledBlob)
|
||||
fn = js.eval_compile(fn)
|
||||
log.console("use: using compiled version " + compiledPath)
|
||||
} else {
|
||||
var script = utf8.decode(fd.slurp(path))
|
||||
var mod_script = `(function setup_${mod_name}_module(arg, $_){${script};})`
|
||||
fn = js.compile(path, mod_script)
|
||||
|
||||
// Save compiled version to .cell directory
|
||||
// var compiled = js.compile_blob(fn)
|
||||
// write_file(compiledPath, compiled)
|
||||
var compiled = js.compile_blob(fn)
|
||||
write_file(compiledPath, compiled)
|
||||
|
||||
fn = js.eval_compile(fn)
|
||||
}
|
||||
@@ -738,7 +704,7 @@ $_.start = function start(cb, program, ...args) {
|
||||
if (!program) return
|
||||
|
||||
// Resolve the actor program path with package awareness
|
||||
var resolved_program = resolve_actor_path(program, current_package)
|
||||
var resolved_program = resolve_path(program, current_package, ACTOR_EXT)
|
||||
if (!resolved_program) {
|
||||
throw new Error(`Actor program ${program} could not be found (package context: ${current_package || 'none'})`)
|
||||
}
|
||||
@@ -1074,25 +1040,54 @@ actor_mod.setname(cell.args.program)
|
||||
var prog = cell.args.program
|
||||
|
||||
// Resolve the main program path
|
||||
var resolved_prog = resolve_actor_path(cell.args.program, current_package)
|
||||
var resolved_prog = resolve_path(cell.args.program, current_package, ACTOR_EXT)
|
||||
if (!resolved_prog) {
|
||||
throw new Error(`Main program ${cell.args.program} could not be found`)
|
||||
}
|
||||
|
||||
prog = resolved_prog.path
|
||||
|
||||
var progContent
|
||||
var startfn
|
||||
var compiledPath = get_compiled_path(resolved_prog)
|
||||
var useCompiled = false
|
||||
|
||||
if (resolved_prog.isCore) {
|
||||
progContent = utf8.decode(core_qop.read(prog))
|
||||
// For core, we check if we have a compiled version, else we compile it
|
||||
if (is_file(compiledPath)) {
|
||||
useCompiled = true
|
||||
}
|
||||
} else {
|
||||
progContent = utf8.decode(fd.slurp(prog))
|
||||
// For local/modules, check timestamps
|
||||
var srcStat = fd.stat(prog)
|
||||
var compiledStat = fd.stat(compiledPath)
|
||||
if (srcStat && srcStat.isFile && compiledStat && compiledStat.isFile && compiledStat.mtime > srcStat.mtime) {
|
||||
useCompiled = true
|
||||
}
|
||||
}
|
||||
|
||||
var prog_script = `(function ${cell.args.program.name()}_start($_, arg) { var args = arg; ${progContent} })`
|
||||
if (useCompiled) {
|
||||
var compiledBlob = fd.slurp(compiledPath)
|
||||
var fn = js.compile_unblob(compiledBlob)
|
||||
startfn = js.eval_compile(fn)
|
||||
log.console("main: using compiled version " + compiledPath)
|
||||
} else {
|
||||
var progContent
|
||||
if (resolved_prog.isCore) {
|
||||
progContent = utf8.decode(core_qop.read(prog))
|
||||
} else {
|
||||
progContent = utf8.decode(fd.slurp(prog))
|
||||
}
|
||||
|
||||
// queue up its first turn instead of run immediately
|
||||
var prog_script = `(function ${cell.args.program.name()}_start($_, arg) { var args = arg; ${progContent} })`
|
||||
|
||||
var startfn = js.eval(cell.args.program, prog_script);
|
||||
// Compile and save
|
||||
var fn = js.compile(cell.args.program, prog_script)
|
||||
mkdir_p(compiledPath.substring(0, compiledPath.lastIndexOf('/')))
|
||||
var compiled = js.compile_blob(fn)
|
||||
write_file(compiledPath, compiled)
|
||||
|
||||
startfn = js.eval_compile(fn)
|
||||
}
|
||||
|
||||
log.console(`program compiled in ${time.number()-load_program_start} seconds`)
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
var http = this
|
||||
|
||||
http.fetch[cell.DOC] = `Initiate an asynchronous HTTP GET request.
|
||||
|
||||
This function enqueues an HTTP GET request for the specified URL. It supports both buffered responses (full response collected in memory) and streaming data (processed as it arrives). The request is executed asynchronously, and its completion or streaming data is handled via callbacks provided in the options. Use 'poll' to process the results.
|
||||
|
||||
:param url: A string representing the URL to fetch.
|
||||
:param options: Either a callback function or an object with optional properties:
|
||||
- 'callback': A function invoked upon request completion, receiving an object with 'data' (string or null) and 'error' (string or null) properties.
|
||||
- 'on_data': A function invoked for each chunk of streaming data, receiving a string chunk as its argument. If supplied, 'callback.data' will be null.
|
||||
:return: null
|
||||
:throws:
|
||||
- An error if the URL is not a string or is invalid.
|
||||
- An error if the options argument is neither a function nor an object.
|
||||
- An error if memory allocation or CURL initialization fails.
|
||||
`
|
||||
|
||||
return http
|
||||
@@ -1,16 +0,0 @@
|
||||
var miniz = this
|
||||
|
||||
miniz.read[cell.DOC] = `Create a zip reader from the given ArrayBuffer containing an entire ZIP archive.
|
||||
Return null if the data is invalid.
|
||||
|
||||
:param data: An ArrayBuffer with the entire ZIP file.
|
||||
:return: A 'zip reader' object with methods for reading from the archive (mod, exists, slurp).
|
||||
`
|
||||
|
||||
miniz.write[cell.DOC] = `Create a zip writer that writes to the specified file path. Overwrites the file if
|
||||
it already exists. Return null on error.
|
||||
|
||||
:param path: The file path where the ZIP archive will be written.
|
||||
:return: A 'zip writer' object with methods for adding files to the archive (add_file).
|
||||
`
|
||||
return miniz
|
||||
147
scripts/qopconv.ce
Normal file
147
scripts/qopconv.ce
Normal file
@@ -0,0 +1,147 @@
|
||||
var fd = use('fd')
|
||||
var qop = use('qop')
|
||||
var cellfs = use('cellfs')
|
||||
|
||||
function print_usage() {
|
||||
log.console("Usage: qopconv [OPTION...] FILE...")
|
||||
log.console(" -u <archive> ... unpack archive")
|
||||
log.console(" -l <archive> ... list contents of archive")
|
||||
log.console(" -d <dir> ....... change read dir when creating archives")
|
||||
log.console(" <sources...> <archive> ... create archive from sources")
|
||||
}
|
||||
|
||||
function list(archive_path) {
|
||||
var blob = fd.slurp(archive_path)
|
||||
if (!blob) {
|
||||
log.console("Could not open archive " + archive_path)
|
||||
return
|
||||
}
|
||||
var archive = null
|
||||
try {
|
||||
archive = qop.open(blob)
|
||||
} catch(e) {
|
||||
log.console("Could not open archive " + archive_path + ": " + e.message)
|
||||
return
|
||||
}
|
||||
|
||||
var files = archive.list()
|
||||
for (var f of files) {
|
||||
var s = archive.stat(f)
|
||||
// Format: index hash size path
|
||||
// We don't have index/hash easily available in JS binding yet, just size/path
|
||||
log.console(`${f} (${s.size} bytes)`)
|
||||
}
|
||||
archive.close()
|
||||
}
|
||||
|
||||
function unpack(archive_path) {
|
||||
var blob = fd.slurp(archive_path)
|
||||
if (!blob) {
|
||||
log.console("Could not open archive " + archive_path)
|
||||
return
|
||||
}
|
||||
var archive = null
|
||||
try {
|
||||
archive = qop.open(blob)
|
||||
} catch(e) {
|
||||
log.console("Could not open archive " + archive_path + ": " + e.message)
|
||||
return
|
||||
}
|
||||
|
||||
var files = archive.list()
|
||||
for (var f of files) {
|
||||
var data = archive.read(f)
|
||||
if (data) {
|
||||
// Ensure directory exists
|
||||
var dir = f.substring(0, f.lastIndexOf('/'))
|
||||
if (dir) {
|
||||
// recursive mkdir
|
||||
var parts = dir.split('/')
|
||||
var curr = "."
|
||||
for (var p of parts) {
|
||||
curr += "/" + p
|
||||
try { fd.mkdir(curr) } catch(e) {}
|
||||
}
|
||||
}
|
||||
var fh = fd.open(f, "w")
|
||||
fd.write(fh, data)
|
||||
fd.close(fh)
|
||||
log.console("Extracted " + f)
|
||||
}
|
||||
}
|
||||
archive.close()
|
||||
}
|
||||
|
||||
function pack(sources, archive_path, read_dir) {
|
||||
var writer = qop.write(archive_path)
|
||||
|
||||
var base_dir = read_dir || "."
|
||||
|
||||
function add_recursive(path) {
|
||||
var full_path = base_dir + "/" + path
|
||||
if (path == ".") full_path = base_dir
|
||||
if (read_dir == null && path != ".") full_path = path
|
||||
|
||||
var st = fd.stat(full_path)
|
||||
if (!st) {
|
||||
log.console("Could not stat " + full_path)
|
||||
return
|
||||
}
|
||||
|
||||
if (st.isDirectory) {
|
||||
var list = fd.readdir(full_path)
|
||||
for (var item of list) {
|
||||
if (item == "." || item == "..") continue
|
||||
var sub = path == "." ? item : path + "/" + item
|
||||
add_recursive(sub)
|
||||
}
|
||||
} else {
|
||||
var data = fd.slurp(full_path)
|
||||
if (data) {
|
||||
writer.add_file(path, data)
|
||||
log.console("Added " + path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var s of sources) {
|
||||
add_recursive(s)
|
||||
}
|
||||
|
||||
writer.finalize()
|
||||
log.console("Created " + archive_path)
|
||||
}
|
||||
|
||||
if (typeof arg == 'undefined' || arg.length < 1) {
|
||||
print_usage()
|
||||
} else {
|
||||
if (arg[0] == "-l") {
|
||||
if (arg.length < 2) print_usage()
|
||||
else list(arg[1])
|
||||
} else if (arg[0] == "-u") {
|
||||
if (arg.length < 2) print_usage()
|
||||
else unpack(arg[1])
|
||||
} else {
|
||||
var sources = []
|
||||
var archive = null
|
||||
var read_dir = null
|
||||
var i = 0
|
||||
if (arg[0] == "-d") {
|
||||
read_dir = arg[1]
|
||||
i = 2
|
||||
}
|
||||
|
||||
for (; i < arg.length - 1; i++) {
|
||||
sources.push(arg[i])
|
||||
}
|
||||
archive = arg[arg.length - 1]
|
||||
|
||||
if (sources.length == 0) {
|
||||
print_usage()
|
||||
} else {
|
||||
pack(sources, archive, read_dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$_.stop()
|
||||
109
source/qjs_fd.c
109
source/qjs_fd.c
@@ -79,7 +79,7 @@ JSC_SCALL(fd_open,
|
||||
|
||||
int fd = open(str, flags, mode);
|
||||
if (fd < 0)
|
||||
ret = JS_ThrowReferenceError(js, "open failed: %s", strerror(errno));
|
||||
ret = JS_ThrowInternalError(js, "open failed: %s", strerror(errno));
|
||||
else
|
||||
ret = JS_NewInt32(js, fd);
|
||||
)
|
||||
@@ -90,7 +90,7 @@ JSC_CCALL(fd_write,
|
||||
|
||||
ssize_t wrote = js_fd_write_helper(js, fd, argv[1]);
|
||||
if (wrote < 0)
|
||||
return JS_ThrowReferenceError(js, "write failed: %s", strerror(errno));
|
||||
return JS_ThrowInternalError(js, "write failed: %s", strerror(errno));
|
||||
|
||||
return JS_NewInt64(js, wrote);
|
||||
)
|
||||
@@ -105,12 +105,12 @@ JSC_CCALL(fd_read,
|
||||
|
||||
void *buf = malloc(size);
|
||||
if (!buf)
|
||||
return JS_ThrowReferenceError(js, "malloc failed");
|
||||
return JS_ThrowInternalError(js, "malloc failed");
|
||||
|
||||
ssize_t bytes_read = read(fd, buf, size);
|
||||
if (bytes_read < 0) {
|
||||
free(buf);
|
||||
return JS_ThrowReferenceError(js, "read failed: %s", strerror(errno));
|
||||
return JS_ThrowInternalError(js, "read failed: %s", strerror(errno));
|
||||
}
|
||||
|
||||
ret = js_new_blob_stoned_copy(js, buf, bytes_read);
|
||||
@@ -121,7 +121,7 @@ JSC_CCALL(fd_read,
|
||||
JSC_SCALL(fd_slurp,
|
||||
struct stat st;
|
||||
if (stat(str, &st) != 0)
|
||||
return JS_ThrowReferenceError(js, "stat failed: %s", strerror(errno));
|
||||
return JS_ThrowInternalError(js, "stat failed: %s", strerror(errno));
|
||||
|
||||
if (!S_ISREG(st.st_mode))
|
||||
return JS_ThrowTypeError(js, "path is not a regular file");
|
||||
@@ -133,12 +133,12 @@ JSC_SCALL(fd_slurp,
|
||||
#ifndef _WIN32
|
||||
int fd = open(str, O_RDONLY);
|
||||
if (fd < 0)
|
||||
return JS_ThrowReferenceError(js, "open failed: %s", strerror(errno));
|
||||
return JS_ThrowInternalError(js, "open failed: %s", strerror(errno));
|
||||
|
||||
void *data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (data == MAP_FAILED) {
|
||||
close(fd);
|
||||
return JS_ThrowReferenceError(js, "mmap failed: %s", strerror(errno));
|
||||
return JS_ThrowInternalError(js, "mmap failed: %s", strerror(errno));
|
||||
}
|
||||
ret = js_new_blob_stoned_copy(js, data, size);
|
||||
munmap(data, size);
|
||||
@@ -147,19 +147,19 @@ JSC_SCALL(fd_slurp,
|
||||
// Windows: use memory mapping for optimal performance
|
||||
HANDLE hFile = CreateFileA(str, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
if (hFile == INVALID_HANDLE_VALUE)
|
||||
return JS_ThrowReferenceError(js, "CreateFile failed: %lu", GetLastError());
|
||||
return JS_ThrowInternalError(js, "CreateFile failed: %lu", GetLastError());
|
||||
|
||||
HANDLE hMapping = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
|
||||
if (hMapping == NULL) {
|
||||
CloseHandle(hFile);
|
||||
return JS_ThrowReferenceError(js, "CreateFileMapping failed: %lu", GetLastError());
|
||||
return JS_ThrowInternalError(js, "CreateFileMapping failed: %lu", GetLastError());
|
||||
}
|
||||
|
||||
void *data = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
|
||||
if (data == NULL) {
|
||||
CloseHandle(hMapping);
|
||||
CloseHandle(hFile);
|
||||
return JS_ThrowReferenceError(js, "MapViewOfFile failed: %lu", GetLastError());
|
||||
return JS_ThrowInternalError(js, "MapViewOfFile failed: %lu", GetLastError());
|
||||
}
|
||||
|
||||
ret = js_new_blob_stoned_copy(js, data, size);
|
||||
@@ -185,7 +185,7 @@ JSC_CCALL(fd_lseek,
|
||||
|
||||
off_t new_pos = lseek(fd, offset, whence);
|
||||
if (new_pos < 0)
|
||||
return JS_ThrowReferenceError(js, "lseek failed: %s", strerror(errno));
|
||||
return JS_ThrowInternalError(js, "lseek failed: %s", strerror(errno));
|
||||
|
||||
return JS_NewInt64(js, new_pos);
|
||||
)
|
||||
@@ -193,23 +193,90 @@ JSC_CCALL(fd_lseek,
|
||||
JSC_CCALL(fd_getcwd,
|
||||
char buf[PATH_MAX];
|
||||
if (getcwd(buf, sizeof(buf)) == NULL)
|
||||
return JS_ThrowReferenceError(js, "getcwd failed: %s", strerror(errno));
|
||||
return JS_ThrowInternalError(js, "getcwd failed: %s", strerror(errno));
|
||||
return JS_NewString(js, buf);
|
||||
)
|
||||
|
||||
JSC_SCALL(fd_rmdir,
|
||||
if (rmdir(str) != 0)
|
||||
ret = JS_ThrowReferenceError(js, "could not remove directory %s: %s", str, strerror(errno));
|
||||
ret = JS_ThrowInternalError(js, "could not remove directory %s: %s", str, strerror(errno));
|
||||
)
|
||||
|
||||
JSC_SCALL(fd_mkdir,
|
||||
if (mkdir(str, 0755) != 0)
|
||||
ret = JS_ThrowReferenceError(js, "could not make directory %s: %s", str, strerror(errno));
|
||||
ret = JS_ThrowInternalError(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));
|
||||
ret = JS_ThrowInternalError(js, "could not remove file %s: %s", str, strerror(errno));
|
||||
)
|
||||
|
||||
JSC_SCALL(fd_mv,
|
||||
if (argc < 2)
|
||||
ret = JS_ThrowTypeError(js, "fd.mv requires 2 arguments: old path and new path");
|
||||
else if (!JS_IsString(argv[1]))
|
||||
ret = JS_ThrowTypeError(js, "second argument must be a string (new path)");
|
||||
else {
|
||||
const char *new_path = JS_ToCString(js, argv[1]);
|
||||
if (rename(str, new_path) != 0)
|
||||
ret = JS_ThrowInternalError(js, "could not rename %s to %s: %s", str, new_path, strerror(errno));
|
||||
JS_FreeCString(js, new_path);
|
||||
}
|
||||
)
|
||||
|
||||
// Helper function for recursive removal
|
||||
static int remove_recursive(const char *path) {
|
||||
struct stat st;
|
||||
if (stat(path, &st) != 0)
|
||||
return -1;
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
// Directory: remove contents first
|
||||
#ifdef _WIN32
|
||||
WIN32_FIND_DATA ffd;
|
||||
char search_path[PATH_MAX];
|
||||
snprintf(search_path, sizeof(search_path), "%s\\*", path);
|
||||
HANDLE hFind = FindFirstFile(search_path, &ffd);
|
||||
if (hFind != INVALID_HANDLE_VALUE) {
|
||||
do {
|
||||
if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue;
|
||||
char child_path[PATH_MAX];
|
||||
snprintf(child_path, sizeof(child_path), "%s\\%s", path, ffd.cFileName);
|
||||
if (remove_recursive(child_path) != 0) {
|
||||
FindClose(hFind);
|
||||
return -1;
|
||||
}
|
||||
} while (FindNextFile(hFind, &ffd) != 0);
|
||||
FindClose(hFind);
|
||||
}
|
||||
#else
|
||||
DIR *d = opendir(path);
|
||||
if (d) {
|
||||
struct dirent *dir;
|
||||
while ((dir = readdir(d)) != NULL) {
|
||||
if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue;
|
||||
char child_path[PATH_MAX];
|
||||
snprintf(child_path, sizeof(child_path), "%s/%s", path, dir->d_name);
|
||||
if (remove_recursive(child_path) != 0) {
|
||||
closedir(d);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
}
|
||||
#endif
|
||||
// Remove the now-empty directory
|
||||
return rmdir(path);
|
||||
} else {
|
||||
// File: just unlink
|
||||
return unlink(path);
|
||||
}
|
||||
}
|
||||
|
||||
JSC_SCALL(fd_rm,
|
||||
if (remove_recursive(str) != 0)
|
||||
ret = JS_ThrowInternalError(js, "could not remove %s: %s", str, strerror(errno));
|
||||
)
|
||||
|
||||
JSC_CCALL(fd_fsync,
|
||||
@@ -217,7 +284,7 @@ JSC_CCALL(fd_fsync,
|
||||
if (fd < 0) return JS_EXCEPTION;
|
||||
|
||||
if (fsync(fd) != 0)
|
||||
return JS_ThrowReferenceError(js, "fsync failed: %s", strerror(errno));
|
||||
return JS_ThrowInternalError(js, "fsync failed: %s", strerror(errno));
|
||||
|
||||
return JS_NULL;
|
||||
)
|
||||
@@ -227,7 +294,7 @@ JSC_CCALL(fd_close,
|
||||
if (fd < 0) return JS_EXCEPTION;
|
||||
|
||||
if (close(fd) != 0)
|
||||
return JS_ThrowReferenceError(js, "close failed: %s", strerror(errno));
|
||||
return JS_ThrowInternalError(js, "close failed: %s", strerror(errno));
|
||||
|
||||
return JS_NULL;
|
||||
)
|
||||
@@ -238,7 +305,7 @@ JSC_CCALL(fd_fstat,
|
||||
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) != 0)
|
||||
return JS_ThrowReferenceError(js, "fstat failed: %s", strerror(errno));
|
||||
return JS_ThrowInternalError(js, "fstat failed: %s", strerror(errno));
|
||||
|
||||
printf("fstat on %s\n", argv[0]);
|
||||
JSValue obj = JS_NewObject(js);
|
||||
@@ -317,7 +384,7 @@ JSC_SCALL(fd_readdir,
|
||||
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);
|
||||
ret = JS_ThrowInternalError(js, "FindFirstFile failed for %s", path);
|
||||
} else {
|
||||
ret = JS_NewArray(js);
|
||||
int i = 0;
|
||||
@@ -340,7 +407,7 @@ JSC_SCALL(fd_readdir,
|
||||
}
|
||||
closedir(d);
|
||||
} else {
|
||||
ret = JS_ThrowReferenceError(js, "opendir failed for %s: %s", str, strerror(errno));
|
||||
ret = JS_ThrowInternalError(js, "opendir failed for %s: %s", str, strerror(errno));
|
||||
}
|
||||
#endif
|
||||
)
|
||||
@@ -355,6 +422,8 @@ static const JSCFunctionListEntry js_fd_funcs[] = {
|
||||
MIST_FUNC_DEF(fd, rmdir, 1),
|
||||
MIST_FUNC_DEF(fd, unlink, 1),
|
||||
MIST_FUNC_DEF(fd, mkdir, 1),
|
||||
MIST_FUNC_DEF(fd, mv, 2),
|
||||
MIST_FUNC_DEF(fd, rm, 1),
|
||||
MIST_FUNC_DEF(fd, fsync, 1),
|
||||
MIST_FUNC_DEF(fd, close, 1),
|
||||
MIST_FUNC_DEF(fd, stat, 1),
|
||||
|
||||
268
source/qjs_qop.c
268
source/qjs_qop.c
@@ -21,6 +21,74 @@ static JSClassDef js_qop_archive_class = {
|
||||
.finalizer = js_qop_archive_finalizer,
|
||||
};
|
||||
|
||||
static JSClassID js_qop_writer_class_id;
|
||||
|
||||
typedef struct {
|
||||
FILE *fh;
|
||||
qop_file *files;
|
||||
int len;
|
||||
int capacity;
|
||||
unsigned int size;
|
||||
} qop_writer;
|
||||
|
||||
static void js_qop_writer_finalizer(JSRuntime *rt, JSValue val) {
|
||||
qop_writer *w = JS_GetOpaque(val, js_qop_writer_class_id);
|
||||
if (w) {
|
||||
if (w->fh) fclose(w->fh);
|
||||
if (w->files) js_free_rt(rt, w->files);
|
||||
js_free_rt(rt, w);
|
||||
}
|
||||
}
|
||||
|
||||
static JSClassDef js_qop_writer_class = {
|
||||
"qop writer",
|
||||
.finalizer = js_qop_writer_finalizer,
|
||||
};
|
||||
|
||||
static qop_writer *js2writer(JSContext *js, JSValue v) {
|
||||
return JS_GetOpaque(v, js_qop_writer_class_id);
|
||||
}
|
||||
|
||||
// Helper functions for writer
|
||||
static void write_16(unsigned int v, FILE *fh) {
|
||||
unsigned char b[2];
|
||||
b[0] = 0xff & (v);
|
||||
b[1] = 0xff & (v >> 8);
|
||||
fwrite(b, 2, 1, fh);
|
||||
}
|
||||
|
||||
static void write_32(unsigned int v, FILE *fh) {
|
||||
unsigned char b[4];
|
||||
b[0] = 0xff & (v);
|
||||
b[1] = 0xff & (v >> 8);
|
||||
b[2] = 0xff & (v >> 16);
|
||||
b[3] = 0xff & (v >> 24);
|
||||
fwrite(b, 4, 1, fh);
|
||||
}
|
||||
|
||||
static void write_64(unsigned long long v, FILE *fh) {
|
||||
unsigned char b[8];
|
||||
b[0] = 0xff & (v);
|
||||
b[1] = 0xff & (v >> 8);
|
||||
b[2] = 0xff & (v >> 16);
|
||||
b[3] = 0xff & (v >> 24);
|
||||
b[4] = 0xff & (v >> 32);
|
||||
b[5] = 0xff & (v >> 40);
|
||||
b[6] = 0xff & (v >> 48);
|
||||
b[7] = 0xff & (v >> 56);
|
||||
fwrite(b, 8, 1, fh);
|
||||
}
|
||||
|
||||
static unsigned long long qop_hash(const char *key) {
|
||||
unsigned long long h = 525201411107845655ull;
|
||||
for (;*key;++key) {
|
||||
h ^= (unsigned char)*key;
|
||||
h *= 0x5bd1e9955bd1e995ull;
|
||||
h ^= h >> 47;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
static qop_desc *js2qop(JSContext *js, JSValue v) {
|
||||
return JS_GetOpaque(v, js_qop_archive_class_id);
|
||||
}
|
||||
@@ -60,6 +128,36 @@ JSC_SCALL(qop_open,
|
||||
}
|
||||
)
|
||||
|
||||
JSC_SCALL(qop_write,
|
||||
const char *path = JS_ToCString(js, argv[0]);
|
||||
if (!path) return JS_EXCEPTION;
|
||||
|
||||
FILE *fh = fopen(path, "wb");
|
||||
JS_FreeCString(js, path);
|
||||
if (!fh) return JS_ThrowInternalError(js, "Could not open file for writing");
|
||||
|
||||
qop_writer *w = js_malloc(js, sizeof(qop_writer));
|
||||
if (!w) {
|
||||
fclose(fh);
|
||||
return JS_ThrowOutOfMemory(js);
|
||||
}
|
||||
|
||||
w->fh = fh;
|
||||
w->capacity = 1024;
|
||||
w->len = 0;
|
||||
w->size = 0;
|
||||
w->files = js_malloc(js, sizeof(qop_file) * w->capacity);
|
||||
if (!w->files) {
|
||||
fclose(fh);
|
||||
js_free(js, w);
|
||||
return JS_ThrowOutOfMemory(js);
|
||||
}
|
||||
|
||||
JSValue obj = JS_NewObjectClass(js, js_qop_writer_class_id);
|
||||
JS_SetOpaque(obj, w);
|
||||
ret = obj;
|
||||
)
|
||||
|
||||
|
||||
static JSValue js_qop_close(JSContext *js, JSValue self, int argc, JSValue *argv) {
|
||||
qop_desc *qop = js2qop(js, self);
|
||||
@@ -183,15 +281,179 @@ static JSValue js_qop_list(JSContext *js, JSValue self, int argc, JSValue *argv)
|
||||
return arr;
|
||||
}
|
||||
|
||||
static JSValue js_qop_stat(JSContext *js, JSValue self, int argc, JSValue *argv) {
|
||||
qop_desc *qop = js2qop(js, self);
|
||||
if (!qop) return JS_ThrowInternalError(js, "Invalid QOP archive");
|
||||
|
||||
const char *path = JS_ToCString(js, argv[0]);
|
||||
if (!path) return JS_EXCEPTION;
|
||||
|
||||
if (!js_qop_ensure_index(js, qop)) {
|
||||
JS_FreeCString(js, path);
|
||||
return JS_ThrowReferenceError(js, "Failed to read QOP index");
|
||||
}
|
||||
|
||||
qop_file *file = qop_find(qop, path);
|
||||
JS_FreeCString(js, path);
|
||||
|
||||
if (!file) return JS_NULL;
|
||||
|
||||
JSValue obj = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, obj, "size", JS_NewInt64(js, file->size));
|
||||
JS_SetPropertyStr(js, obj, "modtime", JS_NewInt64(js, 0));
|
||||
JS_SetPropertyStr(js, obj, "isDirectory", JS_NewBool(js, 0));
|
||||
return obj;
|
||||
}
|
||||
|
||||
static JSValue js_qop_is_directory(JSContext *js, JSValue self, int argc, JSValue *argv) {
|
||||
qop_desc *qop = js2qop(js, self);
|
||||
if (!qop) return JS_ThrowInternalError(js, "Invalid QOP archive");
|
||||
|
||||
const char *path = JS_ToCString(js, argv[0]);
|
||||
if (!path) return JS_EXCEPTION;
|
||||
|
||||
if (!js_qop_ensure_index(js, qop)) {
|
||||
JS_FreeCString(js, path);
|
||||
return JS_ThrowReferenceError(js, "Failed to read QOP index");
|
||||
}
|
||||
|
||||
// Check if any file starts with path + "/"
|
||||
size_t path_len = strlen(path);
|
||||
char *prefix = js_malloc(js, path_len + 2);
|
||||
memcpy(prefix, path, path_len);
|
||||
prefix[path_len] = '/';
|
||||
prefix[path_len + 1] = '\0';
|
||||
|
||||
int found = 0;
|
||||
|
||||
// This is inefficient but simple. QOP doesn't have a directory structure.
|
||||
// We iterate all files to check prefixes.
|
||||
for (unsigned int i = 0; i < qop->hashmap_len; i++) {
|
||||
qop_file *file = &qop->hashmap[i];
|
||||
if (file->size == 0) continue;
|
||||
|
||||
// We need to read the path to check it
|
||||
// Optimization: check if we can read just the prefix?
|
||||
// qop_read_path reads the whole path.
|
||||
|
||||
// Let's read the path.
|
||||
char file_path[1024]; // MAX_PATH_LEN
|
||||
if (file->path_len > 1024) continue; // Should not happen based on spec
|
||||
|
||||
qop_read_path(qop, file, file_path);
|
||||
if (strncmp(file_path, prefix, path_len + 1) == 0) {
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
js_free(js, prefix);
|
||||
JS_FreeCString(js, path);
|
||||
return JS_NewBool(js, found);
|
||||
}
|
||||
|
||||
// Writer methods
|
||||
|
||||
static JSValue js_writer_add_file(JSContext *js, JSValue self, int argc, JSValue *argv) {
|
||||
qop_writer *w = js2writer(js, self);
|
||||
if (!w) return JS_ThrowInternalError(js, "Invalid QOP writer");
|
||||
|
||||
const char *path = JS_ToCString(js, argv[0]);
|
||||
if (!path) return JS_EXCEPTION;
|
||||
|
||||
size_t data_len;
|
||||
void *data = js_get_blob_data(js, &data_len, argv[1]);
|
||||
if (!data) {
|
||||
JS_FreeCString(js, path);
|
||||
return JS_ThrowTypeError(js, "Second argument must be a blob");
|
||||
}
|
||||
|
||||
if (w->len >= w->capacity) {
|
||||
w->capacity *= 2;
|
||||
qop_file *new_files = js_realloc(js, w->files, sizeof(qop_file) * w->capacity);
|
||||
if (!new_files) {
|
||||
JS_FreeCString(js, path);
|
||||
return JS_ThrowOutOfMemory(js);
|
||||
}
|
||||
w->files = new_files;
|
||||
}
|
||||
|
||||
// Strip leading "./"
|
||||
const char *archive_path = path;
|
||||
if (path[0] == '.' && path[1] == '/') {
|
||||
archive_path = path + 2;
|
||||
}
|
||||
|
||||
unsigned long long hash = qop_hash(archive_path);
|
||||
int path_len = strlen(archive_path) + 1;
|
||||
|
||||
// Write path
|
||||
fwrite(archive_path, 1, path_len, w->fh);
|
||||
|
||||
// Write data
|
||||
fwrite(data, 1, data_len, w->fh);
|
||||
|
||||
w->files[w->len] = (qop_file){
|
||||
.hash = hash,
|
||||
.offset = w->size,
|
||||
.size = (unsigned int)data_len,
|
||||
.path_len = (unsigned short)path_len,
|
||||
.flags = QOP_FLAG_NONE
|
||||
};
|
||||
|
||||
w->size += data_len + path_len;
|
||||
w->len++;
|
||||
|
||||
JS_FreeCString(js, path);
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
static JSValue js_writer_finalize(JSContext *js, JSValue self, int argc, JSValue *argv) {
|
||||
qop_writer *w = js2writer(js, self);
|
||||
if (!w || !w->fh) return JS_ThrowInternalError(js, "Invalid QOP writer or already closed");
|
||||
|
||||
unsigned int total_size = w->size + 12; // Header size
|
||||
|
||||
for (int i = 0; i < w->len; i++) {
|
||||
write_64(w->files[i].hash, w->fh);
|
||||
write_32(w->files[i].offset, w->fh);
|
||||
write_32(w->files[i].size, w->fh);
|
||||
write_16(w->files[i].path_len, w->fh);
|
||||
write_16(w->files[i].flags, w->fh);
|
||||
total_size += 20;
|
||||
}
|
||||
|
||||
// Magic "qopf"
|
||||
unsigned int magic = (((unsigned int)'q') << 0 | ((unsigned int)'o') << 8 |
|
||||
((unsigned int)'p') << 16 | ((unsigned int)'f') << 24);
|
||||
|
||||
write_32(w->len, w->fh);
|
||||
write_32(total_size, w->fh);
|
||||
write_32(magic, w->fh);
|
||||
|
||||
fclose(w->fh);
|
||||
w->fh = NULL;
|
||||
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
static const JSCFunctionListEntry js_qop_archive_funcs[] = {
|
||||
JS_CFUNC_DEF("close", 0, js_qop_close),
|
||||
JS_CFUNC_DEF("list", 0, js_qop_list),
|
||||
JS_CFUNC_DEF("read", 1, js_qop_read),
|
||||
JS_CFUNC_DEF("read_ex", 3, js_qop_read_ex),
|
||||
JS_CFUNC_DEF("stat", 1, js_qop_stat),
|
||||
JS_CFUNC_DEF("is_directory", 1, js_qop_is_directory),
|
||||
};
|
||||
|
||||
static const JSCFunctionListEntry js_qop_writer_funcs[] = {
|
||||
JS_CFUNC_DEF("add_file", 2, js_writer_add_file),
|
||||
JS_CFUNC_DEF("finalize", 0, js_writer_finalize),
|
||||
};
|
||||
|
||||
static const JSCFunctionListEntry js_qop_funcs[] = {
|
||||
MIST_FUNC_DEF(qop, open, 1),
|
||||
MIST_FUNC_DEF(qop, write, 1),
|
||||
JS_PROP_INT32_DEF("FLAG_NONE", QOP_FLAG_NONE, JS_PROP_ENUMERABLE),
|
||||
JS_PROP_INT32_DEF("FLAG_COMPRESSED_ZSTD", QOP_FLAG_COMPRESSED_ZSTD, JS_PROP_ENUMERABLE),
|
||||
JS_PROP_INT32_DEF("FLAG_COMPRESSED_DEFLATE", QOP_FLAG_COMPRESSED_DEFLATE, JS_PROP_ENUMERABLE),
|
||||
@@ -205,6 +467,12 @@ JSValue js_qop_use(JSContext *js) {
|
||||
JS_SetPropertyFunctionList(js, archive_proto, js_qop_archive_funcs, countof(js_qop_archive_funcs));
|
||||
JS_SetClassProto(js, js_qop_archive_class_id, archive_proto);
|
||||
|
||||
JS_NewClassID(&js_qop_writer_class_id);
|
||||
JS_NewClass(JS_GetRuntime(js), js_qop_writer_class_id, &js_qop_writer_class);
|
||||
JSValue writer_proto = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, writer_proto, js_qop_writer_funcs, countof(js_qop_writer_funcs));
|
||||
JS_SetClassProto(js, js_qop_writer_class_id, writer_proto);
|
||||
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod, js_qop_funcs, countof(js_qop_funcs));
|
||||
return mod;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
var i = 0
|
||||
function loop(time)
|
||||
{
|
||||
log.console(`loop ${i} with time ${time}`)
|
||||
// log.console(`loop ${i} with time ${time}`)
|
||||
i++
|
||||
if (i > 60) $_.stop()
|
||||
$_.clock(loop)
|
||||
|
||||
Reference in New Issue
Block a user