Merge branch 'master' into fix_aot
This commit is contained in:
47
CLAUDE.md
47
CLAUDE.md
@@ -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
|
||||||
|
|
||||||
|
|||||||
2
Makefile
2
Makefile
@@ -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
271
add.ce
@@ -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
144
analyze.cm
Normal 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
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
52
audit.ce
52
audit.ce
@@ -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()
|
||||||
|
|||||||
2
bench.ce
2
bench.ce
@@ -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')
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 ---
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
28755
boot/fold.cm.mcode
28755
boot/fold.cm.mcode
File diff suppressed because one or more lines are too long
41871
boot/mcode.cm.mcode
41871
boot/mcode.cm.mcode
File diff suppressed because one or more lines are too long
44268
boot/parse.cm.mcode
44268
boot/parse.cm.mcode
File diff suppressed because one or more lines are too long
41274
boot/streamline.cm.mcode
41274
boot/streamline.cm.mcode
File diff suppressed because one or more lines are too long
11016
boot/tokenize.cm.mcode
11016
boot/tokenize.cm.mcode
File diff suppressed because one or more lines are too long
74
boot_miscompile_bad.cm
Normal file
74
boot_miscompile_bad.cm
Normal 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
116
build.ce
@@ -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
569
build.cm
@@ -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
|
||||||
|
|||||||
24
cellfs.cm
24
cellfs.cm
@@ -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
456
cfg.ce
Normal 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()
|
||||||
75
clean.ce
75
clean.ce
@@ -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
113
clone.ce
@@ -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()
|
||||||
|
|||||||
@@ -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')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
55
diff.ce
@@ -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
310
diff_ir.ce
Normal 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
265
disasm.ce
Normal 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()
|
||||||
@@ -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 |
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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) |
|
||||||
|
|||||||
@@ -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).
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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) |
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
22
fd.cm
@@ -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
113
fetch.ce
@@ -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()
|
||||||
|
|||||||
2
fold.ce
2
fold.ce
@@ -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
53
fold.cm
@@ -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
48
fuzz.ce
@@ -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) {
|
||||||
|
|||||||
73
graph.ce
73
graph.ce
@@ -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
189
imports.ce
Normal 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()
|
||||||
3
index.ce
3
index.ce
@@ -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()
|
||||||
|
|||||||
4
index.cm
4
index.cm
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
309
install.ce
309
install.ce
@@ -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()
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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));
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -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
431
internal/nota.c
Normal 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;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
626
internal/shop.cm
626
internal/shop.cm
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
461
internal/wota.c
Normal 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;
|
||||||
|
}
|
||||||
23
ir_report.ce
23
ir_report.ce
@@ -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
67
json.c
Normal 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
67
link.ce
@@ -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
56
link.cm
@@ -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
51
list.ce
@@ -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
32
log.ce
@@ -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
66
math/cycles.c
Normal 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
67
math/degrees.c
Normal 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
64
math/radians.c
Normal 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;
|
||||||
|
}
|
||||||
10
mcode.ce
10
mcode.ce
@@ -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
415
mcode.cm
@@ -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 = {}
|
||||||
|
|||||||
18
meson.build
18
meson.build
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
59
net/socket.c
59
net/socket.c
@@ -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,
|
||||||
|
|||||||
86
package.cm
86
package.cm
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
2
parse.ce
2
parse.ce
@@ -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))
|
||||||
|
|||||||
19
parse.cm
19
parse.cm
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
22
pronto.cm
22
pronto.cm
@@ -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
2
qbe.ce
@@ -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)
|
||||||
|
|||||||
@@ -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
137
query.ce
@@ -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)`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
55
query.cm
55
query.cm
@@ -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
|
||||||
|
|||||||
@@ -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
168
remove.ce
@@ -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()
|
||||||
|
|||||||
81
resolve.ce
81
resolve.ce
@@ -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()
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user