Merge branch 'master' into fix_aot

This commit is contained in:
2026-02-21 13:31:33 -06:00
146 changed files with 57219 additions and 124492 deletions

View File

@@ -124,16 +124,19 @@ This project uses a **copying garbage collector**. ANY JS allocation (`JS_NewObj
JS_FRAME(js); JS_FRAME(js);
JS_ROOT(obj, JS_NewObject(js)); JS_ROOT(obj, JS_NewObject(js));
JS_SetPropertyStr(js, obj.val, "x", JS_NewInt32(js, 42)); JS_SetPropertyStr(js, obj.val, "x", JS_NewInt32(js, 42));
JS_SetPropertyStr(js, obj.val, "name", JS_NewString(js, "hello")); JSValue name = JS_NewString(js, "hello");
JS_SetPropertyStr(js, obj.val, "name", name);
JS_RETURN(obj.val); JS_RETURN(obj.val);
``` ```
**Pattern — array with loop:** **Pattern — array with loop (declare root BEFORE the loop):**
```c ```c
JS_FRAME(js); JS_FRAME(js);
JS_ROOT(arr, JS_NewArray(js)); JS_ROOT(arr, JS_NewArray(js));
JSGCRef item = { .val = JS_NULL, .prev = NULL };
JS_PushGCRef(js, &item);
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
JS_ROOT(item, JS_NewObject(js)); item.val = JS_NewObject(js);
JS_SetPropertyStr(js, item.val, "v", JS_NewInt32(js, i)); JS_SetPropertyStr(js, item.val, "v", JS_NewInt32(js, i));
JS_SetPropertyNumber(js, arr.val, i, item.val); JS_SetPropertyNumber(js, arr.val, i, item.val);
} }
@@ -142,18 +145,28 @@ JS_RETURN(arr.val);
**Rules:** **Rules:**
- Access rooted values via `.val` (e.g., `obj.val`, not `obj`) - Access rooted values via `.val` (e.g., `obj.val`, not `obj`)
- NEVER put `JS_ROOT` inside a loop — it pushes the same stack address twice, corrupting the GC chain
- Error returns before `JS_FRAME` use plain `return` - Error returns before `JS_FRAME` use plain `return`
- Error returns after `JS_FRAME` must use `JS_RETURN_EX()` or `JS_RETURN_NULL()` - Error returns after `JS_FRAME` must use `JS_RETURN_EX()` or `JS_RETURN_NULL()`
- When calling a helper that itself returns a JSValue, that return value is safe to pass directly into `JS_SetPropertyStr` — no need to root temporaries that aren't stored in a local
**Common mistake — UNSAFE (will crash under GC pressure):** **CRITICAL — C argument evaluation order bug:**
Allocating functions (`JS_NewString`, `JS_NewFloat64`, `js_new_blob_stoned_copy`, etc.) used as arguments to `JS_SetPropertyStr` can crash because C evaluates arguments in unspecified order. The compiler may read `obj.val` BEFORE the allocating call, then GC moves the object, leaving a stale pointer.
```c ```c
JSValue obj = JS_NewObject(js); // NOT rooted // UNSAFE — intermittent crash:
JS_SetPropertyStr(js, obj, "pixels", js_new_blob_stoned_copy(js, data, len)); JS_SetPropertyStr(js, obj.val, "format", JS_NewString(js, "rgba32"));
// ^^^ blob allocation can GC, invalidating obj JS_SetPropertyStr(js, obj.val, "pixels", js_new_blob_stoned_copy(js, data, len));
return obj; // obj may be a dangling pointer
// SAFE — separate the allocation:
JSValue fmt = JS_NewString(js, "rgba32");
JS_SetPropertyStr(js, obj.val, "format", fmt);
JSValue pixels = js_new_blob_stoned_copy(js, data, len);
JS_SetPropertyStr(js, obj.val, "pixels", pixels);
``` ```
`JS_NewInt32`, `JS_NewUint32`, and `JS_NewBool` do NOT allocate and are safe inline.
See `docs/c-modules.md` for the full GC safety reference. See `docs/c-modules.md` for the full GC safety reference.
## Project Layout ## Project Layout
@@ -167,17 +180,19 @@ See `docs/c-modules.md` for the full GC safety reference.
## Package Management (Shop CLI) ## Package Management (Shop CLI)
When running locally with `./cell --dev`, these commands manage packages: **Two shops:** `cell <cmd>` uses the global shop at `~/.cell/packages/`. `cell --dev <cmd>` uses the local shop at `.cell/packages/`. Linked packages (via `cell link`) are symlinked into the shop — edit the source directory directly.
``` ```
./cell --dev add <path> # add a package (local path or remote) cell add <path> # add a package (local path or remote)
./cell --dev remove <path> # remove a package (cleans lock, symlink, dylibs) cell remove <path> # remove a package (cleans lock, symlink, dylibs)
./cell --dev build <path> # build C modules for a package cell build <path> # build C modules for a package
./cell --dev test package <path> # run tests for a package cell build <path> --force # force rebuild (ignore stat cache)
./cell --dev list # list installed packages cell test package <path> # run tests for a package
cell list # list installed packages
cell link # list linked packages
``` ```
Local paths are symlinked into `.cell/packages/`. The build step compiles C files to content-addressed dylibs in `~/.cell/build/<hash>` and writes a per-package manifest so the runtime can find them. C files in `src/` are support files linked into module dylibs, not standalone modules. The build step compiles C files to content-addressed dylibs in `~/.cell/build/<hash>` and writes a per-package manifest so the runtime can find them. C files in `src/` are support files linked into module dylibs, not standalone modules.
## Debugging Compiler Issues ## Debugging Compiler Issues

View File

@@ -11,7 +11,7 @@ all: $(BUILD)/build.ninja
cp $(BUILD)/cell . cp $(BUILD)/cell .
$(BUILD)/build.ninja: $(BUILD)/build.ninja:
meson setup $(BUILD) -Dbuildtype=debugoptimized meson setup $(BUILD) -Dbuildtype=release
debug: $(BUILD_DBG)/build.ninja debug: $(BUILD_DBG)/build.ninja
meson compile -C $(BUILD_DBG) meson compile -C $(BUILD_DBG)

271
add.ce
View File

@@ -9,188 +9,127 @@
var shop = use('internal/shop') var shop = use('internal/shop')
var pkg = use('package') var pkg = use('package')
var build = use('build')
var fd = use('fd') var fd = use('fd')
var locator = null var locator = null
var alias = null var alias = null
var resolved = null
var parts = null
var cwd = null
var build_target = null
var recursive = false var recursive = false
var cwd = fd.realpath('.')
var parts = null
var locators = null var locators = null
var added = 0 var added = 0
var failed = 0 var failed = 0
var summary = null var _add_dep = null
var _install = null
var i = 0
array(args, function(arg) { var run = function() {
if (arg == '--help' || arg == '-h') { for (i = 0; i < length(args); i++) {
log.console("Usage: cell add <locator> [alias]") if (args[i] == '--help' || args[i] == '-h') {
log.console("") log.console("Usage: cell add <locator> [alias]")
log.console("Add a dependency to the current package.") log.console("")
log.console("") log.console("Add a dependency to the current package.")
log.console("Examples:") log.console("")
log.console(" cell add gitea.pockle.world/john/prosperon") log.console("Examples:")
log.console(" cell add gitea.pockle.world/john/cell-image image") log.console(" cell add gitea.pockle.world/john/prosperon")
log.console(" cell add ../local-package") log.console(" cell add gitea.pockle.world/john/cell-image image")
log.console(" cell add -r ../packages") log.console(" cell add ../local-package")
$stop() log.console(" cell add -r ../packages")
} else if (arg == '-r') { return
recursive = true } else if (args[i] == '-r') {
} else if (!starts_with(arg, '-')) { recursive = true
if (!locator) { } else if (!starts_with(args[i], '-')) {
locator = arg if (!locator) {
} else if (!alias) { locator = args[i]
alias = arg } else if (!alias) {
} alias = args[i]
}
})
if (!locator && !recursive) {
log.console("Usage: cell add <locator> [alias]")
$stop()
}
// Resolve relative paths to absolute paths
if (locator && (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator))) {
resolved = fd.realpath(locator)
if (resolved) {
locator = resolved
}
}
// Generate default alias from locator
if (!alias && locator) {
// Use the last component of the locator as alias
parts = array(locator, '/')
alias = parts[length(parts) - 1]
// Remove any version suffix
if (search(alias, '@') != null) {
alias = array(alias, '@')[0]
}
}
// Check we're in a package directory
cwd = fd.realpath('.')
if (!fd.is_file(cwd + '/cell.toml')) {
log.error("Not in a package directory (no cell.toml found)")
$stop()
}
// Recursively find all cell packages in a directory
function find_packages(dir) {
var found = []
var list = fd.readdir(dir)
if (!list) return found
if (fd.is_file(dir + '/cell.toml')) {
push(found, dir)
}
arrfor(list, function(item) {
if (item == '.' || item == '..' || item == '.cell' || item == '.git') return
var full = dir + '/' + item
var st = fd.stat(full)
var sub = null
if (st && st.isDirectory) {
sub = find_packages(full)
arrfor(sub, function(p) {
push(found, p)
})
}
})
return found
}
// If -r flag, find all packages recursively and add each
if (recursive) {
if (!locator) {
locator = '.'
}
resolved = fd.realpath(locator)
if (!resolved || !fd.is_dir(resolved)) {
log.error(`${locator} is not a directory`)
$stop()
}
locators = find_packages(resolved)
if (length(locators) == 0) {
log.console("No packages found in " + resolved)
$stop()
}
log.console(`Found ${text(length(locators))} package(s) in ${resolved}`)
arrfor(locators, function(loc) {
// Generate alias from directory name
var loc_parts = array(loc, '/')
var loc_alias = loc_parts[length(loc_parts) - 1]
log.console(" Adding " + loc + " as '" + loc_alias + "'...")
var _add = function() {
pkg.add_dependency(null, loc, loc_alias)
shop.get(loc)
shop.extract(loc)
shop.build_package_scripts(loc)
var _build_c = function() {
build_target = build.detect_host_target()
build.build_dynamic(loc, build_target, 'release')
} disruption {
// Not all packages have C code
} }
_build_c()
added++
} disruption {
log.console(` Warning: Failed to add ${loc}`)
failed++
} }
_add()
})
summary = "Added " + text(added) + " package(s)."
if (failed > 0) {
summary += " Failed: " + text(failed) + "."
}
log.console(summary)
$stop()
}
log.console("Adding " + locator + " as '" + alias + "'...")
// Add to local project's cell.toml
var _add_dep = function() {
pkg.add_dependency(null, locator, alias)
log.console(" Added to cell.toml")
} disruption {
log.error("Failed to update cell.toml")
$stop()
}
_add_dep()
// Install to shop
var _install = function() {
shop.get(locator)
shop.extract(locator)
// Build scripts
var script_result = shop.build_package_scripts(locator)
if (length(script_result.errors) > 0) {
log.console(" Warning: " + text(length(script_result.errors)) + " script(s) failed to compile")
} }
// Build C code if any if (!locator && !recursive) {
var _build_c = function() { log.console("Usage: cell add <locator> [alias]")
build_target = build.detect_host_target() return
build.build_dynamic(locator, build_target, 'release') }
if (locator)
locator = shop.resolve_locator(locator)
// Generate default alias from locator
if (!alias && locator) {
parts = array(locator, '/')
alias = parts[length(parts) - 1]
if (search(alias, '@') != null)
alias = array(alias, '@')[0]
}
// Check we're in a package directory
if (!fd.is_file(cwd + '/cell.toml')) {
log.error("Not in a package directory (no cell.toml found)")
return
}
// Recursive mode
if (recursive) {
if (!locator) locator = '.'
locator = shop.resolve_locator(locator)
if (!fd.is_dir(locator)) {
log.error(`${locator} is not a directory`)
return
}
locators = filter(pkg.find_packages(locator), function(p) {
return p != cwd
})
if (length(locators) == 0) {
log.console("No packages found in " + locator)
return
}
log.console(`Found ${text(length(locators))} package(s) in ${locator}`)
added = 0
failed = 0
arrfor(locators, function(loc) {
var loc_parts = array(loc, '/')
var loc_alias = loc_parts[length(loc_parts) - 1]
log.console(" Adding " + loc + " as '" + loc_alias + "'...")
var _add = function() {
pkg.add_dependency(null, loc, loc_alias)
shop.sync(loc)
added = added + 1
} disruption {
log.console(` Warning: Failed to add ${loc}`)
failed = failed + 1
}
_add()
})
log.console("Added " + text(added) + " package(s)." + (failed > 0 ? " Failed: " + text(failed) + "." : ""))
return
}
// Single package add
log.console("Adding " + locator + " as '" + alias + "'...")
_add_dep = function() {
pkg.add_dependency(null, locator, alias)
log.console(" Added to cell.toml")
} disruption { } disruption {
// Not all packages have C code log.error("Failed to update cell.toml")
return
} }
_build_c() _add_dep()
log.console(" Installed to shop") _install = function() {
} disruption { shop.sync_with_deps(locator)
log.error("Failed to install") log.console(" Installed to shop")
$stop() } disruption {
log.error("Failed to install")
return
}
_install()
log.console("Added " + alias + " (" + locator + ")")
} }
_install() run()
log.console("Added " + alias + " (" + locator + ")")
$stop() $stop()

144
analyze.cm Normal file
View File

@@ -0,0 +1,144 @@
// analyze.cm — Static analysis over index data.
//
// All functions take an index object (from index.cm) and return structured results.
// Does not depend on streamline — operates purely on source-semantic data.
var analyze = {}
// Find all references to a name, with optional scope filter.
// scope: "top" (enclosing == null), "fn" (enclosing != null), null (all)
analyze.find_refs = function(idx, name, scope) {
var hits = []
var i = 0
var ref = null
while (i < length(idx.references)) {
ref = idx.references[i]
if (ref.name == name) {
if (scope == null) {
hits[] = ref
} else if (scope == "top" && ref.enclosing == null) {
hits[] = ref
} else if (scope == "fn" && ref.enclosing != null) {
hits[] = ref
}
}
i = i + 1
}
return hits
}
// Find all <name>.<property> usage patterns (channel analysis).
// Only counts unshadowed uses (name not declared as local var in scope).
analyze.channels = function(idx, name) {
var channels = {}
var summary = {}
var i = 0
var cs = null
var callee = null
var prop = null
var prefix_dot = name + "."
while (i < length(idx.call_sites)) {
cs = idx.call_sites[i]
callee = cs.callee
if (callee != null && starts_with(callee, prefix_dot)) {
prop = text(callee, length(prefix_dot), length(callee))
if (channels[prop] == null) {
channels[prop] = []
}
channels[prop][] = {span: cs.span}
if (summary[prop] == null) {
summary[prop] = 0
}
summary[prop] = summary[prop] + 1
}
i = i + 1
}
return {channels: channels, summary: summary}
}
// Find declarations by name, with optional kind filter.
// kind: "var", "def", "fn", "param", or null (any)
analyze.find_decls = function(idx, name, kind) {
var hits = []
var i = 0
var sym = null
while (i < length(idx.symbols)) {
sym = idx.symbols[i]
if (sym.name == name) {
if (kind == null || sym.kind == kind) {
hits[] = sym
}
}
i = i + 1
}
return hits
}
// Find intrinsic usage by name.
analyze.find_intrinsic = function(idx, name) {
var hits = []
var i = 0
var ref = null
if (idx.intrinsic_refs == null) return hits
while (i < length(idx.intrinsic_refs)) {
ref = idx.intrinsic_refs[i]
if (ref.name == name) {
hits[] = ref
}
i = i + 1
}
return hits
}
// Call sites with >4 args — always a compile error (max arity is 4).
analyze.excess_args = function(idx) {
var hits = []
var i = 0
var cs = null
while (i < length(idx.call_sites)) {
cs = idx.call_sites[i]
if (cs.args_count > 4) {
hits[] = {span: cs.span, callee: cs.callee, args_count: cs.args_count}
}
i = i + 1
}
return hits
}
// Extract module export shape from index data (for cross-module analysis).
analyze.module_summary = function(idx) {
var exports = {}
var i = 0
var j = 0
var exp = null
var sym = null
var found = false
if (idx.exports == null) return {exports: exports}
while (i < length(idx.exports)) {
exp = idx.exports[i]
found = false
if (exp.symbol_id != null) {
j = 0
while (j < length(idx.symbols)) {
sym = idx.symbols[j]
if (sym.symbol_id == exp.symbol_id) {
if (sym.kind == "fn" && sym.params != null) {
exports[exp.name] = {type: "function", arity: length(sym.params)}
} else {
exports[exp.name] = {type: sym.kind}
}
found = true
break
}
j = j + 1
}
}
if (!found) {
exports[exp.name] = {type: "unknown"}
}
i = i + 1
}
return {exports: exports}
}
return analyze

View File

@@ -42,7 +42,7 @@ static JSValue js_miniz_read(JSContext *js, JSValue self, int argc, JSValue *arg
{ {
size_t len; size_t len;
void *data = js_get_blob_data(js, &len, argv[0]); void *data = js_get_blob_data(js, &len, argv[0]);
if (data == -1) if (data == (void *)-1)
return JS_EXCEPTION; return JS_EXCEPTION;
mz_zip_archive *zip = calloc(sizeof(*zip), 1); mz_zip_archive *zip = calloc(sizeof(*zip), 1);
@@ -109,35 +109,38 @@ static JSValue js_miniz_compress(JSContext *js, JSValue this_val,
in_ptr = cstring; in_ptr = cstring;
} else { } else {
in_ptr = js_get_blob_data(js, &in_len, argv[0]); in_ptr = js_get_blob_data(js, &in_len, argv[0]);
if (in_ptr == -1) if (in_ptr == (const void *)-1)
return JS_EXCEPTION; return JS_EXCEPTION;
} }
/* ─── 2. Allocate an output buffer big enough ────────────── */ /* ─── 2. Allocate output blob (before getting blob input ptr) ── */
mz_ulong out_len_est = mz_compressBound(in_len); mz_ulong out_len_est = mz_compressBound(in_len);
void *out_buf = js_malloc_rt(out_len_est); void *out_ptr;
if (!out_buf) { JSValue abuf = js_new_blob_alloc(js, (size_t)out_len_est, &out_ptr);
if (JS_IsException(abuf)) {
if (cstring) JS_FreeCString(js, cstring); if (cstring) JS_FreeCString(js, cstring);
return JS_EXCEPTION; return abuf;
}
/* Re-derive blob input pointer after alloc (GC may have moved it) */
if (!cstring) {
in_ptr = js_get_blob_data(js, &in_len, argv[0]);
} }
/* ─── 3. Do the compression (MZ_DEFAULT_COMPRESSION = level 6) */ /* ─── 3. Do the compression (MZ_DEFAULT_COMPRESSION = level 6) */
mz_ulong out_len = out_len_est; mz_ulong out_len = out_len_est;
int st = mz_compress2(out_buf, &out_len, int st = mz_compress2(out_ptr, &out_len,
in_ptr, in_len, MZ_DEFAULT_COMPRESSION); in_ptr, in_len, MZ_DEFAULT_COMPRESSION);
/* clean-up for string input */ /* clean-up for string input */
if (cstring) JS_FreeCString(js, cstring); if (cstring) JS_FreeCString(js, cstring);
if (st != MZ_OK) { if (st != MZ_OK)
js_free_rt(out_buf);
return JS_RaiseDisrupt(js, return JS_RaiseDisrupt(js,
"miniz: compression failed (%d)", st); "miniz: compression failed (%d)", st);
}
/* ─── 4. Hand JavaScript a copy of the compressed data ────── */ /* ─── 4. Stone with actual compressed size ────────────────── */
JSValue abuf = js_new_blob_stoned_copy(js, out_buf, out_len); js_blob_stone(abuf, (size_t)out_len);
js_free_rt(out_buf);
return abuf; return abuf;
} }
@@ -153,7 +156,7 @@ static JSValue js_miniz_decompress(JSContext *js,
/* grab compressed data */ /* grab compressed data */
size_t in_len; size_t in_len;
void *in_ptr = js_get_blob_data(js, &in_len, argv[0]); void *in_ptr = js_get_blob_data(js, &in_len, argv[0]);
if (in_ptr == -1) if (in_ptr == (void *)-1)
return JS_EXCEPTION; return JS_EXCEPTION;
/* zlib header present → tell tinfl to parse it */ /* zlib header present → tell tinfl to parse it */
@@ -196,7 +199,7 @@ JSValue js_writer_add_file(JSContext *js, JSValue self, int argc, JSValue *argv)
size_t dataLen; size_t dataLen;
void *data = js_get_blob_data(js, &dataLen, argv[1]); void *data = js_get_blob_data(js, &dataLen, argv[1]);
if (data == -1) { if (data == (void *)-1) {
JS_FreeCString(js, pathInZip); JS_FreeCString(js, pathInZip);
return JS_EXCEPTION; return JS_EXCEPTION;
} }

View File

@@ -10,32 +10,26 @@
var shop = use('internal/shop') var shop = use('internal/shop')
var pkg = use('package') var pkg = use('package')
var fd = use('fd')
var target_package = null var target_package = null
var i = 0 var i = 0
var resolved = null
for (i = 0; i < length(args); i++) { var run = function() {
if (args[i] == '--help' || args[i] == '-h') { for (i = 0; i < length(args); i++) {
log.console("Usage: cell audit [<locator>]") if (args[i] == '--help' || args[i] == '-h') {
log.console("") log.console("Usage: cell audit [<locator>]")
log.console("Test-compile all .ce and .cm scripts in package(s).") log.console("")
log.console("Reports all errors without stopping at the first failure.") log.console("Test-compile all .ce and .cm scripts in package(s).")
$stop() log.console("Reports all errors without stopping at the first failure.")
} else if (!starts_with(args[i], '-')) { return
target_package = args[i] } else if (!starts_with(args[i], '-')) {
target_package = args[i]
}
} }
}
// Resolve local paths // Resolve local paths
if (target_package) { if (target_package) {
if (target_package == '.' || starts_with(target_package, './') || starts_with(target_package, '../') || fd.is_dir(target_package)) { target_package = shop.resolve_locator(target_package)
resolved = fd.realpath(target_package)
if (resolved) {
target_package = resolved
}
}
} }
var packages = null var packages = null
@@ -43,6 +37,7 @@ var total_ok = 0
var total_errors = 0 var total_errors = 0
var total_scripts = 0 var total_scripts = 0
var all_failures = [] var all_failures = []
var all_unresolved = []
if (target_package) { if (target_package) {
packages = [target_package] packages = [target_package]
@@ -63,6 +58,12 @@ arrfor(packages, function(p) {
arrfor(result.errors, function(e) { arrfor(result.errors, function(e) {
push(all_failures, p + ": " + e) push(all_failures, p + ": " + e)
}) })
// Check use() resolution
var resolution = shop.audit_use_resolution(p)
arrfor(resolution.unresolved, function(u) {
push(all_unresolved, p + '/' + u.script + ": use('" + u.module + "') cannot be resolved")
})
}) })
log.console("") log.console("")
@@ -74,6 +75,19 @@ if (length(all_failures) > 0) {
log.console("") log.console("")
} }
log.console("Audit complete: " + text(total_ok) + "/" + text(total_scripts) + " scripts compiled" + (total_errors > 0 ? ", " + text(total_errors) + " failed" : "")) if (length(all_unresolved) > 0) {
log.console("Unresolved modules:")
arrfor(all_unresolved, function(u) {
log.console(" " + u)
})
log.console("")
}
var summary = "Audit complete: " + text(total_ok) + "/" + text(total_scripts) + " scripts compiled"
if (total_errors > 0) summary = summary + ", " + text(total_errors) + " failed"
if (length(all_unresolved) > 0) summary = summary + ", " + text(length(all_unresolved)) + " unresolved use() calls"
log.console(summary)
}
run()
$stop() $stop()

View File

@@ -6,7 +6,7 @@ var fd = use('fd')
var time = use('time') var time = use('time')
var json = use('json') var json = use('json')
var blob = use('blob') var blob = use('blob')
var os = use('os') var os = use('internal/os')
var testlib = use('internal/testlib') var testlib = use('internal/testlib')
var math = use('math/radians') var math = use('math/radians')

View File

@@ -6,11 +6,11 @@
// Compiles (if needed) and benchmarks a module via both VM and native dylib. // Compiles (if needed) and benchmarks a module via both VM and native dylib.
// Reports median/mean timing per benchmark + speedup ratio. // Reports median/mean timing per benchmark + speedup ratio.
var os = use('os') var os = use('internal/os')
var fd = use('fd') var fd = use('fd')
if (length(args) < 1) { if (length(args) < 1) {
print('usage: cell --dev bench_native.ce <module.cm> [iterations]') log.bench('usage: cell --dev bench_native.ce <module.cm> [iterations]')
return return
} }
@@ -117,12 +117,12 @@ var run_bench = function(fn, label) {
// --- Load VM module --- // --- Load VM module ---
print('loading VM module: ' + file) log.bench('loading VM module: ' + file)
var vm_mod = use(name) var vm_mod = use(name)
var vm_benches = collect_benches(vm_mod) var vm_benches = collect_benches(vm_mod)
if (length(vm_benches) == 0) { if (length(vm_benches) == 0) {
print('no benchmarkable functions found in ' + file) log.bench('no benchmarkable functions found in ' + file)
return return
} }
@@ -134,20 +134,20 @@ var has_native = fd.is_file(dylib_path)
var lib = null var lib = null
if (has_native) { if (has_native) {
print('loading native module: ' + dylib_path) log.bench('loading native module: ' + dylib_path)
lib = os.dylib_open(dylib_path) lib = os.dylib_open(dylib_path)
native_mod = os.dylib_symbol(lib, symbol) native_mod = os.dylib_symbol(lib, symbol)
native_benches = collect_benches(native_mod) native_benches = collect_benches(native_mod)
} else { } else {
print('no ' + dylib_path + ' found -- VM-only benchmarking') log.bench('no ' + dylib_path + ' found -- VM-only benchmarking')
print(' hint: cell --dev compile.ce ' + file) log.bench(' hint: cell --dev compile.ce ' + file)
} }
// --- Run benchmarks --- // --- Run benchmarks ---
print('') log.bench('')
print('samples: ' + text(iterations) + ' (warmup: ' + text(WARMUP) + ')') log.bench('samples: ' + text(iterations) + ' (warmup: ' + text(WARMUP) + ')')
print('') log.bench('')
var pad = function(s, n) { var pad = function(s, n) {
var result = s var result = s
@@ -166,7 +166,7 @@ while (i < length(vm_benches)) {
b = vm_benches[i] b = vm_benches[i]
vm_result = run_bench(b.fn, 'vm') vm_result = run_bench(b.fn, 'vm')
print(pad(b.name, 20) + ' VM: ' + pad(format_ns(vm_result.median), 12) + ' (median) ' + format_ns(vm_result.mean) + ' (mean)') log.bench(pad(b.name, 20) + ' VM: ' + pad(format_ns(vm_result.median), 12) + ' (median) ' + format_ns(vm_result.mean) + ' (mean)')
// find matching native bench // find matching native bench
j = 0 j = 0
@@ -174,11 +174,11 @@ while (i < length(vm_benches)) {
while (j < length(native_benches)) { while (j < length(native_benches)) {
if (native_benches[j].name == b.name) { if (native_benches[j].name == b.name) {
nat_result = run_bench(native_benches[j].fn, 'native') nat_result = run_bench(native_benches[j].fn, 'native')
print(pad('', 20) + ' NT: ' + pad(format_ns(nat_result.median), 12) + ' (median) ' + format_ns(nat_result.mean) + ' (mean)') log.bench(pad('', 20) + ' NT: ' + pad(format_ns(nat_result.median), 12) + ' (median) ' + format_ns(nat_result.mean) + ' (mean)')
if (nat_result.median > 0) { if (nat_result.median > 0) {
speedup = vm_result.median / nat_result.median speedup = vm_result.median / nat_result.median
print(pad('', 20) + ' speedup: ' + text(round(speedup * 100) / 100) + 'x') log.bench(pad('', 20) + ' speedup: ' + text(round(speedup * 100) / 100) + 'x')
} }
found = true found = true
} }
@@ -186,9 +186,9 @@ while (i < length(vm_benches)) {
} }
if (has_native && !found) { if (has_native && !found) {
print(pad('', 20) + ' NT: (no matching function)') log.bench(pad('', 20) + ' NT: (no matching function)')
} }
print('') log.bench('')
i = i + 1 i = i + 1
} }

View File

@@ -1,8 +1,8 @@
// encoders.cm — nota/wota/json encode+decode benchmark // encoders.cm — nota/wota/json encode+decode benchmark
// Isolates per-type bottlenecks across all three serializers. // Isolates per-type bottlenecks across all three serializers.
var nota = use('nota') var nota = use('internal/nota')
var wota = use('wota') var wota = use('internal/wota')
var json = use('json') var json = use('json')
// --- Test data shapes --- // --- Test data shapes ---

View File

@@ -1,5 +1,5 @@
var nota = use('nota') var nota = use('internal/nota')
var os = use('os') var os = use('internal/os')
var io = use('fd') var io = use('fd')
var json = use('json') var json = use('json')

View File

@@ -1,5 +1,5 @@
var wota = use('wota'); var wota = use('internal/wota');
var os = use('os'); var os = use('internal/os');
var i = 0 var i = 0

View File

@@ -1,8 +1,8 @@
var wota = use('wota'); var wota = use('internal/wota');
var nota = use('nota'); var nota = use('internal/nota');
var json = use('json'); var json = use('json');
var jswota = use('jswota') var jswota = use('jswota')
var os = use('os'); var os = use('internal/os');
if (length(arg) != 2) { if (length(arg) != 2) {
log.console('Usage: cell benchmark_wota_nota_json.ce <LibraryName> <ScenarioName>'); log.console('Usage: cell benchmark_wota_nota_json.ce <LibraryName> <ScenarioName>');

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

74
boot_miscompile_bad.cm Normal file
View File

@@ -0,0 +1,74 @@
// boot_miscompile_bad.cm — Documents a boot compiler miscompilation bug.
//
// BUG SUMMARY:
// The boot compiler's optimizer (likely compress_slots, eliminate_moves,
// or infer_param_types) miscompiles a specific pattern when it appears
// inside streamline.cm. The pattern: an array-loaded value used as a
// dynamic index for another array store, inside a guarded block:
//
// sv = instr[j]
// if (is_number(sv) && sv >= 0 && sv < nr_slots) {
// last_ref[sv] = i // <-- miscompiled: sv reads wrong slot
// }
//
// The bug is CONTEXT-DEPENDENT on streamline.cm's exact function/closure
// structure. A standalone module with the same pattern does NOT trigger it.
// The boot optimizer's cross-function analysis (infer_param_types, type
// propagation, etc.) makes different decisions in the full streamline.cm
// context, leading to the miscompilation.
//
// SYMPTOMS:
// - 'log' is not defined (comparison error path fires on non-comparable values)
// - array index must be a number (store_dynamic with corrupted index)
// - Error line has NO reference to 'log' — the reference comes from the
// error-reporting code path of the < operator
// - Non-deterministic: different error messages on different runs
// - NOT a GC bug: persists with --heap 4GB
// - NOT slot overflow: function has only 85 raw slots
//
// TO REPRODUCE:
// In streamline.cm, replace the build_slot_liveness function body with
// this version (raw operand scanning instead of get_slot_refs):
//
// var build_slot_liveness = function(instructions, nr_slots) {
// var last_ref = array(nr_slots, -1)
// var n = length(instructions)
// var i = 0
// var j = 0
// var limit = 0
// var sv = 0
// var instr = null
//
// while (i < n) {
// instr = instructions[i]
// if (is_array(instr)) {
// j = 1
// limit = length(instr) - 2
// while (j < limit) {
// sv = instr[j]
// if (is_number(sv) && sv >= 0 && sv < nr_slots) {
// last_ref[sv] = i
// }
// j = j + 1
// }
// }
// i = i + 1
// }
// return last_ref
// }
//
// Then: rm -rf .cell/build && ./cell --dev vm_suite
//
// WORKAROUND:
// Use get_slot_refs(instr) to iterate only over known slot-reference
// positions. This produces different IR that the boot optimizer handles
// correctly, and is also more semantically correct.
//
// FIXING:
// To find the root cause, compare the boot-compiled bytecodes of
// build_slot_liveness (in the full streamline.cm context) vs the
// source-compiled bytecodes. Use disasm.ce with --optimized to see
// what the source compiler produces. The boot-compiled bytecodes
// would need a C-level MachCode dump to inspect.
return null

116
build.ce
View File

@@ -22,67 +22,60 @@ var dry_run = false
var i = 0 var i = 0
var targets = null var targets = null
var t = 0 var t = 0
var resolved = null
var lib = null var lib = null
var results = null var results = null
var success = 0 var success = 0
var failed = 0 var failed = 0
for (i = 0; i < length(args); i++) { var run = function() {
if (args[i] == '-t' || args[i] == '--target') { for (i = 0; i < length(args); i++) {
if (i + 1 < length(args)) { if (args[i] == '-t' || args[i] == '--target') {
target = args[++i] if (i + 1 < length(args)) {
} else { target = args[++i]
log.error('-t requires a target') } else {
$stop() log.error('-t requires a target')
} return
} else if (args[i] == '-p' || args[i] == '--package') {
// Legacy support for -p flag
if (i + 1 < length(args)) {
target_package = args[++i]
} else {
log.error('-p requires a package name')
$stop()
}
} else if (args[i] == '-b' || args[i] == '--buildtype') {
if (i + 1 < length(args)) {
buildtype = args[++i]
if (buildtype != 'release' && buildtype != 'debug' && buildtype != 'minsize') {
log.error('Invalid buildtype: ' + buildtype + '. Must be release, debug, or minsize')
$stop()
} }
} else { } else if (args[i] == '-p' || args[i] == '--package') {
log.error('-b requires a buildtype (release, debug, minsize)') // Legacy support for -p flag
$stop() if (i + 1 < length(args)) {
target_package = args[++i]
} else {
log.error('-p requires a package name')
return
}
} else if (args[i] == '-b' || args[i] == '--buildtype') {
if (i + 1 < length(args)) {
buildtype = args[++i]
if (buildtype != 'release' && buildtype != 'debug' && buildtype != 'minsize') {
log.error('Invalid buildtype: ' + buildtype + '. Must be release, debug, or minsize')
return
}
} else {
log.error('-b requires a buildtype (release, debug, minsize)')
return
}
} else if (args[i] == '--force') {
force_rebuild = true
} else if (args[i] == '--verbose' || args[i] == '-v') {
verbose = true
} else if (args[i] == '--dry-run') {
dry_run = true
} else if (args[i] == '--list-targets') {
log.console('Available targets:')
targets = build.list_targets()
for (t = 0; t < length(targets); t++) {
log.console(' ' + targets[t])
}
return
} else if (!starts_with(args[i], '-') && !target_package) {
// Positional argument - treat as package locator
target_package = args[i]
} }
} else if (args[i] == '--force') {
force_rebuild = true
} else if (args[i] == '--verbose' || args[i] == '-v') {
verbose = true
} else if (args[i] == '--dry-run') {
dry_run = true
} else if (args[i] == '--list-targets') {
log.console('Available targets:')
targets = build.list_targets()
for (t = 0; t < length(targets); t++) {
log.console(' ' + targets[t])
}
$stop()
} else if (!starts_with(args[i], '-') && !target_package) {
// Positional argument - treat as package locator
target_package = args[i]
} }
}
// Resolve local paths to absolute paths if (target_package)
if (target_package) { target_package = shop.resolve_locator(target_package)
if (target_package == '.' || starts_with(target_package, './') || starts_with(target_package, '../') || fd.is_dir(target_package)) {
resolved = fd.realpath(target_package)
if (resolved) {
target_package = resolved
}
}
}
// Detect target if not specified // Detect target if not specified
if (!target) { if (!target) {
@@ -90,17 +83,16 @@ if (!target) {
if (target) log.console('Target: ' + target) if (target) log.console('Target: ' + target)
} }
if (target && !build.has_target(target)) { if (target && !build.has_target(target)) {
log.error('Invalid target: ' + target) log.error('Invalid target: ' + target)
log.console('Available targets: ' + text(build.list_targets(), ', ')) log.console('Available targets: ' + text(build.list_targets(), ', '))
$stop() return
} }
var packages = shop.list_packages() var packages = shop.list_packages()
log.console('Preparing packages...')
arrfor(packages, function(package) { arrfor(packages, function(package) {
if (package == 'core') return if (package == 'core') return
shop.extract(package) shop.sync(package, {no_build: true})
}) })
var _build = null var _build = null
@@ -108,7 +100,7 @@ if (target_package) {
// Build single package // Build single package
log.console('Building ' + target_package + '...') log.console('Building ' + target_package + '...')
_build = function() { _build = function() {
lib = build.build_dynamic(target_package, target, buildtype, {verbose: verbose}) lib = build.build_dynamic(target_package, target, buildtype, {verbose: verbose, force: force_rebuild})
if (lib) { if (lib) {
log.console(`Built ${text(length(lib))} module(s)`) log.console(`Built ${text(length(lib))} module(s)`)
} }
@@ -120,7 +112,7 @@ if (target_package) {
} else { } else {
// Build all packages // Build all packages
log.console('Building all packages...') log.console('Building all packages...')
results = build.build_all_dynamic(target, buildtype, {verbose: verbose}) results = build.build_all_dynamic(target, buildtype, {verbose: verbose, force: force_rebuild})
success = 0 success = 0
failed = 0 failed = 0
@@ -132,5 +124,7 @@ if (target_package) {
log.console(`Build complete: ${success} libraries built${failed > 0 ? `, ${failed} failed` : ''}`) log.console(`Build complete: ${success} libraries built${failed > 0 ? `, ${failed} failed` : ''}`)
} }
}
run()
$stop() $stop()

569
build.cm
View File

@@ -7,15 +7,42 @@
// Build.build_static(packages, target, output) - Build static binary // Build.build_static(packages, target, output) - Build static binary
var fd = use('fd') var fd = use('fd')
var crypto = use('crypto') var crypto = use('internal/crypto')
var blob = use('blob') var blob = use('blob')
var os = use('os') var os = use('internal/os')
var toolchains = use('toolchains') var toolchains = use('toolchains')
var shop = use('internal/shop') var shop = use('internal/shop')
var pkg_tools = use('package') var pkg_tools = use('package')
var json = use('json')
var Build = {} var Build = {}
// ============================================================================
// Per-run memoization caches (reset when process exits)
// ============================================================================
var _stat_done = {}
var _stat_fp = {}
var _read_cache = {}
function memo_stat(path) {
var st = null
if (!_stat_done[path]) {
_stat_done[path] = true
st = fd.stat(path)
if (st.mtime != null)
_stat_fp[path] = {m: st.mtime, s: st.size}
}
return _stat_fp[path]
}
function memo_read(path) {
if (_read_cache[path] != null) return _read_cache[path]
if (!memo_stat(path)) return null
_read_cache[path] = text(fd.slurp(path))
return _read_cache[path]
}
// ============================================================================ // ============================================================================
// Sigil replacement // Sigil replacement
// ============================================================================ // ============================================================================
@@ -100,6 +127,8 @@ var SALT_MACH = 'mach' // mach bytecode blob
var SALT_MCODE = 'mcode' // mcode IR (JSON) var SALT_MCODE = 'mcode' // mcode IR (JSON)
var SALT_DEPS = 'deps' // cached cc -MM dependency list var SALT_DEPS = 'deps' // cached cc -MM dependency list
var SALT_FAIL = 'fail' // cached compilation failure var SALT_FAIL = 'fail' // cached compilation failure
var SALT_BMFST = 'bmfst' // stat-based build manifest (object level)
var SALT_BMFST_DL = 'bmfst_dl' // stat-based build manifest (dylib level)
function cache_path(content, salt) { function cache_path(content, salt) {
return get_build_dir() + '/' + content_hash(content + '\n' + salt) return get_build_dir() + '/' + content_hash(content + '\n' + salt)
@@ -118,19 +147,92 @@ function get_build_dir() {
return shop.get_build_dir() return shop.get_build_dir()
} }
function ensure_dir(path) { Build.ensure_dir = fd.ensure_dir
if (fd.stat(path).isDirectory) return
var parts = array(path, '/') // ============================================================================
var current = starts_with(path, '/') ? '/' : '' // Stat-based build manifest (zero-read warm cache)
var i = 0 // ============================================================================
for (i = 0; i < length(parts); i++) {
if (parts[i] == '') continue function bmfst_path(cmd_str, src_path) {
current += parts[i] + '/' return cache_path(cmd_str + '\n' + src_path, SALT_BMFST)
if (!fd.stat(current).isDirectory) fd.mkdir(current)
}
} }
Build.ensure_dir = ensure_dir function bmfst_probe(cmd_str, src_path) {
var mf_path = bmfst_path(cmd_str, src_path)
if (!fd.is_file(mf_path)) return null
var mf = json.decode(text(fd.slurp(mf_path)))
if (!mf || !mf.d || !mf.o) return null
if (!fd.is_file(mf.o)) return null
var ok = true
arrfor(mf.d, function(entry) {
if (!ok) return
var st = memo_stat(entry.p)
if (!st || st.m != entry.m || st.s != entry.s)
ok = false
})
if (!ok) return null
return mf.o
}
function bmfst_save(cmd_str, src_path, deps, obj_path) {
var entries = []
arrfor(deps, function(dep_path) {
var st = memo_stat(dep_path)
if (st)
push(entries, {p: dep_path, m: st.m, s: st.s})
})
var mf = {o: obj_path, d: entries}
var mf_path = bmfst_path(cmd_str, src_path)
fd.slurpwrite(mf_path, stone(blob(json.encode(mf))))
}
// Dylib-level stat manifest — keyed on compile cmd + link info + src path.
// All key inputs are available without reading any files.
function bmfst_dl_key(setup, link_info) {
var parts = [setup.cmd_str, setup.src_path]
push(parts, 'target:' + text(link_info.target))
push(parts, 'cc:' + text(link_info.cc))
arrfor(link_info.extra_objects, function(obj) {
if (obj != null) push(parts, 'extra:' + text(obj))
})
arrfor(link_info.ldflags, function(flag) {
push(parts, 'ldflag:' + text(flag))
})
arrfor(link_info.target_ldflags, function(flag) {
push(parts, 'target_ldflag:' + text(flag))
})
return text(parts, '\n')
}
function bmfst_dl_probe(setup, link_info) {
var mf_path = cache_path(bmfst_dl_key(setup, link_info), SALT_BMFST_DL)
if (!fd.is_file(mf_path)) return null
var mf = json.decode(text(fd.slurp(mf_path)))
if (!mf || !mf.d || !mf.dylib) return null
if (!fd.is_file(mf.dylib)) return null
var ok = true
arrfor(mf.d, function(entry) {
if (!ok) return
var st = memo_stat(entry.p)
if (!st || st.m != entry.m || st.s != entry.s)
ok = false
})
if (!ok) return null
return mf.dylib
}
function bmfst_dl_save(setup, link_info, deps, dylib_path) {
var entries = []
arrfor(deps, function(dep_path) {
var st = memo_stat(dep_path)
if (st)
push(entries, {p: dep_path, m: st.m, s: st.s})
})
var mf = {dylib: dylib_path, d: entries}
var mf_path = cache_path(bmfst_dl_key(setup, link_info), SALT_BMFST_DL)
fd.slurpwrite(mf_path, stone(blob(json.encode(mf))))
}
// ============================================================================ // ============================================================================
// Dependency scanning helpers // Dependency scanning helpers
@@ -170,8 +272,9 @@ function get_c_deps(cc, flags, src_path) {
function hash_all_deps(cmd_str, deps) { function hash_all_deps(cmd_str, deps) {
var parts = [cmd_str] var parts = [cmd_str]
arrfor(deps, function(dep_path) { arrfor(deps, function(dep_path) {
if (fd.is_file(dep_path)) var content = memo_read(dep_path)
push(parts, dep_path + '\n' + text(fd.slurp(dep_path))) if (content != null)
push(parts, dep_path + '\n' + content)
else else
push(parts, dep_path + '\n<missing>') push(parts, dep_path + '\n<missing>')
}) })
@@ -182,32 +285,23 @@ function hash_all_deps(cmd_str, deps) {
// Compilation // Compilation
// ============================================================================ // ============================================================================
// Compile a single C file for a package // Build the compile command string and common flags for a C file.
// Returns the object file path (content-addressed in .cell/build) // Returns {cmd_str, src_path, cc, common_flags, pkg_dir} or null if source missing.
Build.compile_file = function(pkg, file, target, opts) { function compile_setup(pkg, file, target, opts) {
var _opts = opts || {} var _opts = opts || {}
var _buildtype = _opts.buildtype || 'release' var _buildtype = _opts.buildtype || 'release'
var pkg_dir = shop.get_package_dir(pkg) var pkg_dir = shop.get_package_dir(pkg)
var src_path = pkg_dir + '/' + file var src_path = pkg_dir + '/' + file
var core_dir = null var core_dir = null
if (!fd.is_file(src_path)) { if (!fd.is_file(src_path)) return null
print('Source file not found: ' + src_path)
return null
}
// Use pre-fetched cflags if provided, otherwise fetch them
var cflags = _opts.cflags || replace_sigils_array(pkg_tools.get_flags(pkg, 'CFLAGS', target), pkg_dir) var cflags = _opts.cflags || replace_sigils_array(pkg_tools.get_flags(pkg, 'CFLAGS', target), pkg_dir)
var target_cflags = toolchains[target].c_args || [] var target_cflags = toolchains[target].c_args || []
var cc = toolchains[target].c var cc = toolchains[target].c
// Symbol name for this file
var sym_name = shop.c_symbol_for_file(pkg, file) var sym_name = shop.c_symbol_for_file(pkg, file)
// Build common flags (shared between dep scan and compilation)
var common_flags = [] var common_flags = []
// Add buildtype-specific flags
if (_buildtype == 'release') { if (_buildtype == 'release') {
common_flags = array(common_flags, ['-O3', '-DNDEBUG']) common_flags = array(common_flags, ['-O3', '-DNDEBUG'])
} else if (_buildtype == 'debug') { } else if (_buildtype == 'debug') {
@@ -219,18 +313,15 @@ Build.compile_file = function(pkg, file, target, opts) {
push(common_flags, '-DCELL_USE_NAME=' + sym_name) push(common_flags, '-DCELL_USE_NAME=' + sym_name)
push(common_flags, '-I"' + pkg_dir + '"') push(common_flags, '-I"' + pkg_dir + '"')
// Auto-discover include/ directory
if (fd.is_dir(pkg_dir + '/include')) { if (fd.is_dir(pkg_dir + '/include')) {
push(common_flags, '-I"' + pkg_dir + '/include"') push(common_flags, '-I"' + pkg_dir + '/include"')
} }
// External packages need core's source dir for cell.h, quickjs.h, blob.h
if (pkg != 'core') { if (pkg != 'core') {
core_dir = shop.get_package_dir('core') core_dir = shop.get_package_dir('core')
push(common_flags, '-I"' + core_dir + '/source"') push(common_flags, '-I"' + core_dir + '/source"')
} }
// Add package CFLAGS (resolve relative -I paths)
arrfor(cflags, function(flag) { arrfor(cflags, function(flag) {
var f = flag var f = flag
var ipath = null var ipath = null
@@ -243,82 +334,140 @@ Build.compile_file = function(pkg, file, target, opts) {
push(common_flags, f) push(common_flags, f)
}) })
// Add target CFLAGS
arrfor(target_cflags, function(flag) { arrfor(target_cflags, function(flag) {
push(common_flags, flag) push(common_flags, flag)
}) })
// Build full compilation command
var cmd_parts = [cc, '-c', '-fPIC'] var cmd_parts = [cc, '-c', '-fPIC']
cmd_parts = array(cmd_parts, common_flags) cmd_parts = array(cmd_parts, common_flags)
push(cmd_parts, '"' + src_path + '"') push(cmd_parts, '"' + src_path + '"')
var cmd_str = text(cmd_parts, ' ') return {
cmd_str: text(cmd_parts, ' '),
if (_opts.verbose) { src_path: src_path,
print('[verbose] CFLAGS: ' + text(cflags, ' ')) cc: cc,
print('[verbose] compile: ' + cmd_str) common_flags: common_flags,
pkg_dir: pkg_dir
} }
}
// Two-level cache: quick hash for deps file, full hash for object // Probe for the full content key (source + all deps + compile flags).
var file_content = fd.slurp(src_path) // Returns {full_content, deps, fail} or null if deps not cached yet (cold path).
var quick_content = cmd_str + '\n' + text(file_content) function probe_source_key(setup, file) {
var deps_path = cache_path(quick_content, SALT_DEPS) var file_content = memo_read(setup.src_path)
var quick_content = setup.cmd_str + '\n' + file_content
var fail_path = cache_path(quick_content, SALT_FAIL) var fail_path = cache_path(quick_content, SALT_FAIL)
var deps_path = cache_path(quick_content, SALT_DEPS)
var build_dir = get_build_dir()
ensure_dir(build_dir)
// Check for cached failure (skip files that previously failed to compile)
if (fd.is_file(fail_path)) {
if (_opts.verbose) print('[verbose] skipping ' + file + ' (cached failure)')
log.shop('skip ' + file + ' (cached failure)')
return null
}
var deps = null var deps = null
var full_content = null var full_content = null
var obj_path = null
// Warm path: read cached dep list, verify by hashing all deps if (fd.is_file(fail_path)) return {fail: true, fail_path: fail_path}
if (fd.is_file(deps_path)) { if (fd.is_file(deps_path)) {
deps = filter(array(text(fd.slurp(deps_path)), '\n'), function(p) { deps = filter(array(text(fd.slurp(deps_path)), '\n'), function(p) {
return length(p) > 0 return length(p) > 0
}) })
full_content = hash_all_deps(cmd_str, deps) full_content = hash_all_deps(setup.cmd_str, deps)
return {full_content: full_content, deps: deps}
}
return null
}
// Compile a single C file for a package
// Returns the object file path (content-addressed in .cell/build)
Build.compile_file = function(pkg, file, target, opts) {
var _opts = opts || {}
var setup = compile_setup(pkg, file, target, _opts)
if (!setup) {
log.error('Source file not found: ' + shop.get_package_dir(pkg) + '/' + file)
return null
}
if (_opts.verbose) {
log.build('[verbose] compile: ' + setup.cmd_str)
}
// Layer 2: stat-based manifest probe (zero file reads on warm cache)
var mf_obj = null
var _linked = fd.is_link(setup.pkg_dir)
var _tag = _linked ? ' [linked]' : ''
if (!_opts.force) {
mf_obj = bmfst_probe(setup.cmd_str, setup.src_path)
if (mf_obj) {
if (_opts.verbose) log.build(`[verbose] manifest hit: ${pkg}/${file}${_tag}`)
log.shop(`manifest hit ${pkg}/${file}${_tag}`)
return mf_obj
}
}
var build_dir = get_build_dir()
fd.ensure_dir(build_dir)
var probe = probe_source_key(setup, file)
var _fail_msg = null
// Check for cached failure
if (probe && probe.fail) {
_fail_msg = probe.fail_path ? text(fd.slurp(probe.fail_path)) : null
log.shop('skip ' + file + ' (cached failure)')
if (_fail_msg) log.console(file + ':\n' + _fail_msg)
return null
}
var full_content = null
var deps = null
var obj_path = null
// Warm path: deps cached, check object
if (probe && probe.full_content) {
full_content = probe.full_content
obj_path = cache_path(full_content, SALT_OBJ) obj_path = cache_path(full_content, SALT_OBJ)
if (fd.is_file(obj_path)) { if (fd.is_file(obj_path)) {
if (_opts.verbose) print('[verbose] cache hit: ' + file) if (_opts.verbose) log.build('[verbose] cache hit: ' + file)
log.shop('cache hit ' + file) log.shop('cache hit ' + file)
bmfst_save(setup.cmd_str, setup.src_path, probe.deps, obj_path)
return obj_path return obj_path
} }
log.shop('cache stale ' + file + ' (header changed)') log.shop('cache stale ' + file + ' (header changed)')
deps = probe.deps
} }
// Cold path: run cc -MM to discover deps var file_content = null
log.shop('dep scan ' + file) var quick_content = null
deps = get_c_deps(cc, common_flags, src_path) var err_path = null
full_content = hash_all_deps(cmd_str, deps) var full_cmd = null
obj_path = cache_path(full_content, SALT_OBJ) var err_text = null
var missing = null
var err_lines = null
var first_err = null
var ret = null
// Check if object exists (might exist from previous build with same deps) // Cold path: run cc -MM to discover deps
if (fd.is_file(obj_path)) { if (!deps) {
fd.slurpwrite(deps_path, stone(blob(text(deps, '\n')))) log.shop('dep scan ' + file)
if (_opts.verbose) print('[verbose] cache hit: ' + file + ' (after dep scan)') deps = get_c_deps(setup.cc, setup.common_flags, setup.src_path)
log.shop('cache hit ' + file + ' (after dep scan)') full_content = hash_all_deps(setup.cmd_str, deps)
return obj_path obj_path = cache_path(full_content, SALT_OBJ)
// Check if object exists (might exist from previous build with same deps)
if (fd.is_file(obj_path)) {
file_content = memo_read(setup.src_path)
quick_content = setup.cmd_str + '\n' + file_content
fd.slurpwrite(cache_path(quick_content, SALT_DEPS), stone(blob(text(deps, '\n'))))
if (_opts.verbose) log.build('[verbose] cache hit: ' + file + ' (after dep scan)')
log.shop('cache hit ' + file + ' (after dep scan)')
bmfst_save(setup.cmd_str, setup.src_path, deps, obj_path)
return obj_path
}
} }
// Compile // Compile
log.shop('compiling ' + file) log.shop('compiling ' + file)
log.console('Compiling ' + file) log.console('Compiling ' + file)
var err_path = '/tmp/cell_build_err_' + content_hash(src_path) + '.log' err_path = '/tmp/cell_build_err_' + content_hash(setup.src_path) + '.log'
var full_cmd = cmd_str + ' -o "' + obj_path + '" 2>"' + err_path + '"' full_cmd = setup.cmd_str + ' -o "' + obj_path + '" 2>"' + err_path + '"'
var err_text = null ret = os.system(full_cmd)
var missing = null
var err_lines = null
var first_err = null
var ret = os.system(full_cmd)
if (ret != 0) { if (ret != 0) {
if (fd.is_file(err_path)) { if (fd.is_file(err_path)) {
err_text = text(fd.slurp(err_path)) err_text = text(fd.slurp(err_path))
@@ -330,19 +479,23 @@ Build.compile_file = function(pkg, file, target, opts) {
if (missing != null) { if (missing != null) {
err_lines = array(err_text, "\n") err_lines = array(err_text, "\n")
first_err = length(err_lines) > 0 ? err_lines[0] : err_text first_err = length(err_lines) > 0 ? err_lines[0] : err_text
print(file + ': ' + first_err + ' (SDK not installed?)') log.error(file + ': ' + first_err + ' (SDK not installed?)')
} else { } else {
print('Compilation failed: ' + file) log.error('Compilation failed: ' + file)
if (err_text) print(err_text) if (err_text) log.error(err_text)
else print('Command: ' + full_cmd) else log.error('Command: ' + full_cmd)
} }
// Cache the failure so we don't retry on every build file_content = memo_read(setup.src_path)
fd.slurpwrite(fail_path, stone(blob(err_text || 'compilation failed'))) quick_content = setup.cmd_str + '\n' + file_content
fd.slurpwrite(cache_path(quick_content, SALT_FAIL), stone(blob(err_text || 'compilation failed')))
return null return null
} }
// Save deps for future warm-path lookups // Save deps for future warm-path lookups
fd.slurpwrite(deps_path, stone(blob(text(deps, '\n')))) file_content = memo_read(setup.src_path)
quick_content = setup.cmd_str + '\n' + file_content
fd.slurpwrite(cache_path(quick_content, SALT_DEPS), stone(blob(text(deps, '\n'))))
bmfst_save(setup.cmd_str, setup.src_path, deps, obj_path)
return obj_path return obj_path
} }
@@ -370,46 +523,42 @@ Build.build_package = function(pkg, target, exclude_main, buildtype) {
// Dynamic library building // Dynamic library building
// ============================================================================ // ============================================================================
// Compute link content string from all inputs that affect the dylib output // Compute dylib content key from source content key + link info
function compute_link_content(objects, ldflags, target_ldflags, opts) { // link_opts: {extra_objects, ldflags, target_ldflags, target, cc}
// Sort objects for deterministic hash function compute_dylib_content(full_content, link_opts) {
var sorted_objects = sort(objects) var parts = [full_content]
push(parts, 'target:' + text(link_opts.target))
// Build a string representing all link inputs push(parts, 'cc:' + text(link_opts.cc))
var parts = [] arrfor(link_opts.extra_objects, function(obj) {
push(parts, 'target:' + text(opts.target)) if (obj != null) push(parts, 'extra:' + text(obj))
push(parts, 'cc:' + text(opts.cc))
arrfor(sorted_objects, function(obj) {
// Object paths are content-addressed, so the path itself is the hash
push(parts, 'obj:' + text(obj))
}) })
arrfor(ldflags, function(flag) { arrfor(link_opts.ldflags, function(flag) {
push(parts, 'ldflag:' + text(flag)) push(parts, 'ldflag:' + text(flag))
}) })
arrfor(target_ldflags, function(flag) { arrfor(link_opts.target_ldflags, function(flag) {
push(parts, 'target_ldflag:' + text(flag)) push(parts, 'target_ldflag:' + text(flag))
}) })
return text(parts, '\n') return text(parts, '\n')
} }
// Build a per-module dynamic library for a single C file // Build a per-module dynamic library for a single C file
// Returns the content-addressed dylib path in .cell/build/<hash>.<target>.dylib // Returns the content-addressed dylib path in .cell/build/<hash>
// Checks dylib cache first; only compiles the object if the dylib is stale.
Build.build_module_dylib = function(pkg, file, target, opts) { Build.build_module_dylib = function(pkg, file, target, opts) {
var _opts = opts || {} var _opts = opts || {}
var _target = target || Build.detect_host_target() var _target = target || Build.detect_host_target()
var _buildtype = _opts.buildtype || 'release' var _buildtype = _opts.buildtype || 'release'
var _extra = _opts.extra_objects || [] var _extra = _opts.extra_objects || []
var obj = Build.compile_file(pkg, file, _target, {buildtype: _buildtype, cflags: _opts.cflags})
if (!obj) return null var setup = compile_setup(pkg, file, _target, {buildtype: _buildtype, cflags: _opts.cflags})
if (!setup) return null
var tc = toolchains[_target] var tc = toolchains[_target]
var cc = tc.cpp || tc.c var cc = tc.cpp || tc.c
var local_dir = get_local_dir() var local_dir = get_local_dir()
var pkg_dir = shop.get_package_dir(pkg)
// Get link flags // Get link flags
var ldflags = replace_sigils_array(pkg_tools.get_flags(pkg, 'LDFLAGS', _target), pkg_dir) var ldflags = replace_sigils_array(pkg_tools.get_flags(pkg, 'LDFLAGS', _target), setup.pkg_dir)
var target_ldflags = tc.c_link_args || [] var target_ldflags = tc.c_link_args || []
var resolved_ldflags = [] var resolved_ldflags = []
arrfor(ldflags, function(flag) { arrfor(ldflags, function(flag) {
@@ -417,72 +566,151 @@ Build.build_module_dylib = function(pkg, file, target, opts) {
var lpath = null var lpath = null
if (starts_with(f, '-L') && !starts_with(f, '-L/')) { if (starts_with(f, '-L') && !starts_with(f, '-L/')) {
lpath = text(f, 2) lpath = text(f, 2)
if (!starts_with(lpath, pkg_dir)) { if (!starts_with(lpath, setup.pkg_dir)) {
f = '-L"' + pkg_dir + '/' + lpath + '"' f = '-L"' + setup.pkg_dir + '/' + lpath + '"'
} }
} }
push(resolved_ldflags, f) push(resolved_ldflags, f)
}) })
// Content-addressed output: hash of (all objects + link flags + target)
var all_objects = [obj]
all_objects = array(all_objects, _extra)
var link_content = compute_link_content(all_objects, resolved_ldflags, target_ldflags, {target: _target, cc: cc})
var build_dir = get_build_dir() var build_dir = get_build_dir()
ensure_dir(build_dir) fd.ensure_dir(build_dir)
var dylib_path = cache_path(link_content, SALT_DYLIB)
var link_info = {extra_objects: _extra, ldflags: resolved_ldflags, target_ldflags: target_ldflags, target: _target, cc: cc}
// Stat-based dylib manifest — zero file reads on warm cache
var mf_dylib = null
var _linked = fd.is_link(setup.pkg_dir)
var _tag = _linked ? ' [linked]' : ''
if (!_opts.force) {
mf_dylib = bmfst_dl_probe(setup, link_info)
if (mf_dylib) {
if (_opts.verbose) log.build(`[verbose] manifest hit: ${pkg}/${file}${_tag}`)
log.shop(`manifest hit ${pkg}/${file}${_tag}`)
return mf_dylib
}
}
// Probe source key — check dylib cache before compiling
var probe = probe_source_key(setup, file)
var dylib_path = null
var dylib_content = null
var obj = null
var obj_path = null
var cmd_parts = null var cmd_parts = null
var cmd_str = null var cmd_str = null
var ret = null var ret = null
var post_probe = null
var fallback_probe = null
var _fail_msg2 = null
if (_opts.verbose) { if (probe && probe.fail) {
print('[verbose] LDFLAGS: ' + text(resolved_ldflags, ' ')) _fail_msg2 = probe.fail_path ? text(fd.slurp(probe.fail_path)) : null
log.shop('skip ' + file + ' (cached failure)')
if (_fail_msg2) log.console(file + ':\n' + _fail_msg2)
return null
} }
if (!fd.is_file(dylib_path)) { if (probe && probe.full_content) {
cmd_parts = [cc, '-shared', '-fPIC'] dylib_content = compute_dylib_content(probe.full_content, link_info)
dylib_path = cache_path(dylib_content, SALT_DYLIB)
if (tc.system == 'darwin') { if (!_opts.force && fd.is_file(dylib_path)) {
cmd_parts = array(cmd_parts, [ log.shop('cache hit ' + file)
'-undefined', 'dynamic_lookup', bmfst_dl_save(setup, link_info, probe.deps, dylib_path)
'-Wl,-dead_strip', return dylib_path
'-Wl,-rpath,@loader_path/../local',
'-Wl,-rpath,' + local_dir
])
} else if (tc.system == 'linux') {
cmd_parts = array(cmd_parts, [
'-Wl,--allow-shlib-undefined',
'-Wl,--gc-sections',
'-Wl,-rpath,$ORIGIN/../local',
'-Wl,-rpath,' + local_dir
])
} else if (tc.system == 'windows') {
push(cmd_parts, '-Wl,--allow-shlib-undefined')
} }
push(cmd_parts, '-L"' + local_dir + '"') // Dylib stale but object might be cached — check before compiling
push(cmd_parts, '"' + text(obj) + '"') obj_path = cache_path(probe.full_content, SALT_OBJ)
arrfor(_extra, function(extra_obj) { if (fd.is_file(obj_path)) {
if (extra_obj != null) push(cmd_parts, '"' + text(extra_obj) + '"') obj = obj_path
}) }
cmd_parts = array(cmd_parts, resolved_ldflags) }
cmd_parts = array(cmd_parts, target_ldflags)
push(cmd_parts, '-o')
push(cmd_parts, '"' + dylib_path + '"')
cmd_str = text(cmd_parts, ' ') // Object not cached — compile it
if (_opts.verbose) print('[verbose] link: ' + cmd_str) if (!obj) {
log.shop('linking ' + file) obj = Build.compile_file(pkg, file, _target, {buildtype: _buildtype, cflags: _opts.cflags, force: _opts.force})
log.console('Linking module ' + file + ' -> ' + fd.basename(dylib_path)) if (!obj) return null
ret = os.system(cmd_str)
if (ret != 0) { // Recompute dylib key with the now-known source key
print('Linking failed: ' + file) if (!dylib_path) {
post_probe = probe_source_key(setup, file)
if (post_probe && post_probe.full_content) {
dylib_content = compute_dylib_content(post_probe.full_content, link_info)
dylib_path = cache_path(dylib_content, SALT_DYLIB)
if (fd.is_file(dylib_path)) {
bmfst_dl_save(setup, link_info, post_probe.deps, dylib_path)
return dylib_path
}
}
}
}
// Need dylib_path for output
if (!dylib_path) {
// Fallback: probe should succeed now since compile_file cached deps
fallback_probe = probe_source_key(setup, file)
if (fallback_probe && fallback_probe.full_content) {
dylib_content = compute_dylib_content(fallback_probe.full_content, link_info)
dylib_path = cache_path(dylib_content, SALT_DYLIB)
} else {
log.error('Cannot compute dylib key for ' + file)
return null return null
} }
} else {
log.shop('link cache hit ' + file)
} }
if (_opts.verbose) {
log.build('[verbose] LDFLAGS: ' + text(resolved_ldflags, ' '))
}
// Link
cmd_parts = [cc, '-shared', '-fPIC']
if (tc.system == 'darwin') {
cmd_parts = array(cmd_parts, [
'-undefined', 'dynamic_lookup',
'-Wl,-dead_strip',
'-Wl,-rpath,@loader_path/../local',
'-Wl,-rpath,' + local_dir
])
} else if (tc.system == 'linux') {
cmd_parts = array(cmd_parts, [
'-Wl,--allow-shlib-undefined',
'-Wl,--gc-sections',
'-Wl,-rpath,$ORIGIN/../local',
'-Wl,-rpath,' + local_dir
])
} else if (tc.system == 'windows') {
push(cmd_parts, '-Wl,--allow-shlib-undefined')
}
push(cmd_parts, '-L"' + local_dir + '"')
push(cmd_parts, '"' + text(obj) + '"')
arrfor(_extra, function(extra_obj) {
if (extra_obj != null) push(cmd_parts, '"' + text(extra_obj) + '"')
})
cmd_parts = array(cmd_parts, resolved_ldflags)
cmd_parts = array(cmd_parts, target_ldflags)
push(cmd_parts, '-o')
push(cmd_parts, '"' + dylib_path + '"')
cmd_str = text(cmd_parts, ' ')
if (_opts.verbose) log.build('[verbose] link: ' + cmd_str)
log.shop('linking ' + file)
log.console('Linking module ' + file + ' -> ' + fd.basename(dylib_path))
ret = os.system(cmd_str)
if (ret != 0) {
log.error('Linking failed: ' + file)
return null
}
// Save dylib manifest for future stat-based probes
var mf_deps = null
if (fallback_probe && fallback_probe.deps) mf_deps = fallback_probe.deps
if (post_probe && post_probe.deps) mf_deps = post_probe.deps
if (probe && probe.deps) mf_deps = probe.deps
if (mf_deps) bmfst_dl_save(setup, link_info, mf_deps, dylib_path)
return dylib_path return dylib_path
} }
@@ -505,21 +733,20 @@ Build.build_dynamic = function(pkg, target, buildtype, opts) {
var support_objects = [] var support_objects = []
if (pkg != 'core') { if (pkg != 'core') {
arrfor(sources, function(src_file) { arrfor(sources, function(src_file) {
var obj = Build.compile_file(pkg, src_file, _target, {buildtype: _buildtype, cflags: cached_cflags, verbose: _opts.verbose}) var obj = Build.compile_file(pkg, src_file, _target, {buildtype: _buildtype, cflags: cached_cflags, verbose: _opts.verbose, force: _opts.force})
if (obj != null) push(support_objects, obj) if (obj != null) push(support_objects, obj)
}) })
} }
arrfor(c_files, function(file) { arrfor(c_files, function(file) {
var sym_name = shop.c_symbol_for_file(pkg, file) var sym_name = shop.c_symbol_for_file(pkg, file)
var dylib = Build.build_module_dylib(pkg, file, _target, {buildtype: _buildtype, extra_objects: support_objects, cflags: cached_cflags, verbose: _opts.verbose}) var dylib = Build.build_module_dylib(pkg, file, _target, {buildtype: _buildtype, extra_objects: support_objects, cflags: cached_cflags, verbose: _opts.verbose, force: _opts.force})
if (dylib) { if (dylib) {
push(results, {file: file, symbol: sym_name, dylib: dylib}) push(results, {file: file, symbol: sym_name, dylib: dylib})
} }
}) })
// Write manifest so runtime can find dylibs without the build module // Write manifest so runtime can find dylibs without the build module
var json = use('json')
var mpath = manifest_path(pkg) var mpath = manifest_path(pkg)
fd.slurpwrite(mpath, stone(blob(json.encode(results)))) fd.slurpwrite(mpath, stone(blob(json.encode(results))))
@@ -574,7 +801,7 @@ Build.build_static = function(packages, target, output, buildtype) {
}) })
if (length(all_objects) == 0) { if (length(all_objects) == 0) {
print('No object files to link'); disrupt log.error('No object files to link'); disrupt
} }
// Link // Link
@@ -608,7 +835,7 @@ Build.build_static = function(packages, target, output, buildtype) {
log.console('Linking ' + out_path) log.console('Linking ' + out_path)
var ret = os.system(cmd_str) var ret = os.system(cmd_str)
if (ret != 0) { if (ret != 0) {
print('Linking failed: ' + cmd_str); disrupt log.error('Linking failed: ' + cmd_str); disrupt
} }
log.console('Built ' + out_path) log.console('Built ' + out_path)
@@ -637,7 +864,7 @@ function compile_native_single(il_parts, cc, tmp_prefix, extra_flags) {
fd.slurpwrite(s_path, stone(blob(asm_text))) fd.slurpwrite(s_path, stone(blob(asm_text)))
rc = os.system(cc + _extra + ' -c ' + s_path + ' -o ' + o_path) rc = os.system(cc + _extra + ' -c ' + s_path + ' -o ' + o_path)
if (rc != 0) { if (rc != 0) {
print('Assembly failed'); disrupt log.error('Assembly failed'); disrupt
} }
return [o_path] return [o_path]
} }
@@ -679,7 +906,7 @@ Build.compile_native = function(src_path, target, buildtype, pkg) {
var qbe_rt_path = null var qbe_rt_path = null
if (!fd.is_file(src_path)) { if (!fd.is_file(src_path)) {
print('Source file not found: ' + src_path); disrupt log.error('Source file not found: ' + src_path); disrupt
} }
var tc = toolchains[_target] var tc = toolchains[_target]
@@ -703,7 +930,7 @@ Build.compile_native = function(src_path, target, buildtype, pkg) {
var src = text(fd.slurp(src_path)) var src = text(fd.slurp(src_path))
var native_key = native_cache_content(src, _target, san_flags) var native_key = native_cache_content(src, _target, san_flags)
var build_dir = get_build_dir() var build_dir = get_build_dir()
ensure_dir(build_dir) fd.ensure_dir(build_dir)
var dylib_path = cache_path(native_key, SALT_NATIVE) var dylib_path = cache_path(native_key, SALT_NATIVE)
if (fd.is_file(dylib_path)) if (fd.is_file(dylib_path))
@@ -718,10 +945,10 @@ Build.compile_native = function(src_path, target, buildtype, pkg) {
// Compile QBE runtime stubs if needed // Compile QBE runtime stubs if needed
var rc = null var rc = null
if (!fd.is_file(rt_o_path)) { if (!fd.is_file(rt_o_path)) {
qbe_rt_path = shop.get_package_dir('core') + '/qbe_rt.c' qbe_rt_path = shop.get_package_dir('core') + '/src/qbe_rt.c'
rc = os.system(cc + san_flags + ' -c ' + qbe_rt_path + ' -o ' + rt_o_path + ' -fPIC') rc = os.system(cc + san_flags + ' -c ' + qbe_rt_path + ' -o ' + rt_o_path + ' -fPIC')
if (rc != 0) { if (rc != 0) {
print('QBE runtime stubs compilation failed'); disrupt log.error('QBE runtime stubs compilation failed'); disrupt
} }
} }
@@ -741,7 +968,7 @@ Build.compile_native = function(src_path, target, buildtype, pkg) {
rc = os.system(link_cmd) rc = os.system(link_cmd)
if (rc != 0) { if (rc != 0) {
print('Linking native dylib failed for: ' + src_path); disrupt log.error('Linking native dylib failed for: ' + src_path); disrupt
} }
log.console('Built native: ' + fd.basename(dylib_path)) log.console('Built native: ' + fd.basename(dylib_path))
@@ -775,7 +1002,7 @@ Build.compile_native_ir = function(optimized, src_path, opts) {
var src = text(fd.slurp(src_path)) var src = text(fd.slurp(src_path))
var native_key = native_cache_content(src, _target, san_flags) var native_key = native_cache_content(src, _target, san_flags)
var build_dir = get_build_dir() var build_dir = get_build_dir()
ensure_dir(build_dir) fd.ensure_dir(build_dir)
var dylib_path = cache_path(native_key, SALT_NATIVE) var dylib_path = cache_path(native_key, SALT_NATIVE)
if (fd.is_file(dylib_path)) if (fd.is_file(dylib_path))
@@ -790,10 +1017,10 @@ Build.compile_native_ir = function(optimized, src_path, opts) {
// Compile QBE runtime stubs if needed // Compile QBE runtime stubs if needed
var rc = null var rc = null
if (!fd.is_file(rt_o_path)) { if (!fd.is_file(rt_o_path)) {
qbe_rt_path = shop.get_package_dir('core') + '/qbe_rt.c' qbe_rt_path = shop.get_package_dir('core') + '/src/qbe_rt.c'
rc = os.system(cc + san_flags + ' -c ' + qbe_rt_path + ' -o ' + rt_o_path + ' -fPIC') rc = os.system(cc + san_flags + ' -c ' + qbe_rt_path + ' -o ' + rt_o_path + ' -fPIC')
if (rc != 0) { if (rc != 0) {
print('QBE runtime stubs compilation failed'); disrupt log.error('QBE runtime stubs compilation failed'); disrupt
} }
} }
@@ -813,7 +1040,7 @@ Build.compile_native_ir = function(optimized, src_path, opts) {
rc = os.system(link_cmd) rc = os.system(link_cmd)
if (rc != 0) { if (rc != 0) {
print('Linking native dylib failed for: ' + src_path); disrupt log.error('Linking native dylib failed for: ' + src_path); disrupt
} }
log.console('Built native: ' + fd.basename(dylib_path)) log.console('Built native: ' + fd.basename(dylib_path))
@@ -829,9 +1056,8 @@ Build.compile_native_ir = function(optimized, src_path, opts) {
// Returns the raw mach bytes as a blob // Returns the raw mach bytes as a blob
Build.compile_cm_to_mach = function(src_path) { Build.compile_cm_to_mach = function(src_path) {
if (!fd.is_file(src_path)) { if (!fd.is_file(src_path)) {
print('Source file not found: ' + src_path); disrupt log.error('Source file not found: ' + src_path); disrupt
} }
var json = use('json')
var optimized = shop.compile_file(src_path) var optimized = shop.compile_file(src_path)
return mach_compile_mcode_bin(src_path, json.encode(optimized)) return mach_compile_mcode_bin(src_path, json.encode(optimized))
} }
@@ -841,7 +1067,6 @@ Build.compile_cm_to_mach = function(src_path) {
// output: path to write the generated .c file // output: path to write the generated .c file
Build.generate_module_table = function(modules, output) { Build.generate_module_table = function(modules, output) {
var lines = [] var lines = []
var json = use('json')
push(lines, '/* Generated module table — do not edit */') push(lines, '/* Generated module table — do not edit */')
push(lines, '#include <stddef.h>') push(lines, '#include <stddef.h>')
push(lines, '#include <string.h>') push(lines, '#include <string.h>')
@@ -919,7 +1144,7 @@ Build.build_all_dynamic = function(target, buildtype, opts) {
}) })
// Print build report // Print build report
print('\n--- Build Report ---') log.build('--- Build Report ---')
arrfor(results, function(r) { arrfor(results, function(r) {
var pkg_dir = shop.get_package_dir(r.package) var pkg_dir = shop.get_package_dir(r.package)
var c_files = pkg_tools.get_c_files(r.package, _target, true) var c_files = pkg_tools.get_c_files(r.package, _target, true)
@@ -931,10 +1156,10 @@ Build.build_all_dynamic = function(target, buildtype, opts) {
total_fail = total_fail + fail_count total_fail = total_fail + fail_count
if (file_count == 0) return if (file_count == 0) return
var status = fail_count == 0 ? 'OK' : `${ok_count}/${file_count}` var status = fail_count == 0 ? 'OK' : `${ok_count}/${file_count}`
print(` ${r.package}: ${status}`) log.build(` ${r.package}: ${status}`)
}) })
print(`Total: ${total_ok}/${total_files} compiled, ${total_fail} failed`) log.build(`Total: ${total_ok}/${total_files} compiled, ${total_fail} failed`)
print('--------------------\n') log.build('--------------------')
return results return results
} }
@@ -947,6 +1172,8 @@ Build.SALT_MACH = SALT_MACH
Build.SALT_MCODE = SALT_MCODE Build.SALT_MCODE = SALT_MCODE
Build.SALT_DEPS = SALT_DEPS Build.SALT_DEPS = SALT_DEPS
Build.SALT_FAIL = SALT_FAIL Build.SALT_FAIL = SALT_FAIL
Build.SALT_BMFST = SALT_BMFST
Build.SALT_BMFST_DL = SALT_BMFST_DL
Build.cache_path = cache_path Build.cache_path = cache_path
Build.manifest_path = manifest_path Build.manifest_path = manifest_path
Build.native_sanitize_flags = native_sanitize_flags Build.native_sanitize_flags = native_sanitize_flags

View File

@@ -2,8 +2,8 @@ var cellfs = {}
var fd = use('fd') var fd = use('fd')
var miniz = use('miniz') var miniz = use('miniz')
var qop = use('qop') var qop = use('internal/qop')
var wildstar = use('wildstar') var wildstar = use('internal/wildstar')
var mounts = [] var mounts = []
@@ -96,7 +96,7 @@ function resolve(path, must_exist) {
}, false, true) }, false, true)
if (!mount) { if (!mount) {
print("Unknown mount point: @" + mount_name); disrupt log.error("Unknown mount point: @" + mount_name); disrupt
} }
return { mount: mount, path: rel_path } return { mount: mount, path: rel_path }
@@ -114,7 +114,7 @@ function resolve(path, must_exist) {
} }
if (must_exist) { if (must_exist) {
print("File not found in any mount: " + npath); disrupt log.error("File not found in any mount: " + npath); disrupt
} }
} }
@@ -152,7 +152,7 @@ function mount(source, name) {
} else { } else {
zip = miniz.read(blob) zip = miniz.read(blob)
if (!is_object(zip) || !is_function(zip.count)) { if (!is_object(zip) || !is_function(zip.count)) {
print("Invalid archive file (not zip or qop): " + source); disrupt log.error("Invalid archive file (not zip or qop): " + source); disrupt
} }
mount_info.type = 'zip' mount_info.type = 'zip'
@@ -160,7 +160,7 @@ function mount(source, name) {
mount_info.zip_blob = blob mount_info.zip_blob = blob
} }
} else { } else {
print("Unsupported mount source type: " + source); disrupt log.error("Unsupported mount source type: " + source); disrupt
} }
push(mounts, mount_info) push(mounts, mount_info)
@@ -176,13 +176,13 @@ function slurp(path) {
var res = resolve(path, true) var res = resolve(path, true)
var data = null var data = null
var full_path = null var full_path = null
if (!res) { print("File not found: " + path); disrupt } if (!res) { log.error("File not found: " + path); disrupt }
if (res.mount.type == 'zip') { if (res.mount.type == 'zip') {
return res.mount.handle.slurp(res.path) return res.mount.handle.slurp(res.path)
} else if (res.mount.type == 'qop') { } else if (res.mount.type == 'qop') {
data = res.mount.handle.read(res.path) data = res.mount.handle.read(res.path)
if (!data) { print("File not found in qop: " + path); disrupt } if (!data) { log.error("File not found in qop: " + path); disrupt }
return data return data
} else { } else {
full_path = fd.join_paths(res.mount.source, res.path) full_path = fd.join_paths(res.mount.source, res.path)
@@ -211,7 +211,7 @@ function stat(path) {
var mod = null var mod = null
var s = null var s = null
var full_path = null var full_path = null
if (!res) { print("File not found: " + path); disrupt } if (!res) { log.error("File not found: " + path); disrupt }
if (res.mount.type == 'zip') { if (res.mount.type == 'zip') {
mod = res.mount.handle.mod(res.path) mod = res.mount.handle.mod(res.path)
@@ -222,7 +222,7 @@ function stat(path) {
} }
} else if (res.mount.type == 'qop') { } else if (res.mount.type == 'qop') {
s = res.mount.handle.stat(res.path) s = res.mount.handle.stat(res.path)
if (!s) { print("File not found in qop: " + path); disrupt } if (!s) { log.error("File not found in qop: " + path); disrupt }
return { return {
filesize: s.size, filesize: s.size,
modtime: s.modtime, modtime: s.modtime,
@@ -253,7 +253,7 @@ function mount_package(name) {
var dir = shop.get_package_dir(name) var dir = shop.get_package_dir(name)
if (!dir) { if (!dir) {
print("Package not found: " + name); disrupt log.error("Package not found: " + name); disrupt
} }
mount(dir, name) mount(dir, name)
@@ -267,7 +267,7 @@ function rm(path) {
var res = resolve(path, true) var res = resolve(path, true)
var full_path = null var full_path = null
var st = null var st = null
if (res.mount.type != 'fs') { print("Cannot delete from non-fs mount"); disrupt } if (res.mount.type != 'fs') { log.error("Cannot delete from non-fs mount"); disrupt }
full_path = fd.join_paths(res.mount.source, res.path) full_path = fd.join_paths(res.mount.source, res.path)
st = fd.stat(full_path) st = fd.stat(full_path)

456
cfg.ce Normal file
View File

@@ -0,0 +1,456 @@
// cfg.ce — control flow graph
//
// Usage:
// cell cfg --fn <N|name> <file> Text CFG for function
// cell cfg --dot --fn <N|name> <file> DOT output for graphviz
// cell cfg <file> Text CFG for all functions
var shop = use("internal/shop")
var pad_right = function(s, w) {
var r = s
while (length(r) < w) {
r = r + " "
}
return r
}
var fmt_val = function(v) {
if (is_null(v)) return "null"
if (is_number(v)) return text(v)
if (is_text(v)) return `"${v}"`
if (is_object(v)) return text(v)
if (is_logical(v)) return v ? "true" : "false"
return text(v)
}
var is_jump_op = function(op) {
return op == "jump" || op == "jump_true" || op == "jump_false" || op == "jump_null" || op == "jump_not_null"
}
var is_conditional_jump = function(op) {
return op == "jump_true" || op == "jump_false" || op == "jump_null" || op == "jump_not_null"
}
var is_terminator = function(op) {
return op == "return" || op == "disrupt" || op == "tail_invoke" || op == "goinvoke"
}
var run = function() {
var filename = null
var fn_filter = null
var show_dot = false
var use_optimized = false
var i = 0
var compiled = null
var main_name = null
var fi = 0
var func = null
var fname = null
while (i < length(args)) {
if (args[i] == '--fn') {
i = i + 1
fn_filter = args[i]
} else if (args[i] == '--dot') {
show_dot = true
} else if (args[i] == '--optimized') {
use_optimized = true
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell cfg [--fn <N|name>] [--dot] [--optimized] <file>")
log.console("")
log.console(" --fn <N|name> Filter to function by index or name")
log.console(" --dot Output DOT format for graphviz")
log.console(" --optimized Use optimized IR")
return null
} else if (!starts_with(args[i], '-')) {
filename = args[i]
}
i = i + 1
}
if (!filename) {
log.console("Usage: cell cfg [--fn <N|name>] [--dot] [--optimized] <file>")
return null
}
if (use_optimized) {
compiled = shop.compile_file(filename)
} else {
compiled = shop.mcode_file(filename)
}
var fn_matches = function(index, name) {
var match = null
if (fn_filter == null) return true
if (index >= 0 && fn_filter == text(index)) return true
if (name != null) {
match = search(name, fn_filter)
if (match != null && match >= 0) return true
}
return false
}
var build_cfg = function(func) {
var instrs = func.instructions
var blocks = []
var label_to_block = {}
var pc_to_block = {}
var label_to_pc = {}
var block_start_pcs = {}
var after_terminator = false
var current_block = null
var current_label = null
var pc = 0
var ii = 0
var bi = 0
var instr = null
var op = null
var n = 0
var line_num = null
var blk = null
var last_instr_data = null
var last_op = null
var target_label = null
var target_bi = null
var edge_type = null
if (instrs == null || length(instrs) == 0) return []
// Pass 1: identify block start PCs
block_start_pcs["0"] = true
pc = 0
ii = 0
while (ii < length(instrs)) {
instr = instrs[ii]
if (is_array(instr)) {
op = instr[0]
if (after_terminator) {
block_start_pcs[text(pc)] = true
after_terminator = false
}
if (is_jump_op(op) || is_terminator(op)) {
after_terminator = true
}
pc = pc + 1
}
ii = ii + 1
}
// Pass 2: map labels to PCs and mark as block starts
pc = 0
ii = 0
while (ii < length(instrs)) {
instr = instrs[ii]
if (is_text(instr) && !starts_with(instr, "_nop_")) {
label_to_pc[instr] = pc
block_start_pcs[text(pc)] = true
} else if (is_array(instr)) {
pc = pc + 1
}
ii = ii + 1
}
// Pass 3: build basic blocks
pc = 0
ii = 0
current_label = null
while (ii < length(instrs)) {
instr = instrs[ii]
if (is_text(instr)) {
if (!starts_with(instr, "_nop_")) {
current_label = instr
}
ii = ii + 1
continue
}
if (is_array(instr)) {
if (block_start_pcs[text(pc)]) {
if (current_block != null) {
push(blocks, current_block)
}
current_block = {
id: length(blocks),
label: current_label,
start_pc: pc,
end_pc: pc,
instrs: [],
edges: [],
first_line: null,
last_line: null
}
current_label = null
}
if (current_block != null) {
push(current_block.instrs, {pc: pc, instr: instr})
current_block.end_pc = pc
n = length(instr)
line_num = instr[n - 2]
if (line_num != null) {
if (current_block.first_line == null) {
current_block.first_line = line_num
}
current_block.last_line = line_num
}
}
pc = pc + 1
}
ii = ii + 1
}
if (current_block != null) {
push(blocks, current_block)
}
// Build block index
bi = 0
while (bi < length(blocks)) {
pc_to_block[text(blocks[bi].start_pc)] = bi
if (blocks[bi].label != null) {
label_to_block[blocks[bi].label] = bi
}
bi = bi + 1
}
// Pass 4: compute edges
bi = 0
while (bi < length(blocks)) {
blk = blocks[bi]
if (length(blk.instrs) > 0) {
last_instr_data = blk.instrs[length(blk.instrs) - 1]
last_op = last_instr_data.instr[0]
n = length(last_instr_data.instr)
if (is_jump_op(last_op)) {
if (last_op == "jump") {
target_label = last_instr_data.instr[1]
} else {
target_label = last_instr_data.instr[2]
}
target_bi = label_to_block[target_label]
if (target_bi != null) {
edge_type = "jump"
if (target_bi <= bi) {
edge_type = "loop back-edge"
}
push(blk.edges, {target: target_bi, kind: edge_type})
}
if (is_conditional_jump(last_op)) {
if (bi + 1 < length(blocks)) {
push(blk.edges, {target: bi + 1, kind: "fallthrough"})
}
}
} else if (is_terminator(last_op)) {
push(blk.edges, {target: -1, kind: "EXIT (" + last_op + ")"})
} else {
if (bi + 1 < length(blocks)) {
push(blk.edges, {target: bi + 1, kind: "fallthrough"})
}
}
}
bi = bi + 1
}
return blocks
}
var print_cfg_text = function(blocks, name) {
var bi = 0
var blk = null
var header = null
var ii = 0
var idata = null
var instr = null
var op = null
var n = 0
var parts = null
var j = 0
var operands = null
var ei = 0
var edge = null
var target_label = null
log.compile(`\n=== ${name} ===`)
if (length(blocks) == 0) {
log.compile(" (empty)")
return null
}
bi = 0
while (bi < length(blocks)) {
blk = blocks[bi]
header = ` B${text(bi)}`
if (blk.label != null) {
header = header + ` "${blk.label}"`
}
header = header + ` [pc ${text(blk.start_pc)}-${text(blk.end_pc)}`
if (blk.first_line != null) {
if (blk.first_line == blk.last_line) {
header = header + `, line ${text(blk.first_line)}`
} else {
header = header + `, lines ${text(blk.first_line)}-${text(blk.last_line)}`
}
}
header = header + "]:"
log.compile(header)
ii = 0
while (ii < length(blk.instrs)) {
idata = blk.instrs[ii]
instr = idata.instr
op = instr[0]
n = length(instr)
parts = []
j = 1
while (j < n - 2) {
push(parts, fmt_val(instr[j]))
j = j + 1
}
operands = text(parts, ", ")
log.compile(` ${pad_right(text(idata.pc), 6)}${pad_right(op, 15)}${operands}`)
ii = ii + 1
}
ei = 0
while (ei < length(blk.edges)) {
edge = blk.edges[ei]
if (edge.target == -1) {
log.compile(` -> ${edge.kind}`)
} else {
target_label = blocks[edge.target].label
if (target_label != null) {
log.compile(` -> B${text(edge.target)} "${target_label}" (${edge.kind})`)
} else {
log.compile(` -> B${text(edge.target)} (${edge.kind})`)
}
}
ei = ei + 1
}
log.compile("")
bi = bi + 1
}
return null
}
var print_cfg_dot = function(blocks, name) {
var safe_name = replace(replace(name, '"', '\\"'), ' ', '_')
var bi = 0
var blk = null
var label_text = null
var ii = 0
var idata = null
var instr = null
var op = null
var n = 0
var parts = null
var j = 0
var operands = null
var ei = 0
var edge = null
var style = null
log.compile(`digraph "${safe_name}" {`)
log.compile(" rankdir=TB;")
log.compile(" node [shape=record, fontname=monospace, fontsize=10];")
bi = 0
while (bi < length(blocks)) {
blk = blocks[bi]
label_text = "B" + text(bi)
if (blk.label != null) {
label_text = label_text + " (" + blk.label + ")"
}
label_text = label_text + "\\npc " + text(blk.start_pc) + "-" + text(blk.end_pc)
if (blk.first_line != null) {
label_text = label_text + "\\nline " + text(blk.first_line)
}
label_text = label_text + "|"
ii = 0
while (ii < length(blk.instrs)) {
idata = blk.instrs[ii]
instr = idata.instr
op = instr[0]
n = length(instr)
parts = []
j = 1
while (j < n - 2) {
push(parts, fmt_val(instr[j]))
j = j + 1
}
operands = text(parts, ", ")
label_text = label_text + text(idata.pc) + " " + op + " " + replace(operands, '"', '\\"') + "\\l"
ii = ii + 1
}
log.compile(" B" + text(bi) + " [label=\"{" + label_text + "}\"];")
bi = bi + 1
}
// Edges
bi = 0
while (bi < length(blocks)) {
blk = blocks[bi]
ei = 0
while (ei < length(blk.edges)) {
edge = blk.edges[ei]
if (edge.target >= 0) {
style = ""
if (edge.kind == "loop back-edge") {
style = " [style=bold, color=red, label=\"loop\"]"
} else if (edge.kind == "fallthrough") {
style = " [style=dashed]"
}
log.compile(` B${text(bi)} -> B${text(edge.target)}${style};`)
}
ei = ei + 1
}
bi = bi + 1
}
log.compile("}")
return null
}
var process_function = function(func, name, index) {
var blocks = build_cfg(func)
if (show_dot) {
print_cfg_dot(blocks, name)
} else {
print_cfg_text(blocks, name)
}
return null
}
// Process functions
main_name = compiled.name != null ? compiled.name : "<main>"
if (compiled.main != null) {
if (fn_matches(-1, main_name)) {
process_function(compiled.main, main_name, -1)
}
}
if (compiled.functions != null) {
fi = 0
while (fi < length(compiled.functions)) {
func = compiled.functions[fi]
fname = func.name != null ? func.name : "<anonymous>"
if (fn_matches(fi, fname)) {
process_function(func, `[${text(fi)}] ${fname}`, fi)
}
fi = fi + 1
}
}
return null
}
run()
$stop()

View File

@@ -24,42 +24,42 @@ var clean_fetch = false
var deep = false var deep = false
var dry_run = false var dry_run = false
var i = 0 var i = 0
var resolved = null
var deps = null var deps = null
for (i = 0; i < length(args); i++) { var run = function() {
if (args[i] == '--build') { for (i = 0; i < length(args); i++) {
clean_build = true if (args[i] == '--build') {
} else if (args[i] == '--fetch') { clean_build = true
clean_fetch = true } else if (args[i] == '--fetch') {
} else if (args[i] == '--all') { clean_fetch = true
clean_build = true } else if (args[i] == '--all') {
clean_fetch = true clean_build = true
} else if (args[i] == '--deep') { clean_fetch = true
deep = true } else if (args[i] == '--deep') {
} else if (args[i] == '--dry-run') { deep = true
dry_run = true } else if (args[i] == '--dry-run') {
} else if (args[i] == '--help' || args[i] == '-h') { dry_run = true
log.console("Usage: cell clean [<scope>] [options]") } else if (args[i] == '--help' || args[i] == '-h') {
log.console("") log.console("Usage: cell clean [<scope>] [options]")
log.console("Remove cached material to force refetch/rebuild.") log.console("")
log.console("") log.console("Remove cached material to force refetch/rebuild.")
log.console("Scopes:") log.console("")
log.console(" <locator> Clean specific package") log.console("Scopes:")
log.console(" shop Clean entire shop") log.console(" <locator> Clean specific package")
log.console(" world Clean all world packages") log.console(" shop Clean entire shop")
log.console("") log.console(" world Clean all world packages")
log.console("Options:") log.console("")
log.console(" --build Remove build outputs only (default)") log.console("Options:")
log.console(" --fetch Remove fetched sources only") log.console(" --build Remove build outputs only (default)")
log.console(" --all Remove both build outputs and fetched sources") log.console(" --fetch Remove fetched sources only")
log.console(" --deep Apply to full dependency closure") log.console(" --all Remove both build outputs and fetched sources")
log.console(" --dry-run Show what would be deleted") log.console(" --deep Apply to full dependency closure")
$stop() log.console(" --dry-run Show what would be deleted")
} else if (!starts_with(args[i], '-')) { return
scope = args[i] } else if (!starts_with(args[i], '-')) {
scope = args[i]
}
} }
}
// Default to --build if nothing specified // Default to --build if nothing specified
if (!clean_build && !clean_fetch) { if (!clean_build && !clean_fetch) {
@@ -76,12 +76,7 @@ var is_shop_scope = (scope == 'shop')
var is_world_scope = (scope == 'world') var is_world_scope = (scope == 'world')
if (!is_shop_scope && !is_world_scope) { if (!is_shop_scope && !is_world_scope) {
if (scope == '.' || starts_with(scope, './') || starts_with(scope, '../') || fd.is_dir(scope)) { scope = shop.resolve_locator(scope)
resolved = fd.realpath(scope)
if (resolved) {
scope = resolved
}
}
} }
var files_to_delete = [] var files_to_delete = []
@@ -196,5 +191,7 @@ if (dry_run) {
log.console("Clean complete: " + text(deleted_count) + " item(s) deleted.") log.console("Clean complete: " + text(deleted_count) + " item(s) deleted.")
} }
} }
}
run()
$stop() $stop()

113
clone.ce
View File

@@ -5,115 +5,56 @@ var shop = use('internal/shop')
var link = use('link') var link = use('link')
var fd = use('fd') var fd = use('fd')
var http = use('http') var http = use('http')
var miniz = use('miniz')
var resolved = null var run = function() {
var cwd = null if (length(args) < 2) {
var parent = null log.console("Usage: cell clone <origin> <path>")
log.console("Clones a cell package to a local path and links it.")
if (length(args) < 2) { return
log.console("Usage: cell clone <origin> <path>") }
log.console("Clones a cell package to a local path and links it.")
$stop()
}
var origin = args[0] var origin = args[0]
var target_path = args[1] var target_path = args[1]
// Resolve target path to absolute // Resolve target path to absolute
if (target_path == '.' || starts_with(target_path, './') || starts_with(target_path, '../')) { target_path = shop.resolve_locator(target_path)
resolved = fd.realpath(target_path)
if (resolved) {
target_path = resolved
} else {
// Path doesn't exist yet, resolve relative to cwd
cwd = fd.realpath('.')
if (target_path == '.') {
target_path = cwd
} else if (starts_with(target_path, './')) {
target_path = cwd + text(target_path, 1)
} else if (starts_with(target_path, '../')) {
// Go up one directory from cwd
parent = fd.dirname(cwd)
target_path = parent + text(target_path, 2)
}
}
}
// Check if target already exists // Check if target already exists
if (fd.is_dir(target_path)) { if (fd.is_dir(target_path)) {
log.console("Error: " + target_path + " already exists") log.console("Error: " + target_path + " already exists")
$stop() return
} }
log.console("Cloning " + origin + " to " + target_path + "...") log.console("Cloning " + origin + " to " + target_path + "...")
// Get the latest commit // Get the latest commit
var info = shop.resolve_package_info(origin) var info = shop.resolve_package_info(origin)
if (!info || info == 'local') { if (!info || info == 'local') {
log.console("Error: " + origin + " is not a remote package") log.console("Error: " + origin + " is not a remote package")
$stop() return
} }
// Update to get the commit hash // Update to get the commit hash
var update_result = shop.update(origin) var update_result = shop.update(origin)
if (!update_result) { if (!update_result) {
log.console("Error: Could not fetch " + origin) log.console("Error: Could not fetch " + origin)
$stop() return
} }
// Fetch and extract to the target path // Fetch and extract to the target path
var lock = shop.load_lock() var lock = shop.load_lock()
var entry = lock[origin] var entry = lock[origin]
if (!entry || !entry.commit) { if (!entry || !entry.commit) {
log.console("Error: No commit found for " + origin) log.console("Error: No commit found for " + origin)
$stop() return
} }
var download_url = shop.get_download_url(origin, entry.commit) var download_url = shop.get_download_url(origin, entry.commit)
log.console("Downloading from " + download_url) log.console("Downloading from " + download_url)
var zip_blob = null
var zip = null
var count = 0
var i = 0
var filename = null
var first_slash = null
var rel_path = null
var full_path = null
var dir_path = null
var _clone = function() { var _clone = function() {
zip_blob = http.fetch(download_url) var zip_blob = http.fetch(download_url)
shop.install_zip(zip_blob, target_path)
// Extract zip to target path
zip = miniz.read(zip_blob)
if (!zip) {
log.console("Error: Failed to read zip archive")
$stop()
}
// Create target directory
fd.mkdir(target_path)
count = zip.count()
for (i = 0; i < count; i++) {
if (zip.is_directory(i)) continue
filename = zip.get_filename(i)
first_slash = search(filename, '/')
if (first_slash == null) continue
if (first_slash + 1 >= length(filename)) continue
rel_path = text(filename, first_slash + 1)
full_path = target_path + '/' + rel_path
dir_path = fd.dirname(full_path)
// Ensure directory exists
if (!fd.is_dir(dir_path)) {
fd.mkdir(dir_path)
}
fd.slurpwrite(full_path, zip.slurp(filename))
}
log.console("Extracted to " + target_path) log.console("Extracted to " + target_path)
@@ -123,6 +64,8 @@ var _clone = function() {
} disruption { } disruption {
log.console("Error during clone") log.console("Error during clone")
} }
_clone() _clone()
}
run()
$stop() $stop()

View File

@@ -5,7 +5,7 @@
var build = use('build') var build = use('build')
var fd_mod = use('fd') var fd_mod = use('fd')
var os = use('os') var os = use('internal/os')
var json = use('json') var json = use('json')
var time = use('time') var time = use('time')
@@ -15,7 +15,7 @@ var show = function(v) {
} }
if (length(args) < 1) { if (length(args) < 1) {
print('usage: cell --dev compare_aot.ce <file>') log.compile('usage: cell --dev compare_aot.ce <file>')
return return
} }
@@ -26,7 +26,7 @@ if (!fd_mod.is_file(file)) {
else if (!ends_with(file, '.cm') && fd_mod.is_file(file + '.cm')) else if (!ends_with(file, '.cm') && fd_mod.is_file(file + '.cm'))
file = file + '.cm' file = file + '.cm'
else { else {
print('file not found: ' + file) log.error('file not found: ' + file)
return return
} }
} }
@@ -54,14 +54,14 @@ var t5 = time.number()
var optimized = streamline_mod(compiled) var optimized = streamline_mod(compiled)
var t6 = time.number() var t6 = time.number()
print('--- front-end timing ---') log.compile('--- front-end timing ---')
print(' read: ' + text(t1 - t0) + 's') log.compile(' read: ' + text(t1 - t0) + 's')
print(' tokenize: ' + text(t2 - t1) + 's') log.compile(' tokenize: ' + text(t2 - t1) + 's')
print(' parse: ' + text(t3 - t2) + 's') log.compile(' parse: ' + text(t3 - t2) + 's')
print(' fold: ' + text(t4 - t3) + 's') log.compile(' fold: ' + text(t4 - t3) + 's')
print(' mcode: ' + text(t5 - t4) + 's') log.compile(' mcode: ' + text(t5 - t4) + 's')
print(' streamline: ' + text(t6 - t5) + 's') log.compile(' streamline: ' + text(t6 - t5) + 's')
print(' total: ' + text(t6 - t0) + 's') log.compile(' total: ' + text(t6 - t0) + 's')
// Shared env for both paths — only non-intrinsic runtime functions. // Shared env for both paths — only non-intrinsic runtime functions.
// Intrinsics (starts_with, ends_with, logical, some, every, etc.) live on // Intrinsics (starts_with, ends_with, logical, some, every, etc.) live on
@@ -79,15 +79,15 @@ var env = stone({
var result_interp = null var result_interp = null
var interp_ok = false var interp_ok = false
var run_interp = function() { var run_interp = function() {
print('--- interpreted ---') log.compile('--- interpreted ---')
var mcode_json = json.encode(optimized) var mcode_json = json.encode(optimized)
var mach_blob = mach_compile_mcode_bin(abs, mcode_json) var mach_blob = mach_compile_mcode_bin(abs, mcode_json)
result_interp = mach_load(mach_blob, env) result_interp = mach_load(mach_blob, env)
interp_ok = true interp_ok = true
print('result: ' + show(result_interp)) log.compile('result: ' + show(result_interp))
} disruption { } disruption {
interp_ok = true interp_ok = true
print('(disruption escaped from interpreted run)') log.compile('(disruption escaped from interpreted run)')
} }
run_interp() run_interp()
@@ -95,36 +95,36 @@ run_interp()
var result_native = null var result_native = null
var native_ok = false var native_ok = false
var run_native = function() { var run_native = function() {
print('\n--- native ---') log.compile('\n--- native ---')
var dylib_path = build.compile_native_ir(optimized, abs, null) var dylib_path = build.compile_native_ir(optimized, abs, null)
print('dylib: ' + dylib_path) log.compile('dylib: ' + dylib_path)
var handle = os.dylib_open(dylib_path) var handle = os.dylib_open(dylib_path)
if (!handle) { if (!handle) {
print('failed to open dylib') log.error('failed to open dylib')
return return
} }
result_native = os.native_module_load(handle, env) result_native = os.native_module_load(handle, env)
native_ok = true native_ok = true
print('result: ' + show(result_native)) log.compile('result: ' + show(result_native))
} disruption { } disruption {
native_ok = true native_ok = true
print('(disruption escaped from native run)') log.compile('(disruption escaped from native run)')
} }
run_native() run_native()
// --- Comparison --- // --- Comparison ---
print('\n--- comparison ---') log.compile('\n--- comparison ---')
var s_interp = show(result_interp) var s_interp = show(result_interp)
var s_native = show(result_native) var s_native = show(result_native)
if (interp_ok && native_ok) { if (interp_ok && native_ok) {
if (s_interp == s_native) { if (s_interp == s_native) {
print('MATCH') log.compile('MATCH')
} else { } else {
print('MISMATCH') log.error('MISMATCH')
print(' interp: ' + s_interp) log.error(' interp: ' + s_interp)
print(' native: ' + s_native) log.error(' native: ' + s_native)
} }
} else { } else {
if (!interp_ok) print('interpreted run failed') if (!interp_ok) log.error('interpreted run failed')
if (!native_ok) print('native run failed') if (!native_ok) log.error('native run failed')
} }

View File

@@ -10,13 +10,13 @@ var build = use('build')
var fd = use('fd') var fd = use('fd')
if (length(args) < 1) { if (length(args) < 1) {
print('usage: cell compile <file.cm|file.ce>') log.compile('usage: cell compile <file.cm|file.ce>')
return return
} }
var file = args[0] var file = args[0]
if (!fd.is_file(file)) { if (!fd.is_file(file)) {
print('file not found: ' + file) log.error('file not found: ' + file)
return return
} }

55
diff.ce
View File

@@ -8,12 +8,13 @@ var shop = use('internal/shop')
var pkg = use('package') var pkg = use('package')
var fd = use('fd') var fd = use('fd')
var time = use('time') var time = use('time')
var testlib = use('internal/testlib')
var _args = args == null ? [] : args var _args = args == null ? [] : args
var analyze = use('os').analyze var analyze = use('internal/os').analyze
var run_ast_fn = use('os').run_ast_fn var run_ast_fn = use('internal/os').run_ast_fn
var run_ast_noopt_fn = use('os').run_ast_noopt_fn var run_ast_noopt_fn = use('internal/os').run_ast_noopt_fn
if (!run_ast_noopt_fn) { if (!run_ast_noopt_fn) {
log.console("error: run_ast_noopt_fn not available (rebuild bootstrap)") log.console("error: run_ast_noopt_fn not available (rebuild bootstrap)")
@@ -27,10 +28,7 @@ if (length(_args) > 0) {
target_test = _args[0] target_test = _args[0]
} }
function is_valid_package(dir) { var is_valid_package = testlib.is_valid_package
var _dir = dir == null ? '.' : dir
return fd.is_file(_dir + '/cell.toml')
}
if (!is_valid_package('.')) { if (!is_valid_package('.')) {
log.console('No cell.toml found in current directory') log.console('No cell.toml found in current directory')
@@ -63,47 +61,8 @@ function collect_tests(specific_test) {
return test_files return test_files
} }
// Deep comparison of two values var values_equal = testlib.values_equal
function values_equal(a, b) { var describe = testlib.describe
var i = 0
var ka = null
var kb = null
if (a == b) return true
if (is_null(a) && is_null(b)) return true
if (is_null(a) || is_null(b)) return false
if (is_array(a) && is_array(b)) {
if (length(a) != length(b)) return false
i = 0
while (i < length(a)) {
if (!values_equal(a[i], b[i])) return false
i = i + 1
}
return true
}
if (is_object(a) && is_object(b)) {
ka = array(a)
kb = array(b)
if (length(ka) != length(kb)) return false
i = 0
while (i < length(ka)) {
if (!values_equal(a[ka[i]], b[ka[i]])) return false
i = i + 1
}
return true
}
return false
}
function describe(val) {
if (is_null(val)) return "null"
if (is_text(val)) return `"${val}"`
if (is_number(val)) return text(val)
if (is_logical(val)) return text(val)
if (is_function(val)) return "<function>"
if (is_array(val)) return `[array length=${text(length(val))}]`
if (is_object(val)) return `{record keys=${text(length(array(val)))}}`
return "<unknown>"
}
// Run a single test file through both paths // Run a single test file through both paths
function diff_test_file(file_path) { function diff_test_file(file_path) {

310
diff_ir.ce Normal file
View File

@@ -0,0 +1,310 @@
// diff_ir.ce — mcode vs streamline diff
//
// Usage:
// cell diff_ir <file> Diff all functions
// cell diff_ir --fn <N|name> <file> Diff only one function
// cell diff_ir --summary <file> Counts only
var fd = use("fd")
var shop = use("internal/shop")
var pad_right = function(s, w) {
var r = s
while (length(r) < w) {
r = r + " "
}
return r
}
var fmt_val = function(v) {
if (is_null(v)) return "null"
if (is_number(v)) return text(v)
if (is_text(v)) return `"${v}"`
if (is_object(v)) return text(v)
if (is_logical(v)) return v ? "true" : "false"
return text(v)
}
var run = function() {
var fn_filter = null
var show_summary = false
var filename = null
var i = 0
var mcode_ir = null
var opt_ir = null
var source_text = null
var source_lines = null
var main_name = null
var fi = 0
var func = null
var opt_func = null
var fname = null
while (i < length(args)) {
if (args[i] == '--fn') {
i = i + 1
fn_filter = args[i]
} else if (args[i] == '--summary') {
show_summary = true
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell diff_ir [--fn <N|name>] [--summary] <file>")
log.console("")
log.console(" --fn <N|name> Filter to function by index or name")
log.console(" --summary Show counts only")
return null
} else if (!starts_with(args[i], '-')) {
filename = args[i]
}
i = i + 1
}
if (!filename) {
log.console("Usage: cell diff_ir [--fn <N|name>] [--summary] <file>")
return null
}
mcode_ir = shop.mcode_file(filename)
opt_ir = shop.compile_file(filename)
source_text = text(fd.slurp(filename))
source_lines = array(source_text, "\n")
var get_source_line = function(line_num) {
if (line_num < 1 || line_num > length(source_lines)) return null
return source_lines[line_num - 1]
}
var fn_matches = function(index, name) {
var match = null
if (fn_filter == null) return true
if (index >= 0 && fn_filter == text(index)) return true
if (name != null) {
match = search(name, fn_filter)
if (match != null && match >= 0) return true
}
return false
}
var fmt_instr = function(instr) {
var op = instr[0]
var n = length(instr)
var parts = []
var j = 1
var operands = null
var line_str = null
while (j < n - 2) {
push(parts, fmt_val(instr[j]))
j = j + 1
}
operands = text(parts, ", ")
line_str = instr[n - 2] != null ? `:${text(instr[n - 2])}` : ""
return pad_right(`${pad_right(op, 15)}${operands}`, 45) + line_str
}
var classify = function(before, after) {
var bn = 0
var an = 0
var k = 0
if (is_text(after) && starts_with(after, "_nop_")) return "eliminated"
if (is_array(before) && is_array(after)) {
if (before[0] != after[0]) return "rewritten"
bn = length(before)
an = length(after)
if (bn != an) return "rewritten"
k = 1
while (k < bn - 2) {
if (before[k] != after[k]) return "rewritten"
k = k + 1
}
return "identical"
}
return "identical"
}
var total_eliminated = 0
var total_rewritten = 0
var total_funcs = 0
var diff_function = function(mcode_func, opt_func, name, index) {
var nr_args = mcode_func.nr_args != null ? mcode_func.nr_args : 0
var nr_slots = mcode_func.nr_slots != null ? mcode_func.nr_slots : 0
var m_instrs = mcode_func.instructions
var o_instrs = opt_func.instructions
var eliminated = 0
var rewritten = 0
var mi = 0
var oi = 0
var pc = 0
var m_instr = null
var o_instr = null
var kind = null
var last_line = null
var instr_line = null
var n = 0
var src = null
var annotation = null
if (m_instrs == null) m_instrs = []
if (o_instrs == null) o_instrs = []
// First pass: count changes
mi = 0
oi = 0
while (mi < length(m_instrs) && oi < length(o_instrs)) {
m_instr = m_instrs[mi]
o_instr = o_instrs[oi]
if (is_text(m_instr)) {
mi = mi + 1
oi = oi + 1
continue
}
if (is_text(o_instr) && starts_with(o_instr, "_nop_")) {
if (is_array(m_instr)) {
eliminated = eliminated + 1
}
mi = mi + 1
oi = oi + 1
continue
}
if (is_array(m_instr) && is_array(o_instr)) {
kind = classify(m_instr, o_instr)
if (kind == "rewritten") {
rewritten = rewritten + 1
}
}
mi = mi + 1
oi = oi + 1
}
total_eliminated = total_eliminated + eliminated
total_rewritten = total_rewritten + rewritten
total_funcs = total_funcs + 1
if (show_summary) {
if (eliminated == 0 && rewritten == 0) {
log.compile(` ${pad_right(name + ":", 40)} 0 eliminated, 0 rewritten (unchanged)`)
} else {
log.compile(` ${pad_right(name + ":", 40)} ${text(eliminated)} eliminated, ${text(rewritten)} rewritten`)
}
return null
}
if (eliminated == 0 && rewritten == 0) return null
log.compile(`\n=== ${name} (args=${text(nr_args)}, slots=${text(nr_slots)}) ===`)
log.compile(` ${text(eliminated)} eliminated, ${text(rewritten)} rewritten`)
// Second pass: show diffs
mi = 0
oi = 0
pc = 0
last_line = null
while (mi < length(m_instrs) && oi < length(o_instrs)) {
m_instr = m_instrs[mi]
o_instr = o_instrs[oi]
if (is_text(m_instr) && !starts_with(m_instr, "_nop_")) {
mi = mi + 1
oi = oi + 1
continue
}
if (is_text(m_instr) && starts_with(m_instr, "_nop_")) {
mi = mi + 1
oi = oi + 1
continue
}
if (is_text(o_instr) && starts_with(o_instr, "_nop_")) {
if (is_array(m_instr)) {
n = length(m_instr)
instr_line = m_instr[n - 2]
if (instr_line != last_line && instr_line != null) {
src = get_source_line(instr_line)
if (src != null) src = trim(src)
if (last_line != null) log.compile("")
if (src != null && length(src) > 0) {
log.compile(` --- line ${text(instr_line)}: ${src} ---`)
}
last_line = instr_line
}
log.compile(` - ${pad_right(text(pc), 6)}${fmt_instr(m_instr)}`)
log.compile(` + ${pad_right(text(pc), 6)}${pad_right(o_instr, 45)} (eliminated)`)
}
mi = mi + 1
oi = oi + 1
pc = pc + 1
continue
}
if (is_array(m_instr) && is_array(o_instr)) {
kind = classify(m_instr, o_instr)
if (kind != "identical") {
n = length(m_instr)
instr_line = m_instr[n - 2]
if (instr_line != last_line && instr_line != null) {
src = get_source_line(instr_line)
if (src != null) src = trim(src)
if (last_line != null) log.compile("")
if (src != null && length(src) > 0) {
log.compile(` --- line ${text(instr_line)}: ${src} ---`)
}
last_line = instr_line
}
annotation = ""
if (kind == "rewritten") {
if (o_instr[0] == "concat" && m_instr[0] != "concat") {
annotation = "(specialized)"
} else {
annotation = "(rewritten)"
}
}
log.compile(` - ${pad_right(text(pc), 6)}${fmt_instr(m_instr)}`)
log.compile(` + ${pad_right(text(pc), 6)}${fmt_instr(o_instr)} ${annotation}`)
}
pc = pc + 1
}
mi = mi + 1
oi = oi + 1
}
return null
}
// Process functions
main_name = mcode_ir.name != null ? mcode_ir.name : "<main>"
if (mcode_ir.main != null && opt_ir.main != null) {
if (fn_matches(-1, main_name)) {
diff_function(mcode_ir.main, opt_ir.main, main_name, -1)
}
}
if (mcode_ir.functions != null && opt_ir.functions != null) {
fi = 0
while (fi < length(mcode_ir.functions) && fi < length(opt_ir.functions)) {
func = mcode_ir.functions[fi]
opt_func = opt_ir.functions[fi]
fname = func.name != null ? func.name : "<anonymous>"
if (fn_matches(fi, fname)) {
diff_function(func, opt_func, `[${text(fi)}] ${fname}`, fi)
}
fi = fi + 1
}
}
if (show_summary) {
log.compile(`\n total: ${text(total_eliminated)} eliminated, ${text(total_rewritten)} rewritten across ${text(total_funcs)} functions`)
}
return null
}
run()
$stop()

265
disasm.ce Normal file
View File

@@ -0,0 +1,265 @@
// disasm.ce — source-interleaved disassembly
//
// Usage:
// cell disasm <file> Disassemble all functions (mcode)
// cell disasm --optimized <file> Disassemble optimized IR (streamline)
// cell disasm --fn <N|name> <file> Show only function N or named function
// cell disasm --line <N> <file> Show instructions from source line N
var fd = use("fd")
var shop = use("internal/shop")
var pad_right = function(s, w) {
var r = s
while (length(r) < w) {
r = r + " "
}
return r
}
var fmt_val = function(v) {
if (is_null(v)) return "null"
if (is_number(v)) return text(v)
if (is_text(v)) return `"${v}"`
if (is_object(v)) return text(v)
if (is_logical(v)) return v ? "true" : "false"
return text(v)
}
var run = function() {
var use_optimized = false
var fn_filter = null
var line_filter = null
var filename = null
var i = 0
var compiled = null
var source_text = null
var source_lines = null
var main_name = null
var fi = 0
var func = null
var fname = null
while (i < length(args)) {
if (args[i] == '--optimized') {
use_optimized = true
} else if (args[i] == '--fn') {
i = i + 1
fn_filter = args[i]
} else if (args[i] == '--line') {
i = i + 1
line_filter = number(args[i])
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell disasm [--optimized] [--fn <N|name>] [--line <N>] <file>")
log.console("")
log.console(" --optimized Use optimized IR (streamline) instead of raw mcode")
log.console(" --fn <N|name> Filter to function by index or name")
log.console(" --line <N> Show only instructions from source line N")
return null
} else if (!starts_with(args[i], '-')) {
filename = args[i]
}
i = i + 1
}
if (!filename) {
log.console("Usage: cell disasm [--optimized] [--fn <N|name>] [--line <N>] <file>")
return null
}
// Compile
if (use_optimized) {
compiled = shop.compile_file(filename)
} else {
compiled = shop.mcode_file(filename)
}
// Read source file
source_text = text(fd.slurp(filename))
source_lines = array(source_text, "\n")
// Helpers
var get_source_line = function(line_num) {
if (line_num < 1 || line_num > length(source_lines)) return null
return source_lines[line_num - 1]
}
var first_instr_line = function(func) {
var instrs = func.instructions
var i = 0
var n = 0
if (instrs == null) return null
while (i < length(instrs)) {
if (is_array(instrs[i])) {
n = length(instrs[i])
return instrs[i][n - 2]
}
i = i + 1
}
return null
}
var func_has_line = function(func, target) {
var instrs = func.instructions
var i = 0
var n = 0
if (instrs == null) return false
while (i < length(instrs)) {
if (is_array(instrs[i])) {
n = length(instrs[i])
if (instrs[i][n - 2] == target) return true
}
i = i + 1
}
return false
}
var fn_matches = function(index, name) {
var match = null
if (fn_filter == null) return true
if (index >= 0 && fn_filter == text(index)) return true
if (name != null) {
match = search(name, fn_filter)
if (match != null && match >= 0) return true
}
return false
}
var func_name_by_index = function(fi) {
var f = null
if (compiled.functions == null) return null
if (fi < 0 || fi >= length(compiled.functions)) return null
f = compiled.functions[fi]
return f.name
}
var dump_function = function(func, name, index) {
var nr_args = func.nr_args != null ? func.nr_args : 0
var nr_slots = func.nr_slots != null ? func.nr_slots : 0
var nr_close = func.nr_close_slots != null ? func.nr_close_slots : 0
var instrs = func.instructions
var start_line = first_instr_line(func)
var header = null
var i = 0
var pc = 0
var instr = null
var op = null
var n = 0
var parts = null
var j = 0
var operands = null
var instr_line = null
var last_line = null
var src = null
var line_str = null
var instr_text = null
var target_name = null
header = `\n=== ${name} (args=${text(nr_args)}, slots=${text(nr_slots)}, closures=${text(nr_close)})`
if (start_line != null) {
header = header + ` [line ${text(start_line)}]`
}
header = header + " ==="
log.compile(header)
if (instrs == null || length(instrs) == 0) {
log.compile(" (empty)")
return null
}
while (i < length(instrs)) {
instr = instrs[i]
if (is_text(instr)) {
if (!starts_with(instr, "_nop_") && line_filter == null) {
log.compile(` ${instr}:`)
}
} else if (is_array(instr)) {
op = instr[0]
n = length(instr)
instr_line = instr[n - 2]
if (line_filter != null && instr_line != line_filter) {
pc = pc + 1
i = i + 1
continue
}
if (instr_line != last_line && instr_line != null) {
src = get_source_line(instr_line)
if (src != null) {
src = trim(src)
}
if (last_line != null) {
log.compile("")
}
if (src != null && length(src) > 0) {
log.compile(` --- line ${text(instr_line)}: ${src} ---`)
} else {
log.compile(` --- line ${text(instr_line)} ---`)
}
last_line = instr_line
}
parts = []
j = 1
while (j < n - 2) {
push(parts, fmt_val(instr[j]))
j = j + 1
}
operands = text(parts, ", ")
line_str = instr_line != null ? `:${text(instr_line)}` : ""
instr_text = ` ${pad_right(text(pc), 6)}${pad_right(op, 15)}${operands}`
// Cross-reference for function creation instructions
target_name = null
if (op == "function" && n >= 5) {
target_name = func_name_by_index(instr[2])
}
if (target_name != null) {
instr_text = pad_right(instr_text, 65) + line_str + ` ; -> [${text(instr[2])}] ${target_name}`
} else {
instr_text = pad_right(instr_text, 65) + line_str
}
log.compile(instr_text)
pc = pc + 1
}
i = i + 1
}
return null
}
// Process functions
main_name = compiled.name != null ? compiled.name : "<main>"
if (compiled.main != null) {
if (fn_matches(-1, main_name)) {
if (line_filter == null || func_has_line(compiled.main, line_filter)) {
dump_function(compiled.main, main_name, -1)
}
}
}
if (compiled.functions != null) {
fi = 0
while (fi < length(compiled.functions)) {
func = compiled.functions[fi]
fname = func.name != null ? func.name : "<anonymous>"
if (fn_matches(fi, fname)) {
if (line_filter == null || func_has_line(func, line_filter)) {
dump_function(func, `[${text(fi)}] ${fname}`, fi)
}
}
fi = fi + 1
}
}
return null
}
run()
$stop()

View File

@@ -52,10 +52,13 @@ Where:
Examples: Examples:
- `mypackage/math.c` -> `js_mypackage_math_use` - `mypackage/math.c` -> `js_mypackage_math_use`
- `gitea.pockle.world/john/lib/render.c` -> `js_gitea_pockle_world_john_lib_render_use` - `gitea.pockle.world/john/lib/render.c` -> `js_gitea_pockle_world_john_lib_render_use`
- `mypackage/internal/helpers.c` -> `js_mypackage_internal_helpers_use`
- `mypackage/game.ce` (AOT actor) -> `js_mypackage_game_program` - `mypackage/game.ce` (AOT actor) -> `js_mypackage_game_program`
Actor files (`.ce`) use the `_program` suffix instead of `_use`. Actor files (`.ce`) use the `_program` suffix instead of `_use`.
Internal modules (in `internal/` subdirectories) follow the same convention — the `internal` directory name becomes part of the symbol. For example, `internal/os.c` in the core package has the symbol `js_core_internal_os_use`.
**Note:** Having both a `.cm` and `.c` file with the same stem at the same scope is a build error. **Note:** Having both a `.cm` and `.c` file with the same stem at the same scope is a build error.
## Required Headers ## Required Headers
@@ -450,6 +453,53 @@ JSC_CCALL(mymod_make,
) )
``` ```
### C Argument Evaluation Order (critical)
In C, the **order of evaluation of function arguments is unspecified**. This interacts with the copying GC to create intermittent crashes that are extremely difficult to diagnose.
```c
// UNSAFE — crashes intermittently:
JS_FRAME(js);
JS_ROOT(obj, JS_NewObject(js));
JS_SetPropertyStr(js, obj.val, "format", JS_NewString(js, "rgba32"));
// ^^^^^^^ may be evaluated BEFORE JS_NewString runs
// If JS_NewString triggers GC, the already-read obj.val is a dangling pointer.
```
The compiler is free to evaluate `obj.val` into a register, *then* call `JS_NewString`. If `JS_NewString` triggers GC, the object moves to a new address. The rooted `obj` is updated by GC, but the **register copy** is not — it still holds the old address. `JS_SetPropertyStr` then writes to freed memory.
**Fix:** always separate the allocating call into a local variable:
```c
// SAFE:
JS_FRAME(js);
JS_ROOT(obj, JS_NewObject(js));
JSValue fmt = JS_NewString(js, "rgba32");
JS_SetPropertyStr(js, obj.val, "format", fmt);
// obj.val is read AFTER JS_NewString completes — guaranteed correct.
```
This applies to **any** allocating function used as an argument when another argument references a rooted `.val`:
```c
// ALL of these are UNSAFE:
JS_SetPropertyStr(js, obj.val, "pixels", js_new_blob_stoned_copy(js, data, len));
JS_SetPropertyStr(js, obj.val, "x", JS_NewFloat64(js, 3.14));
JS_SetPropertyStr(js, obj.val, "name", JS_NewString(js, name));
// SAFE versions — separate the allocation:
JSValue pixels = js_new_blob_stoned_copy(js, data, len);
JS_SetPropertyStr(js, obj.val, "pixels", pixels);
JSValue x = JS_NewFloat64(js, 3.14);
JS_SetPropertyStr(js, obj.val, "x", x);
JSValue s = JS_NewString(js, name);
JS_SetPropertyStr(js, obj.val, "name", s);
```
**Functions that allocate** (must be separated): `JS_NewString`, `JS_NewFloat64`, `JS_NewInt64`, `JS_NewObject`, `JS_NewArray`, `JS_NewCFunction`, `js_new_blob_stoned_copy`
**Functions that do NOT allocate** (safe inline): `JS_NewInt32`, `JS_NewUint32`, `JS_NewBool`, `JS_NULL`, `JS_TRUE`, `JS_FALSE`
### Macros ### Macros
| Macro | Purpose | | Macro | Purpose |

View File

@@ -317,11 +317,13 @@ Options:
- `--format=pretty|bare|json` — output format (default: `pretty` for console, `json` for file) - `--format=pretty|bare|json` — output format (default: `pretty` for console, `json` for file)
- `--channels=ch1,ch2` — channels to subscribe (default: `console,error,system`). Use `'*'` for all channels (quote to prevent shell glob expansion). - `--channels=ch1,ch2` — channels to subscribe (default: `console,error,system`). Use `'*'` for all channels (quote to prevent shell glob expansion).
- `--exclude=ch1,ch2` — channels to exclude (useful with `'*'`) - `--exclude=ch1,ch2` — channels to exclude (useful with `'*'`)
- `--stack=ch1,ch2` — channels that capture a full stack trace (default: `error`)
```bash ```bash
pit log add terminal console --format=bare --channels=console pit log add terminal console --format=bare --channels=console
pit log add errors file .cell/logs/errors.jsonl --channels=error pit log add errors file .cell/logs/errors.jsonl --channels=error
pit log add dump file .cell/logs/dump.jsonl '--channels=*' --exclude=console pit log add dump file .cell/logs/dump.jsonl '--channels=*' --exclude=console
pit log add debug console --channels=error,debug --stack=error,debug
``` ```
### pit log remove ### pit log remove

View File

@@ -28,6 +28,12 @@ Each stage has a corresponding CLI tool that lets you see its output.
| streamline | `streamline.ce --types` | Optimized IR with type annotations | | streamline | `streamline.ce --types` | Optimized IR with type annotations |
| streamline | `streamline.ce --stats` | Per-function summary stats | | streamline | `streamline.ce --stats` | Per-function summary stats |
| streamline | `streamline.ce --ir` | Human-readable canonical IR | | streamline | `streamline.ce --ir` | Human-readable canonical IR |
| disasm | `disasm.ce` | Source-interleaved disassembly |
| disasm | `disasm.ce --optimized` | Optimized source-interleaved disassembly |
| diff | `diff_ir.ce` | Mcode vs streamline instruction diff |
| xref | `xref.ce` | Cross-reference / call creation graph |
| cfg | `cfg.ce` | Control flow graph (basic blocks) |
| slots | `slots.ce` | Slot data flow / use-def chains |
| all | `ir_report.ce` | Structured optimizer flight recorder | | all | `ir_report.ce` | Structured optimizer flight recorder |
All tools take a source file as input and run the pipeline up to the relevant stage. All tools take a source file as input and run the pipeline up to the relevant stage.
@@ -38,6 +44,9 @@ All tools take a source file as input and run the pipeline up to the relevant st
# see raw mcode IR (pretty-printed) # see raw mcode IR (pretty-printed)
cell mcode --pretty myfile.ce cell mcode --pretty myfile.ce
# source-interleaved disassembly
cell disasm myfile.ce
# see optimized IR with type annotations # see optimized IR with type annotations
cell streamline --types myfile.ce cell streamline --types myfile.ce
@@ -72,6 +81,7 @@ cell streamline --stats <file.ce|file.cm> # summary stats per function
cell streamline --ir <file.ce|file.cm> # human-readable IR cell streamline --ir <file.ce|file.cm> # human-readable IR
cell streamline --check <file.ce|file.cm> # warnings only cell streamline --check <file.ce|file.cm> # warnings only
cell streamline --types <file.ce|file.cm> # IR with type annotations cell streamline --types <file.ce|file.cm> # IR with type annotations
cell streamline --diagnose <file.ce|file.cm> # compile-time diagnostics
``` ```
| Flag | Description | | Flag | Description |
@@ -81,9 +91,214 @@ cell streamline --types <file.ce|file.cm> # IR with type annotations
| `--ir` | Human-readable canonical IR (same format as `ir_report.ce`) | | `--ir` | Human-readable canonical IR (same format as `ir_report.ce`) |
| `--check` | Warnings only (e.g. `nr_slots > 200` approaching 255 limit) | | `--check` | Warnings only (e.g. `nr_slots > 200` approaching 255 limit) |
| `--types` | Optimized IR with inferred type annotations per slot | | `--types` | Optimized IR with inferred type annotations per slot |
| `--diagnose` | Run compile-time diagnostics (type errors and warnings) |
Flags can be combined. Flags can be combined.
## disasm.ce
Source-interleaved disassembly. Shows mcode or optimized IR with source lines interleaved, making it easy to see which instructions were generated from which source code.
```bash
cell disasm <file> # disassemble all functions (mcode)
cell disasm --optimized <file> # disassemble optimized IR (streamline)
cell disasm --fn 87 <file> # show only function 87
cell disasm --fn my_func <file> # show only functions named "my_func"
cell disasm --line 235 <file> # show instructions generated from line 235
```
| Flag | Description |
|------|-------------|
| (none) | Raw mcode IR with source interleaving (default) |
| `--optimized` | Use optimized IR (streamline) instead of raw mcode |
| `--fn <N\|name>` | Filter to specific function by index or name substring |
| `--line <N>` | Show only instructions generated from a specific source line |
### Output Format
Functions are shown with a header including argument count, slot count, and the source line where the function begins. Instructions are grouped by source line, with the source text shown before each group:
```
=== [87] <anonymous> (args=0, slots=12, closures=0) [line 234] ===
--- line 235: var result = compute(x, y) ---
0 access 2, "compute" :235
1 get 3, 1, 0 :235
2 get 4, 1, 1 :235
3 invoke 3, 2, 2 :235
--- line 236: if (result > 0) { ---
4 access 5, 0 :236
5 gt 6, 4, 5 :236
6 jump_false 6, "else_1" :236
```
Each instruction line shows:
- Program counter (left-aligned)
- Opcode
- Operands (comma-separated)
- Source line number (`:N` suffix, right-aligned)
Function creation instructions include a cross-reference annotation showing the target function's name:
```
3 function 5, 12 :235 ; -> [12] helper_fn
```
## diff_ir.ce
Compares mcode IR (before optimization) with streamline IR (after optimization), showing what the optimizer changed. Useful for understanding which instructions were eliminated, specialized, or rewritten.
```bash
cell diff_ir <file> # diff all functions
cell diff_ir --fn <N|name> <file> # diff only one function
cell diff_ir --summary <file> # counts only
```
| Flag | Description |
|------|-------------|
| (none) | Show all diffs with source interleaving |
| `--fn <N\|name>` | Filter to specific function by index or name |
| `--summary` | Show only eliminated/rewritten counts per function |
### Output Format
Changed instructions are shown in diff style with `-` (before) and `+` (after) lines:
```
=== [0] <anonymous> (args=1, slots=40) ===
17 eliminated, 51 rewritten
--- line 4: if (n <= 1) { ---
- 1 is_int 4, 1 :4
+ 1 is_int 3, 1 :4 (specialized)
- 3 is_int 5, 2 :4
+ 3 _nop_tc_1 (eliminated)
```
Summary mode gives a quick overview:
```
[0] <anonymous>: 17 eliminated, 51 rewritten
[1] <anonymous>: 65 eliminated, 181 rewritten
total: 86 eliminated, 250 rewritten across 4 functions
```
## xref.ce
Cross-reference / call graph tool. Shows which functions create other functions (via `function` instructions), building a creation tree.
```bash
cell xref <file> # full creation tree
cell xref --callers <N> <file> # who creates function [N]?
cell xref --callees <N> <file> # what does [N] create/call?
cell xref --dot <file> # DOT graph for graphviz
cell xref --optimized <file> # use optimized IR
```
| Flag | Description |
|------|-------------|
| (none) | Indented creation tree from main |
| `--callers <N>` | Show which functions create function [N] |
| `--callees <N>` | Show what function [N] creates (use -1 for main) |
| `--dot` | Output DOT format for graphviz |
| `--optimized` | Use optimized IR instead of raw mcode |
### Output Format
Default tree view:
```
demo_disasm.cm
[0] <anonymous>
[1] <anonymous>
[2] <anonymous>
```
Caller/callee query:
```
Callers of [0] <anonymous>:
demo_disasm.cm at line 3
```
DOT output can be piped to graphviz: `cell xref --dot file.cm | dot -Tpng -o xref.png`
## cfg.ce
Control flow graph tool. Identifies basic blocks from labels and jumps, computes edges, and detects loop back-edges.
```bash
cell cfg --fn <N|name> <file> # text CFG for function
cell cfg --dot --fn <N|name> <file> # DOT output for graphviz
cell cfg <file> # text CFG for all functions
cell cfg --optimized <file> # use optimized IR
```
| Flag | Description |
|------|-------------|
| `--fn <N\|name>` | Filter to specific function by index or name |
| `--dot` | Output DOT format for graphviz |
| `--optimized` | Use optimized IR instead of raw mcode |
### Output Format
```
=== [0] <anonymous> ===
B0 [pc 0-2, line 4]:
0 access 2, 1
1 is_int 4, 1
2 jump_false 4, "rel_ni_2"
-> B3 "rel_ni_2" (jump)
-> B1 (fallthrough)
B1 [pc 3-4, line 4]:
3 is_int 5, 2
4 jump_false 5, "rel_ni_2"
-> B3 "rel_ni_2" (jump)
-> B2 (fallthrough)
```
Each block shows its ID, PC range, source lines, instructions, and outgoing edges. Loop back-edges (target PC <= source PC) are annotated.
## slots.ce
Slot data flow analysis. Builds use-def chains for every slot in a function, showing where each slot is defined and used. Optionally captures type information from streamline.
```bash
cell slots --fn <N|name> <file> # slot summary for function
cell slots --slot <N> --fn <N|name> <file> # trace slot N
cell slots <file> # slot summary for all functions
```
| Flag | Description |
|------|-------------|
| `--fn <N\|name>` | Filter to specific function by index or name |
| `--slot <N>` | Show chronological DEF/USE trace for a specific slot |
### Output Format
Summary shows each slot with its def count, use count, inferred type, and first definition. Dead slots (defined but never used) are flagged:
```
=== [0] <anonymous> (args=1, slots=40) ===
slot defs uses type first-def
s0 0 0 - (this)
s1 0 10 - (arg 0)
s2 1 6 - pc 0: access
s10 1 0 - pc 29: invoke <- dead
```
Slot trace (`--slot N`) shows every DEF and USE in program order:
```
=== slot 3 in [0] <anonymous> ===
DEF pc 5: le_int 3, 1, 2 :4
DEF pc 11: le_float 3, 1, 2 :4
DEF pc 17: le_text 3, 1, 2 :4
USE pc 31: jump_false 3, "if_else_0" :4
```
## seed.ce ## seed.ce
Regenerates the boot seed files in `boot/`. These are pre-compiled mcode IR (JSON) files that bootstrap the compilation pipeline on cold start. Regenerates the boot seed files in `boot/`. These are pre-compiled mcode IR (JSON) files that bootstrap the compilation pipeline on cold start.

View File

@@ -395,16 +395,6 @@ Returns the opposite logical. Returns null for non-logicals.
Returns a requestor that starts all requestors in the array. Results are collected into an array matching the input order. Optional throttle limits concurrent requestors. Optional need specifies the minimum number of successes required. See [Requestors](/docs/requestors/) for usage. Returns a requestor that starts all requestors in the array. Results are collected into an array matching the input order. Optional throttle limits concurrent requestors. Optional need specifies the minimum number of successes required. See [Requestors](/docs/requestors/) for usage.
### print(value)
Print a value to standard output.
```javascript
print("hello")
print(42)
print(`result: ${x}`)
```
### race(requestor_array, throttle, need) ### race(requestor_array, throttle, need)
Like parallel, but returns as soon as the needed number of results are obtained. Default need is 1. Unfinished requestors are cancelled. See [Requestors](/docs/requestors/) for usage. Like parallel, but returns as soon as the needed number of results are obtained. Default need is 1. Unfinished requestors are cancelled. See [Requestors](/docs/requestors/) for usage.

View File

@@ -595,7 +595,7 @@ var safe_divide = function(a, b) {
if (b == 0) disrupt if (b == 0) disrupt
return a / b return a / b
} disruption { } disruption {
print("something went wrong") log.error("something went wrong")
} }
``` ```

View File

@@ -29,14 +29,16 @@ Non-text values are JSON-encoded automatically.
## Default Behavior ## Default Behavior
With no configuration, a default sink routes `console`, `error`, and `system` to the terminal in pretty format: With no configuration, a default sink routes `console`, `error`, and `system` to the terminal in pretty format. The `error` channel includes a stack trace by default:
``` ```
[a3f12] [console] main.ce:5 server started on port 8080 [a3f12] [console] server started on port 8080
[a3f12] [error] main.ce:12 connection refused [a3f12] [error] connection refused
at handle_request (server.ce:42:3)
at main (main.ce:5:1)
``` ```
The format is `[actor_id] [channel] file:line message`. The format is `[actor_id] [channel] message`. Error stack traces are always on unless you explicitly configure a sink without them.
## Configuration ## Configuration
@@ -114,14 +116,22 @@ File sinks write one JSON-encoded record per line. Console sinks format the reco
## Stack Traces ## Stack Traces
Add a `stack` field to a sink to capture a full call stack for specific channels. The value is an array of channel names. The `error` channel captures stack traces by default. To enable stack traces for other channels, add a `stack` field to a sink — an array of channel names that should include a call stack.
Via the CLI:
```bash
pit log add terminal console --channels=console,error,debug --stack=error,debug
```
Or in `log.toml`:
```toml ```toml
[sink.terminal] [sink.terminal]
type = "console" type = "console"
format = "bare" format = "bare"
channels = ["console", "error"] channels = ["console", "error", "debug"]
stack = ["error"] stack = ["error", "debug"]
``` ```
Only channels listed in `stack` get stack traces. Other channels on the same sink print without one: Only channels listed in `stack` get stack traces. Other channels on the same sink print without one:
@@ -150,6 +160,7 @@ The `pit log` command manages sinks and reads log files. See [CLI — pit log](/
pit log list # show sinks pit log list # show sinks
pit log add terminal console --format=bare --channels=console pit log add terminal console --format=bare --channels=console
pit log add dump file .cell/logs/dump.jsonl '--channels=*' --exclude=console pit log add dump file .cell/logs/dump.jsonl '--channels=*' --exclude=console
pit log add debug console --channels=error,debug --stack=error,debug
pit log remove terminal pit log remove terminal
pit log read dump --lines=20 --channel=error pit log read dump --lines=20 --channel=error
pit log tail dump pit log tail dump

View File

@@ -5,7 +5,7 @@ weight: 85
type: "docs" type: "docs"
--- ---
Nota is a binary message format developed for use in the Procession Protocol. It provides a compact, JSON-like encoding that supports blobs, text, arrays, records, numbers, and symbols. Nota is a binary message format developed for use in the Procession Protocol. It provides a compact, JSON-like encoding that supports blobs, text, arrays, records, numbers, and symbols. Nota is an internal module: `use('internal/nota')`.
Nota stands for Network Object Transfer Arrangement. Nota stands for Network Object Transfer Arrangement.

View File

@@ -230,4 +230,4 @@ If `shop.toml` is missing or has no `[policy]` section, all methods are enabled
| `internal/os.c` | OS intrinsics: dylib ops, internal symbol lookup, embedded modules | | `internal/os.c` | OS intrinsics: dylib ops, internal symbol lookup, embedded modules |
| `package.cm` | Package directory detection, alias resolution, file listing | | `package.cm` | Package directory detection, alias resolution, file listing |
| `link.cm` | Development link management (link.toml read/write) | | `link.cm` | Development link management (link.toml read/write) |
| `boot/*.cm.mcode` | Pre-compiled pipeline seeds (tokenize, parse, fold, mcode, bootstrap) | | `boot/*.cm.mcode` | Pre-compiled pipeline seeds (tokenize, parse, fold, mcode, streamline, bootstrap) |

View File

@@ -93,3 +93,13 @@ Arithmetic ops (ADD, SUB, MUL, DIV, MOD, POW) are executed inline without callin
DIV and MOD check for zero divisor (→ null). POW uses `pow()` with non-finite handling for finite inputs. DIV and MOD check for zero divisor (→ null). POW uses `pow()` with non-finite handling for finite inputs.
Comparison ops (EQ through GE) and bitwise ops still use `reg_vm_binop()` for their slow paths, as they handle a wider range of type combinations (string comparisons, null equality, etc.). Comparison ops (EQ through GE) and bitwise ops still use `reg_vm_binop()` for their slow paths, as they handle a wider range of type combinations (string comparisons, null equality, etc.).
## String Concatenation
CONCAT has a three-tier dispatch for self-assign patterns (`concat R(A), R(A), R(C)` where dest equals the left operand):
1. **In-place append**: If `R(A)` is a mutable heap text (S bit clear) with `length + rhs_length <= cap56`, characters are appended directly. Zero allocation, zero GC.
2. **Growth allocation** (`JS_ConcatStringGrow`): Allocates a new text with 2x capacity and does **not** stone the result, leaving it mutable for subsequent appends.
3. **Exact-fit stoned** (`JS_ConcatString`): Used when dest differs from the left operand (normal non-self-assign concat).
The `stone_text` instruction (iABC, B=0, C=0) sets the S bit on a mutable heap text in `R(A)`. For non-pointer values or already-stoned text, it is a no-op. This instruction is emitted by the streamline optimizer at escape points; see [Streamline — insert_stone_text](streamline.md#7-insert_stone_text-mutable-text-escape-analysis) and [Stone Memory — Mutable Text](stone.md#mutable-text-concatenation).

View File

@@ -101,6 +101,11 @@ Operands are register slot numbers (integers), constant values (strings, numbers
| Instruction | Operands | Description | | Instruction | Operands | Description |
|-------------|----------|-------------| |-------------|----------|-------------|
| `concat` | `dest, a, b` | `dest = a ~ b` (text concatenation) | | `concat` | `dest, a, b` | `dest = a ~ b` (text concatenation) |
| `stone_text` | `slot` | Stone a mutable text value (see below) |
The `stone_text` instruction is emitted by the streamline optimizer's escape analysis pass (`insert_stone_text`). It freezes a mutable text value before it escapes its defining slot — for example, before a `move`, `setarg`, `store_field`, `push`, or `put`. The instruction is only inserted when the slot is provably `T_TEXT`; non-text values never need stoning. See [Streamline Optimizer — insert_stone_text](streamline.md#7-insert_stone_text-mutable-text-escape-analysis) for details.
At the VM level, `stone_text` is a single-operand instruction (iABC with B=0, C=0). If the slot holds a heap text without the S bit set, it sets the S bit. For all other values (integers, booleans, already-stoned text, etc.), it is a no-op.
### Comparison — Integer ### Comparison — Integer

View File

@@ -69,6 +69,7 @@ Optimizes the Mcode IR through a series of independent passes. Operates per-func
6. **Move elimination**: Removes self-moves (`move a, a`). 6. **Move elimination**: Removes self-moves (`move a, a`).
7. **Unreachable elimination**: Nops dead code after `return` until the next label. 7. **Unreachable elimination**: Nops dead code after `return` until the next label.
8. **Dead jump elimination**: Removes jumps to the immediately following label. 8. **Dead jump elimination**: Removes jumps to the immediately following label.
9. **Compile-time diagnostics** (optional): When `_warn` is set on the mcode input, emits errors for provably wrong operations (storing named property on array, invoking null, etc.) and warnings for suspicious patterns (named property access on array/text). The engine aborts compilation if any error-severity diagnostics are emitted.
See [Streamline Optimizer](streamline.md) for detailed pass descriptions. See [Streamline Optimizer](streamline.md) for detailed pass descriptions.
@@ -134,6 +135,7 @@ Seeds are used during cold start (empty cache) to compile the pipeline modules f
| `mcode.ce --pretty` | Print raw Mcode IR before streamlining | | `mcode.ce --pretty` | Print raw Mcode IR before streamlining |
| `streamline.ce --types` | Print streamlined IR with type annotations | | `streamline.ce --types` | Print streamlined IR with type annotations |
| `streamline.ce --stats` | Print IR after streamlining with before/after stats | | `streamline.ce --stats` | Print IR after streamlining with before/after stats |
| `streamline.ce --diagnose` | Print compile-time diagnostics (type errors and warnings) |
## Test Files ## Test Files
@@ -146,3 +148,4 @@ Seeds are used during cold start (empty cache) to compile the pipeline modules f
| `qbe_test.ce` | End-to-end QBE IL generation | | `qbe_test.ce` | End-to-end QBE IL generation |
| `test_intrinsics.cm` | Inlined intrinsic opcodes (is_array, length, push, etc.) | | `test_intrinsics.cm` | Inlined intrinsic opcodes (is_array, length, push, etc.) |
| `test_backward.cm` | Backward type propagation for parameters | | `test_backward.cm` | Backward type propagation for parameters |
| `tests/compile.cm` | Compile-time diagnostics (type errors and warnings) |

View File

@@ -77,6 +77,30 @@ Messages between actors are stoned before delivery, ensuring actors never share
Literal objects and arrays that can be determined at compile time may be allocated directly in stone memory. Literal objects and arrays that can be determined at compile time may be allocated directly in stone memory.
## Mutable Text Concatenation
String concatenation in a loop (`s = s + "x"`) is optimized to O(n) amortized by leaving concat results **unstoned** with over-allocated capacity. On the next concatenation, if the destination text is mutable (S bit clear) and has enough room, the VM appends in-place with zero allocation.
### How It Works
When the VM executes `concat dest, dest, src` (same destination and left operand — a self-assign pattern):
1. **Inline fast path**: If `dest` holds a heap text, is not stoned, and `length + src_length <= capacity` — append characters in place, update length, done. No allocation, no GC possible.
2. **Growth path** (`JS_ConcatStringGrow`): Allocate a new text with `capacity = max(new_length * 2, 16)`, copy both operands, and return the result **without stoning** it. The 2x growth factor means a loop of N concatenations does O(log N) allocations totaling O(N) character copies.
3. **Exact-fit path** (`JS_ConcatString`): When `dest != left` (not self-assign), the existing exact-fit stoned path is used. This is the normal case for expressions like `var c = a + b`.
### Safety Invariant
**An unstoned heap text is uniquely referenced by exactly one slot.** This is enforced by the `stone_text` mcode instruction, which the [streamline optimizer](streamline.md#7-insert_stone_text-mutable-text-escape-analysis) inserts before any instruction that would create a second reference to the value (move, store, push, setarg, put). Two VM-level guards cover cases where the compiler cannot prove the type: `get` (closure reads) and `return` (inter-frame returns).
### Why Over-Allocation Is GC-Safe
- The copying collector copies based on `cap56` (the object header's capacity field), not `length`. Over-allocated capacity survives GC.
- `js_alloc_string` zero-fills the packed data region, so padding beyond `length` is always clean.
- String comparisons, hashing, and interning all use `length`, not `cap56`. Extra capacity is invisible to string operations.
## Relationship to GC ## Relationship to GC
The Cheney copying collector only operates on the mutable heap. During collection, when the collector encounters a pointer to stone memory (S bit set), it skips it — stone objects are roots that never move. This means stone memory acts as a permanent root set with zero GC overhead. The Cheney copying collector only operates on the mutable heap. During collection, when the collector encounters a pointer to stone memory (S bit set), it skips it — stone objects are roots that never move. This means stone memory acts as a permanent root set with zero GC overhead.

View File

@@ -164,7 +164,44 @@ Removes `move a, a` instructions where the source and destination are the same s
**Nop prefix:** `_nop_mv_` **Nop prefix:** `_nop_mv_`
### 7. eliminate_unreachable (dead code after return) ### 7. insert_stone_text (mutable text escape analysis)
Inserts `stone_text` instructions before mutable text values escape their defining slot. This pass supports the mutable text concatenation optimization (see [Stone Memory — Mutable Text](stone.md#mutable-text-concatenation)), which leaves `concat` results unstoned with excess capacity so that subsequent `s = s + x` can append in-place.
The invariant is: **an unstoned heap text is uniquely referenced by exactly one slot.** This pass ensures that whenever a text value is copied or shared (via move, store, push, function argument, closure write, etc.), it is stoned first.
**Algorithm:**
1. **Compute liveness.** Build `first_ref[slot]` and `last_ref[slot]` arrays by scanning all instructions. Extend live ranges for backward jumps (loops): if a backward jump targets label L at position `lpos`, every slot referenced between `lpos` and the jump has its `last_ref` extended to the jump position.
2. **Forward walk with type tracking.** Walk instructions using `track_types` to maintain per-slot types. At each escape point, if the escaping slot is provably `T_TEXT`, insert `stone_text slot` before the instruction.
3. **Move special case.** For `move dest, src`: only insert `stone_text src` if the source is `T_TEXT` **and** `last_ref[src] > i` (the source slot is still live after the move, meaning both slots alias the same text). If the source is dead after the move, the value transfers uniquely — no stoning needed.
**Escape points and the slot that gets stoned:**
| Instruction | Stoned slot | Why it escapes |
|---|---|---|
| `move` | source (if still live) | Two slots alias the same value |
| `store_field` | value | Stored to object property |
| `store_index` | value | Stored to array element |
| `store_dynamic` | value | Dynamic property store |
| `push` | value | Pushed to array |
| `setarg` | value | Passed as function argument |
| `put` | source | Written to outer closure frame |
**Not handled by this pass** (handled by VM guards instead):
| Instruction | Reason |
|---|---|
| `get` (closure read) | Value arrives from outer frame; type may be T_UNKNOWN at compile time |
| `return` | Return value's type may be T_UNKNOWN; VM stones at inter-frame boundary |
These two cases use runtime `stone_mutable_text` guards in the VM because the streamline pass cannot always prove the slot type across frame boundaries.
**Nop prefix:** none (inserts instructions, does not create nops)
### 8. eliminate_unreachable (dead code after return)
Nops instructions after `return` until the next real label. Only `return` is treated as a terminal instruction; `disrupt` is not, because the disruption handler code immediately follows `disrupt` and must remain reachable. Nops instructions after `return` until the next real label. Only `return` is treated as a terminal instruction; `disrupt` is not, because the disruption handler code immediately follows `disrupt` and must remain reachable.
@@ -172,12 +209,42 @@ The mcode compiler emits a label at disruption handler entry points (see `emit_l
**Nop prefix:** `_nop_ur_` **Nop prefix:** `_nop_ur_`
### 8. eliminate_dead_jumps (jump-to-next-label elimination) ### 9. eliminate_dead_jumps (jump-to-next-label elimination)
Removes `jump L` instructions where `L` is the immediately following label (skipping over any intervening nop strings). These are common after other passes eliminate conditional branches, leaving behind jumps that fall through naturally. Removes `jump L` instructions where `L` is the immediately following label (skipping over any intervening nop strings). These are common after other passes eliminate conditional branches, leaving behind jumps that fall through naturally.
**Nop prefix:** `_nop_dj_` **Nop prefix:** `_nop_dj_`
### 10. diagnose_function (compile-time diagnostics)
Optional pass that runs when `_warn` is set on the mcode input. Performs a forward type-tracking scan and emits diagnostics for provably wrong operations. Diagnostics are collected in `ir._diagnostics` as `{severity, file, line, col, message}` records.
This pass does not modify instructions — it only emits diagnostics.
**Errors** (compilation is aborted):
| Pattern | Message |
|---------|---------|
| `store_field` on T_ARRAY | storing named property on array |
| `store_index` on T_RECORD | storing numeric index on record |
| `store_field` / `store_index` on T_TEXT | storing property/index on text |
| `push` on T_TEXT / T_RECORD | push on text/record |
| `invoke` on T_NULL / T_INT / T_FLOAT / T_NUM / T_TEXT / T_BOOL / T_ARRAY | invoking null/number/text/bool/array |
| arity mismatch (module imports only) | function expects N arguments, got M |
**Warnings** (compilation continues):
| Pattern | Message |
|---------|---------|
| `load_field` on T_ARRAY | named property access on array |
| `load_field` on T_TEXT | named property access on text |
| `load_dynamic` with T_TEXT key on T_RECORD | text key on record |
| `load_dynamic` with T_RECORD / T_ARRAY / T_BOOL / T_NULL key on T_RECORD | record/array/bool/null key on record |
The engine (`internal/engine.cm`) prints all diagnostics and aborts compilation if any have severity `"error"`. Warnings are printed but do not block compilation.
**Nop prefix:** none (diagnostics only, does not modify instructions)
## Pass Composition ## Pass Composition
All passes run in sequence in `optimize_function`: All passes run in sequence in `optimize_function`:
@@ -189,8 +256,10 @@ eliminate_type_checks → uses param_types + write_types
simplify_algebra simplify_algebra
simplify_booleans simplify_booleans
eliminate_moves eliminate_moves
insert_stone_text → escape analysis for mutable text
eliminate_unreachable eliminate_unreachable
eliminate_dead_jumps eliminate_dead_jumps
diagnose_function → optional, when _warn is set
``` ```
Each pass is independent and can be commented out for testing or benchmarking. Each pass is independent and can be commented out for testing or benchmarking.
@@ -255,7 +324,9 @@ move 2, 7 // i = temp
subtract 2, 2, 6 // i = i - 1 (direct) subtract 2, 2, 6 // i = i - 1 (direct)
``` ```
The `+` operator is excluded from target slot propagation when it would use the full text+num dispatch (i.e., when neither operand is a known number), because writing both `concat` and `add` to the variable's slot would pollute its write type. When the known-number shortcut applies, `+` uses `emit_numeric_binop` and would be safe for target propagation, but this is not currently implemented — the exclusion is by operator kind, not by dispatch path. The `+` operator uses target slot propagation when the target slot equals the left operand (`target == left_slot`), i.e. for self-assign patterns like `s = s + x`. In this case both `concat` and `add` write to the same slot that already holds the left operand, so write-type pollution is acceptable — the value is being updated in place. For other cases (target differs from left operand), `+` still allocates a temp to avoid polluting the target slot's write type with both T_TEXT and T_NUM.
This enables the VM's in-place append fast path for string concatenation: when `concat dest, dest, src` has the same destination and left operand, the VM can append directly to a mutable text's excess capacity without allocating.
## Debugging Tools ## Debugging Tools
@@ -344,7 +415,7 @@ This was implemented and tested but causes a bootstrap failure during self-hosti
### Target Slot Propagation for Add with Known Numbers ### Target Slot Propagation for Add with Known Numbers
When the known-number add shortcut applies (one operand is a literal number), the generated code uses `emit_numeric_binop` which has a single write path. Target slot propagation should be safe in this case, but is currently blocked by the blanket `kind != "+"` exclusion. Refining the exclusion to check whether the shortcut will apply (by testing `is_known_number` on either operand) would enable direct writes for patterns like `i = i + 1`. When the known-number add shortcut applies (one operand is a literal number), the generated code uses `emit_numeric_binop` which has a single write path. Target slot propagation is already enabled for the self-assign case (`i = i + 1`), but when the target differs from the left operand and neither operand is a known number, a temp is still used. Refining the exclusion to check `is_known_number` would enable direct writes for the remaining non-self-assign cases like `j = i + 1`.
### Forward Type Narrowing from Typed Operations ### Forward Type Narrowing from Typed Operations

View File

@@ -192,6 +192,36 @@ Failures saved to tests/fuzz_failures/
Saved failure files are valid `.cm` modules that can be run directly or added to the test suite. Saved failure files are valid `.cm` modules that can be run directly or added to the test suite.
## Compile-Time Diagnostics Tests
The `tests/compile.cm` test suite verifies that the type checker catches provably wrong operations at compile time. It works by compiling source snippets through the pipeline with `_warn` enabled and checking that the expected diagnostics are emitted.
```javascript
var shop = use('internal/shop')
var streamline = use('streamline')
function get_diagnostics(src) {
fd.slurpwrite(tmpfile, stone(blob(src)))
var compiled = shop.mcode_file(tmpfile)
compiled._warn = true
var optimized = streamline(compiled)
if (optimized._diagnostics == null) return []
return optimized._diagnostics
}
```
The suite covers:
- **Store errors**: storing named property on array, numeric index on record, property/index on text, push on text/record
- **Invoke errors**: invoking null, number, text
- **Warnings**: named property access on array/text, record key on record
- **Clean code**: valid operations produce no diagnostics
Run the compile diagnostics tests with:
```bash
pit test compile
```
## Test File Organization ## Test File Organization
Tests live in the `tests/` directory of a package: Tests live in the `tests/` directory of a package:

View File

@@ -5,7 +5,7 @@ weight: 86
type: "docs" type: "docs"
--- ---
Wota is a binary message format for local inter-process communication. It is similar to Nota but works at word granularity (64-bit words) rather than byte granularity. Wota arrangements are less compact than Nota but faster to arrange and consume. Wota is a binary message format for local inter-process communication. It is similar to Nota but works at word granularity (64-bit words) rather than byte granularity. Wota arrangements are less compact than Nota but faster to arrange and consume. Wota is an internal module: `use('internal/wota')`.
Wota stands for Word Object Transfer Arrangement. Wota stands for Word Object Transfer Arrangement.

View File

@@ -8,6 +8,6 @@ var optimized = shop.compile_file(args[0])
var instrs = optimized.main.instructions var instrs = optimized.main.instructions
var i = 0 var i = 0
while (i < length(instrs)) { while (i < length(instrs)) {
print(text(i) + ': ' + json.encode(instrs[i])) log.compile(text(i) + ': ' + json.encode(instrs[i]))
i = i + 1 i = i + 1
} }

View File

@@ -86,8 +86,7 @@ if (mode == "span") {
if (result == null) { if (result == null) {
log.console("Nothing found at " + filename + ":" + text(line) + ":" + text(col)) log.console("Nothing found at " + filename + ":" + text(line) + ":" + text(col))
} else { } else {
print(json.encode(result, true)) log.compile(json.encode(result, true))
print("\n")
} }
} }
@@ -116,8 +115,7 @@ if (mode == "symbol") {
if (result == null || length(result.symbols) == 0) { if (result == null || length(result.symbols) == 0) {
log.console("Symbol '" + symbol_name + "' not found in " + filename) log.console("Symbol '" + symbol_name + "' not found in " + filename)
} else { } else {
print(json.encode(result, true)) log.compile(json.encode(result, true))
print("\n")
} }
} else if (length(files) > 1) { } else if (length(files) > 1) {
indexes = [] indexes = []
@@ -132,8 +130,7 @@ if (mode == "symbol") {
if (result == null || length(result.symbols) == 0) { if (result == null || length(result.symbols) == 0) {
log.console("Symbol '" + symbol_name + "' not found in " + text(length(files)) + " files") log.console("Symbol '" + symbol_name + "' not found in " + text(length(files)) + " files")
} else { } else {
print(json.encode(result, true)) log.compile(json.encode(result, true))
print("\n")
} }
} }
} }

22
fd.cm
View File

@@ -1,5 +1,5 @@
var fd = use('internal/fd') var fd = use('internal/fd')
var wildstar = use('wildstar') var wildstar = use('internal/wildstar')
function last_pos(str, sep) { function last_pos(str, sep) {
var last = null var last = null
@@ -97,4 +97,24 @@ fd.globfs = function(globs, dir) {
return results return results
} }
fd.ensure_dir = function ensure_dir(path) {
if (fd.is_dir(path)) return true
var parts = array(path, '/')
var current = starts_with(path, '/') ? '/' : ''
var i = 0
for (i = 0; i < length(parts); i++) {
if (parts[i] == '') continue
current = current + parts[i] + '/'
if (!fd.is_dir(current))
fd.mkdir(current)
}
return true
}
fd.safe_package_path = function safe_package_path(pkg) {
if (pkg && starts_with(pkg, '/'))
return replace(replace(pkg, '/', '_'), '@', '_')
return replace(pkg, '@', '_')
}
return fd return fd

113
fetch.ce
View File

@@ -1,90 +1,49 @@
// cell fetch - Fetch package zips from remote sources // cell fetch - Sync packages from remote sources
// //
// This command ensures that the zip files on disk match what's in the lock file. // Ensures all packages are fetched, extracted, compiled, and ready to use.
// For local packages, this is a no-op. // For local packages, this is a no-op (symlinks only).
// For remote packages, downloads the zip if not present or hash mismatch.
// //
// Usage: // Usage:
// cell fetch - Fetch all packages // cell fetch - Sync all packages
// cell fetch <package> - Fetch a specific package // cell fetch <package> - Sync a specific package
var shop = use('internal/shop') var shop = use('internal/shop')
// Parse arguments
var target_pkg = null var target_pkg = null
var i = 0 var i = 0
var packages = null
var count = 0
for (i = 0; i < length(args); i++) { var run = function() {
if (args[i] == '--help' || args[i] == '-h') { for (i = 0; i < length(args); i++) {
log.console("Usage: cell fetch [package]") if (args[i] == '--help' || args[i] == '-h') {
log.console("Fetch package zips from remote sources.") log.console("Usage: cell fetch [package]")
log.console("") log.console("Sync packages from remote sources.")
log.console("Arguments:") log.console("")
log.console(" package Optional package name to fetch. If omitted, fetches all.") log.console("Arguments:")
log.console("") log.console(" package Optional package to sync. If omitted, syncs all.")
log.console("This command ensures that the zip files on disk match what's in") return
log.console("the lock file. For local packages, this is a no-op.") } else if (!starts_with(args[i], '-')) {
$stop() target_pkg = args[i]
} else if (!starts_with(args[i], '-')) { }
target_pkg = args[i] }
if (target_pkg) {
target_pkg = shop.resolve_locator(target_pkg)
log.console("Syncing " + target_pkg + "...")
shop.sync(target_pkg)
log.console("Done.")
} else {
packages = shop.list_packages()
count = 0
arrfor(packages, function(pkg) {
if (pkg == 'core') return
shop.sync(pkg)
count = count + 1
})
log.console("Synced " + text(count) + " package(s).")
} }
} }
run()
var all_packages = shop.list_packages()
var lock = shop.load_lock()
var packages_to_fetch = []
if (target_pkg) {
// Fetch specific package
if (find(all_packages, target_pkg) == null) {
log.error("Package not found: " + target_pkg)
$stop()
}
push(packages_to_fetch, target_pkg)
} else {
// Fetch all packages
packages_to_fetch = all_packages
}
var remote_count = 0
arrfor(packages_to_fetch, function(pkg) {
var entry = lock[pkg]
if (pkg != 'core' && (!entry || entry.type != 'local'))
remote_count++
}, null, null)
if (remote_count > 0)
log.console(`Fetching ${text(remote_count)} remote package(s)...`)
var downloaded_count = 0
var cached_count = 0
var fail_count = 0
arrfor(packages_to_fetch, function(pkg) {
// Skip core (handled separately)
if (pkg == 'core') return
var result = shop.fetch(pkg)
if (result.status == 'local') {
// Local packages are just symlinks, nothing to fetch
return
} else if (result.status == 'cached') {
cached_count++
} else if (result.status == 'downloaded') {
log.console(" Downloaded: " + pkg)
downloaded_count++
} else if (result.status == 'error') {
log.error(" Failed: " + pkg + (result.message ? " - " + result.message : ""))
fail_count++
}
}, null, null)
log.console("")
var parts = []
if (downloaded_count > 0) push(parts, `${text(downloaded_count)} downloaded`)
if (cached_count > 0) push(parts, `${text(cached_count)} cached`)
if (fail_count > 0) push(parts, `${text(fail_count)} failed`)
if (length(parts) == 0) push(parts, "nothing to fetch")
log.console("Fetch complete: " + text(parts, ", "))
$stop() $stop()

View File

@@ -4,4 +4,4 @@ var json = use("json")
var shop = use("internal/shop") var shop = use("internal/shop")
var filename = args[0] var filename = args[0]
var folded = shop.analyze_file(filename) var folded = shop.analyze_file(filename)
print(json.encode(folded)) log.compile(json.encode(folded))

53
fold.cm
View File

@@ -4,6 +4,7 @@
var fold = function(ast) { var fold = function(ast) {
var scopes = ast.scopes var scopes = ast.scopes
var nr_scopes = length(scopes) var nr_scopes = length(scopes)
ast._diagnostics = []
var type_tag_map = { var type_tag_map = {
array: "array", record: "record", text: "text", array: "array", record: "record", text: "text",
@@ -72,6 +73,7 @@ var fold = function(ast) {
if (k == "record") { if (k == "record") {
i = 0 i = 0
while (i < length(expr.list)) { while (i < length(expr.list)) {
if (expr.list[i].computed && !is_pure(expr.list[i].left)) return false
if (!is_pure(expr.list[i].right)) return false if (!is_pure(expr.list[i].right)) return false
i = i + 1 i = i + 1
} }
@@ -285,6 +287,7 @@ var fold = function(ast) {
if (k == "record") { if (k == "record") {
i = 0 i = 0
while (i < length(expr.list)) { while (i < length(expr.list)) {
if (expr.list[i].computed) pre_scan_expr_fns(expr.list[i].left)
pre_scan_expr_fns(expr.list[i].right) pre_scan_expr_fns(expr.list[i].right)
i = i + 1 i = i + 1
} }
@@ -411,6 +414,9 @@ var fold = function(ast) {
} else if (k == "record") { } else if (k == "record") {
i = 0 i = 0
while (i < length(expr.list)) { while (i < length(expr.list)) {
if (expr.list[i].computed) {
expr.list[i].left = fold_expr(expr.list[i].left, fn_nr)
}
expr.list[i].right = fold_expr(expr.list[i].right, fn_nr) expr.list[i].right = fold_expr(expr.list[i].right, fn_nr)
i = i + 1 i = i + 1
} }
@@ -701,8 +707,30 @@ var fold = function(ast) {
name = stmt.left.name name = stmt.left.name
if (name != null) { if (name != null) {
sv = scope_var(fn_nr, name) sv = scope_var(fn_nr, name)
if (sv != null && sv.nr_uses == 0 && is_pure(stmt.right)) { if (sv != null && sv.nr_uses == 0) {
stmt.dead = true if (is_pure(stmt.right)) stmt.dead = true
if (stmt.right != null && stmt.right.kind == "(" && stmt.right.expression != null && stmt.right.expression.name == "use") {
push(ast._diagnostics, {
severity: "warning",
line: stmt.left.from_row + 1,
col: stmt.left.from_column + 1,
message: `unused import '${name}'`
})
} else if (stmt.kind == "def") {
push(ast._diagnostics, {
severity: "warning",
line: stmt.left.from_row + 1,
col: stmt.left.from_column + 1,
message: `unused constant '${name}'`
})
} else {
push(ast._diagnostics, {
severity: "warning",
line: stmt.left.from_row + 1,
col: stmt.left.from_column + 1,
message: `unused variable '${name}'`
})
}
} }
} }
} }
@@ -715,6 +743,12 @@ var fold = function(ast) {
sv = scope_var(fn_nr, stmt.name) sv = scope_var(fn_nr, stmt.name)
if (sv != null && sv.nr_uses == 0) { if (sv != null && sv.nr_uses == 0) {
stmt.dead = true stmt.dead = true
push(ast._diagnostics, {
severity: "warning",
line: stmt.from_row + 1,
col: stmt.from_column + 1,
message: `unused function '${stmt.name}'`
})
} }
} }
if (stmt.dead != true) push(out, stmt) if (stmt.dead != true) push(out, stmt)
@@ -831,6 +865,7 @@ var fold = function(ast) {
if (k == "record") { if (k == "record") {
i = 0 i = 0
while (i < length(expr.list)) { while (i < length(expr.list)) {
if (expr.list[i].computed) walk_expr_for_fns(expr.list[i].left)
walk_expr_for_fns(expr.list[i].right) walk_expr_for_fns(expr.list[i].right)
i = i + 1 i = i + 1
} }
@@ -920,6 +955,7 @@ var fold = function(ast) {
if (k == "record") { if (k == "record") {
i = 0 i = 0
while (i < length(expr.list)) { while (i < length(expr.list)) {
if (expr.list[i].computed) collect_expr_intrinsics(expr.list[i].left)
collect_expr_intrinsics(expr.list[i].right) collect_expr_intrinsics(expr.list[i].right)
i = i + 1 i = i + 1
} }
@@ -1028,9 +1064,22 @@ var fold = function(ast) {
// Remove dead top-level functions // Remove dead top-level functions
var live_fns = [] var live_fns = []
var fn = null var fn = null
var fn_sv = null
fi = 0 fi = 0
while (fi < length(ast.functions)) { while (fi < length(ast.functions)) {
fn = ast.functions[fi] fn = ast.functions[fi]
if (fn.name != null) {
fn_sv = scope_var(0, fn.name)
if (fn_sv != null && fn_sv.nr_uses == 0) {
fn.dead = true
push(ast._diagnostics, {
severity: "warning",
line: fn.from_row + 1,
col: fn.from_column + 1,
message: `unused function '${fn.name}'`
})
}
}
if (fn.dead != true) { if (fn.dead != true) {
push(live_fns, fn) push(live_fns, fn)
} }

48
fuzz.ce
View File

@@ -12,8 +12,9 @@
var fd = use('fd') var fd = use('fd')
var time = use('time') var time = use('time')
var json = use('json') var json = use('json')
var testlib = use('internal/testlib')
var os_ref = use('os') var os_ref = use('internal/os')
var analyze = os_ref.analyze var analyze = os_ref.analyze
var run_ast_fn = os_ref.run_ast_fn var run_ast_fn = os_ref.run_ast_fn
var run_ast_noopt_fn = os_ref.run_ast_noopt_fn var run_ast_noopt_fn = os_ref.run_ast_noopt_fn
@@ -54,48 +55,9 @@ if (!run_ast_noopt_fn) {
// Ensure failures directory exists // Ensure failures directory exists
var failures_dir = "tests/fuzz_failures" var failures_dir = "tests/fuzz_failures"
function ensure_dir(path) { var ensure_dir = fd.ensure_dir
if (fd.is_dir(path)) return var values_equal = testlib.values_equal
var parts = array(path, '/') var describe = testlib.describe
var current = ''
var j = 0
while (j < length(parts)) {
if (parts[j] != '') {
current = current + parts[j] + '/'
if (!fd.is_dir(current)) {
fd.mkdir(current)
}
}
j = j + 1
}
}
// Deep comparison
function values_equal(a, b) {
var j = 0
if (a == b) return true
if (is_null(a) && is_null(b)) return true
if (is_null(a) || is_null(b)) return false
if (is_array(a) && is_array(b)) {
if (length(a) != length(b)) return false
j = 0
while (j < length(a)) {
if (!values_equal(a[j], b[j])) return false
j = j + 1
}
return true
}
return false
}
function describe(val) {
if (is_null(val)) return "null"
if (is_text(val)) return `"${val}"`
if (is_number(val)) return text(val)
if (is_logical(val)) return text(val)
if (is_function(val)) return "<function>"
return "<other>"
}
// Run a single fuzz iteration // Run a single fuzz iteration
function run_fuzz(seed_val) { function run_fuzz(seed_val) {

View File

@@ -15,7 +15,6 @@
var shop = use('internal/shop') var shop = use('internal/shop')
var pkg = use('package') var pkg = use('package')
var link = use('link') var link = use('link')
var fd = use('fd')
var json = use('json') var json = use('json')
var target_locator = null var target_locator = null
@@ -23,41 +22,41 @@ var format = 'tree'
var show_locked = false var show_locked = false
var show_world = false var show_world = false
var i = 0 var i = 0
var resolved = null
for (i = 0; i < length(args); i++) { var run = function() {
if (args[i] == '--format' || args[i] == '-f') { for (i = 0; i < length(args); i++) {
if (i + 1 < length(args)) { if (args[i] == '--format' || args[i] == '-f') {
format = args[++i] if (i + 1 < length(args)) {
if (format != 'tree' && format != 'dot' && format != 'json') { format = args[++i]
log.error('Invalid format: ' + format + '. Must be tree, dot, or json') if (format != 'tree' && format != 'dot' && format != 'json') {
$stop() log.error('Invalid format: ' + format + '. Must be tree, dot, or json')
return
}
} else {
log.error('--format requires a format type')
return
} }
} else { } else if (args[i] == '--resolved') {
log.error('--format requires a format type') show_locked = false
$stop() } else if (args[i] == '--locked') {
show_locked = true
} else if (args[i] == '--world') {
show_world = true
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell graph [<locator>] [options]")
log.console("")
log.console("Emit the dependency graph.")
log.console("")
log.console("Options:")
log.console(" --format <fmt> Output format: tree (default), dot, json")
log.console(" --resolved Show resolved view with links applied (default)")
log.console(" --locked Show lock view without links")
log.console(" --world Graph all packages in shop")
return
} else if (!starts_with(args[i], '-')) {
target_locator = args[i]
} }
} else if (args[i] == '--resolved') {
show_locked = false
} else if (args[i] == '--locked') {
show_locked = true
} else if (args[i] == '--world') {
show_world = true
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell graph [<locator>] [options]")
log.console("")
log.console("Emit the dependency graph.")
log.console("")
log.console("Options:")
log.console(" --format <fmt> Output format: tree (default), dot, json")
log.console(" --resolved Show resolved view with links applied (default)")
log.console(" --locked Show lock view without links")
log.console(" --world Graph all packages in shop")
$stop()
} else if (!starts_with(args[i], '-')) {
target_locator = args[i]
} }
}
var links = show_locked ? {} : link.load() var links = show_locked ? {} : link.load()
@@ -127,13 +126,7 @@ if (show_world) {
target_locator = '.' target_locator = '.'
} }
// Resolve local paths target_locator = shop.resolve_locator(target_locator)
if (target_locator == '.' || starts_with(target_locator, './') || starts_with(target_locator, '../') || fd.is_dir(target_locator)) {
resolved = fd.realpath(target_locator)
if (resolved) {
target_locator = resolved
}
}
push(roots, target_locator) push(roots, target_locator)
} }
@@ -244,5 +237,7 @@ if (format == 'tree') {
log.console(json.encode(output)) log.console(json.encode(output))
} }
}
run()
$stop() $stop()

189
imports.ce Normal file
View File

@@ -0,0 +1,189 @@
// cell imports [<file>] - Trace module-level imports
//
// Usage:
// cell imports Trace imports for current package entry
// cell imports <file.ce> Trace imports starting from a .ce or .cm file
// cell imports <file.cm> Trace imports starting from a .cm file
//
// Options:
// --flat Flat list instead of tree
// --packages Only show unique packages, not individual modules
var shop = use('internal/shop')
var pkg = use('package')
var fd = use('fd')
var target = null
var flat_mode = false
var packages_only = false
var i = 0
var pkg_dir = null
var config = null
var target_path = null
for (i = 0; i < length(args); i++) {
if (args[i] == '--flat') {
flat_mode = true
} else if (args[i] == '--packages') {
packages_only = true
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell imports [<file>] [options]")
log.console("")
log.console("Trace module-level imports across packages.")
log.console("")
log.console("Options:")
log.console(" --flat Flat list instead of tree")
log.console(" --packages Only show unique packages, not individual modules")
$stop()
} else if (!starts_with(args[i], '-')) {
target = args[i]
}
}
// Resolve target file
if (target) {
if (!ends_with(target, '.ce') && !ends_with(target, '.cm'))
target = target + '.ce'
if (fd.is_file(target))
target_path = fd.realpath(target)
else {
pkg_dir = pkg.find_package_dir('.')
if (pkg_dir)
target_path = pkg_dir + '/' + target
}
} else {
pkg_dir = pkg.find_package_dir('.')
if (pkg_dir) {
config = pkg.load_config(null)
if (config.entry)
target_path = pkg_dir + '/' + config.entry
}
}
if (!target_path || !fd.is_file(target_path)) {
log.error('Could not find file: ' + (target || '(no target specified)'))
$stop()
}
// Collect all imports recursively
var visited = {}
var all_imports = []
var all_packages = {}
function trace_imports(file_path, depth) {
if (visited[file_path]) return
visited[file_path] = true
var fi = shop.file_info(file_path)
var file_pkg = fi.package || '(local)'
var idx = null
var j = 0
var imp = null
var mod_path = null
var resolved = null
var imp_pkg = null
var imp_type = null
var rinfo = null
all_packages[file_pkg] = true
var _trace = function() {
idx = shop.index_file(file_path)
if (!idx || !idx.imports) return
j = 0
while (j < length(idx.imports)) {
imp = idx.imports[j]
mod_path = imp.module_path
resolved = null
imp_pkg = '?'
imp_type = 'unresolved'
// Use the full resolver that checks .cm files, C symbols, and aliases
rinfo = shop.resolve_import_info(mod_path, file_pkg)
if (rinfo) {
resolved = rinfo.resolved_path
imp_pkg = rinfo.package || '?'
imp_type = rinfo.type
}
all_packages[imp_pkg] = true
push(all_imports, {
from: file_path,
from_pkg: file_pkg,
module_path: mod_path,
resolved_path: resolved,
package: imp_pkg,
type: imp_type,
depth: depth
})
// Recurse into resolved scripts
if (resolved && (ends_with(resolved, '.cm') || ends_with(resolved, '.ce'))) {
trace_imports(resolved, depth + 1)
}
j = j + 1
}
} disruption {
// File might fail to parse/index
}
_trace()
}
trace_imports(target_path, 0)
// Output results
var fi2 = null
if (packages_only) {
log.console("Packages used by " + target_path + ":")
log.console("")
arrfor(array(all_packages), function(p) {
log.console(" " + p)
})
} else if (flat_mode) {
log.console("Imports from " + target_path + ":")
log.console("")
arrfor(all_imports, function(imp) {
var suffix = ''
if (imp.type == 'native') suffix = ' [native]'
if (imp.type == 'unresolved') suffix = ' [unresolved]'
log.console(" " + imp.module_path + " -> " + imp.package + suffix)
})
} else {
// Tree output
function print_tree(file_path, prefix, is_last) {
var children = filter(all_imports, function(imp) {
return imp.from == file_path
})
var j = 0
var imp = null
var last = false
var connector = null
var suffix = null
var child_prefix = null
while (j < length(children)) {
imp = children[j]
last = (j == length(children) - 1)
connector = last ? "\\-- " : "|-- "
suffix = " (" + imp.package + ")"
if (imp.type == 'native') suffix = suffix + " [native]"
if (imp.type == 'unresolved') suffix = suffix + " [unresolved]"
log.console(prefix + connector + imp.module_path + suffix)
if (imp.resolved_path) {
child_prefix = prefix + (last ? " " : "| ")
print_tree(imp.resolved_path, child_prefix, last)
}
j = j + 1
}
}
fi2 = shop.file_info(target_path)
log.console(target_path + " (" + (fi2.package || 'local') + ")")
print_tree(target_path, "", true)
}
$stop()

View File

@@ -52,8 +52,7 @@ if (output_path != null) {
fd.slurpwrite(output_path, out) fd.slurpwrite(output_path, out)
log.console('Wrote index to ' + output_path) log.console('Wrote index to ' + output_path)
} else { } else {
print(out) log.compile(out)
print("\n")
} }
$stop() $stop()

View File

@@ -201,7 +201,9 @@ var index_ast = function(ast, tokens, filename) {
if (node.expression.left != null && node.expression.left.kind == "name") { if (node.expression.left != null && node.expression.left.kind == "name") {
callee_name = node.expression.left.name callee_name = node.expression.left.name
} }
if (node.expression.right != null && node.expression.right.name != null) { if (is_text(node.expression.right)) {
callee_name = (callee_name != null ? callee_name + "." : "") + node.expression.right
} else if (node.expression.right != null && node.expression.right.name != null) {
callee_name = (callee_name != null ? callee_name + "." : "") + node.expression.right.name callee_name = (callee_name != null ? callee_name + "." : "") + node.expression.right.name
} }
} }

View File

@@ -1,4 +1,4 @@
// cell install <locator> - Install a package to the shop // cell install <locator> - Install a package and its dependencies
// //
// Usage: // Usage:
// cell install <locator> Install a package and its dependencies // cell install <locator> Install a package and its dependencies
@@ -6,266 +6,113 @@
// //
// Options: // Options:
// --target <triple> Build for target platform // --target <triple> Build for target platform
// --refresh Refresh floating refs before locking
// --dry-run Show what would be installed // --dry-run Show what would be installed
// -r Recursively find and install all packages in directory // -r Recursively find and install all packages in directory
var shop = use('internal/shop') var shop = use('internal/shop')
var build = use('build')
var pkg = use('package') var pkg = use('package')
var fd = use('fd') var fd = use('fd')
if (length(args) < 1) {
log.console("Usage: cell install <locator> [options]")
log.console("")
log.console("Options:")
log.console(" --target <triple> Build for target platform")
log.console(" --refresh Refresh floating refs before locking")
log.console(" --dry-run Show what would be installed")
log.console(" -r Recursively find and install all packages in directory")
$stop()
}
var locator = null var locator = null
var target_triple = null var target_triple = null
var refresh = false
var dry_run = false var dry_run = false
var recursive = false var recursive = false
var i = 0 var i = 0
var resolved = null
var locators = null var locators = null
var cwd = fd.realpath('.')
var lock = null
var installed = 0
var failed = 0
for (i = 0; i < length(args); i++) { var run = function() {
if (args[i] == '--target' || args[i] == '-t') { for (i = 0; i < length(args); i++) {
if (i + 1 < length(args)) { if (args[i] == '--target' || args[i] == '-t') {
target_triple = args[++i] if (i + 1 < length(args)) {
} else { target_triple = args[++i]
log.error('--target requires a triple') } else {
$stop() log.error('--target requires a triple')
} return
} else if (args[i] == '--refresh') {
refresh = true
} else if (args[i] == '--dry-run') {
dry_run = true
} else if (args[i] == '-r') {
recursive = true
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell install <locator> [options]")
log.console("")
log.console("Install a package and its dependencies to the shop.")
log.console("")
log.console("Options:")
log.console(" --target <triple> Build for target platform")
log.console(" --refresh Refresh floating refs before locking")
log.console(" --dry-run Show what would be installed")
log.console(" -r Recursively find and install all packages in directory")
$stop()
} else if (!starts_with(args[i], '-')) {
locator = args[i]
}
}
if (!locator && !recursive) {
log.console("Usage: cell install <locator>")
$stop()
}
// Resolve relative paths to absolute paths
// Local paths like '.' or '../foo' need to be converted to absolute paths
if (locator && (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator))) {
resolved = fd.realpath(locator)
if (resolved) {
locator = resolved
}
}
// Recursively find all cell packages in a directory
function find_packages(dir) {
var found = []
var list = fd.readdir(dir)
if (!list) return found
if (fd.is_file(dir + '/cell.toml')) {
push(found, dir)
}
arrfor(list, function(item) {
if (item == '.' || item == '..' || item == '.cell' || item == '.git') return
var full = dir + '/' + item
var st = fd.stat(full)
var sub = null
if (st && st.isDirectory) {
sub = find_packages(full)
arrfor(sub, function(p) {
push(found, p)
})
}
})
return found
}
// If -r flag, find all packages recursively and install each
if (recursive) {
if (!locator) {
locator = '.'
}
resolved = fd.realpath(locator)
if (!resolved || !fd.is_dir(resolved)) {
log.error(`${locator} is not a directory`)
$stop()
}
locators = find_packages(resolved)
if (length(locators) == 0) {
log.console("No packages found in " + resolved)
$stop()
}
log.console(`Found ${text(length(locators))} package(s) in ${resolved}`)
}
// Default target
if (!target_triple) {
target_triple = build.detect_host_target()
}
// Gather all packages that will be installed
var packages_to_install = []
var skipped_packages = []
var summary = null
var visited = {}
// Recursive mode: install all found packages and exit
if (recursive) {
arrfor(locators, function(loc) {
log.console(" Installing " + loc + "...")
var _inst = function() {
shop.update(loc)
shop.extract(loc)
shop.build_package_scripts(loc)
var _build_c = function() {
build.build_dynamic(loc, target_triple, 'release')
} disruption {
// Not all packages have C code
} }
_build_c() } else if (args[i] == '--dry-run') {
push(packages_to_install, loc) dry_run = true
} disruption { } else if (args[i] == '-r') {
push(skipped_packages, loc) recursive = true
log.console(` Warning: Failed to install ${loc}`) } else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell install <locator> [options]")
log.console("")
log.console("Install a package and its dependencies.")
log.console("")
log.console("Options:")
log.console(" --target <triple> Build for target platform")
log.console(" --dry-run Show what would be installed")
log.console(" -r Recursively find and install all packages in directory")
return
} else if (!starts_with(args[i], '-')) {
locator = args[i]
} }
_inst()
})
summary = "Installed " + text(length(packages_to_install)) + " package(s)."
if (length(skipped_packages) > 0) {
summary += " Failed: " + text(length(skipped_packages)) + "."
} }
log.console(summary)
$stop()
}
log.console("Installing " + locator + "...") if (!locator && !recursive) {
log.console("Usage: cell install <locator> [options]")
function gather_packages(pkg_locator) {
var lock = null
var update_result = null
var deps = null
if (visited[pkg_locator]) return
visited[pkg_locator] = true
// Check if this is a local path that doesn't exist
if (starts_with(pkg_locator, '/') && !fd.is_dir(pkg_locator)) {
push(skipped_packages, pkg_locator)
log.console(" Skipping missing local package: " + pkg_locator)
return return
} }
push(packages_to_install, pkg_locator) if (locator)
locator = shop.resolve_locator(locator)
// Try to read dependencies // Recursive mode: find all packages in directory and install each
var _gather = function() { if (recursive) {
// For packages not yet extracted, we need to update and extract first to read deps if (!locator) locator = '.'
lock = shop.load_lock() locator = shop.resolve_locator(locator)
if (!lock[pkg_locator]) { if (!fd.is_dir(locator)) {
if (!dry_run) { log.error(`${locator} is not a directory`)
update_result = shop.update(pkg_locator) return
if (update_result) {
shop.extract(pkg_locator)
} else {
// Update failed - package might not be fetchable
log.console("Warning: Could not fetch " + pkg_locator)
return
}
}
} else {
// Package is in lock, ensure it's extracted
if (!dry_run) {
shop.extract(pkg_locator)
}
}
deps = pkg.dependencies(pkg_locator)
if (deps) {
arrfor(array(deps), function(alias) {
var dep_locator = deps[alias]
gather_packages(dep_locator)
})
}
} disruption {
// Package might not have dependencies or cell.toml issue
if (!dry_run) {
log.console(`Warning: Could not read dependencies for ${pkg_locator}`)
}
} }
_gather() locators = filter(pkg.find_packages(locator), function(p) {
} return p != cwd
// Gather all packages
gather_packages(locator)
if (dry_run) {
log.console("Would install:")
arrfor(packages_to_install, function(p) {
var lock = shop.load_lock()
var exists = lock[p] != null
log.console(" " + p + (exists ? " (already installed)" : ""))
}) })
if (length(skipped_packages) > 0) { if (length(locators) == 0) {
log.console("") log.console("No packages found in " + locator)
log.console("Would skip (missing local paths):") return
arrfor(skipped_packages, function(p) { }
log.console(" " + p) log.console(`Found ${text(length(locators))} package(s) in ${locator}`)
if (dry_run) {
log.console("Would install:")
arrfor(locators, function(loc) {
lock = shop.load_lock()
log.console(" " + loc + (lock[loc] ? " (already installed)" : ""))
}) })
} else {
installed = 0
failed = 0
arrfor(locators, function(loc) {
log.console(" Installing " + loc + "...")
var _inst = function() {
shop.sync(loc, {target: target_triple})
installed = installed + 1
} disruption {
failed = failed + 1
log.console(` Warning: Failed to install ${loc}`)
}
_inst()
})
log.console("Installed " + text(installed) + " package(s)." + (failed > 0 ? " Failed: " + text(failed) + "." : ""))
} }
$stop() return
} }
// Install each package // Single package install with dependencies
function install_package(pkg_locator) { if (dry_run) {
// Update lock entry log.console("Would install: " + locator + " (and dependencies)")
shop.update(pkg_locator) return
// Extract/symlink the package
shop.extract(pkg_locator)
// Build scripts
shop.build_package_scripts(pkg_locator)
// Build C code
var _build_c = function() {
build.build_dynamic(pkg_locator, target_triple, 'release')
} disruption {
// Not all packages have C code
} }
_build_c()
}
arrfor(packages_to_install, function(p) { log.console("Installing " + locator + "...")
log.console(" Installing " + p + "...") shop.sync_with_deps(locator, {refresh: true, target: target_triple})
install_package(p) log.console("Done.")
})
summary = "Installed " + text(length(packages_to_install)) + " package(s)."
if (length(skipped_packages) > 0) {
summary += " Skipped " + text(length(skipped_packages)) + " missing local path(s)."
} }
log.console(summary) run()
$stop() $stop()

View File

@@ -8,7 +8,7 @@ function use_embed(name) {
var fd = use_embed('internal_fd') var fd = use_embed('internal_fd')
var json_mod = use_embed('json') var json_mod = use_embed('json')
var crypto = use_embed('crypto') var crypto = use_embed('internal_crypto')
function content_hash(content) { function content_hash(content) {
var data = content var data = content
@@ -34,7 +34,7 @@ function boot_load(name) {
var mcode_blob = null var mcode_blob = null
var mach_blob = null var mach_blob = null
if (!fd.is_file(mcode_path)) { if (!fd.is_file(mcode_path)) {
print("error: missing seed: " + name + "\n") os.print("error: missing seed: " + name + "\n")
disrupt disrupt
} }
mcode_blob = fd.slurp(mcode_path) mcode_blob = fd.slurp(mcode_path)
@@ -60,9 +60,9 @@ function analyze(src, filename) {
e = ast.errors[_i] e = ast.errors[_i]
msg = e.message msg = e.message
if (e.line != null && e.column != null) if (e.line != null && e.column != null)
print(`${filename}:${text(e.line)}:${text(e.column)}: error: ${msg}`) os.print(`${filename}:${text(e.line)}:${text(e.column)}: error: ${msg}\n`)
else else
print(`${filename}: error: ${msg}`) os.print(`${filename}: error: ${msg}\n`)
_i = _i + 1 _i = _i + 1
} }
disrupt disrupt
@@ -105,4 +105,4 @@ while (_i < length(seed_files)) {
compile_and_cache(entry.name, core_path + '/' + entry.path) compile_and_cache(entry.name, core_path + '/' + entry.path)
_i = _i + 1 _i = _i + 1
} }
print("bootstrap: cache seeded\n") os.print("bootstrap: cache seeded\n")

View File

@@ -56,7 +56,7 @@
static void *get_blob_check_bits(JSContext *js, JSValue val, size_t expected_bits, const char *name) { static void *get_blob_check_bits(JSContext *js, JSValue val, size_t expected_bits, const char *name) {
size_t bits; size_t bits;
void* result = js_get_blob_data_bits(js, &bits, val); void* result = js_get_blob_data_bits(js, &bits, val);
if (result == -1) { if (result == (void *)-1) {
return NULL; // Exception already thrown by js_get_blob_data_bits return NULL; // Exception already thrown by js_get_blob_data_bits
} }
@@ -70,7 +70,7 @@ static void *get_blob_check_bits(JSContext *js, JSValue val, size_t expected_bit
// Helper to get any blob data (checking it is a stoned blob) // Helper to get any blob data (checking it is a stoned blob)
static void *get_blob_any(JSContext *js, JSValue val, size_t *out_bits, const char *name) { static void *get_blob_any(JSContext *js, JSValue val, size_t *out_bits, const char *name) {
void *result = js_get_blob_data_bits(js, out_bits, val); void *result = js_get_blob_data_bits(js, out_bits, val);
if (result == -1) if (result == (void *)-1)
return NULL; return NULL;
return result; return result;
} }
@@ -238,7 +238,7 @@ static const JSCFunctionListEntry js_crypto_funcs[] = {
JS_CFUNC_DEF("unlock", 3, js_crypto_unlock), JS_CFUNC_DEF("unlock", 3, js_crypto_unlock),
}; };
JSValue js_core_crypto_use(JSContext *js) JSValue js_core_internal_crypto_use(JSContext *js)
{ {
JS_FRAME(js); JS_FRAME(js);
JS_ROOT(mod, JS_NewObject(js)); JS_ROOT(mod, JS_NewObject(js));

View File

@@ -25,7 +25,7 @@ function use_embed(name) {
var fd = use_embed('internal_fd') var fd = use_embed('internal_fd')
var js = use_embed('js') var js = use_embed('js')
var crypto = use_embed('crypto') var crypto = use_embed('internal_crypto')
// core_path and shop_path come from env (C runtime passes them through) // core_path and shop_path come from env (C runtime passes them through)
// shop_path may be null if --core was used without --shop // shop_path may be null if --core was used without --shop
@@ -58,7 +58,7 @@ function boot_load(name) {
var mcode_blob = null var mcode_blob = null
var mach_blob = null var mach_blob = null
if (!fd.is_file(mcode_path)) { if (!fd.is_file(mcode_path)) {
print("error: missing boot seed: " + name + "\n") os.print("error: missing boot seed: " + name + "\n")
disrupt disrupt
} }
mcode_blob = fd.slurp(mcode_path) mcode_blob = fd.slurp(mcode_path)
@@ -103,7 +103,7 @@ function load_pipeline_module(name, env) {
tok_result = boot_tok(src, source_path) tok_result = boot_tok(src, source_path)
ast = boot_par(tok_result.tokens, src, source_path, boot_tok) ast = boot_par(tok_result.tokens, src, source_path, boot_tok)
if (ast.errors != null && length(ast.errors) > 0) { if (ast.errors != null && length(ast.errors) > 0) {
print("error: failed to compile pipeline module: " + name + "\n") os.print("error: failed to compile pipeline module: " + name + "\n")
disrupt disrupt
} }
ast = boot_fld(ast) ast = boot_fld(ast)
@@ -126,7 +126,7 @@ function load_pipeline_module(name, env) {
mach_blob = mach_compile_mcode_bin(name, text(mcode_blob)) mach_blob = mach_compile_mcode_bin(name, text(mcode_blob))
return mach_load(mach_blob, env) return mach_load(mach_blob, env)
} }
print("error: cannot load pipeline module: " + name + "\n") os.print("error: cannot load pipeline module: " + name + "\n")
disrupt disrupt
} }
@@ -158,6 +158,7 @@ function analyze(src, filename) {
var line = null var line = null
var col = null var col = null
var has_errors = _ast.errors != null && length(_ast.errors) > 0 var has_errors = _ast.errors != null && length(_ast.errors) > 0
var folded = null
if (has_errors) { if (has_errors) {
while (_i < length(_ast.errors)) { while (_i < length(_ast.errors)) {
e = _ast.errors[_i] e = _ast.errors[_i]
@@ -166,9 +167,9 @@ function analyze(src, filename) {
col = e.column col = e.column
if (msg != prev_msg || line != prev_line) { if (msg != prev_msg || line != prev_line) {
if (line != null && col != null) if (line != null && col != null)
print(`${filename}:${text(line)}:${text(col)}: error: ${msg}`) os.print(`${filename}:${text(line)}:${text(col)}: error: ${msg}\n`)
else else
print(`${filename}: error: ${msg}`) os.print(`${filename}: error: ${msg}\n`)
} }
prev_line = line prev_line = line
prev_msg = msg prev_msg = msg
@@ -176,15 +177,126 @@ function analyze(src, filename) {
} }
disrupt disrupt
} }
return fold_mod(_ast) folded = fold_mod(_ast)
if (!_no_warn && folded._diagnostics != null && length(folded._diagnostics) > 0) {
_i = 0
while (_i < length(folded._diagnostics)) {
e = folded._diagnostics[_i]
os.print(`${filename}:${text(e.line)}:${text(e.col)}: ${e.severity}: ${e.message}\n`)
_i = _i + 1
}
}
folded._diagnostics = null
return folded
} }
// Lazy-loaded verify_ir module (loaded on first use) // Lazy-loaded verify_ir module (loaded on first use)
var _verify_ir_mod = null var _verify_ir_mod = null
// Module summary extraction for cross-program analysis.
// Scans mcode IR for use() call patterns and attaches summaries.
// _summary_resolver is set after shop loads (null during bootstrap).
var _summary_resolver = null
function extract_module_summaries(compiled, ctx) {
if (_summary_resolver == null) return null
var instrs = null
var summaries = []
var unresolved = []
var i = 0
var j = 0
var n = 0
var instr = null
var prev = null
var op = null
var use_slots = {}
var frame_map = {}
var arg_map = {}
var val_slot = 0
var f_slot = 0
var path = null
var result_slot = 0
var summary = null
var inv_n = 0
if (compiled.main == null) return null
instrs = compiled.main.instructions
if (instrs == null) return null
n = length(instrs)
// Pass 1: find access(slot, {make:"intrinsic", name:"use"})
i = 0
while (i < n) {
instr = instrs[i]
if (is_array(instr) && instr[0] == "access") {
if (is_object(instr[2]) && instr[2].make == "intrinsic" && instr[2].name == "use") {
use_slots[text(instr[1])] = true
}
}
i = i + 1
}
// Pass 2: find frame(frame_slot, use_slot), setarg with string, invoke
i = 0
while (i < n) {
instr = instrs[i]
if (is_array(instr)) {
op = instr[0]
if (op == "frame" || op == "goframe") {
if (use_slots[text(instr[2])] == true) {
frame_map[text(instr[1])] = true
}
} else if (op == "setarg") {
if (frame_map[text(instr[1])] == true) {
val_slot = instr[3]
j = i - 1
while (j >= 0) {
prev = instrs[j]
if (is_array(prev) && prev[0] == "access" && prev[1] == val_slot && is_text(prev[2])) {
arg_map[text(instr[1])] = prev[2]
break
}
j = j - 1
}
}
} else if (op == "invoke" || op == "tail_invoke") {
f_slot = instr[1]
path = arg_map[text(f_slot)]
if (path != null) {
result_slot = instr[2]
summary = _summary_resolver(path, ctx)
if (summary != null) {
if (summary._native != true) {
summaries[] = {slot: result_slot, summary: summary}
}
} else {
inv_n = length(instr)
unresolved[] = {path: path, line: instr[inv_n - 2], col: instr[inv_n - 1]}
}
}
}
}
i = i + 1
}
if (length(summaries) > 0 || length(unresolved) > 0) {
return {summaries: summaries, unresolved: unresolved}
}
return null
}
// Run AST through mcode pipeline -> register VM // Run AST through mcode pipeline -> register VM
function run_ast_fn(name, ast, env) { function run_ast_fn(name, ast, env, pkg) {
var compiled = mcode_mod(ast) var compiled = mcode_mod(ast)
var ms = null
var _ui = 0
var _ur = null
var optimized = null
var _di = 0
var _diag = null
var _has_errors = false
var mcode_json = null
var mach_blob = null
if (os._verify_ir) { if (os._verify_ir) {
if (_verify_ir_mod == null) { if (_verify_ir_mod == null) {
_verify_ir_mod = load_pipeline_module('verify_ir', pipeline_env) _verify_ir_mod = load_pipeline_module('verify_ir', pipeline_env)
@@ -192,13 +304,45 @@ function run_ast_fn(name, ast, env) {
compiled._verify = true compiled._verify = true
compiled._verify_mod = _verify_ir_mod compiled._verify_mod = _verify_ir_mod
} }
var optimized = streamline_mod(compiled) if (!_no_warn) {
compiled._warn = true
ms = extract_module_summaries(compiled, pkg)
if (ms != null) {
if (length(ms.summaries) > 0) {
compiled._module_summaries = ms.summaries
}
if (length(ms.unresolved) > 0) {
compiled._unresolved_imports = ms.unresolved
}
}
}
if (compiled._unresolved_imports != null) {
_ui = 0
while (_ui < length(compiled._unresolved_imports)) {
_ur = compiled._unresolved_imports[_ui]
os.print(`${name}:${text(_ur.line)}:${text(_ur.col)}: error: cannot resolve module '${_ur.path}'\n`)
_ui = _ui + 1
}
disrupt
}
optimized = streamline_mod(compiled)
if (optimized._verify) { if (optimized._verify) {
delete optimized._verify delete optimized._verify
delete optimized._verify_mod delete optimized._verify_mod
} }
var mcode_json = json.encode(optimized) if (optimized._diagnostics != null && length(optimized._diagnostics) > 0) {
var mach_blob = mach_compile_mcode_bin(name, mcode_json) _di = 0
_has_errors = false
while (_di < length(optimized._diagnostics)) {
_diag = optimized._diagnostics[_di]
os.print(`${_diag.file}:${text(_diag.line)}:${text(_diag.col)}: ${_diag.severity}: ${_diag.message}\n`)
if (_diag.severity == "error") _has_errors = true
_di = _di + 1
}
if (_has_errors) disrupt
}
mcode_json = json.encode(optimized)
mach_blob = mach_compile_mcode_bin(name, mcode_json)
return mach_load(mach_blob, env) return mach_load(mach_blob, env)
} }
@@ -217,6 +361,52 @@ function compile_to_blob(name, ast) {
return mach_compile_mcode_bin(name, json.encode(optimized)) return mach_compile_mcode_bin(name, json.encode(optimized))
} }
// Compile user program AST to blob with diagnostics
function compile_user_blob(name, ast, pkg) {
var compiled = mcode_mod(ast)
var ms = null
var _ui = 0
var _ur = null
var optimized = null
var _di = 0
var _diag = null
var _has_errors = false
if (!_no_warn) {
compiled._warn = true
ms = extract_module_summaries(compiled, pkg)
if (ms != null) {
if (length(ms.summaries) > 0) {
compiled._module_summaries = ms.summaries
}
if (length(ms.unresolved) > 0) {
compiled._unresolved_imports = ms.unresolved
}
}
}
if (compiled._unresolved_imports != null) {
_ui = 0
while (_ui < length(compiled._unresolved_imports)) {
_ur = compiled._unresolved_imports[_ui]
os.print(`${name}:${text(_ur.line)}:${text(_ur.col)}: error: cannot resolve module '${_ur.path}'\n`)
_ui = _ui + 1
}
disrupt
}
optimized = streamline_mod(compiled)
if (optimized._diagnostics != null && length(optimized._diagnostics) > 0) {
_di = 0
_has_errors = false
while (_di < length(optimized._diagnostics)) {
_diag = optimized._diagnostics[_di]
os.print(`${_diag.file}:${text(_diag.line)}:${text(_diag.col)}: ${_diag.severity}: ${_diag.message}\n`)
if (_diag.severity == "error") _has_errors = true
_di = _di + 1
}
if (_has_errors) disrupt
}
return mach_compile_mcode_bin(name, json.encode(optimized))
}
// If loaded directly by C runtime (not via bootstrap), convert args -> init // If loaded directly by C runtime (not via bootstrap), convert args -> init
var _program = null var _program = null
var _user_args = [] var _user_args = []
@@ -227,6 +417,9 @@ var _init = init
if (_init != null && _init.native_mode) if (_init != null && _init.native_mode)
native_mode = true native_mode = true
// Inherit warn mode from init (set by C for --no-warn)
var _no_warn = (_init != null && _init.no_warn) ? true : false
// CLI path: convert args to init record // CLI path: convert args to init record
if (args != null && (_init == null || !_init.program)) { if (args != null && (_init == null || !_init.program)) {
_program = args[0] _program = args[0]
@@ -242,7 +435,7 @@ if (args != null && (_init == null || !_init.program)) {
} }
} }
use_cache['core/os'] = os use_cache['core/internal/os'] = os
// Extra env properties added as engine initializes (log, runtime fns, etc.) // Extra env properties added as engine initializes (log, runtime fns, etc.)
var core_extras = {} var core_extras = {}
@@ -291,7 +484,7 @@ function use_core(path) {
result = mach_load(mach_blob, env) result = mach_load(mach_blob, env)
} }
} disruption { } disruption {
print("use('" + path + "'): failed to compile or load " + file_path + "\n") os.print("use('" + path + "'): failed to compile or load " + file_path + "\n")
disrupt disrupt
} }
_load_mod() _load_mod()
@@ -314,8 +507,8 @@ function actor() {
} }
var actor_mod = use_core('actor') var actor_mod = use_core('actor')
var wota = use_core('wota') var wota = use_core('internal/wota')
var nota = use_core('nota') var nota = use_core('internal/nota')
var ENETSERVICE = 0.1 var ENETSERVICE = 0.1
@@ -324,17 +517,37 @@ var REPLYTIMEOUT = 60 // seconds before replies are ignored
// --- Logging system (bootstrap phase) --- // --- Logging system (bootstrap phase) ---
// Early log: prints to console before toml/time/json are loaded. // Early log: prints to console before toml/time/json are loaded.
// Upgraded to full sink-based system after config loads (see load_log_config below). // Upgraded to full sink-based system after config loads (see load_log_config below).
// The bootstrap log forwards to _log_full once the full system is ready, so that
// modules loaded early (like shop.cm) get full logging even though they captured
// the bootstrap function reference.
var log_config = null var log_config = null
var channel_sinks = {} var channel_sinks = {}
var wildcard_sinks = [] var wildcard_sinks = []
var warned_channels = {} var warned_channels = {}
var stack_channels = {} var stack_channels = {}
var _log_full = null
var log_quiet_channels = { shop: true }
function log(name, args) { function log(name, args) {
if (_log_full) return _log_full(name, args)
if (log_quiet_channels[name]) return
var msg = args[0] var msg = args[0]
var stk = null
var i = 0
var fr = null
if (msg == null) msg = "" if (msg == null) msg = ""
os.print(`[${text(_cell.id, 0, 5)}] [${name}]: ${msg}\n`) os.print(`[${text(_cell.id, 0, 5)}] [${name}]: ${msg}\n`)
if (name == "error") {
stk = os.stack(2)
if (stk && length(stk) > 0) {
for (i = 0; i < length(stk); i = i + 1) {
fr = stk[i]
os.print(` at ${fr.fn} (${fr.file}:${text(fr.line)}:${text(fr.col)})\n`)
}
}
}
} }
function actor_die(err) function actor_die(err)
@@ -420,7 +633,23 @@ core_extras.native_mode = native_mode
// NOW load shop -- it receives all of the above via env // NOW load shop -- it receives all of the above via env
var shop = use_core('internal/shop') var shop = use_core('internal/shop')
if (native_mode) use_core('build') use_core('build')
// Wire up module summary resolver now that shop is available
_summary_resolver = function(path, ctx) {
var info = shop.resolve_import_info(path, ctx)
if (info == null) return null
if (info.type == 'native') return {_native: true}
var resolved = info.resolved_path
if (resolved == null) return null
var summary_fn = function() {
return shop.summary_file(resolved)
} disruption {
return null
}
return summary_fn()
}
var time = use_core('time') var time = use_core('time')
var toml = use_core('toml') var toml = use_core('toml')
@@ -447,6 +676,7 @@ function build_sink_routing() {
var names = array(log_config.sink) var names = array(log_config.sink)
arrfor(names, function(name) { arrfor(names, function(name) {
var sink = log_config.sink[name] var sink = log_config.sink[name]
if (!sink || !is_object(sink)) return
sink._name = name sink._name = name
if (!is_array(sink.channels)) sink.channels = [] if (!is_array(sink.channels)) sink.channels = []
if (is_text(sink.exclude)) sink.exclude = [sink.exclude] if (is_text(sink.exclude)) sink.exclude = [sink.exclude]
@@ -476,13 +706,13 @@ function load_log_config() {
log_config = toml.decode(text(fd.slurp(log_path))) log_config = toml.decode(text(fd.slurp(log_path)))
} }
} }
if (!log_config || !log_config.sink) { if (!log_config || !log_config.sink || length(array(log_config.sink)) == 0) {
log_config = { log_config = {
sink: { sink: {
terminal: { terminal: {
type: "console", type: "console",
format: "pretty", format: "pretty",
channels: ["console", "error", "system"], channels: ["*"],
stack: ["error"] stack: ["error"]
} }
} }
@@ -498,10 +728,8 @@ function pretty_format(rec) {
var out = null var out = null
var i = 0 var i = 0
var fr = null var fr = null
if (rec.source && rec.source.file)
src = rec.source.file + ":" + text(rec.source.line)
ev = is_text(rec.event) ? rec.event : json.encode(rec.event, false) ev = is_text(rec.event) ? rec.event : json.encode(rec.event, false)
out = `[${aid}] [${rec.channel}] ${src} ${ev}\n` out = `[${aid}] [${rec.channel}] ${ev}\n`
if (rec.stack && length(rec.stack) > 0) { if (rec.stack && length(rec.stack) > 0) {
for (i = 0; i < length(rec.stack); i = i + 1) { for (i = 0; i < length(rec.stack); i = i + 1) {
fr = rec.stack[i] fr = rec.stack[i]
@@ -561,13 +789,7 @@ log = function(name, args) {
var stack = null var stack = null
var rec = null var rec = null
if (!sinks && length(wildcard_sinks) == 0) { if (!sinks && length(wildcard_sinks) == 0) return
if (!warned_channels[name]) {
warned_channels[name] = true
os.print(`[warn] log channel '${name}' has no sinks configured\n`)
}
return
}
// C-provided stack (from JS_Log callback) overrides caller_info/os.stack // C-provided stack (from JS_Log callback) overrides caller_info/os.stack
if (c_stack && length(c_stack) > 0) { if (c_stack && length(c_stack) > 0) {
@@ -594,6 +816,10 @@ log = function(name, args) {
// Wire C-level JS_Log through the ƿit log system // Wire C-level JS_Log through the ƿit log system
actor_mod.set_log(log) actor_mod.set_log(log)
// Let the bootstrap log forward to the full system — modules loaded early
// (before the full log was ready) captured the bootstrap function reference.
_log_full = log
var pronto = use_core('pronto') var pronto = use_core('pronto')
var fallback = pronto.fallback var fallback = pronto.fallback
var parallel = pronto.parallel var parallel = pronto.parallel
@@ -1209,27 +1435,38 @@ if (ends_with(prog, '.ce')) prog = text(prog, 0, -3)
var package = use_core('package') var package = use_core('package')
// Find the .ce file // Find the .ce file using unified resolver
var prog_path = prog + ".ce" var cwd_package = package.find_package_dir(".")
var pkg_dir = null var prog_info = shop.resolve_program ? shop.resolve_program(prog, cwd_package) : null
var core_dir = null var prog_path = null
if (!fd.is_file(prog_path)) { if (prog_info) {
pkg_dir = package.find_package_dir(".") prog_path = prog_info.path
if (pkg_dir) } else {
prog_path = pkg_dir + '/' + prog + '.ce' // Fallback: check CWD, package dir, and core
} prog_path = prog + ".ce"
if (!fd.is_file(prog_path)) { if (!fd.is_file(prog_path) && cwd_package)
// Check core packages prog_path = cwd_package + '/' + prog + '.ce'
core_dir = core_path if (!fd.is_file(prog_path))
prog_path = core_dir + '/' + prog + '.ce' prog_path = core_path + '/' + prog + '.ce'
} if (!fd.is_file(prog_path)) {
if (!fd.is_file(prog_path)) { os.print(`Main program ${prog} could not be found\n`)
os.print(`Main program ${prog} could not be found\n`) os.exit(1)
os.exit(1) }
} }
$_.clock(_ => { $_.clock(_ => {
var file_info = shop.file_info ? shop.file_info(prog_path) : null var _file_info_ok = false
var file_info = null
var _try_fi = function() {
file_info = shop.file_info ? shop.file_info(prog_path) : null
_file_info_ok = true
} disruption {}
_try_fi()
if (!_file_info_ok || !file_info)
file_info = {path: prog_path, is_module: false, is_actor: true, package: null, name: prog}
// If the unified resolver found the package, use that as the authoritative source
if (prog_info && prog_info.pkg)
file_info.package = prog_info.pkg
var inject = shop.script_inject_for ? shop.script_inject_for(file_info) : [] var inject = shop.script_inject_for ? shop.script_inject_for(file_info) : []
// Build env with runtime functions + capability injections // Build env with runtime functions + capability injections
@@ -1248,12 +1485,49 @@ $_.clock(_ => {
} }
var pkg = file_info ? file_info.package : null var pkg = file_info ? file_info.package : null
// Verify all transitive dependency packages are present, auto-install if missing
var _deps = null
var _di = 0
var _dep_dir = null
var _auto_install = null
if (pkg) {
_deps = package.gather_dependencies(pkg)
_di = 0
while (_di < length(_deps)) {
_dep_dir = package.get_dir(_deps[_di])
if (!fd.is_dir(_dep_dir)) {
log.console('installing missing dependency: ' + _deps[_di])
_auto_install = function() {
shop.sync(_deps[_di])
} disruption {
log.error('failed to install dependency: ' + _deps[_di])
disrupt
}
_auto_install()
}
_di = _di + 1
}
}
env.use = function(path) { env.use = function(path) {
var ck = 'core/' + path var ck = 'core/' + path
var _use_core_result = null
var _use_core_ok = false
if (use_cache[ck]) return use_cache[ck] if (use_cache[ck]) return use_cache[ck]
var core_mod = use_core(path) var _try_core = function() {
if (core_mod) return core_mod _use_core_result = use_core(path)
return shop.use(path, pkg) _use_core_ok = true
} disruption {}
_try_core()
if (_use_core_ok && _use_core_result) return _use_core_result
var _shop_use = function() {
return shop.use(path, pkg)
} disruption {
log.error(`use('${path}') failed (package: ${pkg})`)
disrupt
}
return _shop_use()
} }
env.args = _cell.args.arg env.args = _cell.args.arg
env.log = log env.log = log
@@ -1293,7 +1567,7 @@ $_.clock(_ => {
} else { } else {
script = text(source_blob) script = text(source_blob)
ast = analyze(script, prog_path) ast = analyze(script, prog_path)
mach_blob = compile_to_blob(prog, ast) mach_blob = compile_user_blob(prog, ast, pkg)
if (cached_path) { if (cached_path) {
ensure_build_dir() ensure_build_dir()
fd.slurpwrite(cached_path, mach_blob) fd.slurpwrite(cached_path, mach_blob)

View File

@@ -40,7 +40,8 @@ static int js2fd(JSContext *ctx, JSValueConst val)
// Helper function for writing // Helper function for writing
static ssize_t js_fd_write_helper(JSContext *js, int fd, JSValue val) static ssize_t js_fd_write_helper(JSContext *js, int fd, JSValue val)
{ {
(void)js; (void)fd; (void)val;
return -1;
} }
@@ -85,7 +86,7 @@ JSC_CCALL(fd_write,
JS_FreeCString(js, data); JS_FreeCString(js, data);
} else { } else {
void *data = js_get_blob_data(js, &len, argv[1]); void *data = js_get_blob_data(js, &len, argv[1]);
if (data == -1) if (data == (void *)-1)
return JS_EXCEPTION; return JS_EXCEPTION;
wrote = write(fd, data, len); wrote = write(fd, data, len);
} }
@@ -101,18 +102,15 @@ JSC_CCALL(fd_read,
if (argc > 1) if (argc > 1)
size = js2number(js, argv[1]); size = js2number(js, argv[1]);
void *buf = malloc(size); void *out;
if (!buf) ret = js_new_blob_alloc(js, size, &out);
return JS_RaiseDisrupt(js, "malloc failed"); if (JS_IsException(ret)) return ret;
ssize_t bytes_read = read(fd, buf, size); ssize_t bytes_read = read(fd, out, size);
if (bytes_read < 0) { if (bytes_read < 0)
free(buf);
return JS_RaiseDisrupt(js, "read failed: %s", strerror(errno)); return JS_RaiseDisrupt(js, "read failed: %s", strerror(errno));
}
js_blob_stone(ret, bytes_read);
ret = js_new_blob_stoned_copy(js, buf, bytes_read);
free(buf);
return ret; return ret;
) )
@@ -127,39 +125,48 @@ JSC_SCALL(fd_slurp,
size_t size = st.st_size; size_t size = st.st_size;
if (size == 0) if (size == 0)
return js_new_blob_stoned_copy(js, NULL, 0); return js_new_blob_stoned_copy(js, NULL, 0);
#ifndef _WIN32 #ifndef _WIN32
int fd = open(str, O_RDONLY); int fd = open(str, O_RDONLY);
if (fd < 0) if (fd < 0)
return JS_RaiseDisrupt(js, "open failed: %s", strerror(errno)); return JS_RaiseDisrupt(js, "open failed: %s", strerror(errno));
void *data = mmap(NULL, size, PROT_READ, MAP_PRIVATE, fd, 0); void *out;
if (data == MAP_FAILED) { ret = js_new_blob_alloc(js, size, &out);
close(fd); if (JS_IsException(ret)) { close(fd); return ret; }
return JS_RaiseDisrupt(js, "mmap failed: %s", strerror(errno));
size_t total = 0;
while (total < size) {
ssize_t n = read(fd, (uint8_t *)out + total, size - total);
if (n < 0) {
if (errno == EINTR) continue;
close(fd);
return JS_RaiseDisrupt(js, "read failed: %s", strerror(errno));
}
if (n == 0) break;
total += n;
} }
ret = js_new_blob_stoned_copy(js, data, size);
munmap(data, size);
close(fd); close(fd);
js_blob_stone(ret, total);
#else #else
// Windows: use memory mapping for optimal performance // Windows: use memory mapping for optimal performance
HANDLE hFile = CreateFileA(str, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); HANDLE hFile = CreateFileA(str, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) if (hFile == INVALID_HANDLE_VALUE)
return JS_RaiseDisrupt(js, "CreateFile failed: %lu", GetLastError()); return JS_RaiseDisrupt(js, "CreateFile failed: %lu", GetLastError());
HANDLE hMapping = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL); HANDLE hMapping = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (hMapping == NULL) { if (hMapping == NULL) {
CloseHandle(hFile); CloseHandle(hFile);
return JS_RaiseDisrupt(js, "CreateFileMapping failed: %lu", GetLastError()); return JS_RaiseDisrupt(js, "CreateFileMapping failed: %lu", GetLastError());
} }
void *data = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0); void *data = MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0, 0);
if (data == NULL) { if (data == NULL) {
CloseHandle(hMapping); CloseHandle(hMapping);
CloseHandle(hFile); CloseHandle(hFile);
return JS_RaiseDisrupt(js, "MapViewOfFile failed: %lu", GetLastError()); return JS_RaiseDisrupt(js, "MapViewOfFile failed: %lu", GetLastError());
} }
ret = js_new_blob_stoned_copy(js, data, size); ret = js_new_blob_stoned_copy(js, data, size);
UnmapViewOfFile(data); UnmapViewOfFile(data);
CloseHandle(hMapping); CloseHandle(hMapping);

View File

@@ -98,19 +98,17 @@ JSC_CCALL(fd_read,
if (argc > 1) if (argc > 1)
size = js2number(js, argv[1]); size = js2number(js, argv[1]);
void *buf = malloc(size); void *out;
if (!buf) ret = js_new_blob_alloc(js, size, &out);
return JS_RaiseDisrupt(js, "malloc failed"); if (JS_IsException(ret)) return ret;
int bytes_read = pd_file->read(fd, buf, (unsigned int)size); int bytes_read = pd_file->read(fd, out, (unsigned int)size);
if (bytes_read < 0) { if (bytes_read < 0) {
free(buf);
const char* err = pd_file->geterr(); const char* err = pd_file->geterr();
return JS_RaiseDisrupt(js, "read failed: %s", err ? err : "unknown error"); return JS_RaiseDisrupt(js, "read failed: %s", err ? err : "unknown error");
} }
ret = js_new_blob_stoned_copy(js, buf, bytes_read); js_blob_stone(ret, bytes_read);
free(buf);
return ret; return ret;
) )
@@ -134,22 +132,17 @@ JSC_SCALL(fd_slurp,
return JS_RaiseDisrupt(js, "open failed: %s", err ? err : "unknown error"); return JS_RaiseDisrupt(js, "open failed: %s", err ? err : "unknown error");
} }
void *data = malloc(size); void *out;
if (!data) { ret = js_new_blob_alloc(js, size, &out);
pd_file->close(fd); if (JS_IsException(ret)) { pd_file->close(fd); return ret; }
return JS_RaiseDisrupt(js, "malloc failed");
}
int bytes_read = pd_file->read(fd, data, (unsigned int)size); int bytes_read = pd_file->read(fd, out, (unsigned int)size);
pd_file->close(fd); pd_file->close(fd);
if (bytes_read < 0) { if (bytes_read < 0)
free(data);
return JS_RaiseDisrupt(js, "read failed"); return JS_RaiseDisrupt(js, "read failed");
}
ret = js_new_blob_stoned_copy(js, data, bytes_read); js_blob_stone(ret, bytes_read);
free(data);
) )
JSC_CCALL(fd_lseek, JSC_CCALL(fd_lseek,

View File

@@ -8,26 +8,25 @@
JSC_CCALL(kim_encode, JSC_CCALL(kim_encode,
const char *utf8_str = JS_ToCString(js, argv[0]); const char *utf8_str = JS_ToCString(js, argv[0]);
if (!utf8_str) return JS_EXCEPTION; if (!utf8_str) return JS_EXCEPTION;
// Count runes to estimate kim buffer size // Count runes to estimate kim buffer size
int rune_count = utf8_count(utf8_str); int rune_count = utf8_count(utf8_str);
// Allocate kim buffer (worst case: 5 bytes per rune) // Allocate blob (worst case: 5 bytes per rune)
size_t kim_size = rune_count * 5; size_t kim_size = rune_count * 5;
char *kim_buffer = malloc(kim_size); void *out;
char *kim_ptr = kim_buffer; ret = js_new_blob_alloc(js, kim_size, &out);
if (JS_IsException(ret)) { JS_FreeCString(js, utf8_str); return ret; }
// Encode utf8 to kim
// Encode utf8 to kim directly into blob
char *kim_ptr = (char *)out;
long long runes_encoded; long long runes_encoded;
utf8_to_kim(&utf8_str, &kim_ptr, &runes_encoded); utf8_to_kim(&utf8_str, &kim_ptr, &runes_encoded);
// Calculate actual size used // Stone with actual size used
size_t actual_size = kim_ptr - kim_buffer; size_t actual_size = kim_ptr - (char *)out;
js_blob_stone(ret, actual_size);
// Create blob with the encoded data
ret = js_new_blob_stoned_copy(js, kim_buffer, actual_size);
free(kim_buffer);
JS_FreeCString(js, utf8_str); JS_FreeCString(js, utf8_str);
) )
@@ -73,7 +72,7 @@ static const JSCFunctionListEntry js_kim_funcs[] = {
MIST_FUNC_DEF(kim, decode, 1), MIST_FUNC_DEF(kim, decode, 1),
}; };
JSValue js_core_kim_use(JSContext *js) JSValue js_core_internal_kim_use(JSContext *js)
{ {
JS_FRAME(js); JS_FRAME(js);
JS_ROOT(mod, JS_NewObject(js)); JS_ROOT(mod, JS_NewObject(js));

431
internal/nota.c Normal file
View File

@@ -0,0 +1,431 @@
#define NOTA_IMPLEMENTATION
#include "quickjs-internal.h"
#include "cell.h"
static int nota_get_arr_len (JSContext *ctx, JSValue arr) {
int64_t len;
JS_GetLength (ctx, arr, &len);
return (int)len;
}
typedef struct NotaVisitedNode {
JSGCRef ref;
struct NotaVisitedNode *next;
} NotaVisitedNode;
typedef struct NotaEncodeContext {
JSContext *ctx;
NotaVisitedNode *visited_list;
NotaBuffer nb;
int cycle;
JSGCRef *replacer_ref; /* pointer to GC-rooted ref */
} NotaEncodeContext;
static void nota_stack_push (NotaEncodeContext *enc, JSValueConst val) {
NotaVisitedNode *node = (NotaVisitedNode *)sys_malloc (sizeof (NotaVisitedNode));
JS_PushGCRef (enc->ctx, &node->ref);
node->ref.val = JS_DupValue (enc->ctx, val);
node->next = enc->visited_list;
enc->visited_list = node;
}
static void nota_stack_pop (NotaEncodeContext *enc) {
NotaVisitedNode *node = enc->visited_list;
enc->visited_list = node->next;
JS_FreeValue (enc->ctx, node->ref.val);
JS_PopGCRef (enc->ctx, &node->ref);
sys_free (node);
}
static int nota_stack_has (NotaEncodeContext *enc, JSValueConst val) {
NotaVisitedNode *node = enc->visited_list;
while (node) {
if (JS_StrictEq (enc->ctx, node->ref.val, val))
return 1;
node = node->next;
}
return 0;
}
static JSValue nota_apply_replacer (NotaEncodeContext *enc, JSValueConst holder, JSValueConst key, JSValueConst val) {
if (!enc->replacer_ref || JS_IsNull (enc->replacer_ref->val)) return JS_DupValue (enc->ctx, val);
JSValue args[2] = { JS_DupValue (enc->ctx, key), JS_DupValue (enc->ctx, val) };
JSValue result = JS_Call (enc->ctx, enc->replacer_ref->val, holder, 2, args);
JS_FreeValue (enc->ctx, args[0]);
JS_FreeValue (enc->ctx, args[1]);
if (JS_IsException (result)) return JS_DupValue (enc->ctx, val);
return result;
}
static char *js_do_nota_decode (JSContext *js, JSValue *tmp, char *nota, JSValue holder, JSValue key, JSValue reviver) {
int type = nota_type (nota);
JSValue ret2;
long long n;
double d;
int b;
char *str;
uint8_t *blob;
switch (type) {
case NOTA_BLOB:
nota = nota_read_blob (&n, (char **)&blob, nota);
*tmp = js_new_blob_stoned_copy (js, blob, n);
sys_free (blob);
break;
case NOTA_TEXT:
nota = nota_read_text (&str, nota);
*tmp = JS_NewString (js, str);
sys_free (str);
break;
case NOTA_ARR:
nota = nota_read_array (&n, nota);
*tmp = JS_NewArrayLen (js, n);
for (int i = 0; i < n; i++) {
nota = js_do_nota_decode (js, &ret2, nota, *tmp, JS_NewInt32 (js, i), reviver);
JS_SetPropertyNumber (js, *tmp, i, ret2);
}
break;
case NOTA_REC:
nota = nota_read_record (&n, nota);
*tmp = JS_NewObject (js);
for (int i = 0; i < n; i++) {
JSGCRef prop_key_ref, sub_val_ref;
JS_PushGCRef (js, &prop_key_ref);
JS_PushGCRef (js, &sub_val_ref);
nota = nota_read_text (&str, nota);
prop_key_ref.val = JS_NewString (js, str);
sub_val_ref.val = JS_NULL;
nota = js_do_nota_decode (js, &sub_val_ref.val, nota, *tmp, prop_key_ref.val, reviver);
JS_SetPropertyStr (js, *tmp, str, sub_val_ref.val);
JS_PopGCRef (js, &sub_val_ref);
JS_PopGCRef (js, &prop_key_ref);
sys_free (str);
}
break;
case NOTA_INT:
nota = nota_read_int (&n, nota);
*tmp = JS_NewInt64 (js, n);
break;
case NOTA_SYM:
nota = nota_read_sym (&b, nota);
if (b == NOTA_PRIVATE) {
JSGCRef inner_ref, obj_ref2;
JS_PushGCRef (js, &inner_ref);
JS_PushGCRef (js, &obj_ref2);
inner_ref.val = JS_NULL;
nota = js_do_nota_decode (js, &inner_ref.val, nota, holder, JS_NULL, reviver);
obj_ref2.val = JS_NewObject (js);
if (!JS_IsNull (js->actor_sym))
JS_SetPropertyKey (js, obj_ref2.val, js->actor_sym, inner_ref.val);
JS_CellStone (js, obj_ref2.val);
*tmp = obj_ref2.val;
JS_PopGCRef (js, &obj_ref2);
JS_PopGCRef (js, &inner_ref);
} else {
switch (b) {
case NOTA_NULL: *tmp = JS_NULL; break;
case NOTA_FALSE: *tmp = JS_NewBool (js, 0); break;
case NOTA_TRUE: *tmp = JS_NewBool (js, 1); break;
default: *tmp = JS_NULL; break;
}
}
break;
default:
case NOTA_FLOAT:
nota = nota_read_float (&d, nota);
*tmp = JS_NewFloat64 (js, d);
break;
}
if (!JS_IsNull (reviver)) {
JSValue args[2] = { JS_DupValue (js, key), JS_DupValue (js, *tmp) };
JSValue revived = JS_Call (js, reviver, holder, 2, args);
JS_FreeValue (js, args[0]);
JS_FreeValue (js, args[1]);
if (!JS_IsException (revived)) {
JS_FreeValue (js, *tmp);
*tmp = revived;
} else {
JS_FreeValue (js, revived);
}
}
return nota;
}
static void nota_encode_value (NotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValueConst key) {
JSContext *ctx = enc->ctx;
JSGCRef replaced_ref, keys_ref, elem_ref, prop_ref;
JS_PushGCRef (ctx, &replaced_ref);
replaced_ref.val = nota_apply_replacer (enc, holder, key, val);
int tag = JS_VALUE_GET_TAG (replaced_ref.val);
switch (tag) {
case JS_TAG_INT:
case JS_TAG_FLOAT64: {
double d;
JS_ToFloat64 (ctx, &d, replaced_ref.val);
nota_write_number (&enc->nb, d);
break;
}
case JS_TAG_STRING: {
const char *str = JS_ToCString (ctx, replaced_ref.val);
nota_write_text (&enc->nb, str);
JS_FreeCString (ctx, str);
break;
}
case JS_TAG_BOOL:
if (JS_VALUE_GET_BOOL (replaced_ref.val)) nota_write_sym (&enc->nb, NOTA_TRUE);
else nota_write_sym (&enc->nb, NOTA_FALSE);
break;
case JS_TAG_NULL:
nota_write_sym (&enc->nb, NOTA_NULL);
break;
case JS_TAG_PTR: {
if (JS_IsText (replaced_ref.val)) {
const char *str = JS_ToCString (ctx, replaced_ref.val);
nota_write_text (&enc->nb, str);
JS_FreeCString (ctx, str);
break;
}
if (js_is_blob (ctx, replaced_ref.val)) {
size_t buf_len;
void *buf_data = js_get_blob_data (ctx, &buf_len, replaced_ref.val);
if (buf_data == (void *)-1) {
JS_PopGCRef (ctx, &replaced_ref);
return;
}
nota_write_blob (&enc->nb, (unsigned long long)buf_len * 8, (const char *)buf_data);
break;
}
if (JS_IsArray (replaced_ref.val)) {
if (nota_stack_has (enc, replaced_ref.val)) {
enc->cycle = 1;
break;
}
nota_stack_push (enc, replaced_ref.val);
int arr_len = nota_get_arr_len (ctx, replaced_ref.val);
nota_write_array (&enc->nb, arr_len);
JS_PushGCRef (ctx, &elem_ref);
for (int i = 0; i < arr_len; i++) {
elem_ref.val = JS_GetPropertyNumber (ctx, replaced_ref.val, i);
JSValue elem_key = JS_NewInt32 (ctx, i);
nota_encode_value (enc, elem_ref.val, replaced_ref.val, elem_key);
}
JS_PopGCRef (ctx, &elem_ref);
nota_stack_pop (enc);
break;
}
JSValue adata = JS_NULL;
if (!JS_IsNull (ctx->actor_sym)) {
int has = JS_HasPropertyKey (ctx, replaced_ref.val, ctx->actor_sym);
if (has > 0) adata = JS_GetPropertyKey (ctx, replaced_ref.val, ctx->actor_sym);
}
if (!JS_IsNull (adata)) {
nota_write_sym (&enc->nb, NOTA_PRIVATE);
nota_encode_value (enc, adata, replaced_ref.val, JS_NULL);
JS_FreeValue (ctx, adata);
break;
}
JS_FreeValue (ctx, adata);
if (nota_stack_has (enc, replaced_ref.val)) {
enc->cycle = 1;
break;
}
nota_stack_push (enc, replaced_ref.val);
JSValue to_json = JS_GetPropertyStr (ctx, replaced_ref.val, "toJSON");
if (JS_IsFunction (to_json)) {
JSValue result = JS_Call (ctx, to_json, replaced_ref.val, 0, NULL);
if (!JS_IsException (result)) {
nota_encode_value (enc, result, holder, key);
} else {
nota_write_sym (&enc->nb, NOTA_NULL);
}
nota_stack_pop (enc);
break;
}
JS_PushGCRef (ctx, &keys_ref);
keys_ref.val = JS_GetOwnPropertyNames (ctx, replaced_ref.val);
if (JS_IsException (keys_ref.val)) {
nota_write_sym (&enc->nb, NOTA_NULL);
nota_stack_pop (enc);
JS_PopGCRef (ctx, &keys_ref);
break;
}
int64_t plen64;
if (JS_GetLength (ctx, keys_ref.val, &plen64) < 0) {
nota_write_sym (&enc->nb, NOTA_NULL);
nota_stack_pop (enc);
JS_PopGCRef (ctx, &keys_ref);
break;
}
uint32_t plen = (uint32_t)plen64;
JS_PushGCRef (ctx, &prop_ref);
JS_PushGCRef (ctx, &elem_ref);
uint32_t non_function_count = 0;
for (uint32_t i = 0; i < plen; i++) {
elem_ref.val = JS_GetPropertyNumber (ctx, keys_ref.val, i);
prop_ref.val = JS_GetProperty (ctx, replaced_ref.val, elem_ref.val);
if (!JS_IsFunction (prop_ref.val)) non_function_count++;
}
nota_write_record (&enc->nb, non_function_count);
for (uint32_t i = 0; i < plen; i++) {
elem_ref.val = JS_GetPropertyNumber (ctx, keys_ref.val, i);
prop_ref.val = JS_GetProperty (ctx, replaced_ref.val, elem_ref.val);
if (!JS_IsFunction (prop_ref.val)) {
const char *prop_name = JS_ToCString (ctx, elem_ref.val);
nota_write_text (&enc->nb, prop_name ? prop_name : "");
nota_encode_value (enc, prop_ref.val, replaced_ref.val, elem_ref.val);
JS_FreeCString (ctx, prop_name);
}
}
JS_PopGCRef (ctx, &elem_ref);
JS_PopGCRef (ctx, &prop_ref);
JS_PopGCRef (ctx, &keys_ref);
nota_stack_pop (enc);
break;
}
default:
nota_write_sym (&enc->nb, NOTA_NULL);
break;
}
JS_PopGCRef (ctx, &replaced_ref);
}
void *value2nota (JSContext *ctx, JSValue v) {
JSGCRef val_ref, key_ref;
JS_PushGCRef (ctx, &val_ref);
JS_PushGCRef (ctx, &key_ref);
val_ref.val = v;
NotaEncodeContext enc_s, *enc = &enc_s;
enc->ctx = ctx;
enc->visited_list = NULL;
enc->cycle = 0;
enc->replacer_ref = NULL;
nota_buffer_init (&enc->nb, 128);
key_ref.val = JS_NewString (ctx, "");
nota_encode_value (enc, val_ref.val, JS_NULL, key_ref.val);
if (enc->cycle) {
JS_PopGCRef (ctx, &key_ref);
JS_PopGCRef (ctx, &val_ref);
nota_buffer_free (&enc->nb);
return NULL;
}
JS_PopGCRef (ctx, &key_ref);
JS_PopGCRef (ctx, &val_ref);
void *data_ptr = enc->nb.data;
enc->nb.data = NULL;
nota_buffer_free (&enc->nb);
return data_ptr;
}
JSValue nota2value (JSContext *js, void *nota) {
if (!nota) return JS_NULL;
JSGCRef holder_ref, key_ref, ret_ref;
JS_PushGCRef (js, &holder_ref);
JS_PushGCRef (js, &key_ref);
JS_PushGCRef (js, &ret_ref);
holder_ref.val = JS_NewObject (js);
key_ref.val = JS_NewString (js, "");
ret_ref.val = JS_NULL;
js_do_nota_decode (js, &ret_ref.val, nota, holder_ref.val, key_ref.val, JS_NULL);
JSValue result = ret_ref.val;
JS_PopGCRef (js, &ret_ref);
JS_PopGCRef (js, &key_ref);
JS_PopGCRef (js, &holder_ref);
return result;
}
static JSValue js_nota_encode (JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc < 1) return JS_RaiseDisrupt (ctx, "nota.encode requires at least 1 argument");
JSGCRef val_ref, replacer_ref, key_ref;
JS_PushGCRef (ctx, &val_ref);
JS_PushGCRef (ctx, &replacer_ref);
JS_PushGCRef (ctx, &key_ref);
val_ref.val = argv[0];
replacer_ref.val = (argc > 1 && JS_IsFunction (argv[1])) ? argv[1] : JS_NULL;
NotaEncodeContext enc_s, *enc = &enc_s;
enc->ctx = ctx;
enc->visited_list = NULL;
enc->cycle = 0;
enc->replacer_ref = &replacer_ref;
nota_buffer_init (&enc->nb, 128);
key_ref.val = JS_NewString (ctx, "");
nota_encode_value (enc, val_ref.val, JS_NULL, key_ref.val);
JSValue ret;
if (enc->cycle) {
nota_buffer_free (&enc->nb);
ret = JS_RaiseDisrupt (ctx, "Tried to encode something to nota with a cycle.");
} else {
size_t total_len = enc->nb.size;
void *data_ptr = enc->nb.data;
ret = js_new_blob_stoned_copy (ctx, (uint8_t *)data_ptr, total_len);
nota_buffer_free (&enc->nb);
}
JS_PopGCRef (ctx, &key_ref);
JS_PopGCRef (ctx, &replacer_ref);
JS_PopGCRef (ctx, &val_ref);
return ret;
}
static JSValue js_nota_decode (JSContext *js, JSValueConst self, int argc, JSValueConst *argv) {
if (argc < 1) return JS_NULL;
size_t len;
unsigned char *nota = js_get_blob_data (js, &len, argv[0]);
if (nota == (unsigned char *)-1) return JS_EXCEPTION;
if (!nota) return JS_NULL;
JSGCRef holder_ref, key_ref, ret_ref, reviver_ref;
JS_PushGCRef (js, &holder_ref);
JS_PushGCRef (js, &key_ref);
JS_PushGCRef (js, &ret_ref);
JS_PushGCRef (js, &reviver_ref);
reviver_ref.val = (argc > 1 && JS_IsFunction (argv[1])) ? argv[1] : JS_NULL;
holder_ref.val = JS_NewObject (js);
key_ref.val = JS_NewString (js, "");
ret_ref.val = JS_NULL;
js_do_nota_decode (js, &ret_ref.val, (char *)nota, holder_ref.val, key_ref.val, reviver_ref.val);
JSValue result = ret_ref.val;
JS_PopGCRef (js, &reviver_ref);
JS_PopGCRef (js, &ret_ref);
JS_PopGCRef (js, &key_ref);
JS_PopGCRef (js, &holder_ref);
return result;
}
static const JSCFunctionListEntry js_nota_funcs[] = {
JS_CFUNC_DEF ("encode", 1, js_nota_encode),
JS_CFUNC_DEF ("decode", 1, js_nota_decode),
};
JSValue js_core_internal_nota_use (JSContext *js) {
JSGCRef export_ref;
JS_PushGCRef (js, &export_ref);
export_ref.val = JS_NewObject (js);
JS_SetPropertyFunctionList (js, export_ref.val, js_nota_funcs, sizeof (js_nota_funcs) / sizeof (JSCFunctionListEntry));
JSValue result = export_ref.val;
JS_PopGCRef (js, &export_ref);
return result;
}

View File

@@ -34,14 +34,10 @@
static JSClassID js_dylib_class_id; static JSClassID js_dylib_class_id;
static void js_dylib_finalizer(JSRuntime *rt, JSValue val) { static void js_dylib_finalizer(JSRuntime *rt, JSValue val) {
void *handle = JS_GetOpaque(val, js_dylib_class_id); /* Do NOT dlclose here. Loaded dylibs contain finalizer functions for other
if (handle) { JS objects; if the dylib is freed before those objects during
#ifdef _WIN32 JS_FreeContext teardown, calling their finalizers would SEGV.
FreeLibrary((HMODULE)handle); The OS reclaims all loaded libraries on process exit. */
#else
dlclose(handle);
#endif
}
} }
static JSClassDef js_dylib_class = { static JSClassDef js_dylib_class = {
@@ -740,7 +736,7 @@ static const JSCFunctionListEntry js_os_funcs[] = {
MIST_FUNC_DEF(os, stack, 1), MIST_FUNC_DEF(os, stack, 1),
}; };
JSValue js_core_os_use(JSContext *js) { JSValue js_core_internal_os_use(JSContext *js) {
JS_NewClassID(&js_dylib_class_id); JS_NewClassID(&js_dylib_class_id);
JS_NewClass(js, js_dylib_class_id, &js_dylib_class); JS_NewClass(js, js_dylib_class_id, &js_dylib_class);

View File

@@ -97,7 +97,7 @@ static int js_qop_ensure_index(JSContext *js, qop_desc *qop) {
JSC_CCALL(qop_open, JSC_CCALL(qop_open,
size_t len; size_t len;
void *data = js_get_blob_data(js, &len, argv[0]); void *data = js_get_blob_data(js, &len, argv[0]);
if (data == -1) if (data == (void *)-1)
ret = JS_EXCEPTION; ret = JS_EXCEPTION;
else if (!data) else if (!data)
ret = JS_RaiseDisrupt(js, "Empty blob"); ret = JS_RaiseDisrupt(js, "Empty blob");
@@ -183,18 +183,15 @@ static JSValue js_qop_read(JSContext *js, JSValue self, int argc, JSValue *argv)
return JS_NULL; return JS_NULL;
} }
unsigned char *dest = js_malloc_rt(file->size); void *out;
if (!dest) JSValue blob = js_new_blob_alloc(js, file->size, &out);
return JS_RaiseOOM(js); if (JS_IsException(blob)) return blob;
int bytes = qop_read(qop, file, dest); int bytes = qop_read(qop, file, out);
if (bytes == 0) { if (bytes == 0)
js_free_rt(dest);
return JS_RaiseDisrupt(js, "Failed to read file"); return JS_RaiseDisrupt(js, "Failed to read file");
}
JSValue blob = js_new_blob_stoned_copy(js, dest, bytes); js_blob_stone(blob, bytes);
js_free_rt(dest);
return blob; return blob;
} }
@@ -223,18 +220,15 @@ static JSValue js_qop_read_ex(JSContext *js, JSValue self, int argc, JSValue *ar
if (JS_ToUint32(js, &start, argv[1]) < 0 || JS_ToUint32(js, &len, argv[2]) < 0) if (JS_ToUint32(js, &start, argv[1]) < 0 || JS_ToUint32(js, &len, argv[2]) < 0)
return JS_RaiseDisrupt(js, "Invalid start or len"); return JS_RaiseDisrupt(js, "Invalid start or len");
unsigned char *dest = js_malloc_rt(len); void *out;
if (!dest) JSValue blob = js_new_blob_alloc(js, len, &out);
return JS_RaiseOOM(js); if (JS_IsException(blob)) return blob;
int bytes = qop_read_ex(qop, file, dest, start, len); int bytes = qop_read_ex(qop, file, out, start, len);
if (bytes == 0) { if (bytes == 0)
js_free_rt(dest);
return JS_RaiseDisrupt(js, "Failed to read file part"); return JS_RaiseDisrupt(js, "Failed to read file part");
}
JSValue blob = js_new_blob_stoned_copy(js, dest, bytes); js_blob_stone(blob, bytes);
js_free_rt(dest);
return blob; return blob;
} }
@@ -455,7 +449,7 @@ static const JSCFunctionListEntry js_qop_funcs[] = {
JS_PROP_INT32_DEF("FLAG_ENCRYPTED", QOP_FLAG_ENCRYPTED, 0), JS_PROP_INT32_DEF("FLAG_ENCRYPTED", QOP_FLAG_ENCRYPTED, 0),
}; };
JSValue js_core_qop_use(JSContext *js) { JSValue js_core_internal_qop_use(JSContext *js) {
JS_FRAME(js); JS_FRAME(js);
JS_NewClassID(&js_qop_archive_class_id); JS_NewClassID(&js_qop_archive_class_id);
JS_NewClass(js, js_qop_archive_class_id, &js_qop_archive_class); JS_NewClass(js, js_qop_archive_class_id, &js_qop_archive_class);

File diff suppressed because it is too large Load Diff

View File

@@ -33,26 +33,54 @@ function get_pkg_dir(package_name) {
return shop.get_package_dir(package_name) return shop.get_package_dir(package_name)
} }
// Ensure directory exists // Deep comparison of two values (handles arrays and objects)
function ensure_dir(path) { function values_equal(a, b) {
if (fd.is_dir(path)) return true
var parts = array(path, '/')
var current = starts_with(path, '/') ? '/' : ''
var i = 0 var i = 0
for (i = 0; i < length(parts); i++) { var ka = null
if (parts[i] == '') continue var kb = null
current = current + parts[i] + '/' if (a == b) return true
if (!fd.is_dir(current)) { if (is_null(a) && is_null(b)) return true
fd.mkdir(current) if (is_null(a) || is_null(b)) return false
if (is_array(a) && is_array(b)) {
if (length(a) != length(b)) return false
i = 0
while (i < length(a)) {
if (!values_equal(a[i], b[i])) return false
i = i + 1
} }
return true
} }
return true if (is_object(a) && is_object(b)) {
ka = array(a)
kb = array(b)
if (length(ka) != length(kb)) return false
i = 0
while (i < length(ka)) {
if (!values_equal(a[ka[i]], b[ka[i]])) return false
i = i + 1
}
return true
}
return false
}
// Describe a value for error messages
function describe(val) {
if (is_null(val)) return "null"
if (is_text(val)) return `"${val}"`
if (is_number(val)) return text(val)
if (is_logical(val)) return text(val)
if (is_function(val)) return "<function>"
if (is_array(val)) return `[array length=${text(length(val))}]`
if (is_object(val)) return `{record keys=${text(length(array(val)))}}`
return "<unknown>"
} }
return { return {
is_valid_package: is_valid_package, is_valid_package: is_valid_package,
get_current_package_name: get_current_package_name, get_current_package_name: get_current_package_name,
get_pkg_dir: get_pkg_dir, get_pkg_dir: get_pkg_dir,
ensure_dir: ensure_dir ensure_dir: fd.ensure_dir,
values_equal: values_equal,
describe: describe
} }

View File

@@ -28,7 +28,7 @@ static const JSCFunctionListEntry js_wildstar_funcs[] = {
JS_PROP_INT32_DEF("WM_WILDSTAR", WM_WILDSTAR, 0), JS_PROP_INT32_DEF("WM_WILDSTAR", WM_WILDSTAR, 0),
}; };
JSValue js_core_wildstar_use(JSContext *js) { JSValue js_core_internal_wildstar_use(JSContext *js) {
JS_FRAME(js); JS_FRAME(js);
JS_ROOT(mod, JS_NewObject(js)); JS_ROOT(mod, JS_NewObject(js));
JS_SetPropertyFunctionList(js, mod.val, js_wildstar_funcs, countof(js_wildstar_funcs)); JS_SetPropertyFunctionList(js, mod.val, js_wildstar_funcs, countof(js_wildstar_funcs));

461
internal/wota.c Normal file
View File

@@ -0,0 +1,461 @@
#define WOTA_IMPLEMENTATION
#include "quickjs-internal.h"
#include "cell.h"
typedef struct ObjectRef {
void *ptr;
struct ObjectRef *next;
} ObjectRef;
typedef struct WotaEncodeContext {
JSContext *ctx;
ObjectRef *visited_stack;
WotaBuffer wb;
int cycle;
JSValue replacer;
} WotaEncodeContext;
static void wota_stack_push (WotaEncodeContext *enc, JSValueConst val) {
(void)enc; (void)val;
/* Cycle detection disabled for performance */
}
static void wota_stack_pop (WotaEncodeContext *enc) {
if (!enc->visited_stack) return;
ObjectRef *top = enc->visited_stack;
enc->visited_stack = top->next;
sys_free (top);
}
static int wota_stack_has (WotaEncodeContext *enc, JSValueConst val) {
(void)enc; (void)val;
return 0;
/* Cycle detection disabled for performance */
}
static void wota_stack_free (WotaEncodeContext *enc) {
while (enc->visited_stack) {
wota_stack_pop (enc);
}
}
static JSValue wota_apply_replacer (WotaEncodeContext *enc, JSValueConst holder, JSValue key, JSValueConst val) {
if (JS_IsNull (enc->replacer)) return JS_DupValue (enc->ctx, val);
JSValue key_val = JS_IsNull (key) ? JS_NULL : JS_DupValue (enc->ctx, key);
JSValue args[2] = { key_val, JS_DupValue (enc->ctx, val) };
JSValue result = JS_Call (enc->ctx, enc->replacer, holder, 2, args);
JS_FreeValue (enc->ctx, args[0]);
JS_FreeValue (enc->ctx, args[1]);
if (JS_IsException (result)) return JS_DupValue (enc->ctx, val);
return result;
}
static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValue key);
static void encode_object_properties (WotaEncodeContext *enc, JSValueConst val, JSValueConst holder) {
JSContext *ctx = enc->ctx;
/* Root the input value to protect it during property enumeration */
JSGCRef val_ref, keys_ref;
JS_PushGCRef (ctx, &val_ref);
JS_PushGCRef (ctx, &keys_ref);
val_ref.val = JS_DupValue (ctx, val);
keys_ref.val = JS_GetOwnPropertyNames (ctx, val_ref.val);
if (JS_IsException (keys_ref.val)) {
wota_write_sym (&enc->wb, WOTA_NULL);
JS_FreeValue (ctx, val_ref.val);
JS_PopGCRef (ctx, &keys_ref);
JS_PopGCRef (ctx, &val_ref);
return;
}
int64_t plen64;
if (JS_GetLength (ctx, keys_ref.val, &plen64) < 0) {
JS_FreeValue (ctx, keys_ref.val);
JS_FreeValue (ctx, val_ref.val);
wota_write_sym (&enc->wb, WOTA_NULL);
JS_PopGCRef (ctx, &keys_ref);
JS_PopGCRef (ctx, &val_ref);
return;
}
uint32_t plen = (uint32_t)plen64;
uint32_t non_function_count = 0;
/* Allocate GC-rooted arrays for props and keys */
JSGCRef *prop_refs = sys_malloc (sizeof (JSGCRef) * plen);
JSGCRef *key_refs = sys_malloc (sizeof (JSGCRef) * plen);
for (uint32_t i = 0; i < plen; i++) {
JS_PushGCRef (ctx, &prop_refs[i]);
JS_PushGCRef (ctx, &key_refs[i]);
prop_refs[i].val = JS_NULL;
key_refs[i].val = JS_NULL;
}
for (uint32_t i = 0; i < plen; i++) {
/* Store key into its GCRef slot immediately so it's rooted before
JS_GetProperty can trigger GC and relocate the string. */
key_refs[i].val = JS_GetPropertyNumber (ctx, keys_ref.val, i);
JSValue prop_val = JS_GetProperty (ctx, val_ref.val, key_refs[i].val);
if (!JS_IsFunction (prop_val)) {
if (i != non_function_count) {
key_refs[non_function_count].val = key_refs[i].val;
key_refs[i].val = JS_NULL;
}
prop_refs[non_function_count].val = prop_val;
non_function_count++;
} else {
JS_FreeValue (ctx, prop_val);
JS_FreeValue (ctx, key_refs[i].val);
key_refs[i].val = JS_NULL;
}
}
JS_FreeValue (ctx, keys_ref.val);
wota_write_record (&enc->wb, non_function_count);
for (uint32_t i = 0; i < non_function_count; i++) {
size_t klen;
const char *prop_name = JS_ToCStringLen (ctx, &klen, key_refs[i].val);
wota_write_text_len (&enc->wb, prop_name ? prop_name : "", prop_name ? klen : 0);
wota_encode_value (enc, prop_refs[i].val, val_ref.val, key_refs[i].val);
JS_FreeCString (ctx, prop_name);
JS_FreeValue (ctx, prop_refs[i].val);
JS_FreeValue (ctx, key_refs[i].val);
}
/* Pop all GC refs in reverse order */
for (int i = plen - 1; i >= 0; i--) {
JS_PopGCRef (ctx, &key_refs[i]);
JS_PopGCRef (ctx, &prop_refs[i]);
}
sys_free (prop_refs);
sys_free (key_refs);
JS_FreeValue (ctx, val_ref.val);
JS_PopGCRef (ctx, &keys_ref);
JS_PopGCRef (ctx, &val_ref);
}
static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValue key) {
JSContext *ctx = enc->ctx;
JSValue replaced;
if (!JS_IsNull (enc->replacer) && !JS_IsNull (key))
replaced = wota_apply_replacer (enc, holder, key, val);
else
replaced = JS_DupValue (enc->ctx, val);
int tag = JS_VALUE_GET_TAG (replaced);
switch (tag) {
case JS_TAG_INT: {
int32_t d;
JS_ToInt32 (ctx, &d, replaced);
wota_write_int_word (&enc->wb, d);
break;
}
case JS_TAG_FLOAT64: {
double d;
if (JS_ToFloat64 (ctx, &d, replaced) < 0) {
wota_write_sym (&enc->wb, WOTA_NULL);
break;
}
wota_write_float_word (&enc->wb, d);
break;
}
case JS_TAG_STRING: {
size_t plen;
const char *str = JS_ToCStringLen (ctx, &plen, replaced);
wota_write_text_len (&enc->wb, str ? str : "", str ? plen : 0);
JS_FreeCString (ctx, str);
break;
}
case JS_TAG_BOOL:
wota_write_sym (&enc->wb, JS_VALUE_GET_BOOL (replaced) ? WOTA_TRUE : WOTA_FALSE);
break;
case JS_TAG_NULL:
wota_write_sym (&enc->wb, WOTA_NULL);
break;
case JS_TAG_PTR: {
if (JS_IsText (replaced)) {
size_t plen;
const char *str = JS_ToCStringLen (ctx, &plen, replaced);
wota_write_text_len (&enc->wb, str ? str : "", str ? plen : 0);
JS_FreeCString (ctx, str);
break;
}
if (js_is_blob (ctx, replaced)) {
size_t buf_len;
void *buf_data = js_get_blob_data (ctx, &buf_len, replaced);
if (buf_data == (void *)-1) {
JS_FreeValue (ctx, replaced);
return;
}
if (buf_len == 0) {
wota_write_blob (&enc->wb, 0, "");
} else {
wota_write_blob (&enc->wb, (unsigned long long)buf_len * 8, (const char *)buf_data);
}
break;
}
if (JS_IsArray (replaced)) {
if (wota_stack_has (enc, replaced)) {
enc->cycle = 1;
break;
}
wota_stack_push (enc, replaced);
int64_t arr_len;
JS_GetLength (ctx, replaced, &arr_len);
wota_write_array (&enc->wb, arr_len);
for (int64_t i = 0; i < arr_len; i++) {
JSValue elem_val = JS_GetPropertyNumber (ctx, replaced, i);
wota_encode_value (enc, elem_val, replaced, JS_NewInt32 (ctx, (int32_t)i));
JS_FreeValue (ctx, elem_val);
}
wota_stack_pop (enc);
break;
}
JSValue adata = JS_NULL;
if (!JS_IsNull (ctx->actor_sym)) {
int has = JS_HasPropertyKey (ctx, replaced, ctx->actor_sym);
if (has > 0) adata = JS_GetPropertyKey (ctx, replaced, ctx->actor_sym);
}
if (!JS_IsNull (adata)) {
wota_write_sym (&enc->wb, WOTA_PRIVATE);
wota_encode_value (enc, adata, replaced, JS_NULL);
JS_FreeValue (ctx, adata);
break;
}
JS_FreeValue (ctx, adata);
if (wota_stack_has (enc, replaced)) {
enc->cycle = 1;
break;
}
wota_stack_push (enc, replaced);
JSValue to_json = JS_GetPropertyStr (ctx, replaced, "toJSON");
if (JS_IsFunction (to_json)) {
JSValue result = JS_Call (ctx, to_json, replaced, 0, NULL);
JS_FreeValue (ctx, to_json);
if (!JS_IsException (result)) {
wota_encode_value (enc, result, holder, key);
JS_FreeValue (ctx, result);
} else
wota_write_sym (&enc->wb, WOTA_NULL);
wota_stack_pop (enc);
break;
}
JS_FreeValue (ctx, to_json);
encode_object_properties (enc, replaced, holder);
wota_stack_pop (enc);
break;
}
default:
wota_write_sym (&enc->wb, WOTA_NULL);
break;
}
JS_FreeValue (ctx, replaced);
}
static char *decode_wota_value (JSContext *ctx, char *data_ptr, JSValue *out_val, JSValue holder, JSValue key, JSValue reviver) {
uint64_t first_word = *(uint64_t *)data_ptr;
int type = (int)(first_word & 0xffU);
switch (type) {
case WOTA_INT: {
long long val;
data_ptr = wota_read_int (&val, data_ptr);
*out_val = JS_NewInt64 (ctx, val);
break;
}
case WOTA_FLOAT: {
double d;
data_ptr = wota_read_float (&d, data_ptr);
*out_val = JS_NewFloat64 (ctx, d);
break;
}
case WOTA_SYM: {
int scode;
data_ptr = wota_read_sym (&scode, data_ptr);
if (scode == WOTA_PRIVATE) {
JSGCRef inner_ref, obj_ref2;
JS_PushGCRef (ctx, &inner_ref);
JS_PushGCRef (ctx, &obj_ref2);
inner_ref.val = JS_NULL;
data_ptr = decode_wota_value (ctx, data_ptr, &inner_ref.val, holder, JS_NULL, reviver);
obj_ref2.val = JS_NewObject (ctx);
if (!JS_IsNull (ctx->actor_sym))
JS_SetPropertyKey (ctx, obj_ref2.val, ctx->actor_sym, inner_ref.val);
JS_CellStone (ctx, obj_ref2.val);
*out_val = obj_ref2.val;
JS_PopGCRef (ctx, &obj_ref2);
JS_PopGCRef (ctx, &inner_ref);
} else if (scode == WOTA_NULL) *out_val = JS_NULL;
else if (scode == WOTA_FALSE) *out_val = JS_NewBool (ctx, 0);
else if (scode == WOTA_TRUE) *out_val = JS_NewBool (ctx, 1);
else *out_val = JS_NULL;
break;
}
case WOTA_BLOB: {
long long blen;
char *bdata = NULL;
data_ptr = wota_read_blob (&blen, &bdata, data_ptr);
*out_val = bdata ? js_new_blob_stoned_copy (ctx, (uint8_t *)bdata, (size_t)blen) : js_new_blob_stoned_copy (ctx, NULL, 0);
if (bdata) sys_free (bdata);
break;
}
case WOTA_TEXT: {
char *utf8 = NULL;
data_ptr = wota_read_text (&utf8, data_ptr);
*out_val = JS_NewString (ctx, utf8 ? utf8 : "");
if (utf8) sys_free (utf8);
break;
}
case WOTA_ARR: {
long long c;
data_ptr = wota_read_array (&c, data_ptr);
JSGCRef arr_ref;
JS_PushGCRef (ctx, &arr_ref);
arr_ref.val = JS_NewArrayLen (ctx, c);
for (long long i = 0; i < c; i++) {
JSGCRef elem_ref;
JS_PushGCRef (ctx, &elem_ref);
elem_ref.val = JS_NULL;
JSValue idx_key = JS_NewInt32 (ctx, (int32_t)i);
data_ptr = decode_wota_value (ctx, data_ptr, &elem_ref.val, arr_ref.val, idx_key, reviver);
JS_SetPropertyNumber (ctx, arr_ref.val, i, elem_ref.val);
JS_PopGCRef (ctx, &elem_ref);
}
*out_val = arr_ref.val;
JS_PopGCRef (ctx, &arr_ref);
break;
}
case WOTA_REC: {
long long c;
data_ptr = wota_read_record (&c, data_ptr);
JSGCRef obj_ref;
JS_PushGCRef (ctx, &obj_ref);
obj_ref.val = JS_NewObject (ctx);
for (long long i = 0; i < c; i++) {
char *tkey = NULL;
size_t key_len;
data_ptr = wota_read_text_len (&key_len, &tkey, data_ptr);
if (!tkey) continue;
JSGCRef prop_key_ref, sub_val_ref;
JS_PushGCRef (ctx, &prop_key_ref);
JS_PushGCRef (ctx, &sub_val_ref);
prop_key_ref.val = JS_NewStringLen (ctx, tkey, key_len);
sub_val_ref.val = JS_NULL;
data_ptr = decode_wota_value (ctx, data_ptr, &sub_val_ref.val, obj_ref.val, prop_key_ref.val, reviver);
JS_SetPropertyStr (ctx, obj_ref.val, tkey, sub_val_ref.val);
JS_PopGCRef (ctx, &sub_val_ref);
JS_PopGCRef (ctx, &prop_key_ref);
sys_free (tkey);
}
*out_val = obj_ref.val;
JS_PopGCRef (ctx, &obj_ref);
break;
}
default:
data_ptr += 8;
*out_val = JS_NULL;
break;
}
if (!JS_IsNull (reviver)) {
JSValue key_val = JS_IsNull (key) ? JS_NULL : JS_DupValue (ctx, key);
JSValue args[2] = { key_val, JS_DupValue (ctx, *out_val) };
JSValue revived = JS_Call (ctx, reviver, holder, 2, args);
JS_FreeValue (ctx, args[0]);
JS_FreeValue (ctx, args[1]);
if (!JS_IsException (revived)) {
JS_FreeValue (ctx, *out_val);
*out_val = revived;
} else
JS_FreeValue (ctx, revived);
}
return data_ptr;
}
void *value2wota (JSContext *ctx, JSValue v, JSValue replacer, size_t *bytes) {
JSGCRef val_ref, rep_ref;
JS_PushGCRef (ctx, &val_ref);
JS_PushGCRef (ctx, &rep_ref);
val_ref.val = v;
rep_ref.val = replacer;
WotaEncodeContext enc_s, *enc = &enc_s;
enc->ctx = ctx;
enc->visited_stack = NULL;
enc->cycle = 0;
enc->replacer = rep_ref.val;
wota_buffer_init (&enc->wb, 16);
wota_encode_value (enc, val_ref.val, JS_NULL, JS_NULL);
if (enc->cycle) {
wota_stack_free (enc);
wota_buffer_free (&enc->wb);
JS_PopGCRef (ctx, &rep_ref);
JS_PopGCRef (ctx, &val_ref);
return NULL;
}
wota_stack_free (enc);
size_t total_bytes = enc->wb.size * sizeof (uint64_t);
void *wota = sys_realloc (enc->wb.data, total_bytes);
if (bytes) *bytes = total_bytes;
JS_PopGCRef (ctx, &rep_ref);
JS_PopGCRef (ctx, &val_ref);
return wota;
}
JSValue wota2value (JSContext *ctx, void *wota) {
JSGCRef holder_ref, result_ref;
JS_PushGCRef (ctx, &holder_ref);
JS_PushGCRef (ctx, &result_ref);
result_ref.val = JS_NULL;
holder_ref.val = JS_NewObject (ctx);
decode_wota_value (ctx, wota, &result_ref.val, holder_ref.val, JS_NULL, JS_NULL);
JSValue result = result_ref.val;
JS_PopGCRef (ctx, &result_ref);
JS_PopGCRef (ctx, &holder_ref);
return result;
}
static JSValue js_wota_encode (JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc < 1) return JS_RaiseDisrupt (ctx, "wota.encode requires at least 1 argument");
size_t total_bytes;
void *wota = value2wota (ctx, argv[0], JS_IsFunction (argv[1]) ? argv[1] : JS_NULL, &total_bytes);
JSValue ret = js_new_blob_stoned_copy (ctx, wota, total_bytes);
sys_free (wota);
return ret;
}
static JSValue js_wota_decode (JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
if (argc < 1) return JS_NULL;
size_t len;
uint8_t *buf = js_get_blob_data (ctx, &len, argv[0]);
if (buf == (uint8_t *)-1) return JS_EXCEPTION;
if (!buf || len == 0) return JS_RaiseDisrupt (ctx, "No blob data present");
JSValue reviver = (argc > 1 && JS_IsFunction (argv[1])) ? argv[1] : JS_NULL;
char *data_ptr = (char *)buf;
JSGCRef result_ref, holder_ref, empty_key_ref;
JS_PushGCRef (ctx, &result_ref);
JS_PushGCRef (ctx, &holder_ref);
JS_PushGCRef (ctx, &empty_key_ref);
result_ref.val = JS_NULL;
holder_ref.val = JS_NewObject (ctx);
empty_key_ref.val = JS_NewString (ctx, "");
decode_wota_value (ctx, data_ptr, &result_ref.val, holder_ref.val, empty_key_ref.val, reviver);
JSValue result = result_ref.val;
JS_PopGCRef (ctx, &empty_key_ref);
JS_PopGCRef (ctx, &holder_ref);
JS_PopGCRef (ctx, &result_ref);
return result;
}
static const JSCFunctionListEntry js_wota_funcs[] = {
JS_CFUNC_DEF ("encode", 2, js_wota_encode),
JS_CFUNC_DEF ("decode", 2, js_wota_decode),
};
JSValue js_core_internal_wota_use (JSContext *ctx) {
JSGCRef exports_ref;
JS_PushGCRef (ctx, &exports_ref);
exports_ref.val = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, exports_ref.val, js_wota_funcs, sizeof (js_wota_funcs) / sizeof (js_wota_funcs[0]));
JSValue result = exports_ref.val;
JS_PopGCRef (ctx, &exports_ref);
return result;
}

View File

@@ -55,22 +55,22 @@ while (i < length(args)) {
} else if (!starts_with(arg, "--")) { } else if (!starts_with(arg, "--")) {
filename = arg filename = arg
} else { } else {
print(`unknown option: ${arg}\n`) log.error(`unknown option: ${arg}`)
print("usage: cell --core . ir_report.ce [options] <file>\n") log.error("usage: cell --core . ir_report.ce [options] <file>")
$stop() $stop()
} }
i = i + 1 i = i + 1
} }
if (filename == null) { if (filename == null) {
print("usage: cell --core . ir_report.ce [options] <file.cm|file.ce>\n") log.compile("usage: cell --core . ir_report.ce [options] <file.cm|file.ce>")
print(" --summary per-pass JSON summaries (default)\n") log.compile(" --summary per-pass JSON summaries (default)")
print(" --events include rewrite events\n") log.compile(" --events include rewrite events")
print(" --types include type deltas\n") log.compile(" --types include type deltas")
print(" --ir-before=PASS print canonical IR before PASS\n") log.compile(" --ir-before=PASS print canonical IR before PASS")
print(" --ir-after=PASS print canonical IR after PASS\n") log.compile(" --ir-after=PASS print canonical IR after PASS")
print(" --ir-all print canonical IR before/after every pass\n") log.compile(" --ir-all print canonical IR before/after every pass")
print(" --full everything\n") log.compile(" --full everything")
$stop() $stop()
} }
@@ -114,8 +114,7 @@ var optimized = streamline(compiled, log)
// --- Output --- // --- Output ---
var emit = function(obj) { var emit = function(obj) {
print(json.encode(obj)) log.compile(json.encode(obj))
print("\n")
} }
// Pass summaries (always) // Pass summaries (always)

67
json.c Normal file
View File

@@ -0,0 +1,67 @@
#include "cell.h"
#include <string.h>
/* JSON uses JS_KEY_empty for reviver — define locally from public constants */
#ifndef JS_KEY_empty
#define JS_KEY_empty ((JSValue)JS_TAG_STRING_IMM)
#endif
static JSValue js_cell_json_encode (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 1)
return JS_RaiseDisrupt (ctx, "json.encode requires at least 1 argument");
int pretty = argc <= 1 || JS_ToBool (ctx, argv[1]);
JSValue space = pretty ? JS_NewInt32 (ctx, 2) : JS_NULL;
JSValue replacer = JS_NULL;
if (argc > 2 && JS_IsFunction (argv[2]))
replacer = argv[2];
else if (argc > 3 && JS_IsArray (argv[3]))
replacer = argv[3];
JSValue result = JS_JSONStringify (ctx, argv[0], replacer, space, pretty);
return result;
}
static JSValue js_cell_json_decode (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
if (argc < 1)
return JS_RaiseDisrupt (ctx, "json.decode requires at least 1 argument");
if (!JS_IsText (argv[0]))
return JS_RaiseDisrupt (ctx, "couldn't parse text: not a string");
const char *str = JS_ToCString (ctx, argv[0]);
if (!str) return JS_EXCEPTION;
size_t len = strlen (str);
JSValue result = JS_ParseJSON (ctx, str, len, "<json>");
JS_FreeCString (ctx, str);
/* Apply reviver if provided */
if (argc > 1 && JS_IsFunction (argv[1]) && !JS_IsException (result)) {
/* Create wrapper object to pass to reviver */
JSValue wrapper = JS_NewObject (ctx);
JS_SetPropertyStr (ctx, wrapper, "", result);
JSValue holder = wrapper;
JSValue key = JS_KEY_empty;
JSValue args[2] = { key, JS_GetProperty (ctx, holder, key) };
JSValue final = JS_Call (ctx, argv[1], holder, 2, args);
result = final;
}
return result;
}
static const JSCFunctionListEntry js_cell_json_funcs[] = {
JS_CFUNC_DEF ("encode", 4, js_cell_json_encode),
JS_CFUNC_DEF ("decode", 2, js_cell_json_decode),
};
JSValue js_core_json_use (JSContext *ctx) {
JSGCRef obj_ref;
JS_PushGCRef (ctx, &obj_ref);
obj_ref.val = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, obj_ref.val, js_cell_json_funcs, countof (js_cell_json_funcs));
JSValue result = obj_ref.val;
JS_PopGCRef (ctx, &obj_ref);
return result;
}

67
link.ce
View File

@@ -28,24 +28,24 @@ var target = null
var start_idx = 0 var start_idx = 0
var arg1 = null var arg1 = null
var arg2 = null var arg2 = null
var cwd = null
var toml_path = null var toml_path = null
var content = null var content = null
var _restore = null var _restore = null
var _read_toml = null var _read_toml = null
var _add_link = null var _add_link = null
if (length(args) < 1) { var run = function() {
log.console("Usage: link <command> [args] or link [package] <target>") if (length(args) < 1) {
log.console("Commands:") log.console("Usage: link <command> [args] or link [package] <target>")
log.console(" list List all active links") log.console("Commands:")
log.console(" sync Ensure all symlinks are in place") log.console(" list List all active links")
log.console(" delete <package> Remove a link and restore original") log.console(" sync Ensure all symlinks are in place")
log.console(" clear Remove all links") log.console(" delete <package> Remove a link and restore original")
log.console(" <path> Link the package in <path> to that path") log.console(" clear Remove all links")
log.console(" <package> <target> Link <package> to <target> (path or package)") log.console(" <path> Link the package in <path> to that path")
$stop() log.console(" <package> <target> Link <package> to <target> (path or package)")
} return
}
cmd = args[0] cmd = args[0]
@@ -72,7 +72,7 @@ if (cmd == 'list') {
} else if (cmd == 'delete' || cmd == 'rm') { } else if (cmd == 'delete' || cmd == 'rm') {
if (length(args) < 2) { if (length(args) < 2) {
log.console("Usage: link delete <package>") log.console("Usage: link delete <package>")
$stop() return
} }
pkg = args[1] pkg = args[1]
@@ -114,7 +114,7 @@ if (cmd == 'list') {
if (!arg1) { if (!arg1) {
log.console("Error: target or package required") log.console("Error: target or package required")
$stop() return
} }
if (arg2) { if (arg2) {
@@ -123,50 +123,35 @@ if (cmd == 'list') {
target = arg2 target = arg2
// Resolve target if it's a local path // Resolve target if it's a local path
if (target == '.' || fd.is_dir(target)) { target = shop.resolve_locator(target)
target = fd.realpath(target)
} else if (starts_with(target, './') || starts_with(target, '../')) {
// Relative path that doesn't exist yet - try to resolve anyway
cwd = fd.realpath('.')
if (starts_with(target, './')) {
target = cwd + text(target, 1)
} else {
// For ../ paths, let fd.realpath handle it if possible
target = fd.realpath(target) || target
}
}
// Otherwise target is a package name (e.g., github.com/prosperon)
} else { } else {
// One argument: assume it's a local path, infer package name from cell.toml // One argument: assume it's a local path, infer package name from cell.toml
target = arg1 target = arg1
// Resolve path // Resolve path
if (target == '.' || fd.is_dir(target)) { target = shop.resolve_locator(target)
target = fd.realpath(target)
} else if (starts_with(target, './') || starts_with(target, '../')) {
target = fd.realpath(target) || target
}
// Must be a local path with cell.toml // Must be a local path with cell.toml
toml_path = target + '/cell.toml' toml_path = target + '/cell.toml'
if (!fd.is_file(toml_path)) { if (!fd.is_file(toml_path)) {
log.console("Error: No cell.toml found at " + target) log.console("Error: No cell.toml found at " + target)
log.console("For linking to another package, use: link <package> <target>") log.console("For linking to another package, use: link <package> <target>")
$stop() return
} }
// Read package name from cell.toml // Derive canonical package name from the target directory
_read_toml = function() { _read_toml = function() {
content = toml.decode(text(fd.slurp(toml_path))) var info = shop.file_info(target + '/cell.toml')
if (content.package) { if (info && info.package) {
pkg_name = content.package pkg_name = info.package
} else { } else {
log.console("Error: cell.toml at " + target + " does not define 'package'") log.console("Error: could not determine package name for " + target)
log.console("Ensure it is installed or has a git remote matching a lock entry")
$stop() $stop()
} }
} disruption { } disruption {
log.console("Error reading cell.toml") log.console("Error determining package name for " + target)
$stop() $stop()
} }
_read_toml() _read_toml()
@@ -176,7 +161,7 @@ if (cmd == 'list') {
if (starts_with(target, '/')) { if (starts_with(target, '/')) {
if (!fd.is_file(target + '/cell.toml')) { if (!fd.is_file(target + '/cell.toml')) {
log.console("Error: " + target + " is not a valid package (no cell.toml)") log.console("Error: " + target + " is not a valid package (no cell.toml)")
$stop() return
} }
} }
@@ -189,5 +174,7 @@ if (cmd == 'list') {
} }
_add_link() _add_link()
} }
}
run()
$stop() $stop()

56
link.cm
View File

@@ -20,30 +20,8 @@ function get_packages_dir() {
return global_shop_path + '/packages' return global_shop_path + '/packages'
} }
// return the safe path for the package
function safe_package_path(pkg) {
// For absolute paths, replace / with _ to create a valid directory name
if (pkg && starts_with(pkg, '/'))
return replace(replace(pkg, '/', '_'), '@', '_')
return replace(pkg, '@', '_')
}
function get_package_abs_dir(package) { function get_package_abs_dir(package) {
return get_packages_dir() + '/' + safe_package_path(package) return get_packages_dir() + '/' + fd.safe_package_path(package)
}
function ensure_dir(path) {
if (fd.stat(path).isDirectory) return
var parts = array(path, '/')
var current = starts_with(path, '/') ? '/' : ''
var i = 0
for (i = 0; i < length(parts); i++) {
if (parts[i] == '') continue
current = current + parts[i] + '/'
if (!fd.stat(current).isDirectory) {
fd.mkdir(current)
}
}
} }
// Resolve a link target to its actual path // Resolve a link target to its actual path
@@ -54,7 +32,7 @@ function resolve_link_target(target) {
return target return target
} }
// Target is another package - resolve to its directory // Target is another package - resolve to its directory
return get_packages_dir() + '/' + safe_package_path(target) return get_packages_dir() + '/' + fd.safe_package_path(target)
} }
var Link = {} var Link = {}
@@ -75,7 +53,7 @@ Link.load = function() {
if (cfg && cfg.links) link_cache = cfg.links if (cfg && cfg.links) link_cache = cfg.links
else link_cache = {} else link_cache = {}
} disruption { } disruption {
print("Warning: Failed to load link.toml\n") log.build("Warning: Failed to load link.toml")
link_cache = {} link_cache = {}
} }
_load() _load()
@@ -95,7 +73,7 @@ Link.add = function(canonical, target, shop) {
// Validate canonical package exists in shop // Validate canonical package exists in shop
var lock = shop.load_lock() var lock = shop.load_lock()
if (!lock[canonical]) { if (!lock[canonical]) {
print('Package ' + canonical + ' is not installed. Install it first with: cell get ' + canonical + '\n') log.error('Package ' + canonical + ' is not installed. Install it first with: cell get ' + canonical)
disrupt disrupt
} }
@@ -103,12 +81,12 @@ Link.add = function(canonical, target, shop) {
if (starts_with(target, '/')) { if (starts_with(target, '/')) {
// Local path - must have cell.toml // Local path - must have cell.toml
if (!fd.is_file(target + '/cell.toml')) { if (!fd.is_file(target + '/cell.toml')) {
print('Target ' + target + ' is not a valid package (no cell.toml)\n') log.error('Target ' + target + ' is not a valid package (no cell.toml)')
disrupt disrupt
} }
} else { } else {
// Remote package target - ensure it's installed // Remote package target - ensure it's installed
shop.get(target) shop.sync(target)
} }
var links = Link.load() var links = Link.load()
@@ -132,26 +110,25 @@ Link.add = function(canonical, target, shop) {
var dep_locator = cfg.dependencies[alias] var dep_locator = cfg.dependencies[alias]
// Skip local dependencies that don't exist // Skip local dependencies that don't exist
if (starts_with(dep_locator, '/') && !fd.is_dir(dep_locator)) { if (starts_with(dep_locator, '/') && !fd.is_dir(dep_locator)) {
print(" Skipping missing local dependency: " + dep_locator + "\n") log.build(" Skipping missing local dependency: " + dep_locator)
return return
} }
// Install the dependency if not already in shop // Install the dependency if not already in shop
var _get_dep = function() { var _get_dep = function() {
shop.get(dep_locator) shop.sync(dep_locator)
shop.extract(dep_locator)
} disruption { } disruption {
print(` Warning: Could not install dependency ${dep_locator}\n`) log.build(` Warning: Could not install dependency ${dep_locator}`)
} }
_get_dep() _get_dep()
}) })
} }
} disruption { } disruption {
print(` Warning: Could not read dependencies from ${toml_path}\n`) log.build(` Warning: Could not read dependencies from ${toml_path}`)
} }
_install_deps() _install_deps()
} }
print("Linked " + canonical + " -> " + target + "\n") log.build("Linked " + canonical + " -> " + target)
return true return true
} }
@@ -163,12 +140,12 @@ Link.remove = function(canonical) {
var target_dir = get_package_abs_dir(canonical) var target_dir = get_package_abs_dir(canonical)
if (fd.is_link(target_dir)) { if (fd.is_link(target_dir)) {
fd.unlink(target_dir) fd.unlink(target_dir)
print("Removed symlink at " + target_dir + "\n") log.build("Removed symlink at " + target_dir)
} }
delete links[canonical] delete links[canonical]
Link.save(links) Link.save(links)
print("Unlinked " + canonical + "\n") log.build("Unlinked " + canonical)
return true return true
} }
@@ -183,7 +160,7 @@ Link.clear = function() {
}) })
Link.save({}) Link.save({})
print("Cleared all links\n") log.build("Cleared all links")
return true return true
} }
@@ -194,7 +171,7 @@ Link.sync_one = function(canonical, target, shop) {
// Ensure parent directories exist // Ensure parent directories exist
var parent = fd.dirname(target_dir) var parent = fd.dirname(target_dir)
ensure_dir(parent) fd.ensure_dir(parent)
// Check current state // Check current state
var current_link = null var current_link = null
@@ -255,8 +232,7 @@ Link.sync_all = function(shop) {
} }
// Install the dependency if not already in shop // Install the dependency if not already in shop
var _get = function() { var _get = function() {
shop.get(dep_locator) shop.sync(dep_locator)
shop.extract(dep_locator)
} disruption { } disruption {
// Silently continue - dependency may already be installed // Silently continue - dependency may already be installed
} }

51
list.ce
View File

@@ -8,11 +8,9 @@
var shop = use('internal/shop') var shop = use('internal/shop')
var pkg = use('package') var pkg = use('package')
var link = use('link') var link = use('link')
var fd = use('fd')
var mode = 'local' var mode = 'local'
var target_pkg = null var target_pkg = null
var resolved = null
var i = 0 var i = 0
var deps = null var deps = null
var packages = null var packages = null
@@ -20,37 +18,32 @@ var local_pkgs = null
var linked_pkgs = null var linked_pkgs = null
var remote_pkgs = null var remote_pkgs = null
if (args && length(args) > 0) { var run = function() {
if (args[0] == 'shop') { if (args && length(args) > 0) {
mode = 'shop' if (args[0] == 'shop') {
} else if (args[0] == '--help' || args[0] == '-h') { mode = 'shop'
log.console("Usage: cell list [<scope>]") } else if (args[0] == '--help' || args[0] == '-h') {
log.console("") log.console("Usage: cell list [<scope>]")
log.console("List packages and dependencies.") log.console("")
log.console("") log.console("List packages and dependencies.")
log.console("Scopes:") log.console("")
log.console(" (none) List dependencies of current package") log.console("Scopes:")
log.console(" shop List all packages in shop with status") log.console(" (none) List dependencies of current package")
log.console(" <locator> List dependency tree for a package") log.console(" shop List all packages in shop with status")
$stop() log.console(" <locator> List dependency tree for a package")
} else { return
mode = 'package' } else {
target_pkg = args[0] mode = 'package'
target_pkg = args[0]
// Resolve local paths target_pkg = shop.resolve_locator(target_pkg)
if (target_pkg == '.' || starts_with(target_pkg, './') || starts_with(target_pkg, '../') || fd.is_dir(target_pkg)) {
resolved = fd.realpath(target_pkg)
if (resolved) {
target_pkg = resolved
}
} }
} }
}
var links = link.load() var links = link.load()
var lock = shop.load_lock() var lock = shop.load_lock()
function print_deps(ctx, raw_indent) { function print_deps(ctx, raw_indent) {
var aliases = null var aliases = null
var indent = raw_indent || "" var indent = raw_indent || ""
deps = null deps = null
@@ -181,5 +174,7 @@ if (mode == 'local') {
log.console("Total: " + text(length(packages)) + " package(s)") log.console("Total: " + text(length(packages)) + " package(s)")
} }
} }
}
run()
$stop() $stop()

32
log.ce
View File

@@ -1,12 +1,15 @@
// cell log - Manage and read log sinks // cell log - Manage and read log sinks
// //
// Usage: // Usage:
// cell log list List configured sinks // cell log list List configured sinks
// cell log add <name> console [opts] Add a console sink // cell log add <name> console [opts] Add a console sink
// cell log add <name> file <path> [opts] Add a file sink // cell log add <name> file <path> [opts] Add a file sink
// cell log remove <name> Remove a sink // cell log remove <name> Remove a sink
// cell log read <sink> [opts] Read from a file sink // cell log read <sink> [opts] Read from a file sink
// cell log tail <sink> [--lines=N] Follow a file sink // cell log tail <sink> [--lines=N] Follow a file sink
//
// The --stack option controls which channels capture a stack trace.
// Default: --stack=error (errors always show a stack trace).
var toml = use('toml') var toml = use('toml')
var fd = use('fd') var fd = use('fd')
@@ -53,6 +56,7 @@ function print_help() {
log.console(" --format=pretty|bare|json Output format (default: pretty for console, json for file)") log.console(" --format=pretty|bare|json Output format (default: pretty for console, json for file)")
log.console(" --channels=ch1,ch2 Channels to subscribe (default: console,error,system)") log.console(" --channels=ch1,ch2 Channels to subscribe (default: console,error,system)")
log.console(" --exclude=ch1,ch2 Channels to exclude (for wildcard sinks)") log.console(" --exclude=ch1,ch2 Channels to exclude (for wildcard sinks)")
log.console(" --stack=ch1,ch2 Channels that capture a stack trace (default: error)")
log.console("") log.console("")
log.console("Options for read:") log.console("Options for read:")
log.console(" --lines=N Show last N lines (default: all)") log.console(" --lines=N Show last N lines (default: all)")
@@ -80,21 +84,22 @@ function format_entry(entry) {
function do_list() { function do_list() {
var config = load_config() var config = load_config()
var names = null var names = null
if (!config || !config.sink) { names = (config && config.sink) ? array(config.sink) : []
if (length(names) == 0) {
log.console("No log sinks configured.") log.console("No log sinks configured.")
log.console("Default: console pretty for console/error/system") log.console("Default: console pretty for console/error/system (stack traces on error)")
return return
} }
names = array(config.sink)
arrfor(names, function(n) { arrfor(names, function(n) {
var s = config.sink[n] var s = config.sink[n]
var ch = is_array(s.channels) ? text(s.channels, ', ') : '(none)' var ch = is_array(s.channels) ? text(s.channels, ', ') : '(none)'
var ex = is_array(s.exclude) ? " exclude=" + text(s.exclude, ',') : "" var ex = is_array(s.exclude) ? " exclude=" + text(s.exclude, ',') : ""
var stk = is_array(s.stack) ? " stack=" + text(s.stack, ',') : ""
var fmt = s.format || (s.type == 'file' ? 'json' : 'pretty') var fmt = s.format || (s.type == 'file' ? 'json' : 'pretty')
if (s.type == 'file') if (s.type == 'file')
log.console(" " + n + ": " + s.type + " -> " + s.path + " [" + ch + "] format=" + fmt + ex) log.console(" " + n + ": " + s.type + " -> " + s.path + " [" + ch + "] format=" + fmt + ex + stk)
else else
log.console(" " + n + ": " + s.type + " [" + ch + "] format=" + fmt + ex) log.console(" " + n + ": " + s.type + " [" + ch + "] format=" + fmt + ex + stk)
}) })
} }
@@ -105,6 +110,7 @@ function do_add() {
var format = null var format = null
var channels = ["console", "error", "system"] var channels = ["console", "error", "system"]
var exclude = null var exclude = null
var stack_chs = ["error"]
var config = null var config = null
var val = null var val = null
var i = 0 var i = 0
@@ -138,13 +144,15 @@ function do_add() {
if (val) { channels = array(val, ','); continue } if (val) { channels = array(val, ','); continue }
val = parse_opt(args[i], 'exclude') val = parse_opt(args[i], 'exclude')
if (val) { exclude = array(val, ','); continue } if (val) { exclude = array(val, ','); continue }
val = parse_opt(args[i], 'stack')
if (val) { stack_chs = array(val, ','); continue }
} }
config = load_config() config = load_config()
if (!config) config = {} if (!config) config = {}
if (!config.sink) config.sink = {} if (!config.sink) config.sink = {}
config.sink[name] = {type: sink_type, format: format, channels: channels} config.sink[name] = {type: sink_type, format: format, channels: channels, stack: stack_chs}
if (path) config.sink[name].path = path if (path) config.sink[name].path = path
if (exclude) config.sink[name].exclude = exclude if (exclude) config.sink[name].exclude = exclude
@@ -165,7 +173,7 @@ function do_remove() {
log.error("Sink not found: " + name) log.error("Sink not found: " + name)
return return
} }
config.sink[name] = null delete config.sink[name]
save_config(config) save_config(config)
log.console("Removed sink: " + name) log.console("Removed sink: " + name)
} }

66
math/cycles.c Normal file
View File

@@ -0,0 +1,66 @@
#include "cell.h"
#include "cell_math.h"
#include <math.h>
#define TWOPI (2.0 * 3.14159265358979323846264338327950288419716939937510)
static JSValue js_math_cyc_arc_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, acos (x) / TWOPI);
}
static JSValue js_math_cyc_arc_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, asin (x) / TWOPI);
}
static JSValue js_math_cyc_arc_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, atan (x) / TWOPI);
}
static JSValue js_math_cyc_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, cos (x * TWOPI));
}
static JSValue js_math_cyc_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, sin (x * TWOPI));
}
static JSValue js_math_cyc_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, tan (x * TWOPI));
}
static const JSCFunctionListEntry js_math_cycles_funcs[]
= { JS_CFUNC_DEF ("arc_cosine", 1, js_math_cyc_arc_cosine),
JS_CFUNC_DEF ("arc_sine", 1, js_math_cyc_arc_sine),
JS_CFUNC_DEF ("arc_tangent", 1, js_math_cyc_arc_tangent),
JS_CFUNC_DEF ("cosine", 1, js_math_cyc_cosine),
JS_CFUNC_DEF ("sine", 1, js_math_cyc_sine),
JS_CFUNC_DEF ("tangent", 1, js_math_cyc_tangent),
JS_CFUNC_DEF ("ln", 1, js_math_ln),
JS_CFUNC_DEF ("log", 1, js_math_log10),
JS_CFUNC_DEF ("log2", 1, js_math_log2),
JS_CFUNC_DEF ("power", 2, js_math_power),
JS_CFUNC_DEF ("root", 2, js_math_root),
JS_CFUNC_DEF ("sqrt", 1, js_math_sqrt),
JS_CFUNC_DEF ("e", 1, js_math_e) };
JSValue js_core_math_cycles_use (JSContext *ctx) {
JSGCRef obj_ref;
JS_PushGCRef (ctx, &obj_ref);
obj_ref.val = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, obj_ref.val, js_math_cycles_funcs, countof (js_math_cycles_funcs));
JSValue result = obj_ref.val;
JS_PopGCRef (ctx, &obj_ref);
return result;
}

67
math/degrees.c Normal file
View File

@@ -0,0 +1,67 @@
#include "cell.h"
#include "cell_math.h"
#include <math.h>
#define DEG2RAD (3.14159265358979323846264338327950288419716939937510 / 180.0)
#define RAD2DEG (180.0 / 3.14159265358979323846264338327950288419716939937510)
static JSValue js_math_deg_arc_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, acos (x) * RAD2DEG);
}
static JSValue js_math_deg_arc_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, asin (x) * RAD2DEG);
}
static JSValue js_math_deg_arc_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, atan (x) * RAD2DEG);
}
static JSValue js_math_deg_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, cos (x * DEG2RAD));
}
static JSValue js_math_deg_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, sin (x * DEG2RAD));
}
static JSValue js_math_deg_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, tan (x * DEG2RAD));
}
static const JSCFunctionListEntry js_math_degrees_funcs[]
= { JS_CFUNC_DEF ("arc_cosine", 1, js_math_deg_arc_cosine),
JS_CFUNC_DEF ("arc_sine", 1, js_math_deg_arc_sine),
JS_CFUNC_DEF ("arc_tangent", 1, js_math_deg_arc_tangent),
JS_CFUNC_DEF ("cosine", 1, js_math_deg_cosine),
JS_CFUNC_DEF ("sine", 1, js_math_deg_sine),
JS_CFUNC_DEF ("tangent", 1, js_math_deg_tangent),
JS_CFUNC_DEF ("ln", 1, js_math_ln),
JS_CFUNC_DEF ("log", 1, js_math_log10),
JS_CFUNC_DEF ("log2", 1, js_math_log2),
JS_CFUNC_DEF ("power", 2, js_math_power),
JS_CFUNC_DEF ("root", 2, js_math_root),
JS_CFUNC_DEF ("sqrt", 1, js_math_sqrt),
JS_CFUNC_DEF ("e", 1, js_math_e) };
JSValue js_core_math_degrees_use (JSContext *ctx) {
JSGCRef obj_ref;
JS_PushGCRef (ctx, &obj_ref);
obj_ref.val = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, obj_ref.val, js_math_degrees_funcs, countof (js_math_degrees_funcs));
JSValue result = obj_ref.val;
JS_PopGCRef (ctx, &obj_ref);
return result;
}

64
math/radians.c Normal file
View File

@@ -0,0 +1,64 @@
#include "cell.h"
#include "cell_math.h"
#include <math.h>
static JSValue js_math_rad_arc_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, acos (x));
}
static JSValue js_math_rad_arc_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, asin (x));
}
static JSValue js_math_rad_arc_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, atan (x));
}
static JSValue js_math_rad_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, cos (x));
}
static JSValue js_math_rad_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, sin (x));
}
static JSValue js_math_rad_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) {
double x;
if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION;
return JS_NewFloat64 (ctx, tan (x));
}
static const JSCFunctionListEntry js_math_radians_funcs[]
= { JS_CFUNC_DEF ("arc_cosine", 1, js_math_rad_arc_cosine),
JS_CFUNC_DEF ("arc_sine", 1, js_math_rad_arc_sine),
JS_CFUNC_DEF ("arc_tangent", 1, js_math_rad_arc_tangent),
JS_CFUNC_DEF ("cosine", 1, js_math_rad_cosine),
JS_CFUNC_DEF ("sine", 1, js_math_rad_sine),
JS_CFUNC_DEF ("tangent", 1, js_math_rad_tangent),
JS_CFUNC_DEF ("ln", 1, js_math_ln),
JS_CFUNC_DEF ("log", 1, js_math_log10),
JS_CFUNC_DEF ("log2", 1, js_math_log2),
JS_CFUNC_DEF ("power", 2, js_math_power),
JS_CFUNC_DEF ("root", 2, js_math_root),
JS_CFUNC_DEF ("sqrt", 1, js_math_sqrt),
JS_CFUNC_DEF ("e", 1, js_math_e) };
JSValue js_core_math_radians_use (JSContext *ctx) {
JSGCRef obj_ref;
JS_PushGCRef (ctx, &obj_ref);
obj_ref.val = JS_NewObject (ctx);
JS_SetPropertyFunctionList (ctx, obj_ref.val, js_math_radians_funcs, countof (js_math_radians_funcs));
JSValue result = obj_ref.val;
JS_PopGCRef (ctx, &obj_ref);
return result;
}

View File

@@ -31,7 +31,7 @@ if (!filename) {
var compiled = shop.mcode_file(filename) var compiled = shop.mcode_file(filename)
if (!show_pretty) { if (!show_pretty) {
print(json.encode(compiled)) log.compile(json.encode(compiled))
$stop() $stop()
} }
@@ -68,16 +68,16 @@ var dump_function = function(func, name) {
var operands = null var operands = null
var pc_str = null var pc_str = null
var op_str = null var op_str = null
print(`\n=== ${name} (args=${text(nr_args)}, slots=${text(nr_slots)}, closures=${text(nr_close)}) ===`) log.compile(`\n=== ${name} (args=${text(nr_args)}, slots=${text(nr_slots)}, closures=${text(nr_close)}) ===`)
if (instrs == null || length(instrs) == 0) { if (instrs == null || length(instrs) == 0) {
print(" (empty)") log.compile(" (empty)")
return null return null
} }
while (i < length(instrs)) { while (i < length(instrs)) {
instr = instrs[i] instr = instrs[i]
if (is_text(instr)) { if (is_text(instr)) {
if (!starts_with(instr, "_nop_")) { if (!starts_with(instr, "_nop_")) {
print(`${instr}:`) log.compile(`${instr}:`)
} }
} else if (is_array(instr)) { } else if (is_array(instr)) {
op = instr[0] op = instr[0]
@@ -91,7 +91,7 @@ var dump_function = function(func, name) {
operands = text(parts, ", ") operands = text(parts, ", ")
pc_str = pad_right(text(pc), 5) pc_str = pad_right(text(pc), 5)
op_str = pad_right(op, 14) op_str = pad_right(op, 14)
print(` ${pc_str} ${op_str} ${operands}`) log.compile(` ${pc_str} ${op_str} ${operands}`)
pc = pc + 1 pc = pc + 1
} }
i = i + 1 i = i + 1

415
mcode.cm
View File

@@ -88,6 +88,7 @@ var mcode = function(ast) {
var s_cur_col = 0 var s_cur_col = 0
var s_filename = null var s_filename = null
var s_has_disruption = false var s_has_disruption = false
var s_slot_types = {}
// Shared closure vars for binop helpers (avoids >4 param functions) // Shared closure vars for binop helpers (avoids >4 param functions)
var _bp_dest = 0 var _bp_dest = 0
@@ -116,7 +117,8 @@ var mcode = function(ast) {
intrinsic_cache: s_intrinsic_cache, intrinsic_cache: s_intrinsic_cache,
cur_line: s_cur_line, cur_line: s_cur_line,
cur_col: s_cur_col, cur_col: s_cur_col,
has_disruption: s_has_disruption has_disruption: s_has_disruption,
slot_types: s_slot_types
} }
} }
@@ -138,6 +140,7 @@ var mcode = function(ast) {
s_cur_line = saved.cur_line s_cur_line = saved.cur_line
s_cur_col = saved.cur_col s_cur_col = saved.cur_col
s_has_disruption = saved.has_disruption s_has_disruption = saved.has_disruption
s_slot_types = saved.slot_types
} }
// Slot allocation // Slot allocation
@@ -330,20 +333,48 @@ var mcode = function(ast) {
return node.kind == "null" return node.kind == "null"
} }
// Slot-type tracking helpers
var slot_is_num = function(slot) {
var t = s_slot_types[text(slot)]
return t == "num" || t == "int"
}
var slot_is_text = function(slot) {
return s_slot_types[text(slot)] == "text"
}
var mark_slot = function(slot, typ) {
s_slot_types[text(slot)] = typ
}
var propagate_slot = function(dest, src) {
s_slot_types[text(dest)] = s_slot_types[text(src)]
}
// emit_add_decomposed: emit type-dispatched add (text → concat, num → add) // emit_add_decomposed: emit type-dispatched add (text → concat, num → add)
// reads _bp_dest, _bp_left, _bp_right, _bp_ln, _bp_rn from closure // reads _bp_dest, _bp_left, _bp_right, _bp_ln, _bp_rn from closure
var emit_add_decomposed = function() { var emit_add_decomposed = function() {
if (is_known_text(_bp_ln) && is_known_text(_bp_rn)) { var left_is_num = is_known_number(_bp_ln) || slot_is_num(_bp_left)
var left_is_text = is_known_text(_bp_ln) || slot_is_text(_bp_left)
var right_is_num = is_known_number(_bp_rn) || slot_is_num(_bp_right)
var right_is_text = is_known_text(_bp_rn) || slot_is_text(_bp_right)
// Both known text → concat
if (left_is_text && right_is_text) {
emit_3("concat", _bp_dest, _bp_left, _bp_right) emit_3("concat", _bp_dest, _bp_left, _bp_right)
mark_slot(_bp_dest, "text")
return null return null
} }
if (is_known_number(_bp_ln) && is_known_number(_bp_rn)) { // Both known number → add
if (left_is_num && right_is_num) {
emit_3("add", _bp_dest, _bp_left, _bp_right) emit_3("add", _bp_dest, _bp_left, _bp_right)
mark_slot(_bp_dest, "num")
return null return null
} }
// If either operand is a known number, concat is impossible // One known number, other unknown → emit_numeric_binop (guard on unknown side)
if (is_known_number(_bp_ln) || is_known_number(_bp_rn)) { if (left_is_num || right_is_num) {
emit_numeric_binop("add") emit_numeric_binop("add")
mark_slot(_bp_dest, "num")
return null return null
} }
// Unknown types: emit full dispatch // Unknown types: emit full dispatch
@@ -380,18 +411,24 @@ var mcode = function(ast) {
// emit_numeric_binop: emit type-guarded numeric binary op // emit_numeric_binop: emit type-guarded numeric binary op
// reads _bp_dest, _bp_left, _bp_right, _bp_ln, _bp_rn from closure // reads _bp_dest, _bp_left, _bp_right, _bp_ln, _bp_rn from closure
var emit_numeric_binop = function(op_str) { var emit_numeric_binop = function(op_str) {
if (is_known_number(_bp_ln) && is_known_number(_bp_rn)) { var left_known = is_known_number(_bp_ln) || slot_is_num(_bp_left)
var right_known = is_known_number(_bp_rn) || slot_is_num(_bp_right)
if (left_known && right_known) {
emit_3(op_str, _bp_dest, _bp_left, _bp_right) emit_3(op_str, _bp_dest, _bp_left, _bp_right)
mark_slot(_bp_dest, "num")
return null return null
} }
var t0 = alloc_slot() var t0 = alloc_slot()
var t1 = alloc_slot()
var err = gen_label("num_err") var err = gen_label("num_err")
var done = gen_label("num_done") var done = gen_label("num_done")
emit_2("is_num", t0, _bp_left) if (!left_known) {
emit_jump_cond("jump_false", t0, err) emit_2("is_num", t0, _bp_left)
emit_2("is_num", t1, _bp_right) emit_jump_cond("jump_false", t0, err)
emit_jump_cond("jump_false", t1, err) }
if (!right_known) {
emit_2("is_num", t0, _bp_right)
emit_jump_cond("jump_false", t0, err)
}
emit_3(op_str, _bp_dest, _bp_left, _bp_right) emit_3(op_str, _bp_dest, _bp_left, _bp_right)
emit_jump(done) emit_jump(done)
@@ -399,238 +436,33 @@ var mcode = function(ast) {
emit_log_error("cannot apply '" + _bp_op_sym + "': operands must be numbers") emit_log_error("cannot apply '" + _bp_op_sym + "': operands must be numbers")
emit_0("disrupt") emit_0("disrupt")
emit_label(done) emit_label(done)
mark_slot(_bp_dest, "num")
return null return null
} }
// emit_eq_decomposed: identical -> int -> float -> text -> null -> bool -> mismatch(false) // emit_eq_decomposed: VM eq handles all types (int fast path, text memcmp, identity, mixed→false)
// reads _bp_dest, _bp_left, _bp_right from closure
var emit_eq_decomposed = function() { var emit_eq_decomposed = function() {
var dest = _bp_dest emit_3("eq", _bp_dest, _bp_left, _bp_right)
var left = _bp_left
var right = _bp_right
var t0 = 0
var t1 = 0
var done = gen_label("eq_done")
var not_int = gen_label("eq_ni")
var not_num = gen_label("eq_nn")
var not_text = gen_label("eq_nt")
var not_null = gen_label("eq_nnl")
var not_bool = gen_label("eq_nb")
// Identical check
emit_3("is_identical", dest, left, right)
emit_jump_cond("jump_true", dest, done)
// Int path
t0 = alloc_slot()
emit_2("is_int", t0, left)
emit_jump_cond("jump_false", t0, not_int)
t1 = alloc_slot()
emit_2("is_int", t1, right)
emit_jump_cond("jump_false", t1, not_int)
emit_3("eq_int", dest, left, right)
emit_jump(done)
// Float path
emit_label(not_int)
emit_2("is_num", t0, left)
emit_jump_cond("jump_false", t0, not_num)
emit_2("is_num", t1, right)
emit_jump_cond("jump_false", t1, not_num)
emit_3("eq_float", dest, left, right)
emit_jump(done)
// Text path
emit_label(not_num)
emit_2("is_text", t0, left)
emit_jump_cond("jump_false", t0, not_text)
emit_2("is_text", t1, right)
emit_jump_cond("jump_false", t1, not_text)
emit_3("eq_text", dest, left, right)
emit_jump(done)
// Null path
emit_label(not_text)
emit_2("is_null", t0, left)
emit_jump_cond("jump_false", t0, not_null)
emit_2("is_null", t1, right)
emit_jump_cond("jump_false", t1, not_null)
emit_1("true", dest)
emit_jump(done)
// Bool path
emit_label(not_null)
emit_2("is_bool", t0, left)
emit_jump_cond("jump_false", t0, not_bool)
emit_2("is_bool", t1, right)
emit_jump_cond("jump_false", t1, not_bool)
emit_3("eq_bool", dest, left, right)
emit_jump(done)
// Mismatch -> false
emit_label(not_bool)
emit_1("false", dest)
emit_label(done)
return null return null
} }
// emit_ne_decomposed: identical -> int -> float -> text -> null -> bool -> mismatch(true) // emit_ne_decomposed: VM ne handles all types (int fast path, text memcmp, identity, mixed→true)
// reads _bp_dest, _bp_left, _bp_right from closure
var emit_ne_decomposed = function() { var emit_ne_decomposed = function() {
var dest = _bp_dest emit_3("ne", _bp_dest, _bp_left, _bp_right)
var left = _bp_left
var right = _bp_right
var t0 = 0
var t1 = 0
var done = gen_label("ne_done")
var not_ident = gen_label("ne_nid")
var not_int = gen_label("ne_ni")
var not_num = gen_label("ne_nn")
var not_text = gen_label("ne_nt")
var not_null = gen_label("ne_nnl")
var not_bool = gen_label("ne_nb")
// Identical -> false
emit_3("is_identical", dest, left, right)
emit_jump_cond("jump_true", dest, not_ident)
// If jump_true doesn't fire, dest already holds false, continue to checks
emit_jump(not_int)
emit_label(not_ident)
emit_1("false", dest)
emit_jump(done)
// Int path
emit_label(not_int)
t0 = alloc_slot()
emit_2("is_int", t0, left)
emit_jump_cond("jump_false", t0, not_num)
t1 = alloc_slot()
emit_2("is_int", t1, right)
emit_jump_cond("jump_false", t1, not_num)
emit_3("ne_int", dest, left, right)
emit_jump(done)
// Float path
emit_label(not_num)
emit_2("is_num", t0, left)
emit_jump_cond("jump_false", t0, not_text)
emit_2("is_num", t1, right)
emit_jump_cond("jump_false", t1, not_text)
emit_3("ne_float", dest, left, right)
emit_jump(done)
// Text path
emit_label(not_text)
emit_2("is_text", t0, left)
emit_jump_cond("jump_false", t0, not_null)
emit_2("is_text", t1, right)
emit_jump_cond("jump_false", t1, not_null)
emit_3("ne_text", dest, left, right)
emit_jump(done)
// Null path
emit_label(not_null)
emit_2("is_null", t0, left)
emit_jump_cond("jump_false", t0, not_bool)
emit_2("is_null", t1, right)
emit_jump_cond("jump_false", t1, not_bool)
emit_1("false", dest)
emit_jump(done)
// Bool path
var mismatch = gen_label("ne_mis")
emit_label(not_bool)
emit_2("is_bool", t0, left)
emit_jump_cond("jump_false", t0, mismatch)
emit_2("is_bool", t1, right)
emit_jump_cond("jump_false", t1, mismatch)
emit_3("ne_bool", dest, left, right)
emit_jump(done)
// Mismatch -> true (ne of different types is true)
emit_label(mismatch)
emit_1("true", dest)
emit_label(done)
return null return null
} }
// emit_relational: int -> float -> text -> disrupt // emit_relational: VM lt/le/gt/ge handle numbers and text, disrupt on mismatch
// reads _bp_dest, _bp_left, _bp_right, _bp_ln, _bp_rn from closure var emit_relational = function(op_str) {
var emit_relational = function(int_op, float_op, text_op) { emit_3(op_str, _bp_dest, _bp_left, _bp_right)
var dest = _bp_dest
var left = _bp_left
var right = _bp_right
var t0 = 0
var t1 = 0
var left_is_int = is_known_int(_bp_ln)
var left_is_num = is_known_number(_bp_ln)
var left_is_text = is_known_text(_bp_ln)
var right_is_int = is_known_int(_bp_rn)
var right_is_num = is_known_number(_bp_rn)
var right_is_text = is_known_text(_bp_rn)
var not_int = null
var not_num = null
var done = null
var err = null
// Both known int
if (left_is_int && right_is_int) {
emit_3(int_op, dest, left, right)
return null
}
// Both known number
if (left_is_num && right_is_num) {
emit_3(float_op, dest, left, right)
return null
}
// Both known text
if (left_is_text && right_is_text) {
emit_3(text_op, dest, left, right)
return null
}
not_int = gen_label("rel_ni")
not_num = gen_label("rel_nn")
done = gen_label("rel_done")
err = gen_label("rel_err")
t0 = alloc_slot()
emit_2("is_int", t0, left)
emit_jump_cond("jump_false", t0, not_int)
t1 = alloc_slot()
emit_2("is_int", t1, right)
emit_jump_cond("jump_false", t1, not_int)
emit_3(int_op, dest, left, right)
emit_jump(done)
emit_label(not_int)
emit_2("is_num", t0, left)
emit_jump_cond("jump_false", t0, not_num)
emit_2("is_num", t1, right)
emit_jump_cond("jump_false", t1, not_num)
emit_3(float_op, dest, left, right)
emit_jump(done)
emit_label(not_num)
emit_2("is_text", t0, left)
emit_jump_cond("jump_false", t0, err)
emit_2("is_text", t1, right)
emit_jump_cond("jump_false", t1, err)
emit_3(text_op, dest, left, right)
emit_jump(done)
emit_label(err)
emit_log_error("cannot compare with '" + _bp_op_sym + "': operands must be same type")
emit_0("disrupt")
emit_label(done)
return null return null
} }
// emit_neg_decomposed: emit type-guarded negate // emit_neg_decomposed: emit type-guarded negate
var emit_neg_decomposed = function(dest, src, src_node) { var emit_neg_decomposed = function(dest, src, src_node) {
if (is_known_number(src_node)) { if (is_known_number(src_node) || slot_is_num(src)) {
emit_2("negate", dest, src) emit_2("negate", dest, src)
mark_slot(dest, "num")
return null return null
} }
var t0 = alloc_slot() var t0 = alloc_slot()
@@ -645,19 +477,13 @@ var mcode = function(ast) {
emit_log_error("cannot negate: operand must be a number") emit_log_error("cannot negate: operand must be a number")
emit_0("disrupt") emit_0("disrupt")
emit_label(done) emit_label(done)
mark_slot(dest, "num")
return null return null
} }
// Central router: maps op string to decomposition helper // Central router: maps op string to decomposition helper
// Sets _bp_* closure vars then calls helper with reduced args // Sets _bp_* closure vars then calls helper with reduced args
var relational_ops = {
lt: ["lt_int", "lt_float", "lt_text"],
le: ["le_int", "le_float", "le_text"],
gt: ["gt_int", "gt_float", "gt_text"],
ge: ["ge_int", "ge_float", "ge_text"]
}
var emit_binop = function(op_str, dest, left, right) { var emit_binop = function(op_str, dest, left, right) {
var rel = null
_bp_dest = dest _bp_dest = dest
_bp_left = left _bp_left = left
_bp_right = right _bp_right = right
@@ -668,18 +494,15 @@ var mcode = function(ast) {
emit_eq_decomposed() emit_eq_decomposed()
} else if (op_str == "ne") { } else if (op_str == "ne") {
emit_ne_decomposed() emit_ne_decomposed()
} else if (op_str == "lt" || op_str == "le" || op_str == "gt" || op_str == "ge") {
emit_relational(op_str)
} else if (op_str == "subtract" || op_str == "multiply" ||
op_str == "divide" || op_str == "modulo" || op_str == "remainder" ||
op_str == "pow") {
emit_numeric_binop(op_str)
} else { } else {
rel = relational_ops[op_str] // Passthrough for bitwise, in, etc.
if (rel != null) { emit_3(op_str, dest, left, right)
emit_relational(rel[0], rel[1], rel[2])
} else if (op_str == "subtract" || op_str == "multiply" ||
op_str == "divide" || op_str == "modulo" || op_str == "remainder" ||
op_str == "pow") {
emit_numeric_binop(op_str)
} else {
// Passthrough for bitwise, in, etc.
emit_3(op_str, dest, left, right)
}
} }
return null return null
} }
@@ -716,9 +539,6 @@ var mcode = function(ast) {
var argc = length(args) var argc = length(args)
var frame_slot = alloc_slot() var frame_slot = alloc_slot()
emit_3("frame", frame_slot, func_slot, argc) emit_3("frame", frame_slot, func_slot, argc)
var null_slot = alloc_slot()
emit_1("null", null_slot)
emit_3("setarg", frame_slot, 0, null_slot)
var arg_idx = 1 var arg_idx = 1
var _i = 0 var _i = 0
while (_i < argc) { while (_i < argc) {
@@ -951,6 +771,7 @@ var mcode = function(ast) {
} }
// Scan scope record for variable declarations // Scan scope record for variable declarations
// Closure locals are assigned first so returned frames can be shortened
var scan_scope = function() { var scan_scope = function() {
var scope = find_scope_record(s_function_nr) var scope = find_scope_record(s_function_nr)
if (scope == null) { if (scope == null) {
@@ -963,6 +784,9 @@ var mcode = function(ast) {
var make = null var make = null
var is_const = false var is_const = false
var slot = 0 var slot = 0
// Pass 1: closure locals first
_i = 0
while (_i < length(keys)) { while (_i < length(keys)) {
name = keys[_i] name = keys[_i]
if (name == "function_nr" || name == "nr_close_slots") { if (name == "function_nr" || name == "nr_close_slots") {
@@ -975,14 +799,36 @@ var mcode = function(ast) {
_i = _i + 1 _i = _i + 1
continue continue
} }
if (find_var(name) < 0) { if (v.closure == true && find_var(name) < 0) {
is_const = (make == "def" || make == "function")
slot = 1 + s_nr_args + s_nr_local_slots
s_nr_local_slots = s_nr_local_slots + 1
s_nr_close_slots = s_nr_close_slots + 1
add_var(name, slot, is_const)
s_vars[length(s_vars) - 1].is_closure = true
}
_i = _i + 1
}
// Pass 2: non-closure locals
_i = 0
while (_i < length(keys)) {
name = keys[_i]
if (name == "function_nr" || name == "nr_close_slots") {
_i = _i + 1
continue
}
v = scope[name]
make = v.make
if (make == null || make == "input") {
_i = _i + 1
continue
}
if (v.closure != true && find_var(name) < 0) {
is_const = (make == "def" || make == "function") is_const = (make == "def" || make == "function")
slot = 1 + s_nr_args + s_nr_local_slots slot = 1 + s_nr_args + s_nr_local_slots
s_nr_local_slots = s_nr_local_slots + 1 s_nr_local_slots = s_nr_local_slots + 1
add_var(name, slot, is_const) add_var(name, slot, is_const)
if (v.closure == true) {
s_vars[length(s_vars) - 1].is_closure = true
}
} }
_i = _i + 1 _i = _i + 1
} }
@@ -1034,20 +880,20 @@ var mcode = function(ast) {
emit_1("null", null_s) emit_1("null", null_s)
emit_label(loop_label) emit_label(loop_label)
if (forward) { if (forward) {
emit_3("lt_int", check, i, len) emit_3("lt", check, i, len)
} else { } else {
emit_3("ge_int", check, i, zero) emit_3("ge", check, i, zero)
} }
emit_jump_cond("jump_false", check, done_label) emit_jump_cond("jump_false", check, done_label)
emit_3("load_index", item, arr_slot, i) emit_3("load_index", item, arr_slot, i)
emit_3("eq_int", arity_is_zero, fn_arity, zero) emit_3("eq", arity_is_zero, fn_arity, zero)
emit_jump_cond("jump_false", arity_is_zero, call_one_label) emit_jump_cond("jump_false", arity_is_zero, call_one_label)
emit_3("frame", f, fn_slot, 0) emit_3("frame", f, fn_slot, 0)
emit_3("setarg", f, 0, null_s) emit_3("setarg", f, 0, null_s)
emit_2("invoke", f, acc) emit_2("invoke", f, acc)
emit_jump(call_done_label) emit_jump(call_done_label)
emit_label(call_one_label) emit_label(call_one_label)
emit_3("eq_int", arity_is_one, fn_arity, one) emit_3("eq", arity_is_one, fn_arity, one)
emit_jump_cond("jump_false", arity_is_one, call_two_label) emit_jump_cond("jump_false", arity_is_one, call_two_label)
emit_3("frame", f, fn_slot, 1) emit_3("frame", f, fn_slot, 1)
emit_3("setarg", f, 0, null_s) emit_3("setarg", f, 0, null_s)
@@ -1095,17 +941,17 @@ var mcode = function(ast) {
emit_1("null", null_s) emit_1("null", null_s)
emit_2("length", fn_arity, fn_slot) emit_2("length", fn_arity, fn_slot)
emit_label(loop_label) emit_label(loop_label)
emit_3("lt_int", check, i, len) emit_3("lt", check, i, len)
emit_jump_cond("jump_false", check, done_label) emit_jump_cond("jump_false", check, done_label)
emit_3("load_index", item, arr_slot, i) emit_3("load_index", item, arr_slot, i)
emit_3("eq_int", arity_is_zero, fn_arity, zero) emit_3("eq", arity_is_zero, fn_arity, zero)
emit_jump_cond("jump_false", arity_is_zero, call_one_label) emit_jump_cond("jump_false", arity_is_zero, call_one_label)
emit_3("frame", f, fn_slot, 0) emit_3("frame", f, fn_slot, 0)
emit_3("setarg", f, 0, null_s) emit_3("setarg", f, 0, null_s)
emit_2("invoke", f, discard) emit_2("invoke", f, discard)
emit_jump(call_done_label) emit_jump(call_done_label)
emit_label(call_one_label) emit_label(call_one_label)
emit_3("eq_int", arity_is_one, fn_arity, one) emit_3("eq", arity_is_one, fn_arity, one)
emit_jump_cond("jump_false", arity_is_one, call_two_label) emit_jump_cond("jump_false", arity_is_one, call_two_label)
emit_3("frame", f, fn_slot, 1) emit_3("frame", f, fn_slot, 1)
emit_3("setarg", f, 0, null_s) emit_3("setarg", f, 0, null_s)
@@ -1152,10 +998,10 @@ var mcode = function(ast) {
emit_1("null", null_s) emit_1("null", null_s)
emit_2("length", fn_arity, fn_slot) emit_2("length", fn_arity, fn_slot)
emit_label(loop_label) emit_label(loop_label)
emit_3("lt_int", check, i, len) emit_3("lt", check, i, len)
emit_jump_cond("jump_false", check, ret_true) emit_jump_cond("jump_false", check, ret_true)
emit_3("load_index", item, arr_slot, i) emit_3("load_index", item, arr_slot, i)
emit_3("eq_int", arity_is_zero, fn_arity, zero) emit_3("eq", arity_is_zero, fn_arity, zero)
emit_jump_cond("jump_false", arity_is_zero, call_one_label) emit_jump_cond("jump_false", arity_is_zero, call_one_label)
emit_3("frame", f, fn_slot, 0) emit_3("frame", f, fn_slot, 0)
emit_3("setarg", f, 0, null_s) emit_3("setarg", f, 0, null_s)
@@ -1205,10 +1051,10 @@ var mcode = function(ast) {
emit_1("null", null_s) emit_1("null", null_s)
emit_2("length", fn_arity, fn_slot) emit_2("length", fn_arity, fn_slot)
emit_label(loop_label) emit_label(loop_label)
emit_3("lt_int", check, i, len) emit_3("lt", check, i, len)
emit_jump_cond("jump_false", check, ret_false) emit_jump_cond("jump_false", check, ret_false)
emit_3("load_index", item, arr_slot, i) emit_3("load_index", item, arr_slot, i)
emit_3("eq_int", arity_is_zero, fn_arity, zero) emit_3("eq", arity_is_zero, fn_arity, zero)
emit_jump_cond("jump_false", arity_is_zero, call_one_label) emit_jump_cond("jump_false", arity_is_zero, call_one_label)
emit_3("frame", f, fn_slot, 0) emit_3("frame", f, fn_slot, 0)
emit_3("setarg", f, 0, null_s) emit_3("setarg", f, 0, null_s)
@@ -1261,17 +1107,17 @@ var mcode = function(ast) {
emit_1("null", null_s) emit_1("null", null_s)
emit_2("length", fn_arity, fn_slot) emit_2("length", fn_arity, fn_slot)
emit_label(loop_label) emit_label(loop_label)
emit_3("lt_int", check, i, len) emit_3("lt", check, i, len)
emit_jump_cond("jump_false", check, done_label) emit_jump_cond("jump_false", check, done_label)
emit_3("load_index", item, arr_slot, i) emit_3("load_index", item, arr_slot, i)
emit_3("eq_int", arity_is_zero, fn_arity, zero) emit_3("eq", arity_is_zero, fn_arity, zero)
emit_jump_cond("jump_false", arity_is_zero, call_one_label) emit_jump_cond("jump_false", arity_is_zero, call_one_label)
emit_3("frame", f, fn_slot, 0) emit_3("frame", f, fn_slot, 0)
emit_3("setarg", f, 0, null_s) emit_3("setarg", f, 0, null_s)
emit_2("invoke", f, val) emit_2("invoke", f, val)
emit_jump(call_done_label) emit_jump(call_done_label)
emit_label(call_one_label) emit_label(call_one_label)
emit_3("eq_int", arity_is_one, fn_arity, one) emit_3("eq", arity_is_one, fn_arity, one)
emit_jump_cond("jump_false", arity_is_one, call_two_label) emit_jump_cond("jump_false", arity_is_one, call_two_label)
emit_3("frame", f, fn_slot, 1) emit_3("frame", f, fn_slot, 1)
emit_3("setarg", f, 0, null_s) emit_3("setarg", f, 0, null_s)
@@ -1326,7 +1172,7 @@ var mcode = function(ast) {
if (nargs == 2) { if (nargs == 2) {
null_label = gen_label("reduce_null") null_label = gen_label("reduce_null")
d1 = gen_label("reduce_d1") d1 = gen_label("reduce_d1")
emit_3("lt_int", check, zero, len) emit_3("lt", check, zero, len)
emit_jump_cond("jump_false", check, null_label) emit_jump_cond("jump_false", check, null_label)
emit_3("load_index", acc, arr_slot, zero) emit_3("load_index", acc, arr_slot, zero)
emit_2("move", i, one) emit_2("move", i, one)
@@ -1345,7 +1191,7 @@ var mcode = function(ast) {
emit_2("is_null", check, init_slot) emit_2("is_null", check, init_slot)
emit_jump_cond("jump_false", check, has_init) emit_jump_cond("jump_false", check, has_init)
// No initial, forward // No initial, forward
emit_3("lt_int", check, zero, len) emit_3("lt", check, zero, len)
emit_jump_cond("jump_false", check, null_label) emit_jump_cond("jump_false", check, null_label)
emit_3("load_index", acc, arr_slot, zero) emit_3("load_index", acc, arr_slot, zero)
emit_2("move", i, one) emit_2("move", i, one)
@@ -1377,7 +1223,7 @@ var mcode = function(ast) {
emit_2("is_null", check, init_slot) emit_2("is_null", check, init_slot)
emit_jump_cond("jump_false", check, has_init) emit_jump_cond("jump_false", check, has_init)
// No initial // No initial
emit_3("lt_int", check, zero, len) emit_3("lt", check, zero, len)
emit_jump_cond("jump_false", check, null_label) emit_jump_cond("jump_false", check, null_label)
emit_jump_cond("jump_true", rev_slot, no_init_rev) emit_jump_cond("jump_true", rev_slot, no_init_rev)
// No initial, forward // No initial, forward
@@ -1488,8 +1334,10 @@ var mcode = function(ast) {
// Standard binary ops // Standard binary ops
left_slot = gen_expr(left, -1) left_slot = gen_expr(left, -1)
right_slot = gen_expr(right, -1) right_slot = gen_expr(right, -1)
// Use target slot for ops without multi-type dispatch (add has text+num paths) // Use target slot for ops without multi-type dispatch (add has text+num paths).
dest = (target >= 0 && kind != "+") ? target : alloc_slot() // Exception: allow + to write directly to target when target == left_slot
// (self-assign pattern like s = s + x) since concat/add reads before writing.
dest = (target >= 0 && (kind != "+" || target == left_slot)) ? target : alloc_slot()
op = binop_map[kind] op = binop_map[kind]
if (op == null) { if (op == null) {
op = "add" op = "add"
@@ -1555,6 +1403,7 @@ var mcode = function(ast) {
local = find_var(name) local = find_var(name)
if (local >= 0) { if (local >= 0) {
emit_2("move", local, dest) emit_2("move", local, dest)
propagate_slot(local, dest)
} }
} else if (level > 0) { } else if (level > 0) {
_lv = level - 1 _lv = level - 1
@@ -1657,6 +1506,7 @@ var mcode = function(ast) {
val_slot = gen_expr(right, slot) val_slot = gen_expr(right, slot)
if (val_slot != slot) { if (val_slot != slot) {
emit_2("move", slot, val_slot) emit_2("move", slot, val_slot)
propagate_slot(slot, val_slot)
} }
return val_slot return val_slot
} }
@@ -1781,6 +1631,7 @@ var mcode = function(ast) {
if (kind == "number") { if (kind == "number") {
slot = target >= 0 ? target : alloc_slot() slot = target >= 0 ? target : alloc_slot()
emit_const_num(slot, expr.number) emit_const_num(slot, expr.number)
mark_slot(slot, is_integer(expr.number) ? "int" : "num")
return slot return slot
} }
if (kind == "text") { if (kind == "text") {
@@ -1790,6 +1641,7 @@ var mcode = function(ast) {
val = "" val = ""
} }
emit_const_str(slot, val) emit_const_str(slot, val)
mark_slot(slot, "text")
return slot return slot
} }
// Template literal // Template literal
@@ -1826,6 +1678,7 @@ var mcode = function(ast) {
// Call format(fmt_str, array) // Call format(fmt_str, array)
result_slot = target >= 0 ? target : alloc_slot() result_slot = target >= 0 ? target : alloc_slot()
emit_call(result_slot, fmt_func_slot, [fmt_str_slot, arr_slot]) emit_call(result_slot, fmt_func_slot, [fmt_str_slot, arr_slot])
mark_slot(result_slot, "text")
return result_slot return result_slot
} }
if (kind == "regexp") { if (kind == "regexp") {
@@ -1844,16 +1697,19 @@ var mcode = function(ast) {
if (kind == "true") { if (kind == "true") {
slot = target >= 0 ? target : alloc_slot() slot = target >= 0 ? target : alloc_slot()
emit_const_bool(slot, true) emit_const_bool(slot, true)
mark_slot(slot, "bool")
return slot return slot
} }
if (kind == "false") { if (kind == "false") {
slot = target >= 0 ? target : alloc_slot() slot = target >= 0 ? target : alloc_slot()
emit_const_bool(slot, false) emit_const_bool(slot, false)
mark_slot(slot, "bool")
return slot return slot
} }
if (kind == "null") { if (kind == "null") {
slot = target >= 0 ? target : alloc_slot() slot = target >= 0 ? target : alloc_slot()
emit_const_null(slot) emit_const_null(slot)
mark_slot(slot, null)
return slot return slot
} }
if (kind == "this") { if (kind == "this") {
@@ -2738,17 +2594,17 @@ var mcode = function(ast) {
var disrupt_clause = func_node.disruption var disrupt_clause = func_node.disruption
var null_slot2 = null var null_slot2 = null
var fn_name = func_node.name var fn_name = func_node.name
var fn_scope = null
var nr_cs = 0
var result = null var result = null
var saved_label = 0 var saved_label = 0
var saved_func = 0 var saved_func = 0
var captured_this = 0
push(parent_states, saved) push(parent_states, saved)
s_instructions = [] s_instructions = []
s_vars = [] s_vars = []
s_intrinsic_cache = [] s_intrinsic_cache = []
s_slot_types = {}
s_loop_break = null s_loop_break = null
s_loop_continue = null s_loop_continue = null
s_label_map = {} s_label_map = {}
@@ -2794,6 +2650,13 @@ var mcode = function(ast) {
s_max_slot = s_next_temp_slot s_max_slot = s_next_temp_slot
} }
// Arrow functions capture the enclosing this via closure
if (is_arrow) {
captured_this = alloc_slot()
emit_3("get", captured_this, saved.this_slot, 1)
s_this_slot = captured_this
}
// Default parameter initialization // Default parameter initialization
ps = 1 ps = 1
_i = 0 _i = 0
@@ -2880,15 +2743,10 @@ var mcode = function(ast) {
fn_name = "<anonymous>" fn_name = "<anonymous>"
} }
fn_scope = find_scope_record(s_function_nr)
if (fn_scope != null && fn_scope.nr_close_slots != null) {
nr_cs = fn_scope.nr_close_slots
}
result = { result = {
name: fn_name, name: fn_name,
nr_args: nr_params, nr_args: nr_params,
nr_close_slots: nr_cs, nr_close_slots: s_nr_close_slots,
nr_slots: s_max_slot + 1, nr_slots: s_max_slot + 1,
disruption_pc: disruption_start, disruption_pc: disruption_start,
instructions: s_instructions instructions: s_instructions
@@ -2945,6 +2803,7 @@ var mcode = function(ast) {
s_max_slot = 1 s_max_slot = 1
s_label_counter = 0 s_label_counter = 0
s_func_counter = 0 s_func_counter = 0
s_slot_types = {}
s_loop_break = null s_loop_break = null
s_loop_continue = null s_loop_continue = null
s_label_map = {} s_label_map = {}

View File

@@ -64,11 +64,18 @@ src += ['qbe_helpers.c']
src += ['qbe_backend.c'] src += ['qbe_backend.c']
scripts = [ scripts = [
'json.c',
'internal/nota.c',
'internal/wota.c',
'math/radians.c',
'math/degrees.c',
'math/cycles.c',
'src/cell_math.c',
'debug/js.c', 'debug/js.c',
'qop.c', 'internal/qop.c',
'wildstar.c', 'internal/wildstar.c',
'fit.c', 'fit.c',
'crypto.c', 'internal/crypto.c',
'internal/kim.c', 'internal/kim.c',
'internal/time.c', 'internal/time.c',
'debug/debug.c', 'debug/debug.c',
@@ -76,7 +83,6 @@ scripts = [
'internal/fd.c', 'internal/fd.c',
'net/http.c', 'net/http.c',
'net/enet.c', 'net/enet.c',
'wildstar.c',
'archive/miniz.c', 'archive/miniz.c',
'source/cJSON.c' 'source/cJSON.c'
] ]
@@ -86,7 +92,7 @@ foreach file: scripts
endforeach endforeach
srceng = 'source' srceng = 'source'
includes = [srceng, 'internal', 'debug', 'net', 'archive', 'src/qbe'] includes = [srceng, 'internal', 'debug', 'net', 'archive', 'src/qbe', 'src']
foreach file : src foreach file : src
full_path = join_paths(srceng, file) full_path = join_paths(srceng, file)
@@ -140,7 +146,7 @@ foreach inc : includes
includers += include_directories(inc) includers += include_directories(inc)
endforeach endforeach
qbe_c_args = ['-x', 'c'] qbe_c_args = ['-x', 'c', '-Wno-deprecated-declarations']
qbe_lib = static_library('qbe', qbe_lib = static_library('qbe',
qbe_files, qbe_files,
include_directories: includers, include_directories: includers,

View File

@@ -146,7 +146,6 @@ static JSValue js_enet_host_service(JSContext *ctx, JSValueConst this_val, int a
if (!host) return JS_EXCEPTION; if (!host) return JS_EXCEPTION;
if (argc < 1 || !JS_IsFunction(argv[0])) return JS_RaiseDisrupt(ctx, "Expected a callback function as first argument"); if (argc < 1 || !JS_IsFunction(argv[0])) return JS_RaiseDisrupt(ctx, "Expected a callback function as first argument");
JSValue callback = JS_DupValue(ctx, argv[0]);
double secs; double secs;
JS_ToFloat64(ctx, &secs, argv[1]); JS_ToFloat64(ctx, &secs, argv[1]);
@@ -186,7 +185,6 @@ static JSValue js_enet_host_service(JSContext *ctx, JSValueConst this_val, int a
JS_FreeValue(ctx, event_obj); JS_FreeValue(ctx, event_obj);
} }
JS_FreeValue(ctx, callback);
return JS_NULL; return JS_NULL;
} }

View File

@@ -226,7 +226,10 @@ static int par_easycurl_to_memory(char const *url, par_byte **data, int *nbytes)
CFRelease(cfurlRef); CFRelease(cfurlRef);
if (!request) return 0; if (!request) return 0;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
CFReadStreamRef stream = CFReadStreamCreateForHTTPRequest(NULL, request); CFReadStreamRef stream = CFReadStreamCreateForHTTPRequest(NULL, request);
#pragma clang diagnostic pop
CFRelease(request); CFRelease(request);
if (!stream) return 0; if (!stream) return 0;

View File

@@ -333,19 +333,15 @@ JSC_CCALL(socket_recv,
flags = js2number(js, argv[2]); flags = js2number(js, argv[2]);
} }
void *buf = malloc(len); void *out;
if (!buf) { ret = js_new_blob_alloc(js, len, &out);
return JS_RaiseDisrupt(js, "malloc failed"); if (JS_IsException(ret)) return ret;
}
ssize_t received = recv(sockfd, out, len, flags);
ssize_t received = recv(sockfd, buf, len, flags); if (received < 0)
if (received < 0) {
free(buf);
return JS_RaiseDisrupt(js, "recv failed: %s", strerror(errno)); return JS_RaiseDisrupt(js, "recv failed: %s", strerror(errno));
}
js_blob_stone(ret, received);
ret = js_new_blob_stoned_copy(js, buf, received);
free(buf);
return ret; return ret;
) )
@@ -421,25 +417,20 @@ JSC_CCALL(socket_recvfrom,
flags = js2number(js, argv[2]); flags = js2number(js, argv[2]);
} }
void *buf = malloc(len); void *out;
if (!buf) { JSValue blob = js_new_blob_alloc(js, len, &out);
return JS_RaiseDisrupt(js, "malloc failed"); if (JS_IsException(blob)) return blob;
}
struct sockaddr_storage from_addr; struct sockaddr_storage from_addr;
socklen_t from_len = sizeof from_addr; socklen_t from_len = sizeof from_addr;
ssize_t received = recvfrom(sockfd, buf, len, flags, ssize_t received = recvfrom(sockfd, out, len, flags,
(struct sockaddr *)&from_addr, &from_len); (struct sockaddr *)&from_addr, &from_len);
if (received < 0) { if (received < 0)
free(buf);
return JS_RaiseDisrupt(js, "recvfrom failed: %s", strerror(errno)); return JS_RaiseDisrupt(js, "recvfrom failed: %s", strerror(errno));
}
js_blob_stone(blob, received);
ret = JS_NewObject(js);
JS_SetPropertyStr(js, ret, "data", js_new_blob_stoned_copy(js, buf, received));
free(buf);
// Get source address info // Get source address info
char ipstr[INET6_ADDRSTRLEN]; char ipstr[INET6_ADDRSTRLEN];
int port; int port;
@@ -452,11 +443,15 @@ JSC_CCALL(socket_recvfrom,
inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof ipstr); inet_ntop(AF_INET6, &s->sin6_addr, ipstr, sizeof ipstr);
port = ntohs(s->sin6_port); port = ntohs(s->sin6_port);
} }
JSValue addr_info = JS_NewObject(js); JS_FRAME(js);
JS_SetPropertyStr(js, addr_info, "address", JS_NewString(js, ipstr)); JS_ROOT(ret_r, JS_NewObject(js));
JS_SetPropertyStr(js, addr_info, "port", JS_NewInt32(js, port)); JS_SetPropertyStr(js, ret_r.val, "data", blob);
JS_SetPropertyStr(js, ret, "address", addr_info); JS_ROOT(addr_info, JS_NewObject(js));
JS_SetPropertyStr(js, addr_info.val, "address", JS_NewString(js, ipstr));
JS_SetPropertyStr(js, addr_info.val, "port", JS_NewInt32(js, port));
JS_SetPropertyStr(js, ret_r.val, "address", addr_info.val);
JS_RETURN(ret_r.val);
) )
JSC_CCALL(socket_shutdown, JSC_CCALL(socket_shutdown,

View File

@@ -6,16 +6,6 @@ var link = use('link')
var global_shop_path = runtime.shop_path var global_shop_path = runtime.shop_path
// Convert package name to a safe directory name
// For absolute paths (local packages), replace / with _
// For remote packages, keep slashes as they use nested directories
function safe_package_path(pkg) {
if (!pkg) return pkg
if (starts_with(pkg, '/'))
return replace(replace(pkg, '/', '_'), '@', '_')
return replace(pkg, '@', '_')
}
function get_path(name) function get_path(name)
{ {
// If name is null, return the current project directory // If name is null, return the current project directory
@@ -33,11 +23,11 @@ function get_path(name)
if (starts_with(link_target, '/')) if (starts_with(link_target, '/'))
return link_target return link_target
// Otherwise it's another package name, resolve that // Otherwise it's another package name, resolve that
return global_shop_path + '/packages/' + replace(replace(link_target, '/', '_'), '@', '_') return global_shop_path + '/packages/' + fd.safe_package_path(link_target)
} }
// Remote packages use nested directories, so don't transform slashes // Remote packages use nested directories, so don't transform slashes
return global_shop_path + '/packages/' + replace(name, '@', '_') return global_shop_path + '/packages/' + fd.safe_package_path(name)
} }
var config_cache = {} var config_cache = {}
@@ -50,7 +40,7 @@ package.load_config = function(name)
var config_path = get_path(name) + '/cell.toml' var config_path = get_path(name) + '/cell.toml'
if (!fd.is_file(config_path)) { if (!fd.is_file(config_path)) {
print(`${config_path} does not exist`); disrupt log.error(`${config_path} does not exist`); disrupt
} }
var content = text(fd.slurp(config_path)) var content = text(fd.slurp(config_path))
@@ -59,7 +49,7 @@ package.load_config = function(name)
var result = toml.decode(content) var result = toml.decode(content)
if (!result) { if (!result) {
print(`TOML decode returned null for ${config_path}`) log.error(`TOML decode returned null for ${config_path}`)
return {} return {}
} }
@@ -202,43 +192,32 @@ package.gather_dependencies = function(name)
return array(all_deps) return array(all_deps)
} }
// Recursively find all cell packages (dirs with cell.toml) under a directory
package.find_packages = function(dir) {
var found = []
var list = fd.readdir(dir)
if (!list) return found
if (fd.is_file(dir + '/cell.toml'))
push(found, dir)
arrfor(list, function(item) {
if (item == '.' || item == '..' || item == '.cell' || item == '.git') return
var full = dir + '/' + item
var st = fd.stat(full)
var sub = null
if (st && st.isDirectory) {
sub = package.find_packages(full)
arrfor(sub, function(p) {
push(found, p)
})
}
})
return found
}
package.list_files = function(pkg) { package.list_files = function(pkg) {
var dir = get_path(pkg) var dir = get_path(pkg)
if (!fd.is_dir(dir)) return []
var files = [] return fd.globfs(["**/*", "!.*"], dir)
var walk = function(current_dir, current_prefix) {
var list = fd.readdir(current_dir)
if (!list) return
var i = 0
var item = null
var full_path = null
var rel_path = null
var st = null
for (i = 0; i < length(list); i++) {
item = list[i]
if (item == '.' || item == '..') continue
if (starts_with(item, '.')) continue
// Skip build directories in root
full_path = current_dir + "/" + item
rel_path = current_prefix ? current_prefix + "/" + item : item
st = fd.stat(full_path)
if (st.isDirectory) {
walk(full_path, rel_path)
} else {
push(files, rel_path)
}
}
}
if (fd.is_dir(dir)) {
walk(dir, "")
}
return files
} }
package.list_modules = function(name) { package.list_modules = function(name) {
@@ -386,12 +365,9 @@ package.get_c_files = function(name, target, exclude_main) {
}) })
// Exclude src/ files (support files, not modules) // Exclude src/ files (support files, not modules)
var sources = package.get_sources(name) result = filter(result, function(f) {
if (length(sources) > 0) { return !starts_with(f, 'src/')
result = filter(result, function(f) { })
return find(sources, function(s) { return s == f }) == null
})
}
return result return result
} }

View File

@@ -4,4 +4,4 @@ var json = use("json")
var shop = use("internal/shop") var shop = use("internal/shop")
var filename = args[0] var filename = args[0]
var ast = shop.parse_file(filename) var ast = shop.parse_file(filename)
print(json.encode(ast, true)) log.compile(json.encode(ast, true))

View File

@@ -364,6 +364,7 @@ var parse = function(tokens, src, filename, tokenizer) {
advance() advance()
left = parse_assign_expr() left = parse_assign_expr()
pair.left = left pair.left = left
pair.computed = true
if (tok.kind == "]") advance() if (tok.kind == "]") advance()
else parse_error(tok, "expected ']' after computed property") else parse_error(tok, "expected ']' after computed property")
} else { } else {
@@ -1626,6 +1627,10 @@ var parse = function(tokens, src, filename, tokenizer) {
if (r.v != null) { if (r.v != null) {
left_node.level = r.level left_node.level = r.level
left_node.function_nr = r.def_function_nr left_node.function_nr = r.def_function_nr
if (r.level > 0) {
r.v.nr_uses = r.v.nr_uses + 1
r.v.closure = 1
}
} else { } else {
left_node.level = -1 left_node.level = -1
} }
@@ -1717,6 +1722,10 @@ var parse = function(tokens, src, filename, tokenizer) {
if (r.v != null) { if (r.v != null) {
operand.level = r.level operand.level = r.level
operand.function_nr = r.def_function_nr operand.function_nr = r.def_function_nr
if (r.level > 0) {
r.v.nr_uses = r.v.nr_uses + 1
r.v.closure = 1
}
} else { } else {
operand.level = -1 operand.level = -1
} }
@@ -1727,6 +1736,13 @@ var parse = function(tokens, src, filename, tokenizer) {
return null return null
} }
if (kind == "this") {
if (scope.function_nr == 0) {
sem_error(expr, "'this' cannot be used at the top level of a program")
}
return null
}
if (kind == "[") { if (kind == "[") {
sem_check_expr(scope, expr.left) sem_check_expr(scope, expr.left)
sem_check_expr(scope, expr.right) sem_check_expr(scope, expr.right)
@@ -1790,6 +1806,9 @@ var parse = function(tokens, src, filename, tokenizer) {
prop = expr.list[i] prop = expr.list[i]
val = prop.right val = prop.right
sem_check_expr(scope, val) sem_check_expr(scope, val)
if (prop.computed) {
sem_check_expr(scope, prop.left)
}
i = i + 1 i = i + 1
} }
return null return null

View File

@@ -78,15 +78,13 @@ JSC_CCALL(file_read,
if (!pd_file) return JS_RaiseDisrupt(js, "file not initialized"); if (!pd_file) return JS_RaiseDisrupt(js, "file not initialized");
SDFile *f = js2sdfile(js, argv[0]); SDFile *f = js2sdfile(js, argv[0]);
unsigned int len = (unsigned int)js2number(js, argv[1]); unsigned int len = (unsigned int)js2number(js, argv[1]);
void *buf = malloc(len); void *out;
if (!buf) return JS_RaiseDisrupt(js, "malloc failed"); JSValue blob = js_new_blob_alloc(js, len, &out);
int read = pd_file->read(f, buf, len); if (JS_IsException(blob)) return blob;
if (read < 0) { int bytes_read = pd_file->read(f, out, len);
free(buf); if (bytes_read < 0)
return JS_NULL; return JS_NULL;
} js_blob_stone(blob, bytes_read);
JSValue blob = js_new_blob_stoned_copy(js, buf, read);
free(buf);
return blob; return blob;
) )

View File

@@ -139,15 +139,13 @@ JSC_CCALL(http_read,
if (!pd_network || !pd_network->http) return JS_RaiseDisrupt(js, "network not initialized"); if (!pd_network || !pd_network->http) return JS_RaiseDisrupt(js, "network not initialized");
HTTPConnection *conn = js2http(js, argv[0]); HTTPConnection *conn = js2http(js, argv[0]);
unsigned int buflen = (unsigned int)js2number(js, argv[1]); unsigned int buflen = (unsigned int)js2number(js, argv[1]);
void *buf = malloc(buflen); void *out;
if (!buf) return JS_RaiseDisrupt(js, "malloc failed"); JSValue blob = js_new_blob_alloc(js, buflen, &out);
int read = pd_network->http->read(conn, buf, buflen); if (JS_IsException(blob)) return blob;
if (read < 0) { int bytes_read = pd_network->http->read(conn, out, buflen);
free(buf); if (bytes_read < 0)
return JS_NULL; return JS_NULL;
} js_blob_stone(blob, bytes_read);
JSValue blob = js_new_blob_stoned_copy(js, buf, read);
free(buf);
return blob; return blob;
) )
@@ -218,15 +216,13 @@ JSC_CCALL(tcp_read,
if (!pd_network || !pd_network->tcp) return JS_RaiseDisrupt(js, "network not initialized"); if (!pd_network || !pd_network->tcp) return JS_RaiseDisrupt(js, "network not initialized");
TCPConnection *conn = js2tcp(js, argv[0]); TCPConnection *conn = js2tcp(js, argv[0]);
size_t len = (size_t)js2number(js, argv[1]); size_t len = (size_t)js2number(js, argv[1]);
void *buf = malloc(len); void *out;
if (!buf) return JS_RaiseDisrupt(js, "malloc failed"); JSValue blob = js_new_blob_alloc(js, len, &out);
int read = pd_network->tcp->read(conn, buf, len); if (JS_IsException(blob)) return blob;
if (read < 0) { int bytes_read = pd_network->tcp->read(conn, out, len);
free(buf); if (bytes_read < 0)
return JS_NewInt32(js, read); // Return error code return JS_NewInt32(js, bytes_read); // Return error code
} js_blob_stone(blob, bytes_read);
JSValue blob = js_new_blob_stoned_copy(js, buf, read);
free(buf);
return blob; return blob;
) )

View File

@@ -15,14 +15,14 @@ function is_requestor(fn) {
function check_requestors(list, factory) { function check_requestors(list, factory) {
if (!is_array(list) || some(list, r => !is_requestor(r))) { if (!is_array(list) || some(list, r => !is_requestor(r))) {
print(make_reason(factory, 'Bad requestor array.', list).message + '\n') log.error(make_reason(factory, 'Bad requestor array.', list).message)
disrupt disrupt
} }
} }
function check_callback(cb, factory) { function check_callback(cb, factory) {
if (!is_function(cb) || length(cb) != 2) { if (!is_function(cb) || length(cb) != 2) {
print(make_reason(factory, 'Not a callback.', cb).message + '\n') log.error(make_reason(factory, 'Not a callback.', cb).message)
disrupt disrupt
} }
} }
@@ -32,7 +32,7 @@ function check_callback(cb, factory) {
function fallback(requestor_array) { function fallback(requestor_array) {
def factory = 'fallback' def factory = 'fallback'
if (!is_array(requestor_array) || length(requestor_array) == 0) { if (!is_array(requestor_array) || length(requestor_array) == 0) {
print(make_reason(factory, 'Empty requestor array.').message + '\n') log.error(make_reason(factory, 'Empty requestor array.').message)
disrupt disrupt
} }
check_requestors(requestor_array, factory) check_requestors(requestor_array, factory)
@@ -89,7 +89,7 @@ function fallback(requestor_array) {
function parallel(requestor_array, throttle, need) { function parallel(requestor_array, throttle, need) {
def factory = 'parallel' def factory = 'parallel'
if (!is_array(requestor_array)) { if (!is_array(requestor_array)) {
print(make_reason(factory, 'Not an array.', requestor_array).message + '\n') log.error(make_reason(factory, 'Not an array.', requestor_array).message)
disrupt disrupt
} }
check_requestors(requestor_array, factory) check_requestors(requestor_array, factory)
@@ -101,12 +101,12 @@ function parallel(requestor_array, throttle, need) {
var _need = need var _need = need
if (_need == null) _need = len if (_need == null) _need = len
if (!is_number(_need) || _need < 0 || _need > len) { if (!is_number(_need) || _need < 0 || _need > len) {
print(make_reason(factory, 'Bad need.', _need).message + '\n') log.error(make_reason(factory, 'Bad need.', _need).message)
disrupt disrupt
} }
if (throttle != null && (!is_number(throttle) || throttle < 1)) { if (throttle != null && (!is_number(throttle) || throttle < 1)) {
print(make_reason(factory, 'Bad throttle.', throttle).message + '\n') log.error(make_reason(factory, 'Bad throttle.', throttle).message)
disrupt disrupt
} }
@@ -184,7 +184,7 @@ function parallel(requestor_array, throttle, need) {
function race(requestor_array, throttle, need) { function race(requestor_array, throttle, need) {
def factory = 'race' def factory = 'race'
if (!is_array(requestor_array) || length(requestor_array) == 0) { if (!is_array(requestor_array) || length(requestor_array) == 0) {
print(make_reason(factory, 'Empty requestor array.').message + '\n') log.error(make_reason(factory, 'Empty requestor array.').message)
disrupt disrupt
} }
check_requestors(requestor_array, factory) check_requestors(requestor_array, factory)
@@ -193,12 +193,12 @@ function race(requestor_array, throttle, need) {
var _need = need var _need = need
if (_need == null) _need = 1 if (_need == null) _need = 1
if (!is_number(_need) || _need < 1 || _need > len) { if (!is_number(_need) || _need < 1 || _need > len) {
print(make_reason(factory, 'Bad need.', _need).message + '\n') log.error(make_reason(factory, 'Bad need.', _need).message)
disrupt disrupt
} }
if (throttle != null && (!is_number(throttle) || throttle < 1)) { if (throttle != null && (!is_number(throttle) || throttle < 1)) {
print(make_reason(factory, 'Bad throttle.', throttle).message + '\n') log.error(make_reason(factory, 'Bad throttle.', throttle).message)
disrupt disrupt
} }
@@ -279,7 +279,7 @@ function race(requestor_array, throttle, need) {
function sequence(requestor_array) { function sequence(requestor_array) {
def factory = 'sequence' def factory = 'sequence'
if (!is_array(requestor_array)) { if (!is_array(requestor_array)) {
print(make_reason(factory, 'Not an array.', requestor_array).message + '\n') log.error(make_reason(factory, 'Not an array.', requestor_array).message)
disrupt disrupt
} }
check_requestors(requestor_array, factory) check_requestors(requestor_array, factory)
@@ -339,7 +339,7 @@ function sequence(requestor_array) {
function requestorize(unary) { function requestorize(unary) {
def factory = 'requestorize' def factory = 'requestorize'
if (!is_function(unary)) { if (!is_function(unary)) {
print(make_reason(factory, 'Not a function.', unary).message + '\n') log.error(make_reason(factory, 'Not a function.', unary).message)
disrupt disrupt
} }

2
qbe.ce
View File

@@ -17,4 +17,4 @@ var folded = fold(ast)
var compiled = mcode(folded) var compiled = mcode(folded)
var optimized = streamline(compiled) var optimized = streamline(compiled)
var il = qbe_emit(optimized, qbe_macros) var il = qbe_emit(optimized, qbe_macros)
print(il) log.compile(il)

View File

@@ -1,7 +1,7 @@
// cell qopconv - Convert QOP archive formats // cell qopconv - Convert QOP archive formats
var fd = use('fd') var fd = use('fd')
var qop = use('qop') var qop = use('internal/qop')
function print_usage() { function print_usage() {
log.console("Usage: qopconv [OPTION...] FILE...") log.console("Usage: qopconv [OPTION...] FILE...")

137
query.ce
View File

@@ -1,29 +1,39 @@
// cell query — Semantic queries across packages. // cell query — Semantic queries across packages.
// //
// Usage: // Usage:
// cell query --this [--top|--fn] [<package>] this references // cell query <name> Find all references to <name>
// cell query --intrinsic <name> [<package>] Find built-in intrinsic usage // cell query <name> --top Top-level references only
// cell query --decl <name> [<package>] Variable declarations by name // cell query <name> --fn Inside-function references only
// cell query --help Show usage // cell query <name> --channels Show <name>.<prop> usage summary
// cell query --decl <name> Find declarations of <name>
// cell query --decl <name> --fn Only function declarations
// cell query --intrinsic <name> Find intrinsic usage
// cell query --excess-args Find call sites with >4 args
// cell query --help
var shop = use('internal/shop') var shop = use('internal/shop')
var query_mod = use('query') var analyze_mod = use('analyze')
var fd = use('fd') var fd = use('fd')
var mode = null var mode = null
var name = null var name = null
var this_scope = null var scope_filter = null
var kind_filter = null
var pkg_filter = null var pkg_filter = null
var show_help = false var show_help = false
var i = 0 var i = 0
for (i = 0; i < length(args); i++) { for (i = 0; i < length(args); i++) {
if (args[i] == '--this') { if (args[i] == '--top') {
mode = "this" scope_filter = "top"
} else if (args[i] == '--top') {
this_scope = "top"
} else if (args[i] == '--fn') { } else if (args[i] == '--fn') {
this_scope = "fn" if (mode == "decl") {
kind_filter = "fn"
} else {
scope_filter = "fn"
}
} else if (args[i] == '--channels') {
mode = "channels"
} else if (args[i] == '--intrinsic') { } else if (args[i] == '--intrinsic') {
mode = "intrinsic" mode = "intrinsic"
if (i + 1 < length(args) && !starts_with(args[i + 1], '-')) { if (i + 1 < length(args) && !starts_with(args[i + 1], '-')) {
@@ -42,13 +52,26 @@ for (i = 0; i < length(args); i++) {
log.error('--decl requires a name') log.error('--decl requires a name')
mode = "error" mode = "error"
} }
} else if (args[i] == '--excess-args') {
mode = "excess_args"
} else if (args[i] == '--help' || args[i] == '-h') { } else if (args[i] == '--help' || args[i] == '-h') {
show_help = true show_help = true
} else if (!starts_with(args[i], '-')) { } else if (!starts_with(args[i], '-')) {
pkg_filter = args[i] if (name == null && mode == null) {
name = args[i]
mode = "refs"
} else {
pkg_filter = args[i]
}
} }
} }
// --channels requires a name from positional arg
if (mode == "channels" && name == null) {
log.error('--channels requires a name (e.g., cell query log --channels)')
mode = "error"
}
var all_files = null var all_files = null
var files = [] var files = []
var j = 0 var j = 0
@@ -56,6 +79,10 @@ var idx = null
var hits = null var hits = null
var hit = null var hit = null
var k = 0 var k = 0
var ch_result = null
var props = null
var prop = null
var parts = null
// Use return pattern to avoid closure-over-object issue with disruption. // Use return pattern to avoid closure-over-object issue with disruption.
var safe_index = function(path) { var safe_index = function(path) {
@@ -65,21 +92,24 @@ var safe_index = function(path) {
} }
if (show_help) { if (show_help) {
log.console("Usage: cell query [options] [<package>]") log.console("Usage: cell query [options] [<name>] [<package>]")
log.console("") log.console("")
log.console("Semantic queries across packages.") log.console("Semantic queries across packages.")
log.console("") log.console("")
log.console("Options:") log.console("Commands:")
log.console(" --this All this references") log.console(" <name> Find all references to <name>")
log.console(" --this --top Top-level this only (not inside functions)") log.console(" <name> --top Top-level references only")
log.console(" --this --fn this inside functions only") log.console(" <name> --fn Inside-function references only")
log.console(" --intrinsic <name> Find built-in intrinsic usage (e.g., print)") log.console(" <name> --channels Show <name>.<prop> usage summary")
log.console(" --decl <name> Variable declarations by name") log.console(" --decl <name> Find declarations of <name>")
log.console(" --decl <name> --fn Only function declarations")
log.console(" --intrinsic <name> Find intrinsic usage")
log.console(" --excess-args Find call sites with >4 args")
log.console("") log.console("")
log.console("Without a package argument, searches all installed packages.") log.console("Without a package argument, searches all installed packages.")
} else if (mode == null || mode == "error") { } else if (mode == null || mode == "error") {
if (mode != "error") { if (mode != "error") {
log.error('Specify --this, --intrinsic, or --decl. Use --help for usage.') log.error('Specify a name or --decl/--intrinsic/--excess-args. Use --help for usage.')
} }
} else { } else {
all_files = shop.all_script_paths() all_files = shop.all_script_paths()
@@ -98,22 +128,61 @@ if (show_help) {
idx = safe_index(files[j].full_path) idx = safe_index(files[j].full_path)
if (idx == null) continue if (idx == null) continue
hits = null if (mode == "refs") {
if (mode == "this") { hits = analyze_mod.find_refs(idx, name, scope_filter)
hits = query_mod.find_this(idx, this_scope) if (hits != null && length(hits) > 0) {
for (k = 0; k < length(hits); k++) {
hit = hits[k]
if (hit.span != null) {
if (hit.enclosing != null) {
log.console(`${files[j].package}:${files[j].rel_path}:${text(hit.span.from_row)}:${text(hit.span.from_col)}: ${hit.name} (in: ${hit.enclosing})`)
} else {
log.console(`${files[j].package}:${files[j].rel_path}:${text(hit.span.from_row)}:${text(hit.span.from_col)}: ${hit.name} (top-level)`)
}
}
}
}
} else if (mode == "channels") {
ch_result = analyze_mod.channels(idx, name)
if (ch_result != null && ch_result.summary != null) {
props = array(ch_result.summary)
if (length(props) > 0) {
parts = []
for (k = 0; k < length(props); k++) {
prop = props[k]
parts[] = `${prop}(${text(ch_result.summary[prop])})`
}
log.console(`${files[j].package}:${files[j].rel_path}: ${text(parts, " ")}`)
}
}
} else if (mode == "intrinsic") { } else if (mode == "intrinsic") {
hits = query_mod.intrinsic(idx, name) hits = analyze_mod.find_intrinsic(idx, name)
if (hits != null && length(hits) > 0) {
for (k = 0; k < length(hits); k++) {
hit = hits[k]
if (hit.span != null) {
log.console(`${files[j].package}:${files[j].rel_path}:${text(hit.span.from_row)}:${text(hit.span.from_col)}: ${hit.name}`)
}
}
}
} else if (mode == "decl") { } else if (mode == "decl") {
hits = query_mod.find_decl(idx, name, null) hits = analyze_mod.find_decls(idx, name, kind_filter)
} if (hits != null && length(hits) > 0) {
for (k = 0; k < length(hits); k++) {
if (hits != null && length(hits) > 0) { hit = hits[k]
for (k = 0; k < length(hits); k++) { if (hit.decl_span != null) {
hit = hits[k] log.console(`${files[j].package}:${files[j].rel_path}:${text(hit.decl_span.from_row)}:${text(hit.decl_span.from_col)}: ${hit.kind} ${hit.name}`)
if (hit.span != null) { }
log.console(`${files[j].package}:${files[j].rel_path}:${text(hit.span.from_row)}:${text(hit.span.from_col)}: ${hit.name}`) }
} else if (hit.decl_span != null) { }
log.console(`${files[j].package}:${files[j].rel_path}:${text(hit.decl_span.from_row)}:${text(hit.decl_span.from_col)}: ${hit.kind} ${hit.name}`) } else if (mode == "excess_args") {
hits = analyze_mod.excess_args(idx)
if (hits != null && length(hits) > 0) {
for (k = 0; k < length(hits); k++) {
hit = hits[k]
if (hit.span != null) {
log.console(`${files[j].package}:${files[j].rel_path}:${text(hit.span.from_row)}:${text(hit.span.from_col)}: ${hit.callee}() called with ${text(hit.args_count)} args (max 4)`)
}
} }
} }
} }

View File

@@ -1,62 +1,19 @@
// query.cm — Semantic queries over index data. // query.cm — Backward-compatible wrapper delegating to analyze.cm.
//
// All functions take an index object (from index.cm) and return arrays of hits. var analyze = use('analyze')
var query = {} var query = {}
// Find this references. scope: "top" (top-level only), "fn" (in functions), null (all).
query.find_this = function(idx, scope) { query.find_this = function(idx, scope) {
var hits = [] return analyze.find_refs(idx, "this", scope)
var i = 0
var ref = null
while (i < length(idx.references)) {
ref = idx.references[i]
if (ref.name == "this") {
if (scope == null) {
hits[] = ref
} else if (scope == "top" && ref.enclosing == null) {
hits[] = ref
} else if (scope == "fn" && ref.enclosing != null) {
hits[] = ref
}
}
i = i + 1
}
return hits
} }
// Intrinsic usage: find refs to a built-in name (e.g., print).
query.intrinsic = function(idx, name) { query.intrinsic = function(idx, name) {
var hits = [] return analyze.find_intrinsic(idx, name)
var i = 0
var ref = null
if (idx.intrinsic_refs == null) return hits
while (i < length(idx.intrinsic_refs)) {
ref = idx.intrinsic_refs[i]
if (ref.name == name) {
hits[] = ref
}
i = i + 1
}
return hits
} }
// Variable declarations matching a name and optional kind filter.
// kind is one of "var", "def", "fn", "param", or null (any).
query.find_decl = function(idx, name, kind) { query.find_decl = function(idx, name, kind) {
var hits = [] return analyze.find_decls(idx, name, kind)
var i = 0
var sym = null
while (i < length(idx.symbols)) {
sym = idx.symbols[i]
if (sym.name == name) {
if (kind == null || sym.kind == kind) {
hits[] = sym
}
}
i = i + 1
}
return hits
} }
return query return query

View File

@@ -1,6 +1,6 @@
var rnd = {} var rnd = {}
var os = use('os') var os = use('internal/os')
rnd.random = function() rnd.random = function()
{ {

168
remove.ce
View File

@@ -17,95 +17,91 @@ var target_pkg = null
var prune = false var prune = false
var dry_run = false var dry_run = false
var i = 0 var i = 0
var resolved = null
for (i = 0; i < length(args); i++) { var run = function() {
if (args[i] == '--prune') { for (i = 0; i < length(args); i++) {
prune = true if (args[i] == '--prune') {
} else if (args[i] == '--dry-run') { prune = true
dry_run = true } else if (args[i] == '--dry-run') {
} else if (args[i] == '--help' || args[i] == '-h') { dry_run = true
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell remove <locator> [options]")
log.console("")
log.console("Remove a package from the shop.")
log.console("")
log.console("Options:")
log.console(" --prune Also remove packages no longer needed by any root")
log.console(" --dry-run Show what would be removed")
return
} else if (!starts_with(args[i], '-')) {
target_pkg = args[i]
}
}
if (!target_pkg) {
log.console("Usage: cell remove <locator> [options]") log.console("Usage: cell remove <locator> [options]")
log.console("") return
log.console("Remove a package from the shop.") }
log.console("")
log.console("Options:") target_pkg = shop.resolve_locator(target_pkg)
log.console(" --prune Also remove packages no longer needed by any root")
log.console(" --dry-run Show what would be removed") var packages_to_remove = [target_pkg]
$stop()
} else if (!starts_with(args[i], '-')) { var lock = null
target_pkg = args[i] var all_packages = null
var needed = null
if (prune) {
// Find packages no longer needed
// Get all dependencies of remaining packages
lock = shop.load_lock()
all_packages = shop.list_packages()
// Build set of all needed packages (excluding target)
needed = {}
arrfor(all_packages, function(p) {
if (p == target_pkg || p == 'core') return
// Mark this package and its deps as needed
needed[p] = true
var _gather = function() {
var deps = pkg.gather_dependencies(p)
arrfor(deps, function(dep) {
needed[dep] = true
})
} disruption {
// Skip if can't read deps
}
_gather()
})
// Find packages that are NOT needed
arrfor(all_packages, function(p) {
if (p == 'core') return
if (!needed[p] && find(packages_to_remove, p) == null) {
push(packages_to_remove, p)
}
})
}
if (dry_run) {
log.console("Would remove:")
arrfor(packages_to_remove, function(p) {
log.console(" " + p)
})
} else {
arrfor(packages_to_remove, function(p) {
// Remove any link for this package
if (link.is_linked(p)) {
link.remove(p)
}
// Remove from shop
shop.remove(p)
})
log.console("Removed " + text(length(packages_to_remove)) + " package(s).")
} }
} }
run()
if (!target_pkg) {
log.console("Usage: cell remove <locator> [options]")
$stop()
}
// Resolve relative paths to absolute paths
if (target_pkg == '.' || starts_with(target_pkg, './') || starts_with(target_pkg, '../') || fd.is_dir(target_pkg)) {
resolved = fd.realpath(target_pkg)
if (resolved) {
target_pkg = resolved
}
}
var packages_to_remove = [target_pkg]
var lock = null
var all_packages = null
var needed = null
if (prune) {
// Find packages no longer needed
// Get all dependencies of remaining packages
lock = shop.load_lock()
all_packages = shop.list_packages()
// Build set of all needed packages (excluding target)
needed = {}
arrfor(all_packages, function(p) {
if (p == target_pkg || p == 'core') return
// Mark this package and its deps as needed
needed[p] = true
var _gather = function() {
var deps = pkg.gather_dependencies(p)
arrfor(deps, function(dep) {
needed[dep] = true
})
} disruption {
// Skip if can't read deps
}
_gather()
})
// Find packages that are NOT needed
arrfor(all_packages, function(p) {
if (p == 'core') return
if (!needed[p] && find(packages_to_remove, p) == null) {
push(packages_to_remove, p)
}
})
}
if (dry_run) {
log.console("Would remove:")
arrfor(packages_to_remove, function(p) {
log.console(" " + p)
})
} else {
arrfor(packages_to_remove, function(p) {
// Remove any link for this package
if (link.is_linked(p)) {
link.remove(p)
}
// Remove from shop
shop.remove(p)
})
log.console("Removed " + text(length(packages_to_remove)) + " package(s).")
}
$stop() $stop()

View File

@@ -21,34 +21,34 @@ var target_triple = null
var show_locked = false var show_locked = false
var refresh_first = false var refresh_first = false
var i = 0 var i = 0
var resolved = null
for (i = 0; i < length(args); i++) { var run = function() {
if (args[i] == '--target' || args[i] == '-t') { for (i = 0; i < length(args); i++) {
if (i + 1 < length(args)) { if (args[i] == '--target' || args[i] == '-t') {
target_triple = args[++i] if (i + 1 < length(args)) {
} else { target_triple = args[++i]
log.error('--target requires a triple') } else {
$stop() log.error('--target requires a triple')
return
}
} else if (args[i] == '--locked') {
show_locked = true
} else if (args[i] == '--refresh') {
refresh_first = true
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell resolve [<locator>] [options]")
log.console("")
log.console("Print the fully resolved dependency closure.")
log.console("")
log.console("Options:")
log.console(" --target <triple> Annotate builds for target platform")
log.console(" --locked Show lock state without applying links")
log.console(" --refresh Refresh floating refs before printing")
return
} else if (!starts_with(args[i], '-')) {
target_locator = args[i]
} }
} else if (args[i] == '--locked') {
show_locked = true
} else if (args[i] == '--refresh') {
refresh_first = true
} else if (args[i] == '--help' || args[i] == '-h') {
log.console("Usage: cell resolve [<locator>] [options]")
log.console("")
log.console("Print the fully resolved dependency closure.")
log.console("")
log.console("Options:")
log.console(" --target <triple> Annotate builds for target platform")
log.console(" --locked Show lock state without applying links")
log.console(" --refresh Refresh floating refs before printing")
$stop()
} else if (!starts_with(args[i], '-')) {
target_locator = args[i]
} }
}
// Default to current directory // Default to current directory
if (!target_locator) { if (!target_locator) {
@@ -56,23 +56,18 @@ if (!target_locator) {
} }
// Resolve local paths // Resolve local paths
if (target_locator == '.' || starts_with(target_locator, './') || starts_with(target_locator, '../') || fd.is_dir(target_locator)) { target_locator = shop.resolve_locator(target_locator)
resolved = fd.realpath(target_locator)
if (resolved) {
target_locator = resolved
}
}
// Check if it's a valid package // Check if it's a valid package
var pkg_dir = null var pkg_dir = null
if (!fd.is_file(target_locator + '/cell.toml')) { if (!fd.is_file(target_locator + '/cell.toml')) {
// Try to find it in the shop // Try to find it in the shop
pkg_dir = shop.get_package_dir(target_locator) pkg_dir = shop.get_package_dir(target_locator)
if (!fd.is_file(pkg_dir + '/cell.toml')) { if (!fd.is_file(pkg_dir + '/cell.toml')) {
log.error("Not a valid package: " + target_locator) log.error("Not a valid package: " + target_locator)
$stop() return
}
} }
}
// Detect target if not specified // Detect target if not specified
if (!target_triple) { if (!target_triple) {
@@ -216,7 +211,9 @@ for (i = 0; i < length(sorted); i++) {
} }
} }
log.console("") log.console("")
log.console("Total: " + text(length(sorted)) + " package(s)") log.console("Total: " + text(length(sorted)) + " package(s)")
}
run()
$stop() $stop()

View File

@@ -7,7 +7,7 @@ var shop = use('internal/shop')
var fd = use('fd') var fd = use('fd')
if (length(args) < 1) { if (length(args) < 1) {
print('usage: cell run_aot <program.ce>') log.compile('usage: cell run_aot <program.ce>')
return return
} }
@@ -16,7 +16,7 @@ if (!fd.is_file(file)) {
if (!ends_with(file, '.ce') && fd.is_file(file + '.ce')) if (!ends_with(file, '.ce') && fd.is_file(file + '.ce'))
file = file + '.ce' file = file + '.ce'
else { else {
print('file not found: ' + file) log.error('file not found: ' + file)
return return
} }
} }

Some files were not shown because too many files have changed in this diff Show More