Compare commits
161 Commits
add_opt
...
warningfix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c9d039271 | ||
|
|
d4635f2a75 | ||
|
|
19576533d9 | ||
|
|
c08249b6f1 | ||
|
|
dc348d023f | ||
|
|
fd5e4d155e | ||
|
|
e734353722 | ||
|
|
94f1645be1 | ||
|
|
41e3a6d91a | ||
|
|
04c569eab1 | ||
|
|
dc3c474b3a | ||
|
|
f1117bbd41 | ||
|
|
43faad95e0 | ||
|
|
acc9878b36 | ||
|
|
03c45ee8b0 | ||
|
|
a171a0d2af | ||
|
|
bb8d3930b3 | ||
|
|
522ae6128a | ||
|
|
16c26e4bf2 | ||
|
|
a9804785e0 | ||
|
|
11ae703693 | ||
|
|
f203278c3e | ||
|
|
a80557283a | ||
|
|
3e40885e07 | ||
|
|
e4b7de46f6 | ||
|
|
e680439a9b | ||
|
|
ae11504e00 | ||
|
|
893deaec23 | ||
|
|
69b032d3dc | ||
|
|
256a00c501 | ||
|
|
8e166b8f98 | ||
|
|
ddbdd00496 | ||
|
|
bdf0461e1f | ||
|
|
0b86af1d4c | ||
|
|
beac9608ea | ||
|
|
a04bebd0d7 | ||
|
|
ce74f726dd | ||
|
|
4d1ab60852 | ||
|
|
22ab6c8098 | ||
|
|
9a9775690f | ||
|
|
be71ae3bba | ||
|
|
f2a76cbb55 | ||
|
|
2d834c37b3 | ||
|
|
ba1b92aa78 | ||
|
|
c356fe462d | ||
|
|
b23b918f97 | ||
|
|
e720152bcd | ||
|
|
f093e6f5a3 | ||
|
|
4fc904de63 | ||
|
|
e53f55fb23 | ||
|
|
6150406905 | ||
|
|
a189440769 | ||
|
|
6c3c492446 | ||
|
|
bb83327a52 | ||
|
|
c74bee89a7 | ||
|
|
271a3d6724 | ||
|
|
c5ccc66e51 | ||
|
|
3a0ea31896 | ||
|
|
67e82fd12c | ||
|
|
3c59087c0c | ||
|
|
b79e07f57b | ||
|
|
fdcb374403 | ||
|
|
03feb370fd | ||
|
|
6712755940 | ||
|
|
b3f3bc8a5f | ||
|
|
a49b94e0a1 | ||
|
|
24ecff3f1c | ||
|
|
3ccaf68a5b | ||
|
|
64933260d4 | ||
|
|
561ab9d917 | ||
|
|
bcd6e641a5 | ||
|
|
2857581271 | ||
|
|
378ad6dc98 | ||
|
|
06f7791159 | ||
|
|
086508bacd | ||
|
|
8325253f1a | ||
|
|
802c94085b | ||
|
|
f9170b33e5 | ||
|
|
20f10ab887 | ||
|
|
d8b13548d2 | ||
|
|
0d93741c31 | ||
|
|
c6440ff98c | ||
|
|
a01b48dabc | ||
|
|
beea76949c | ||
|
|
36833db2c9 | ||
|
|
b7615bb801 | ||
|
|
e6838338fc | ||
|
|
4cf0ce00de | ||
|
|
0714017547 | ||
|
|
b60e79ccad | ||
|
|
420c2b859a | ||
|
|
6c1f53ec5f | ||
|
|
45d82438ca | ||
|
|
addb38da65 | ||
|
|
aa847ddf6e | ||
|
|
4da63db16e | ||
|
|
bfdd920178 | ||
|
|
26fce3a5a8 | ||
|
|
ef49606098 | ||
|
|
854d94e5c3 | ||
|
|
dc02d6899d | ||
|
|
b28ef39562 | ||
|
|
2841e91f40 | ||
|
|
8d601dfce3 | ||
|
|
2b60e3a242 | ||
|
|
823183c510 | ||
|
|
c051a99e75 | ||
|
|
b3c0837d49 | ||
|
|
ff18682485 | ||
|
|
9b3891c126 | ||
|
|
38a3697e28 | ||
|
|
cbf99295da | ||
|
|
5271688dd4 | ||
|
|
98cb2c3239 | ||
|
|
e695810e64 | ||
|
|
bbd2d298ba | ||
|
|
a7a323a74e | ||
|
|
97ece8e5cb | ||
|
|
45ee4a337c | ||
|
|
ce7d83ec91 | ||
|
|
b46406f755 | ||
|
|
ac91495679 | ||
|
|
5018901acb | ||
|
|
78a46b4a72 | ||
|
|
66a9ca27e2 | ||
|
|
ac4b47f075 | ||
|
|
6b9f75247e | ||
|
|
b039b0c4ba | ||
|
|
ffe7b61ae2 | ||
|
|
17b9aaaf51 | ||
|
|
5fd29366a6 | ||
|
|
f16586eaa2 | ||
|
|
86a70bce3a | ||
|
|
e04b15973a | ||
|
|
d044bde4f9 | ||
|
|
8403883b9d | ||
|
|
69245f82db | ||
|
|
8203f6d1c3 | ||
|
|
ef94b55058 | ||
|
|
3a3e77eccd | ||
|
|
438c90acb5 | ||
|
|
dd309b1a37 | ||
|
|
63cf76dcf9 | ||
|
|
df07069c38 | ||
|
|
eba0727247 | ||
|
|
b0f0a5f63f | ||
|
|
d12d77c22c | ||
|
|
d6468e7fd2 | ||
|
|
7ae5a0c06b | ||
|
|
0664c11af6 | ||
|
|
058ad89c96 | ||
|
|
a0038a7ab2 | ||
|
|
d0674e7921 | ||
|
|
b586df63ad | ||
|
|
05b57550da | ||
|
|
fa616ee444 | ||
|
|
0720368c48 | ||
|
|
4f3e2819fe | ||
|
|
3b53a9dcc3 | ||
|
|
6d7581eff8 | ||
|
|
dca1963b5d |
@@ -1,6 +0,0 @@
|
||||
[modules]
|
||||
[modules.extramath]
|
||||
hash = "MCLZT3JABTAENS4WVXKGWJ7JPBLZER4YQ5VN2PE7ZD2Z4WYGTIMA===="
|
||||
url = "https://gitea.pockle.world/john/extramath@master"
|
||||
downloaded = "Monday June 2 12:07:20.42 PM -5 2025 AD"
|
||||
commit = "84d81a19a8455bcf8dc494739e9e6d545df6ff2c"
|
||||
@@ -1,9 +1,20 @@
|
||||
BasedOnStyle: GNU
|
||||
Language: C
|
||||
|
||||
IndentWidth: 2
|
||||
TabWidth: 2
|
||||
UseTab: Never
|
||||
ContinuationIndentWidth: 2 # Indents continuation lines by 2 spaces
|
||||
ContinuationIndentWidth: 2
|
||||
|
||||
AllowShortFunctionsOnASingleLine: true
|
||||
AllowShortBlocksOnASingleLine: true
|
||||
AllowShortIfStatementsOnASingleLine: true
|
||||
BreakBeforeBraces: Attach
|
||||
ColumnLimit: 0
|
||||
BreakFunctionDefinitionParameters: false
|
||||
BinPackParameters: false
|
||||
BinPackArguments: false
|
||||
|
||||
# --- Fix the "static T\nname(...)" style ---
|
||||
AlwaysBreakAfterDefinitionReturnType: None
|
||||
BreakAfterReturnType: None
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
.git/
|
||||
.obj/
|
||||
website/
|
||||
bin/
|
||||
build/
|
||||
*.zip
|
||||
|
||||
25
CLAUDE.md
Normal file
25
CLAUDE.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Code style
|
||||
All code is done with 2 spaces for indentation.
|
||||
|
||||
For cell script and its integration files, objects are preferred over classes, and preferrably limited use of prototypes, make objects sendable between actors (.ce files).
|
||||
|
||||
## cell script format
|
||||
Cell script files end in .ce or .cm. Cell script is similar to Javascript but with some differences.
|
||||
|
||||
Variables are delcared with 'var'. Var behaves like let.
|
||||
Constants are declared with 'def'.
|
||||
!= and == are strict, there is no !== or ===.
|
||||
There is no undefined, only null.
|
||||
There are no classes, only objects and prototypes.
|
||||
Prefer backticks for string interpolation. Otherwise, convering non strings with the text() function is required.
|
||||
Everything should be lowercase.
|
||||
|
||||
There are no arraybuffers, only blobs, which work with bits. They must be stoned like stone(blob) before being read from.
|
||||
|
||||
## c format
|
||||
For cell script integration files, everything should be declared static that can be. Most don't have headers at all. Files in a package are not shared between packages.
|
||||
|
||||
There is no undefined, so JS_IsNull and JS_NULL should be used only.
|
||||
|
||||
## how module loading is done in cell script
|
||||
Within a package, a c file, if using the correct macros (CELL_USE_FUNCS etc), will be loaded as a module with its name; so png.c inside ac package is loaded as <package>/png, giving you access to its functions.
|
||||
4
Makefile
4
Makefile
@@ -9,6 +9,8 @@
|
||||
CELL_SHOP = $(HOME)/.cell
|
||||
CELL_CORE_PACKAGE = $(CELL_SHOP)/packages/core
|
||||
|
||||
maker: install
|
||||
|
||||
makecell:
|
||||
cell pack core -o cell
|
||||
cp cell /opt/homebrew/bin/
|
||||
@@ -56,7 +58,7 @@ static:
|
||||
# Bootstrap: build cell from scratch using meson (only needed once)
|
||||
# Also installs core scripts to ~/.cell/core
|
||||
bootstrap:
|
||||
meson setup build_bootstrap -Dbuildtype=debugoptimized
|
||||
meson setup build_bootstrap -Dbuildtype=debug -Db_sanitize=address
|
||||
meson compile -C build_bootstrap
|
||||
cp build_bootstrap/cell .
|
||||
cp build_bootstrap/libcell_runtime.dylib .
|
||||
|
||||
97
add.ce
97
add.ce
@@ -1,28 +1,103 @@
|
||||
// cell add <locator> [alias] - Add and install a package with its dependencies
|
||||
// cell add <locator> [alias] - Add a dependency to the current package
|
||||
//
|
||||
// Usage:
|
||||
// cell add <locator> Add a dependency using default alias
|
||||
// cell add <locator> <alias> Add a dependency with custom alias
|
||||
//
|
||||
// This adds the dependency to cell.toml and installs it to the shop.
|
||||
|
||||
var shop = use('internal/shop')
|
||||
var pkg = use('package')
|
||||
var build = use('build')
|
||||
var fd = use('fd')
|
||||
|
||||
if (args.length < 1) {
|
||||
var locator = null
|
||||
var alias = null
|
||||
|
||||
array(args, function(arg) {
|
||||
if (arg == '--help' || arg == '-h') {
|
||||
log.console("Usage: cell add <locator> [alias]")
|
||||
log.console("")
|
||||
log.console("Add a dependency to the current package.")
|
||||
log.console("")
|
||||
log.console("Examples:")
|
||||
log.console(" cell add gitea.pockle.world/john/prosperon")
|
||||
log.console(" cell add gitea.pockle.world/john/cell-image image")
|
||||
log.console(" cell add ../local-package")
|
||||
$stop()
|
||||
} else if (!starts_with(arg, '-')) {
|
||||
if (!locator) {
|
||||
locator = arg
|
||||
} else if (!alias) {
|
||||
alias = arg
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!locator) {
|
||||
log.console("Usage: cell add <locator> [alias]")
|
||||
log.console("Examples:")
|
||||
log.console(" cell add gitea.pockle.world/john/prosperon@main")
|
||||
log.console(" cell add github.com/user/repo@v1.0.0 myalias")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
|
||||
var locator = args[0]
|
||||
|
||||
// Resolve relative paths to absolute paths
|
||||
if (locator == '.' || locator.startsWith('./') || locator.startsWith('../') || fd.is_dir(locator)) {
|
||||
if (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator)) {
|
||||
var resolved = fd.realpath(locator)
|
||||
if (resolved) {
|
||||
locator = resolved
|
||||
}
|
||||
}
|
||||
var alias = args.length > 1 ? args[1] : null
|
||||
|
||||
shop.get(locator, alias)
|
||||
// Generate default alias from locator
|
||||
if (!alias) {
|
||||
// Use the last component of the locator as alias
|
||||
var 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
|
||||
var cwd = fd.realpath('.')
|
||||
if (!fd.is_file(cwd + '/cell.toml')) {
|
||||
log.error("Not in a package directory (no cell.toml found)")
|
||||
$stop()
|
||||
}
|
||||
|
||||
log.console("Adding " + locator + " as '" + alias + "'...")
|
||||
|
||||
// Add to local project's cell.toml
|
||||
try {
|
||||
pkg.add_dependency(null, locator, alias)
|
||||
log.console(" Added to cell.toml")
|
||||
} catch (e) {
|
||||
log.error("Failed to update cell.toml: " + e)
|
||||
$stop()
|
||||
}
|
||||
|
||||
// Install to shop
|
||||
try {
|
||||
shop.get(locator)
|
||||
shop.extract(locator)
|
||||
|
||||
// Build scripts
|
||||
shop.build_package_scripts(locator)
|
||||
|
||||
// Build C code if any
|
||||
try {
|
||||
var target = build.detect_host_target()
|
||||
build.build_dynamic(locator, target, 'release')
|
||||
} catch (e) {
|
||||
// Not all packages have C code
|
||||
}
|
||||
|
||||
log.console(" Installed to shop")
|
||||
} catch (e) {
|
||||
log.error("Failed to install: " + e)
|
||||
$stop()
|
||||
}
|
||||
|
||||
log.console("Added " + alias + " (" + locator + ")")
|
||||
|
||||
$stop()
|
||||
@@ -8,14 +8,14 @@ static JSClassID js_writer_class_id;
|
||||
static void js_reader_finalizer(JSRuntime *rt, JSValue val) {
|
||||
mz_zip_archive *zip = JS_GetOpaque(val, js_reader_class_id);
|
||||
mz_zip_reader_end(zip);
|
||||
js_free_rt(rt,zip);
|
||||
js_free_rt(zip);
|
||||
}
|
||||
|
||||
static void js_writer_finalizer(JSRuntime *rt, JSValue val) {
|
||||
mz_zip_archive *zip = JS_GetOpaque(val, js_writer_class_id);
|
||||
mz_zip_writer_finalize_archive(zip);
|
||||
mz_zip_writer_end(zip);
|
||||
js_free_rt(rt,zip);
|
||||
js_free_rt(zip);
|
||||
}
|
||||
|
||||
static JSClassDef js_reader_class = {
|
||||
@@ -101,7 +101,7 @@ static JSValue js_miniz_compress(JSContext *js, JSValue this_val,
|
||||
size_t in_len = 0;
|
||||
const void *in_ptr = NULL;
|
||||
|
||||
if (JS_IsString(argv[0])) {
|
||||
if (JS_IsText(argv[0])) {
|
||||
/* String → UTF-8 bytes without the terminating NUL */
|
||||
cstring = JS_ToCStringLen(js, &in_len, argv[0]);
|
||||
if (!cstring)
|
||||
|
||||
263
bench.ce
263
bench.ce
@@ -3,7 +3,7 @@ var pkg = use('package')
|
||||
var fd = use('fd')
|
||||
var time = use('time')
|
||||
var json = use('json')
|
||||
var utf8 = use('utf8')
|
||||
var blob = use('blob')
|
||||
var os = use('os')
|
||||
var testlib = use('internal/testlib')
|
||||
var math = use('math/radians')
|
||||
@@ -24,63 +24,45 @@ def MAX_BATCH_SIZE = 100000000 // 100M iterations max per batch
|
||||
|
||||
// Statistical functions
|
||||
function median(arr) {
|
||||
if (arr.length == 0) return 0
|
||||
var sorted = arr.slice().sort(function(a, b) { return a - b })
|
||||
var mid = number.floor(arr.length / 2)
|
||||
if (arr.length % 2 == 0) {
|
||||
if (length(arr) == 0) return 0
|
||||
var sorted = sort(arr)
|
||||
var mid = floor(length(arr) / 2)
|
||||
if (length(arr) % 2 == 0) {
|
||||
return (sorted[mid - 1] + sorted[mid]) / 2
|
||||
}
|
||||
return sorted[mid]
|
||||
}
|
||||
|
||||
function mean(arr) {
|
||||
if (arr.length == 0) return 0
|
||||
if (length(arr) == 0) return 0
|
||||
var sum = 0
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
sum += arr[i]
|
||||
}
|
||||
return sum / arr.length
|
||||
arrfor(arr, function(val) {
|
||||
sum += val
|
||||
})
|
||||
return sum / length(arr)
|
||||
}
|
||||
|
||||
function stddev(arr, mean_val) {
|
||||
if (arr.length < 2) return 0
|
||||
if (length(arr) < 2) return 0
|
||||
var sum_sq_diff = 0
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var diff = arr[i] - mean_val
|
||||
arrfor(arr, function(val) {
|
||||
var diff = val - mean_val
|
||||
sum_sq_diff += diff * diff
|
||||
}
|
||||
return math.sqrt(sum_sq_diff / (arr.length - 1))
|
||||
})
|
||||
return math.sqrt(sum_sq_diff / (length(arr) - 1))
|
||||
}
|
||||
|
||||
function percentile(arr, p) {
|
||||
if (arr.length == 0) return 0
|
||||
var sorted = arr.slice().sort(function(a, b) { return a - b })
|
||||
var idx = number.floor(arr.length * p / 100)
|
||||
if (idx >= arr.length) idx = arr.length - 1
|
||||
if (length(arr) == 0) return 0
|
||||
var sorted = sort(arr)
|
||||
var idx = floor(arr) * p / 100
|
||||
if (idx >= length(arr)) idx = length(arr) - 1
|
||||
return sorted[idx]
|
||||
}
|
||||
|
||||
function min_val(arr) {
|
||||
if (arr.length == 0) return 0
|
||||
var m = arr[0]
|
||||
for (var i = 1; i < arr.length; i++) {
|
||||
if (arr[i] < m) m = arr[i]
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
function max_val(arr) {
|
||||
if (arr.length == 0) return 0
|
||||
var m = arr[0]
|
||||
for (var i = 1; i < arr.length; i++) {
|
||||
if (arr[i] > m) m = arr[i]
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// Parse arguments similar to test.ce
|
||||
function parse_args() {
|
||||
if (args.length == 0) {
|
||||
if (length(args) == 0) {
|
||||
if (!testlib.is_valid_package('.')) {
|
||||
log.console('No cell.toml found in current directory')
|
||||
return false
|
||||
@@ -99,7 +81,7 @@ function parse_args() {
|
||||
}
|
||||
|
||||
if (args[0] == 'package') {
|
||||
if (args.length < 2) {
|
||||
if (length(args) < 2) {
|
||||
log.console('Usage: cell bench package <name> [bench]')
|
||||
log.console(' cell bench package all')
|
||||
return false
|
||||
@@ -115,7 +97,7 @@ function parse_args() {
|
||||
var lock = shop.load_lock()
|
||||
if (lock[name]) {
|
||||
target_pkg = name
|
||||
} else if (name.startsWith('/') && testlib.is_valid_package(name)) {
|
||||
} else if (starts_with(name, '/') && testlib.is_valid_package(name)) {
|
||||
target_pkg = name
|
||||
} else {
|
||||
if (testlib.is_valid_package('.')) {
|
||||
@@ -132,7 +114,7 @@ function parse_args() {
|
||||
}
|
||||
}
|
||||
|
||||
if (args.length >= 3) {
|
||||
if (length(args) >= 3) {
|
||||
target_bench = args[2]
|
||||
}
|
||||
|
||||
@@ -144,7 +126,7 @@ function parse_args() {
|
||||
var bench_path = args[0]
|
||||
|
||||
// Normalize path - add benches/ prefix if not present
|
||||
if (!bench_path.startsWith('benches/') && !bench_path.startsWith('/')) {
|
||||
if (!starts_with(bench_path, 'benches/') && !starts_with(bench_path, '/')) {
|
||||
if (!fd.is_file(bench_path + '.cm') && !fd.is_file(bench_path)) {
|
||||
if (fd.is_file('benches/' + bench_path + '.cm') || fd.is_file('benches/' + bench_path)) {
|
||||
bench_path = 'benches/' + bench_path
|
||||
@@ -177,19 +159,18 @@ function collect_benches(package_name, specific_bench) {
|
||||
|
||||
var files = pkg.list_files(package_name)
|
||||
var bench_files = []
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
var f = files[i]
|
||||
if (f.startsWith("benches/") && f.endsWith(".cm")) {
|
||||
arrfor(files, function(f) {
|
||||
if (starts_with(f, "benches/") && ends_with(f, ".cm")) {
|
||||
if (specific_bench) {
|
||||
var bench_name = f.substring(0, f.length - 3)
|
||||
var bench_name = text(f, 0, -3)
|
||||
var match_name = specific_bench
|
||||
if (!match_name.startsWith('benches/')) match_name = 'benches/' + match_name
|
||||
var match_base = match_name.endsWith('.cm') ? match_name.substring(0, match_name.length - 3) : match_name
|
||||
if (bench_name != match_base) continue
|
||||
if (!starts_with(match_name, 'benches/')) match_name = 'benches/' + match_name
|
||||
var match_base = ends_with(match_name, '.cm') ? text(match_name, 0, -3) : match_name
|
||||
if (bench_name != match_base) return
|
||||
}
|
||||
bench_files.push(f)
|
||||
push(bench_files, f)
|
||||
}
|
||||
}
|
||||
})
|
||||
return bench_files
|
||||
}
|
||||
|
||||
@@ -203,7 +184,7 @@ function calibrate_batch_size(bench_fn, is_batch) {
|
||||
// Find a batch size that takes at least MIN_SAMPLE_NS
|
||||
while (n < MAX_BATCH_SIZE) {
|
||||
// Ensure n is a valid number before calling
|
||||
if (typeof n != 'number' || n < 1) {
|
||||
if (!is_number(n) || n < 1) {
|
||||
n = 1
|
||||
break
|
||||
}
|
||||
@@ -217,7 +198,7 @@ function calibrate_batch_size(bench_fn, is_batch) {
|
||||
// Double the batch size
|
||||
var new_n = n * 2
|
||||
// Check if multiplication produced a valid number
|
||||
if (typeof new_n != 'number' || new_n > MAX_BATCH_SIZE) {
|
||||
if (!is_number(new_n) || new_n > MAX_BATCH_SIZE) {
|
||||
n = MAX_BATCH_SIZE
|
||||
break
|
||||
}
|
||||
@@ -225,12 +206,12 @@ function calibrate_batch_size(bench_fn, is_batch) {
|
||||
}
|
||||
|
||||
// Adjust to target sample duration
|
||||
if (dt > 0 && dt < TARGET_SAMPLE_NS && typeof n == 'number' && typeof dt == 'number') {
|
||||
if (dt > 0 && dt < TARGET_SAMPLE_NS && is_number(n) && is_number(dt)) {
|
||||
var calc = n * TARGET_SAMPLE_NS / dt
|
||||
if (typeof calc == 'number' && calc > 0) {
|
||||
var target_n = number.floor(calc)
|
||||
if (is_number(calc) && calc > 0) {
|
||||
var target_n = floor(calc)
|
||||
// Check if floor returned a valid number
|
||||
if (typeof target_n == 'number' && target_n > 0) {
|
||||
if (is_number(target_n) && target_n > 0) {
|
||||
if (target_n > MAX_BATCH_SIZE) target_n = MAX_BATCH_SIZE
|
||||
if (target_n < MIN_BATCH_SIZE) target_n = MIN_BATCH_SIZE
|
||||
n = target_n
|
||||
@@ -239,7 +220,7 @@ function calibrate_batch_size(bench_fn, is_batch) {
|
||||
}
|
||||
|
||||
// Safety check - ensure we always return a valid batch size
|
||||
if (typeof n != 'number' || n < 1) {
|
||||
if (!is_number(n) || n < 1) {
|
||||
n = 1
|
||||
}
|
||||
|
||||
@@ -254,7 +235,7 @@ function run_single_bench(bench_fn, bench_name) {
|
||||
// 1. Object with { setup, run, teardown } - structured format
|
||||
// 2. Function that accepts (n) - batch format
|
||||
// 3. Function that accepts () - legacy format
|
||||
var is_structured = typeof bench_fn == 'object' && bench_fn.run
|
||||
var is_structured = is_object(bench_fn) && bench_fn.run
|
||||
var is_batch = false
|
||||
var batch_size = 1
|
||||
var setup_fn = null
|
||||
@@ -285,7 +266,7 @@ function run_single_bench(bench_fn, bench_name) {
|
||||
batch_size = calibrate_batch_size(calibrate_fn, is_batch)
|
||||
|
||||
// Safety check for structured benchmarks
|
||||
if (typeof batch_size != 'number' || batch_size < 1) {
|
||||
if (!is_number(batch_size) || batch_size < 1) {
|
||||
batch_size = 1
|
||||
}
|
||||
} else {
|
||||
@@ -307,8 +288,9 @@ function run_single_bench(bench_fn, bench_name) {
|
||||
// Warmup phase
|
||||
for (var i = 0; i < WARMUP_BATCHES; i++) {
|
||||
// Ensure batch_size is valid before warmup
|
||||
if (typeof batch_size != 'number' || batch_size < 1) {
|
||||
log.console(`WARNING: batch_size became ${typeof batch_size} = ${batch_size}, resetting to 1`)
|
||||
if (!is_number(batch_size) || batch_size < 1) {
|
||||
var type_str = is_null(batch_size) ? 'null' : is_number(batch_size) ? 'number' : is_text(batch_size) ? 'text' : is_object(batch_size) ? 'object' : is_array(batch_size) ? 'array' : is_function(batch_size) ? 'function' : is_logical(batch_size) ? 'logical' : 'unknown'
|
||||
log.console(`WARNING: batch_size became ${type_str} = ${batch_size}, resetting to 1`)
|
||||
batch_size = 1
|
||||
}
|
||||
|
||||
@@ -332,7 +314,7 @@ function run_single_bench(bench_fn, bench_name) {
|
||||
// Measurement phase - collect SAMPLES timing samples
|
||||
for (var i = 0; i < SAMPLES; i++) {
|
||||
// Double-check batch_size is valid (should never happen, but defensive)
|
||||
if (typeof batch_size != 'number' || batch_size < 1) {
|
||||
if (!is_number(batch_size) || batch_size < 1) {
|
||||
batch_size = 1
|
||||
}
|
||||
|
||||
@@ -348,7 +330,7 @@ function run_single_bench(bench_fn, bench_name) {
|
||||
if (teardown_fn) teardown_fn(state)
|
||||
|
||||
var ns_per_op = is_batch ? duration / batch_size : duration
|
||||
timings_per_op.push(ns_per_op)
|
||||
push(timings_per_op, ns_per_op)
|
||||
} else {
|
||||
var start = os.now()
|
||||
if (is_batch) {
|
||||
@@ -359,15 +341,15 @@ function run_single_bench(bench_fn, bench_name) {
|
||||
var duration = os.now() - start
|
||||
|
||||
var ns_per_op = is_batch ? duration / batch_size : duration
|
||||
timings_per_op.push(ns_per_op)
|
||||
push(timings_per_op, ns_per_op)
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate statistics
|
||||
var mean_ns = mean(timings_per_op)
|
||||
var median_ns = median(timings_per_op)
|
||||
var min_ns = min_val(timings_per_op)
|
||||
var max_ns = max_val(timings_per_op)
|
||||
var min_ns = reduce(timings_per_op, min)
|
||||
var max_ns = reduce(timings_per_op, max)
|
||||
var stddev_ns = stddev(timings_per_op, mean_ns)
|
||||
var p95_ns = percentile(timings_per_op, 95)
|
||||
var p99_ns = percentile(timings_per_op, 99)
|
||||
@@ -375,20 +357,20 @@ function run_single_bench(bench_fn, bench_name) {
|
||||
// Calculate ops/s from median
|
||||
var ops_per_sec = 0
|
||||
if (median_ns > 0) {
|
||||
ops_per_sec = number.floor(1000000000 / median_ns)
|
||||
ops_per_sec = floor(1000000000 / median_ns)
|
||||
}
|
||||
|
||||
return {
|
||||
name: bench_name,
|
||||
batch_size: batch_size,
|
||||
samples: SAMPLES,
|
||||
mean_ns: number.round(mean_ns),
|
||||
median_ns: number.round(median_ns),
|
||||
min_ns: number.round(min_ns),
|
||||
max_ns: number.round(max_ns),
|
||||
stddev_ns: number.round(stddev_ns),
|
||||
p95_ns: number.round(p95_ns),
|
||||
p99_ns: number.round(p99_ns),
|
||||
mean_ns: round(mean_ns),
|
||||
median_ns: round(median_ns),
|
||||
min_ns: round(min_ns),
|
||||
max_ns: round(max_ns),
|
||||
stddev_ns: round(stddev_ns),
|
||||
p95_ns: round(p95_ns),
|
||||
p99_ns: round(p99_ns),
|
||||
ops_per_sec: ops_per_sec
|
||||
}
|
||||
}
|
||||
@@ -396,17 +378,17 @@ function run_single_bench(bench_fn, bench_name) {
|
||||
// Format nanoseconds for display
|
||||
function format_ns(ns) {
|
||||
if (ns < 1000) return `${ns}ns`
|
||||
if (ns < 1000000) return `${number.round(ns / 1000 * 100) / 100}µs`
|
||||
if (ns < 1000000000) return `${number.round(ns / 1000000 * 100) / 100}ms`
|
||||
return `${number.round(ns / 1000000000 * 100) / 100}s`
|
||||
if (ns < 1000000) return `${round(ns / 1000 * 100) / 100}µs`
|
||||
if (ns < 1000000000) return `${round(ns / 1000000 * 100) / 100}ms`
|
||||
return `${round(ns / 1000000000 * 100) / 100}s`
|
||||
}
|
||||
|
||||
// Format ops/sec for display
|
||||
function format_ops(ops) {
|
||||
if (ops < 1000) return `${ops} ops/s`
|
||||
if (ops < 1000000) return `${number.round(ops / 1000 * 100) / 100}K ops/s`
|
||||
if (ops < 1000000000) return `${number.round(ops / 1000000 * 100) / 100}M ops/s`
|
||||
return `${number.round(ops / 1000000000 * 100) / 100}G ops/s`
|
||||
if (ops < 1000000) return `${round(ops / 1000 * 100) / 100}K ops/s`
|
||||
if (ops < 1000000000) return `${round(ops / 1000000 * 100) / 100}M ops/s`
|
||||
return `${round(ops / 1000000000 * 100) / 100}G ops/s`
|
||||
}
|
||||
|
||||
// Run benchmarks for a package
|
||||
@@ -419,14 +401,13 @@ function run_benchmarks(package_name, specific_bench) {
|
||||
total: 0
|
||||
}
|
||||
|
||||
if (bench_files.length == 0) return pkg_result
|
||||
if (length(bench_files) == 0) return pkg_result
|
||||
|
||||
if (package_name) log.console(`Running benchmarks for ${package_name}`)
|
||||
else log.console(`Running benchmarks for local package`)
|
||||
|
||||
for (var i = 0; i < bench_files.length; i++) {
|
||||
var f = bench_files[i]
|
||||
var mod_path = f.substring(0, f.length - 3)
|
||||
arrfor(bench_files, function(f) {
|
||||
var mod_path = text(f, 0, -3)
|
||||
|
||||
var file_result = {
|
||||
name: f,
|
||||
@@ -439,24 +420,22 @@ function run_benchmarks(package_name, specific_bench) {
|
||||
bench_mod = shop.use(mod_path, use_pkg)
|
||||
|
||||
var benches = []
|
||||
if (typeof bench_mod == 'function') {
|
||||
benches.push({name: 'main', fn: bench_mod})
|
||||
} else if (typeof bench_mod == 'object') {
|
||||
for (var k in bench_mod) {
|
||||
if (typeof bench_mod[k] == 'function') {
|
||||
benches.push({name: k, fn: bench_mod[k]})
|
||||
}
|
||||
}
|
||||
if (is_function(bench_mod)) {
|
||||
push(benches, {name: 'main', fn: bench_mod})
|
||||
} else if (is_object(bench_mod)) {
|
||||
arrfor(array(bench_mod), function(k) {
|
||||
if (is_function(bench_mod[k]))
|
||||
push(benches, {name: k, fn: bench_mod[k]})
|
||||
})
|
||||
}
|
||||
|
||||
if (benches.length > 0) {
|
||||
if (length(benches) > 0) {
|
||||
log.console(` ${f}`)
|
||||
for (var j = 0; j < benches.length; j++) {
|
||||
var b = benches[j]
|
||||
arrfor(benches, function(b) {
|
||||
try {
|
||||
var result = run_single_bench(b.fn, b.name)
|
||||
result.package = pkg_result.package
|
||||
file_result.benchmarks.push(result)
|
||||
push(file_result.benchmarks, result)
|
||||
pkg_result.total++
|
||||
|
||||
log.console(` ${result.name}`)
|
||||
@@ -473,10 +452,10 @@ function run_benchmarks(package_name, specific_bench) {
|
||||
name: b.name,
|
||||
error: e.toString()
|
||||
}
|
||||
file_result.benchmarks.push(error_result)
|
||||
push(file_result.benchmarks, error_result)
|
||||
pkg_result.total++
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
log.console(` Error loading ${f}: ${e}`)
|
||||
@@ -485,14 +464,14 @@ function run_benchmarks(package_name, specific_bench) {
|
||||
name: "load_module",
|
||||
error: `Error loading module: ${e}`
|
||||
}
|
||||
file_result.benchmarks.push(error_result)
|
||||
push(file_result.benchmarks, error_result)
|
||||
pkg_result.total++
|
||||
}
|
||||
|
||||
if (file_result.benchmarks.length > 0) {
|
||||
pkg_result.files.push(file_result)
|
||||
if (length(file_result.benchmarks) > 0) {
|
||||
push(pkg_result.files, file_result)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return pkg_result
|
||||
}
|
||||
@@ -502,29 +481,29 @@ var all_results = []
|
||||
|
||||
if (all_pkgs) {
|
||||
if (testlib.is_valid_package('.')) {
|
||||
all_results.push(run_benchmarks(null, null))
|
||||
push(all_results, run_benchmarks(null, null))
|
||||
}
|
||||
|
||||
var packages = shop.list_packages()
|
||||
for (var i = 0; i < packages.length; i++) {
|
||||
all_results.push(run_benchmarks(packages[i], null))
|
||||
}
|
||||
arrfor(packages, function(pkg) {
|
||||
push(all_results, run_benchmarks(pkg, null))
|
||||
})
|
||||
} else {
|
||||
all_results.push(run_benchmarks(target_pkg, target_bench))
|
||||
push(all_results, run_benchmarks(target_pkg, target_bench))
|
||||
}
|
||||
|
||||
// Calculate totals
|
||||
var total_benches = 0
|
||||
for (var i = 0; i < all_results.length; i++) {
|
||||
total_benches += all_results[i].total
|
||||
}
|
||||
arrfor(all_results, function(result) {
|
||||
total_benches += result.total
|
||||
})
|
||||
|
||||
log.console(`----------------------------------------`)
|
||||
log.console(`Benchmarks: ${total_benches} total`)
|
||||
|
||||
// Generate reports
|
||||
function generate_reports() {
|
||||
var timestamp = number.floor(time.number()).toString()
|
||||
var timestamp = text(floor(time.number()))
|
||||
var report_dir = shop.get_reports_dir() + '/bench_' + timestamp
|
||||
testlib.ensure_dir(report_dir)
|
||||
|
||||
@@ -534,34 +513,28 @@ Total benchmarks: ${total_benches}
|
||||
|
||||
=== SUMMARY ===
|
||||
`
|
||||
for (var i = 0; i < all_results.length; i++) {
|
||||
var pkg_res = all_results[i]
|
||||
if (pkg_res.total == 0) continue
|
||||
arrfor(all_results, function(pkg_res) {
|
||||
if (pkg_res.total == 0) return
|
||||
txt_report += `Package: ${pkg_res.package}\n`
|
||||
for (var j = 0; j < pkg_res.files.length; j++) {
|
||||
var f = pkg_res.files[j]
|
||||
arrfor(pkg_res.files, function(f) {
|
||||
txt_report += ` ${f.name}\n`
|
||||
for (var k = 0; k < f.benchmarks.length; k++) {
|
||||
var b = f.benchmarks[k]
|
||||
arrfor(f.benchmarks, function(b) {
|
||||
if (b.error) {
|
||||
txt_report += ` ERROR ${b.name}: ${b.error}\n`
|
||||
} else {
|
||||
txt_report += ` ${b.name}: ${format_ns(b.median_ns)}/op (${format_ops(b.ops_per_sec)})\n`
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
txt_report += `\n=== DETAILED RESULTS ===\n`
|
||||
for (var i = 0; i < all_results.length; i++) {
|
||||
var pkg_res = all_results[i]
|
||||
if (pkg_res.total == 0) continue
|
||||
arrfor(all_results, function(pkg_res) {
|
||||
if (pkg_res.total == 0) return
|
||||
|
||||
for (var j = 0; j < pkg_res.files.length; j++) {
|
||||
var f = pkg_res.files[j]
|
||||
for (var k = 0; k < f.benchmarks.length; k++) {
|
||||
var b = f.benchmarks[k]
|
||||
if (b.error) continue
|
||||
arrfor(pkg_res.files, function(f) {
|
||||
arrfor(f.benchmarks, function(b) {
|
||||
if (b.error) return
|
||||
|
||||
txt_report += `\n${pkg_res.package}::${b.name}\n`
|
||||
txt_report += ` batch_size: ${b.batch_size} samples: ${b.samples}\n`
|
||||
@@ -573,30 +546,28 @@ Total benchmarks: ${total_benches}
|
||||
txt_report += ` p95: ${format_ns(b.p95_ns)}\n`
|
||||
txt_report += ` p99: ${format_ns(b.p99_ns)}\n`
|
||||
txt_report += ` ops/s: ${format_ops(b.ops_per_sec)}\n`
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
testlib.ensure_dir(report_dir)
|
||||
fd.slurpwrite(`${report_dir}/bench.txt`, utf8.encode(txt_report))
|
||||
fd.slurpwrite(`${report_dir}/bench.txt`, stone(blob(txt_report)))
|
||||
log.console(`Report written to ${report_dir}/bench.txt`)
|
||||
|
||||
// Generate JSON per package
|
||||
for (var i = 0; i < all_results.length; i++) {
|
||||
var pkg_res = all_results[i]
|
||||
if (pkg_res.total == 0) continue
|
||||
arrfor(all_results, function(pkg_res) {
|
||||
if (pkg_res.total == 0) return
|
||||
|
||||
var pkg_benches = []
|
||||
for (var j = 0; j < pkg_res.files.length; j++) {
|
||||
var f = pkg_res.files[j]
|
||||
for (var k = 0; k < f.benchmarks.length; k++) {
|
||||
pkg_benches.push(f.benchmarks[k])
|
||||
}
|
||||
}
|
||||
arrfor(pkg_res.files, function(f) {
|
||||
arrfor(f.benchmarks, function(benchmark) {
|
||||
push(pkg_benches, benchmark)
|
||||
})
|
||||
})
|
||||
|
||||
var json_path = `${report_dir}/${pkg_res.package.replace(/\//g, '_')}.json`
|
||||
fd.slurpwrite(json_path, utf8.encode(json.encode(pkg_benches)))
|
||||
}
|
||||
var json_path = `${report_dir}/${replace(pkg_res.package, /\//, '_')}.json`
|
||||
fd.slurpwrite(json_path, stone(blob(json.encode(pkg_benches))))
|
||||
})
|
||||
}
|
||||
|
||||
generate_reports()
|
||||
|
||||
@@ -20,14 +20,14 @@ function make_shapes(n) {
|
||||
for (var i = 0; i < n; i++) {
|
||||
var o = { a: i }
|
||||
o[`p${i}`] = i
|
||||
out.push(o)
|
||||
push(out, o)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function make_packed_array(n) {
|
||||
var a = []
|
||||
for (var i = 0; i < n; i++) a.push(i)
|
||||
for (var i = 0; i < n; i++) push(a, i)
|
||||
return a
|
||||
}
|
||||
|
||||
@@ -203,8 +203,8 @@ return {
|
||||
var x = 0
|
||||
for (var j = 0; j < n; j++) {
|
||||
var a = []
|
||||
for (var i = 0; i < 256; i++) a.push(i)
|
||||
x = (x + a.length) | 0
|
||||
for (var i = 0; i < 256; i++) push(a, i)
|
||||
x = (x + length(a)) | 0
|
||||
}
|
||||
return blackhole(sink, x)
|
||||
},
|
||||
@@ -216,7 +216,7 @@ return {
|
||||
for (var j = 0; j < n; j++) {
|
||||
var s = ""
|
||||
for (var i = 0; i < 16; i++) s = s + "x"
|
||||
x = (x + s.length) | 0
|
||||
x = (x + length(s)) | 0
|
||||
}
|
||||
return blackhole(sink, x)
|
||||
},
|
||||
@@ -257,5 +257,6 @@ return {
|
||||
x = (x + o.x) | 0
|
||||
}
|
||||
return blackhole(sink, x)
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
function mainThread() {
|
||||
var maxDepth = number.max(6, Number(arg[0] || 16));
|
||||
var maxDepth = max(6, Number(arg[0] || 16));
|
||||
|
||||
var stretchDepth = maxDepth + 1;
|
||||
var check = itemCheck(bottomUpTree(stretchDepth));
|
||||
@@ -7,7 +7,7 @@ function mainThread() {
|
||||
|
||||
var longLivedTree = bottomUpTree(maxDepth);
|
||||
|
||||
for (let depth = 4; depth <= maxDepth; depth += 2) {
|
||||
for (var depth = 4; depth <= maxDepth; depth += 2) {
|
||||
var iterations = 1 << maxDepth - depth + 4;
|
||||
work(iterations, depth);
|
||||
}
|
||||
@@ -16,8 +16,8 @@ function mainThread() {
|
||||
}
|
||||
|
||||
function work(iterations, depth) {
|
||||
let check = 0;
|
||||
for (let i = 0; i < iterations; i++)
|
||||
var check = 0;
|
||||
for (var i = 0; i < iterations; i++)
|
||||
check += itemCheck(bottomUpTree(depth));
|
||||
log.console(`${iterations}\t trees of depth ${depth}\t check: ${check}`);
|
||||
}
|
||||
@@ -34,8 +34,8 @@ function itemCheck(node) {
|
||||
|
||||
function bottomUpTree(depth) {
|
||||
return depth > 0
|
||||
? new TreeNode(bottomUpTree(depth - 1), bottomUpTree(depth - 1))
|
||||
: new TreeNode(null, null);
|
||||
? TreeNode(bottomUpTree(depth - 1), bottomUpTree(depth - 1))
|
||||
: TreeNode(null, null);
|
||||
}
|
||||
|
||||
mainThread()
|
||||
|
||||
@@ -2,8 +2,8 @@ var blob = use('blob')
|
||||
var math = use('math/radians')
|
||||
|
||||
function eratosthenes (n) {
|
||||
var sieve = new blob(n, true)
|
||||
var sqrtN = number.whole(math.sqrt(n));
|
||||
var sieve = blob(n, true)
|
||||
var sqrtN = whole(math.sqrt(n));
|
||||
|
||||
for (i = 2; i <= sqrtN; i++)
|
||||
if (sieve.read_logical(i))
|
||||
@@ -17,7 +17,7 @@ var sieve = eratosthenes(10000000);
|
||||
stone(sieve)
|
||||
|
||||
var c = 0
|
||||
for (var i = 0; i < sieve.length; i++)
|
||||
for (var i = 0; i < length(sieve); i++)
|
||||
if (sieve.read_logical(i)) c++
|
||||
|
||||
log.console(c)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
function fannkuch(n) {
|
||||
var perm1 = [n]
|
||||
for (let i = 0; i < n; i++) perm1[i] = i
|
||||
for (var i = 0; i < n; i++) perm1[i] = i
|
||||
var perm = [n]
|
||||
var count = [n]
|
||||
var f = 0, flips = 0, nperm = 0, checksum = 0
|
||||
@@ -18,7 +18,7 @@ function fannkuch(n) {
|
||||
while (k != 0) {
|
||||
i = 0
|
||||
while (2*i < k) {
|
||||
let t = perm[i]; perm[i] = perm[k-i]; perm[k-i] = t
|
||||
var t = perm[i]; perm[i] = perm[k-i]; perm[k-i] = t
|
||||
i += 1
|
||||
}
|
||||
k = perm[0]
|
||||
@@ -34,10 +34,10 @@ function fannkuch(n) {
|
||||
log.console( checksum )
|
||||
return flips
|
||||
}
|
||||
let p0 = perm1[0]
|
||||
var p0 = perm1[0]
|
||||
i = 0
|
||||
while (i < r) {
|
||||
let j = i + 1
|
||||
var j = i + 1
|
||||
perm1[i] = perm1[j]
|
||||
i = j
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ function fib(n) {
|
||||
|
||||
var now = time.number()
|
||||
var arr = [1,2,3,4,5]
|
||||
for (var i in arr) {
|
||||
arrfor(arr, function(i) {
|
||||
log.console(fib(28))
|
||||
}
|
||||
})
|
||||
|
||||
log.console(`elapsed: ${time.number()-now}`)
|
||||
|
||||
|
||||
@@ -109,12 +109,12 @@ function benchArrayOps() {
|
||||
var pushTime = measureTime(function() {
|
||||
var arr = [];
|
||||
for (var i = 0; i < iterations.medium; i++) {
|
||||
arr.push(i);
|
||||
push(arr, i);
|
||||
}
|
||||
});
|
||||
|
||||
var arr = [];
|
||||
for (var i = 0; i < 10000; i++) arr.push(i);
|
||||
for (var i = 0; i < 10000; i++) push(arr, i);
|
||||
|
||||
var accessTime = measureTime(function() {
|
||||
var sum = 0;
|
||||
@@ -126,7 +126,7 @@ function benchArrayOps() {
|
||||
var iterateTime = measureTime(function() {
|
||||
var sum = 0;
|
||||
for (var j = 0; j < 1000; j++) {
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
for (var i = 0; i < length(arr); i++) {
|
||||
sum += arr[i];
|
||||
}
|
||||
}
|
||||
@@ -151,13 +151,12 @@ function benchObjectCreation() {
|
||||
});
|
||||
|
||||
function Point(x, y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
return {x,y}
|
||||
}
|
||||
|
||||
var defructorTime = measureTime(function() {
|
||||
for (var i = 0; i < iterations.medium; i++) {
|
||||
var p = new Point(i, i * 2);
|
||||
var p = Point(i, i * 2);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -199,19 +198,19 @@ function benchStringOps() {
|
||||
|
||||
var strings = [];
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
strings.push("string" + i);
|
||||
push(strings, "string" + i);
|
||||
}
|
||||
|
||||
var joinTime = measureTime(function() {
|
||||
for (var i = 0; i < iterations.complex; i++) {
|
||||
var result = strings.join(",");
|
||||
var result = text(strings, ",");
|
||||
}
|
||||
});
|
||||
|
||||
var splitTime = measureTime(function() {
|
||||
var str = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p";
|
||||
for (var i = 0; i < iterations.medium; i++) {
|
||||
var parts = str.split(",");
|
||||
var parts = array(str, ",");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -239,7 +238,7 @@ function benchArithmetic() {
|
||||
var result = 1.5;
|
||||
for (var i = 0; i < iterations.simple; i++) {
|
||||
result = math.sine(result) + math.cosine(i * 0.01);
|
||||
result = math.sqrt(number.abs(result)) + 0.1;
|
||||
result = math.sqrt(abs(result)) + 0.1;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -270,13 +269,13 @@ function benchClosures() {
|
||||
var closureCreateTime = measureTime(function() {
|
||||
var funcs = [];
|
||||
for (var i = 0; i < iterations.medium; i++) {
|
||||
funcs.push(makeAdder(i));
|
||||
push(funcs, makeAdder(i));
|
||||
}
|
||||
});
|
||||
|
||||
var adders = [];
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
adders.push(makeAdder(i));
|
||||
push(adders, makeAdder(i));
|
||||
}
|
||||
|
||||
var closureCallTime = measureTime(function() {
|
||||
|
||||
@@ -8,15 +8,15 @@ var w = h
|
||||
|
||||
log.console(`P4\n${w} ${h}`);
|
||||
|
||||
for (let y = 0; y < h; ++y) {
|
||||
for (var y = 0; y < h; ++y) {
|
||||
// Create a blob for the row - we need w bits
|
||||
var row = new blob(w);
|
||||
var row = blob(w);
|
||||
|
||||
for (let x = 0; x < w; ++x) {
|
||||
for (var x = 0; x < w; ++x) {
|
||||
zr = zi = tr = ti = 0;
|
||||
cr = 2 * x / w - 1.5;
|
||||
ci = 2 * y / h - 1;
|
||||
for (let i = 0; i < iter && (tr + ti <= limit * limit); ++i) {
|
||||
for (var i = 0; i < iter && (tr + ti <= limit * limit); ++i) {
|
||||
zi = 2 * zr * zi + ci;
|
||||
zr = tr - ti + cr;
|
||||
tr = zr * zr;
|
||||
|
||||
@@ -3,17 +3,11 @@ var SOLAR_MASS = 4 * pi * pi;
|
||||
var DAYS_PER_YEAR = 365.24;
|
||||
|
||||
function Body(x, y, z, vx, vy, vz, mass) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
this.vx = vx;
|
||||
this.vy = vy;
|
||||
this.vz = vz;
|
||||
this.mass = mass;
|
||||
return {x, y, z, vx, vy, vz, mass};
|
||||
}
|
||||
|
||||
function Jupiter() {
|
||||
return new Body(
|
||||
return Body(
|
||||
4.84143144246472090e+00,
|
||||
-1.16032004402742839e+00,
|
||||
-1.03622044471123109e-01,
|
||||
@@ -25,7 +19,7 @@ function Jupiter() {
|
||||
}
|
||||
|
||||
function Saturn() {
|
||||
return new Body(
|
||||
return Body(
|
||||
8.34336671824457987e+00,
|
||||
4.12479856412430479e+00,
|
||||
-4.03523417114321381e-01,
|
||||
@@ -37,7 +31,7 @@ function Saturn() {
|
||||
}
|
||||
|
||||
function Uranus() {
|
||||
return new Body(
|
||||
return Body(
|
||||
1.28943695621391310e+01,
|
||||
-1.51111514016986312e+01,
|
||||
-2.23307578892655734e-01,
|
||||
@@ -49,7 +43,7 @@ function Uranus() {
|
||||
}
|
||||
|
||||
function Neptune() {
|
||||
return new Body(
|
||||
return Body(
|
||||
1.53796971148509165e+01,
|
||||
-2.59193146099879641e+01,
|
||||
1.79258772950371181e-01,
|
||||
@@ -61,7 +55,7 @@ function Neptune() {
|
||||
}
|
||||
|
||||
function Sun() {
|
||||
return new Body(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, SOLAR_MASS);
|
||||
return Body(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, SOLAR_MASS);
|
||||
}
|
||||
|
||||
var bodies = Array(Sun(), Jupiter(), Saturn(), Uranus(), Neptune());
|
||||
@@ -70,7 +64,7 @@ function offsetMomentum() {
|
||||
var px = 0;
|
||||
var py = 0;
|
||||
var pz = 0;
|
||||
var size = bodies.length;
|
||||
var size = length(bodies);
|
||||
for (var i = 0; i < size; i++) {
|
||||
var body = bodies[i];
|
||||
var mass = body.mass;
|
||||
@@ -86,7 +80,7 @@ function offsetMomentum() {
|
||||
}
|
||||
|
||||
function advance(dt) {
|
||||
var size = bodies.length;
|
||||
var size = length(bodies);
|
||||
|
||||
for (var i = 0; i < size; i++) {
|
||||
var bodyi = bodies[i];
|
||||
@@ -127,7 +121,7 @@ function advance(dt) {
|
||||
|
||||
function energy() {
|
||||
var e = 0;
|
||||
var size = bodies.length;
|
||||
var size = length(bodies);
|
||||
|
||||
for (var i = 0; i < size; i++) {
|
||||
var bodyi = bodies[i];
|
||||
@@ -158,31 +152,4 @@ for (var i = 0; i < n; i++)
|
||||
advance(0.01);
|
||||
log.console(energy().toFixed(9))
|
||||
|
||||
var js = use('js')
|
||||
|
||||
// Get function metadata
|
||||
var fn_info = js.fn_info(advance)
|
||||
log.console(`${fn_info.filename}:${fn_info.line}:${fn_info.column}: function: ${fn_info.name}`)
|
||||
|
||||
// Display arguments
|
||||
if (fn_info.args && fn_info.args.length > 0) {
|
||||
log.console(` args: ${fn_info.args.join(' ')}`)
|
||||
}
|
||||
|
||||
// Display local variables
|
||||
if (fn_info.locals && fn_info.locals.length > 0) {
|
||||
log.console(' locals:')
|
||||
for (var i = 0; i < fn_info.locals.length; i++) {
|
||||
var local = fn_info.locals[i]
|
||||
log.console(` ${local.index}: ${local.type} ${local.name}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Display stack size
|
||||
log.console(` stack_size: ${fn_info.stack_size}`)
|
||||
|
||||
// Display disassembly
|
||||
log.console(js.disassemble(advance))
|
||||
log.console(js.disassemble(advance).length)
|
||||
|
||||
$stop()
|
||||
@@ -9,7 +9,7 @@ var newarr = []
|
||||
var accstr = ""
|
||||
for (var i = 0; i < 10000; i++) {
|
||||
accstr += i;
|
||||
newarr.push(i.toString())
|
||||
newarrpush(i.toString())
|
||||
}
|
||||
// Arrays to store timing results
|
||||
var jsonDecodeTimes = [];
|
||||
@@ -19,34 +19,35 @@ var notaDecodeTimes = [];
|
||||
var notaSizes = [];
|
||||
|
||||
// Run 100 tests
|
||||
for (let i = 0; i < 100; i++) {
|
||||
for (var i = 0; i < 100; i++) {
|
||||
// JSON Decode test
|
||||
let start = os.now();
|
||||
var start = os.now();
|
||||
var jll = json.decode(ll);
|
||||
jsonDecodeTimes.push((os.now() - start) * 1000);
|
||||
jsonDecodeTimespush((os.now() - start) * 1000);
|
||||
|
||||
// JSON Encode test
|
||||
start = os.now();
|
||||
let jsonStr = JSON.stringify(jll);
|
||||
jsonEncodeTimes.push((os.now() - start) * 1000);
|
||||
var jsonStr = JSON.stringify(jll);
|
||||
jsonEncodeTimespush((os.now() - start) * 1000);
|
||||
|
||||
// NOTA Encode test
|
||||
start = os.now();
|
||||
var nll = nota.encode(jll);
|
||||
notaEncodeTimes.push((os.now() - start) * 1000);
|
||||
notaEncodeTimespush((os.now() - start) * 1000);
|
||||
|
||||
// NOTA Decode test
|
||||
start = os.now();
|
||||
var oll = nota.decode(nll);
|
||||
notaDecodeTimes.push((os.now() - start) * 1000);
|
||||
notaDecodeTimespush((os.now() - start) * 1000);
|
||||
}
|
||||
|
||||
// Calculate statistics
|
||||
function getStats(arr) {
|
||||
def avg = arr.reduce((a, b) => a + b) / arr.length;
|
||||
def min = number.min(...arr);
|
||||
def max = number.max(...arr);
|
||||
return { avg, min, max };
|
||||
return {
|
||||
avg: reduce(arr, (a,b) => a+b, 0) / length(arr),
|
||||
min: reduce(arr, min),
|
||||
max: reduce(arr, max)
|
||||
};
|
||||
}
|
||||
|
||||
// Pretty print results
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
const math = require('math/radians');
|
||||
def math = use('math/radians');
|
||||
|
||||
function A(i,j) {
|
||||
return 1/((i+j)*(i+j+1)/2+i+1);
|
||||
}
|
||||
|
||||
function Au(u,v) {
|
||||
for (var i=0; i<u.length; ++i) {
|
||||
for (var i=0; i<length(u); ++i) {
|
||||
var t = 0;
|
||||
for (var j=0; j<u.length; ++j)
|
||||
for (var j=0; j<length(u); ++j)
|
||||
t += A(i,j) * u[j];
|
||||
|
||||
v[i] = t;
|
||||
@@ -15,9 +15,9 @@ function Au(u,v) {
|
||||
}
|
||||
|
||||
function Atu(u,v) {
|
||||
for (var i=0; i<u.length; ++i) {
|
||||
for (var i=0; i<length(u); ++i) {
|
||||
var t = 0;
|
||||
for (var j=0; j<u.length; ++j)
|
||||
for (var j=0; j<length(u); ++j)
|
||||
t += A(j,i) * u[j];
|
||||
|
||||
v[i] = t;
|
||||
|
||||
@@ -14,18 +14,18 @@
|
||||
// Helper to run a function repeatedly and measure total time in seconds.
|
||||
// Returns elapsed time in seconds.
|
||||
function measureTime(fn, iterations) {
|
||||
let t1 = os.now();
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
var t1 = os.now();
|
||||
for (var i = 0; i < iterations; i++) {
|
||||
fn();
|
||||
}
|
||||
let t2 = os.now();
|
||||
var t2 = os.now();
|
||||
return t2 - t1;
|
||||
}
|
||||
|
||||
// We'll define a function that does `encode -> decode` for a given value:
|
||||
function roundTripWota(value) {
|
||||
let encoded = wota.encode(value);
|
||||
let decoded = wota.decode(encoded);
|
||||
var encoded = wota.encode(value);
|
||||
var decoded = wota.decode(encoded);
|
||||
// Not doing a deep compare here, just measuring performance.
|
||||
// (We trust the test suite to verify correctness.)
|
||||
}
|
||||
@@ -63,15 +63,9 @@ def benchmarks = [
|
||||
{
|
||||
name: "Large Array (1k numbers)",
|
||||
// A thousand random numbers
|
||||
data: [ Array.from({length:1000}, (_, i) => i * 0.5) ],
|
||||
data: [ array(1000, i => i *0.5) ],
|
||||
iterations: 1000
|
||||
},
|
||||
{
|
||||
name: "Large Binary Blob (256KB)",
|
||||
// A 256KB ArrayBuffer
|
||||
data: [ new Uint8Array(256 * 1024).buffer ],
|
||||
iterations: 200
|
||||
}
|
||||
];
|
||||
|
||||
// Print a header
|
||||
@@ -79,28 +73,23 @@ log.console("Wota Encode/Decode Benchmark");
|
||||
log.console("===================\n");
|
||||
|
||||
// We'll run each benchmark scenario in turn.
|
||||
for (let bench of benchmarks) {
|
||||
// We'll measure how long it takes to do 'iterations' *for each test value*
|
||||
// in bench.data. The total loop count is `bench.iterations * bench.data.length`.
|
||||
// Then we compute an overall encode+decode throughput (ops/s).
|
||||
let totalIterations = bench.iterations * bench.data.length;
|
||||
arrfor(benchmarks, function(bench) {
|
||||
var totalIterations = bench.iterations * length(bench.data);
|
||||
|
||||
// We'll define a function that does a roundTrip for *each* data item in bench.data
|
||||
// to measure in one loop iteration. Then we multiply by bench.iterations.
|
||||
function runAllData() {
|
||||
for (let val of bench.data) {
|
||||
roundTripWota(val);
|
||||
}
|
||||
arrfor(bench.data, roundTripWota)
|
||||
}
|
||||
|
||||
let elapsedSec = measureTime(runAllData, bench.iterations);
|
||||
let opsPerSec = (totalIterations / elapsedSec).toFixed(1);
|
||||
var elapsedSec = measureTime(runAllData, bench.iterations);
|
||||
var opsPerSec = (totalIterations / elapsedSec).toFixed(1);
|
||||
|
||||
log.console(`${bench.name}:`);
|
||||
log.console(` Iterations: ${bench.iterations} × ${bench.data.length} data items = ${totalIterations}`);
|
||||
log.console(` Iterations: ${bench.iterations} × ${length(bench.data)} data items = ${totalIterations}`);
|
||||
log.console(` Elapsed: ${elapsedSec.toFixed(3)} s`);
|
||||
log.console(` Throughput: ${opsPerSec} encode+decode ops/sec\n`);
|
||||
}
|
||||
})
|
||||
|
||||
// All done
|
||||
log.console("Benchmark completed.\n");
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
//
|
||||
|
||||
// Parse command line arguments
|
||||
if (arg.length != 2) {
|
||||
if (length(arg) != 2) {
|
||||
log.console('Usage: cell benchmark_wota_nota_json.ce <LibraryName> <ScenarioName>');
|
||||
$stop()
|
||||
}
|
||||
@@ -32,7 +32,7 @@ def libraries = [
|
||||
decode: wota.decode,
|
||||
// wota produces an ArrayBuffer. We'll count `buffer.byteLength` as size.
|
||||
getSize(encoded) {
|
||||
return encoded.length;
|
||||
return length(encoded);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -41,7 +41,7 @@ def libraries = [
|
||||
decode: nota.decode,
|
||||
// nota also produces an ArrayBuffer:
|
||||
getSize(encoded) {
|
||||
return encoded.length;
|
||||
return length(encoded);
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -50,9 +50,8 @@ def libraries = [
|
||||
decode: json.decode,
|
||||
// json produces a JS string. We'll measure its UTF-16 code unit length
|
||||
// as a rough "size". Alternatively, you could convert to UTF-8 for
|
||||
// a more accurate byte size. Here we just use `string.length`.
|
||||
getSize(encodedStr) {
|
||||
return encodedStr.length;
|
||||
return length(encodedStr);
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -98,7 +97,7 @@ def benchmarks = [
|
||||
},
|
||||
{
|
||||
name: "large_array",
|
||||
data: [ Array.from({length:1000}, (_, i) => i) ],
|
||||
data: [ array(1000, i => i) ],
|
||||
iterations: 1000
|
||||
},
|
||||
];
|
||||
@@ -108,9 +107,9 @@ def benchmarks = [
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
function measureTime(fn) {
|
||||
let start = os.now();
|
||||
var start = os.now();
|
||||
fn();
|
||||
let end = os.now();
|
||||
var end = os.now();
|
||||
return (end - start); // in seconds
|
||||
}
|
||||
|
||||
@@ -128,19 +127,19 @@ function runBenchmarkForLibrary(lib, bench) {
|
||||
|
||||
// Pre-store the encoded results for all items so we can measure decode time
|
||||
// in a separate pass. Also measure total size once.
|
||||
let encodedList = [];
|
||||
let totalSize = 0;
|
||||
var encodedList = [];
|
||||
var totalSize = 0;
|
||||
|
||||
// 1) Measure ENCODING
|
||||
let encodeTime = measureTime(() => {
|
||||
for (let i = 0; i < bench.iterations; i++) {
|
||||
var encodeTime = measureTime(() => {
|
||||
for (var i = 0; i < bench.iterations; i++) {
|
||||
// For each data item, encode it
|
||||
for (let j = 0; j < bench.data.length; j++) {
|
||||
let e = lib.encode(bench.data[j]);
|
||||
for (var j = 0; j < length(bench.data); j++) {
|
||||
var e = lib.encode(bench.data[j]);
|
||||
// store only in the very first iteration, so we can decode them later
|
||||
// but do not store them every iteration or we blow up memory.
|
||||
if (i == 0) {
|
||||
encodedList.push(e);
|
||||
push(encodedList, e);
|
||||
totalSize += lib.getSize(e);
|
||||
}
|
||||
}
|
||||
@@ -148,13 +147,9 @@ function runBenchmarkForLibrary(lib, bench) {
|
||||
});
|
||||
|
||||
// 2) Measure DECODING
|
||||
let decodeTime = measureTime(() => {
|
||||
for (let i = 0; i < bench.iterations; i++) {
|
||||
// decode everything we stored during the first iteration
|
||||
for (let e of encodedList) {
|
||||
let decoded = lib.decode(e);
|
||||
// not verifying correctness here, just measuring speed
|
||||
}
|
||||
var decodeTime = measureTime(() => {
|
||||
for (var i = 0; i < bench.iterations; i++) {
|
||||
arrfor(encodedList, lib.decode)
|
||||
}
|
||||
});
|
||||
|
||||
@@ -166,18 +161,18 @@ function runBenchmarkForLibrary(lib, bench) {
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Find the requested library and scenario
|
||||
var lib = libraries.find(l => l.name == lib_name);
|
||||
var bench = benchmarks.find(b => b.name == scenario_name);
|
||||
var lib = libraries[find(libraries, l => l.name == lib_name)];
|
||||
var bench = benchmarks[find(benchmarks, b => b.name == scenario_name)];
|
||||
|
||||
if (!lib) {
|
||||
log.console('Unknown library:', lib_name);
|
||||
log.console('Available libraries:', libraries.map(l => l.name).join(', '));
|
||||
log.console('Available libraries:', text(array(libraries, l => l.name), ', '));
|
||||
$stop()
|
||||
}
|
||||
|
||||
if (!bench) {
|
||||
log.console('Unknown scenario:', scenario_name);
|
||||
log.console('Available scenarios:', benchmarks.map(b => b.name).join(', '));
|
||||
log.console('Available scenarios:', text(array(benchmarks, b => b.name), ', '));
|
||||
$stop()
|
||||
}
|
||||
|
||||
@@ -185,7 +180,7 @@ if (!bench) {
|
||||
var { encodeTime, decodeTime, totalSize } = runBenchmarkForLibrary(lib, bench);
|
||||
|
||||
// Output json for easy parsing by hyperfine or other tools
|
||||
var totalOps = bench.iterations * bench.data.length;
|
||||
var totalOps = bench.iterations * length(bench.data);
|
||||
var result = {
|
||||
lib: lib_name,
|
||||
scenario: scenario_name,
|
||||
|
||||
50
build.ce
50
build.ce
@@ -1,9 +1,11 @@
|
||||
// cell build [options] - Build dynamic libraries locally for the current machine
|
||||
// cell build [<locator>] - Build dynamic libraries locally for the current machine
|
||||
//
|
||||
// Usage:
|
||||
// cell build Build dynamic libraries for all packages
|
||||
// cell build -p <pkg> Build dynamic library for specific package
|
||||
// cell build Build dynamic libraries for all packages in shop
|
||||
// cell build . Build dynamic library for current directory package
|
||||
// cell build <locator> Build dynamic library for specific package
|
||||
// cell build -t <target> Cross-compile dynamic libraries for target platform
|
||||
// cell build -b <type> Build type: release (default), debug, or minsize
|
||||
|
||||
var build = use('build')
|
||||
var shop = use('internal/shop')
|
||||
@@ -12,25 +14,28 @@ var fd = use('fd')
|
||||
|
||||
var target = null
|
||||
var target_package = null
|
||||
var buildtype = 'debug'
|
||||
var buildtype = 'release'
|
||||
var force_rebuild = false
|
||||
var dry_run = false
|
||||
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
for (var i = 0; i < length(args); i++) {
|
||||
if (args[i] == '-t' || args[i] == '--target') {
|
||||
if (i + 1 < args.length) {
|
||||
if (i + 1 < length(args)) {
|
||||
target = args[++i]
|
||||
} else {
|
||||
log.error('-t requires a target')
|
||||
$stop()
|
||||
}
|
||||
} else if (args[i] == '-p' || args[i] == '--package') {
|
||||
if (i + 1 < args.length) {
|
||||
// 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 < args.length) {
|
||||
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')
|
||||
@@ -40,13 +45,30 @@ for (var i = 0; i < args.length; i++) {
|
||||
log.error('-b requires a buildtype (release, debug, minsize)')
|
||||
$stop()
|
||||
}
|
||||
} else if (args[i] == '--force') {
|
||||
force_rebuild = true
|
||||
} else if (args[i] == '--dry-run') {
|
||||
dry_run = true
|
||||
} else if (args[i] == '--list-targets') {
|
||||
log.console('Available targets:')
|
||||
var targets = build.list_targets()
|
||||
for (var t = 0; t < targets.length; t++) {
|
||||
for (var 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 == '.' || starts_with(target_package, './') || starts_with(target_package, '../') || fd.is_dir(target_package)) {
|
||||
var resolved = fd.realpath(target_package)
|
||||
if (resolved) {
|
||||
target_package = resolved
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,16 +80,16 @@ if (!target) {
|
||||
|
||||
if (target && !build.has_target(target)) {
|
||||
log.error('Invalid target: ' + target)
|
||||
log.console('Available targets: ' + build.list_targets().join(', '))
|
||||
log.console('Available targets: ' + text(build.list_targets(), ', '))
|
||||
$stop()
|
||||
}
|
||||
|
||||
var packages = shop.list_packages()
|
||||
log.console('Preparing packages...')
|
||||
for (var package of packages) {
|
||||
if (package == 'core') continue
|
||||
arrfor(packages, function(package) {
|
||||
if (package == 'core') return
|
||||
shop.extract(package)
|
||||
}
|
||||
})
|
||||
|
||||
if (target_package) {
|
||||
// Build single package
|
||||
@@ -88,7 +110,7 @@ if (target_package) {
|
||||
|
||||
var success = 0
|
||||
var failed = 0
|
||||
for (var i = 0; i < results.length; i++) {
|
||||
for (var i = 0; i < length(results); i++) {
|
||||
if (results[i].library) {
|
||||
success++
|
||||
} else if (results[i].error) {
|
||||
|
||||
274
build.cm
274
build.cm
@@ -8,7 +8,7 @@
|
||||
|
||||
var fd = use('fd')
|
||||
var crypto = use('crypto')
|
||||
var utf8 = use('utf8')
|
||||
var blob = use('blob')
|
||||
var os = use('os')
|
||||
var toolchains = use('toolchains')
|
||||
var shop = use('internal/shop')
|
||||
@@ -28,15 +28,15 @@ function get_local_dir() {
|
||||
// Replace sigils in a string
|
||||
// Currently supports: $LOCAL -> .cell/local full path
|
||||
function replace_sigils(str) {
|
||||
return str.replaceAll('$LOCAL', get_local_dir())
|
||||
return replace(str, '$LOCAL', get_local_dir())
|
||||
}
|
||||
|
||||
// Replace sigils in an array of flags
|
||||
function replace_sigils_array(flags) {
|
||||
var result = []
|
||||
for (var i = 0; i < flags.length; i++) {
|
||||
result.push(replace_sigils(flags[i]))
|
||||
}
|
||||
arrfor(flags, function(flag) {
|
||||
push(result, replace_sigils(flag))
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -73,7 +73,8 @@ Build.detect_host_target = function() {
|
||||
// ============================================================================
|
||||
|
||||
function content_hash(str) {
|
||||
return text(crypto.blake2(utf8.encode(str)), 'h')
|
||||
var bb = stone(blob(str))
|
||||
return text(crypto.blake2(bb, 32), 'h')
|
||||
}
|
||||
|
||||
function get_build_dir() {
|
||||
@@ -82,14 +83,12 @@ function get_build_dir() {
|
||||
|
||||
function ensure_dir(path) {
|
||||
if (fd.stat(path).isDirectory) return
|
||||
var parts = path.split('/')
|
||||
var current = path.startsWith('/') ? '/' : ''
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var parts = array(path, '/')
|
||||
var current = starts_with(path, '/') ? '/' : ''
|
||||
for (var i = 0; i < length(parts); i++) {
|
||||
if (parts[i] == '') continue
|
||||
current += parts[i] + '/'
|
||||
if (!fd.stat(current).isDirectory) {
|
||||
fd.mkdir(current)
|
||||
}
|
||||
if (!fd.stat(current).isDirectory) fd.mkdir(current)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +105,7 @@ Build.compile_file = function(pkg, file, target, buildtype = 'release') {
|
||||
var src_path = pkg_dir + '/' + file
|
||||
|
||||
if (!fd.is_file(src_path)) {
|
||||
throw new Error('Source file not found: ' + src_path)
|
||||
throw Error('Source file not found: ' + src_path)
|
||||
}
|
||||
|
||||
// Get flags (with sigil replacement)
|
||||
@@ -122,33 +121,32 @@ Build.compile_file = function(pkg, file, target, buildtype = 'release') {
|
||||
|
||||
// Add buildtype-specific flags
|
||||
if (buildtype == 'release') {
|
||||
cmd_parts.push('-O3', '-DNDEBUG')
|
||||
cmd_parts = array(cmd_parts, ['-O3', '-DNDEBUG'])
|
||||
} else if (buildtype == 'debug') {
|
||||
cmd_parts.push('-O2', '-g')
|
||||
cmd_parts = array(cmd_parts, ['-O2', '-g'])
|
||||
} else if (buildtype == 'minsize') {
|
||||
cmd_parts.push('-Os', '-DNDEBUG')
|
||||
cmd_parts = array(cmd_parts, ['-Os', '-DNDEBUG'])
|
||||
}
|
||||
|
||||
cmd_parts.push('-DCELL_USE_NAME=' + sym_name)
|
||||
cmd_parts.push('-I"' + pkg_dir + '"')
|
||||
push(cmd_parts, '-DCELL_USE_NAME=' + sym_name)
|
||||
push(cmd_parts, '-I"' + pkg_dir + '"')
|
||||
|
||||
// Add package CFLAGS (resolve relative -I paths)
|
||||
for (var i = 0; i < cflags.length; i++) {
|
||||
var flag = cflags[i]
|
||||
if (flag.startsWith('-I') && !flag.startsWith('-I/')) {
|
||||
flag = '-I"' + pkg_dir + '/' + flag.substring(2) + '"'
|
||||
arrfor(cflags, function(flag) {
|
||||
if (starts_with(flag, '-I') && !starts_with(flag, '-I/')) {
|
||||
flag = '-I"' + pkg_dir + '/' + text(flag, 2) + '"'
|
||||
}
|
||||
cmd_parts.push(flag)
|
||||
}
|
||||
push(cmd_parts, flag)
|
||||
})
|
||||
|
||||
// Add target CFLAGS
|
||||
for (var i = 0; i < target_cflags.length; i++) {
|
||||
cmd_parts.push(target_cflags[i])
|
||||
}
|
||||
arrfor(target_cflags, function(flag) {
|
||||
push(cmd_parts, flag)
|
||||
})
|
||||
|
||||
cmd_parts.push('"' + src_path + '"')
|
||||
push(cmd_parts, '"' + src_path + '"')
|
||||
|
||||
var cmd_str = cmd_parts.join(' ')
|
||||
var cmd_str = text(cmd_parts, ' ')
|
||||
|
||||
// Content hash: command + file content
|
||||
var file_content = fd.slurp(src_path)
|
||||
@@ -169,7 +167,7 @@ Build.compile_file = function(pkg, file, target, buildtype = 'release') {
|
||||
log.console('Compiling ' + file)
|
||||
var ret = os.system(full_cmd)
|
||||
if (ret != 0) {
|
||||
throw new Error('Compilation failed: ' + file)
|
||||
throw Error('Compilation failed: ' + file)
|
||||
}
|
||||
|
||||
return obj_path
|
||||
@@ -181,10 +179,10 @@ Build.build_package = function(pkg, target = Build.detect_host_target(), exclude
|
||||
var c_files = pkg_tools.get_c_files(pkg, target, exclude_main)
|
||||
var objects = []
|
||||
|
||||
for (var i = 0; i < c_files.length; i++) {
|
||||
var obj = Build.compile_file(pkg, c_files[i], target, buildtype)
|
||||
objects.push(obj)
|
||||
}
|
||||
arrfor(c_files, function(file) {
|
||||
var obj = Build.compile_file(pkg, file, target, buildtype)
|
||||
push(objects, obj)
|
||||
})
|
||||
|
||||
return objects
|
||||
}
|
||||
@@ -192,23 +190,50 @@ Build.build_package = function(pkg, target = Build.detect_host_target(), exclude
|
||||
// ============================================================================
|
||||
// Dynamic library building
|
||||
// ============================================================================
|
||||
|
||||
// Compute link key from all inputs that affect the dylib output
|
||||
function compute_link_key(objects, ldflags, target_ldflags, target, cc) {
|
||||
// Sort objects for deterministic hash
|
||||
var sorted_objects = sort(objects)
|
||||
|
||||
// Build a string representing all link inputs
|
||||
var parts = []
|
||||
push(parts, 'target:' + target)
|
||||
push(parts, 'cc:' + cc)
|
||||
arrfor(sorted_objects, function(obj) {
|
||||
// Object paths are content-addressed, so the path itself is the hash
|
||||
push(parts, 'obj:' + obj)
|
||||
})
|
||||
arrfor(ldflags, function(flag) {
|
||||
push(parts, 'ldflag:' + flag)
|
||||
})
|
||||
arrfor(target_ldflags, function(flag) {
|
||||
push(parts, 'target_ldflag:' + flag)
|
||||
})
|
||||
|
||||
return content_hash(text(parts, '\n'))
|
||||
}
|
||||
|
||||
// Build a dynamic library for a package
|
||||
// Output goes to .cell/lib/<package_name>.<ext>
|
||||
// Dynamic libraries do NOT link against core; undefined symbols are resolved at dlopen time
|
||||
// Uses content-addressed store + symlink for caching
|
||||
Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildtype = 'release') {
|
||||
var objects = Build.build_package(pkg, target, true, buildtype) // exclude main.c
|
||||
|
||||
if (objects.length == 0) {
|
||||
if (length(objects) == 0) {
|
||||
log.console('No C files in ' + pkg)
|
||||
return null
|
||||
}
|
||||
|
||||
var lib_dir = shop.get_lib_dir()
|
||||
var store_dir = lib_dir + '/store'
|
||||
ensure_dir(lib_dir)
|
||||
ensure_dir(store_dir)
|
||||
|
||||
var lib_name = shop.lib_name_for_package(pkg)
|
||||
var dylib_ext = toolchains[target].system == 'windows' ? '.dll' : (toolchains[target].system == 'darwin' ? '.dylib' : '.so')
|
||||
var lib_path = lib_dir + '/' + lib_name + dylib_ext
|
||||
var stable_path = lib_dir + '/' + lib_name + dylib_ext
|
||||
|
||||
// Get link flags (with sigil replacement)
|
||||
var ldflags = replace_sigils_array(pkg_tools.get_flags(pkg, 'LDFLAGS', target))
|
||||
@@ -218,64 +243,91 @@ Build.build_dynamic = function(pkg, target = Build.detect_host_target(), buildty
|
||||
var local_dir = get_local_dir()
|
||||
var tc = toolchains[target]
|
||||
|
||||
// Resolve relative -L paths in ldflags for hash computation
|
||||
var resolved_ldflags = []
|
||||
arrfor(ldflags, function(flag) {
|
||||
if (starts_with(flag, '-L') && !starts_with(flag, '-L/')) {
|
||||
flag = '-L"' + pkg_dir + '/' + text(flag, 2) + '"'
|
||||
}
|
||||
push(resolved_ldflags, flag)
|
||||
})
|
||||
|
||||
// Compute link key
|
||||
var link_key = compute_link_key(objects, resolved_ldflags, target_ldflags, target, cc)
|
||||
var store_path = store_dir + '/' + lib_name + '-' + link_key + dylib_ext
|
||||
|
||||
// Check if already linked in store
|
||||
if (fd.is_file(store_path)) {
|
||||
// Ensure symlink points to the store file
|
||||
if (fd.is_link(stable_path)) {
|
||||
var current_target = fd.readlink(stable_path)
|
||||
if (current_target == store_path) {
|
||||
// Already up to date
|
||||
return stable_path
|
||||
}
|
||||
fd.unlink(stable_path)
|
||||
} else if (fd.is_file(stable_path)) {
|
||||
fd.unlink(stable_path)
|
||||
}
|
||||
fd.symlink(store_path, stable_path)
|
||||
return stable_path
|
||||
}
|
||||
|
||||
// Build link command
|
||||
var cmd_parts = [cc, '-shared', '-fPIC']
|
||||
|
||||
// Platform-specific flags for undefined symbols (resolved at dlopen) and size optimization
|
||||
if (tc.system == 'darwin') {
|
||||
// Allow undefined symbols - they will be resolved when dlopen'd into the main executable
|
||||
cmd_parts.push('-undefined', 'dynamic_lookup')
|
||||
// Dead-strip unused code
|
||||
cmd_parts.push('-Wl,-dead_strip')
|
||||
// rpath for .cell/local libraries
|
||||
cmd_parts.push('-Wl,-rpath,@loader_path/../local')
|
||||
cmd_parts.push('-Wl,-rpath,' + local_dir)
|
||||
cmd_parts = array(cmd_parts, [
|
||||
'-undefined', 'dynamic_lookup',
|
||||
'-Wl,-dead_strip',
|
||||
'-Wl,-install_name,' + stable_path,
|
||||
'-Wl,-rpath,@loader_path/../local',
|
||||
'-Wl,-rpath,' + local_dir
|
||||
])
|
||||
} else if (tc.system == 'linux') {
|
||||
// Allow undefined symbols at link time
|
||||
cmd_parts.push('-Wl,--allow-shlib-undefined')
|
||||
// Garbage collect unused sections
|
||||
cmd_parts.push('-Wl,--gc-sections')
|
||||
// rpath for .cell/local libraries
|
||||
cmd_parts.push('-Wl,-rpath,$ORIGIN/../local')
|
||||
cmd_parts.push('-Wl,-rpath,' + local_dir)
|
||||
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') {
|
||||
// Windows DLLs: use --allow-shlib-undefined for mingw
|
||||
cmd_parts.push('-Wl,--allow-shlib-undefined')
|
||||
push(cmd_parts, '-Wl,--allow-shlib-undefined')
|
||||
}
|
||||
|
||||
// Add .cell/local to library search path
|
||||
cmd_parts.push('-L"' + local_dir + '"')
|
||||
push(cmd_parts, '-L"' + local_dir + '"')
|
||||
|
||||
for (var i = 0; i < objects.length; i++) {
|
||||
cmd_parts.push('"' + objects[i] + '"')
|
||||
}
|
||||
arrfor(objects, function(obj) {
|
||||
push(cmd_parts, '"' + obj + '"')
|
||||
})
|
||||
|
||||
// Do NOT link against core library - symbols resolved at dlopen time
|
||||
cmd_parts = array(cmd_parts, resolved_ldflags)
|
||||
cmd_parts = array(cmd_parts, target_ldflags)
|
||||
|
||||
// Add LDFLAGS (resolve relative -L paths)
|
||||
for (var i = 0; i < ldflags.length; i++) {
|
||||
var flag = ldflags[i]
|
||||
if (flag.startsWith('-L') && !flag.startsWith('-L/')) {
|
||||
flag = '-L"' + pkg_dir + '/' + flag.substring(2) + '"'
|
||||
}
|
||||
cmd_parts.push(flag)
|
||||
}
|
||||
push(cmd_parts, '-o')
|
||||
push(cmd_parts, '"' + store_path + '"')
|
||||
|
||||
for (var i = 0; i < target_ldflags.length; i++) {
|
||||
cmd_parts.push(target_ldflags[i])
|
||||
}
|
||||
var cmd_str = text(cmd_parts, ' ')
|
||||
|
||||
cmd_parts.push('-o', '"' + lib_path + '"')
|
||||
|
||||
var cmd_str = cmd_parts.join(' ')
|
||||
|
||||
log.console('Linking ' + lib_path)
|
||||
log.console('Linking ' + lib_name + dylib_ext)
|
||||
var ret = os.system(cmd_str)
|
||||
if (ret != 0) {
|
||||
throw new Error('Linking failed: ' + pkg)
|
||||
throw Error('Linking failed: ' + pkg)
|
||||
}
|
||||
|
||||
return lib_path
|
||||
// Update symlink to point to the new store file
|
||||
if (fd.is_link(stable_path)) {
|
||||
fd.unlink(stable_path)
|
||||
} else if (fd.is_file(stable_path)) {
|
||||
fd.unlink(stable_path)
|
||||
}
|
||||
fd.symlink(store_path, stable_path)
|
||||
|
||||
return stable_path
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -291,38 +343,36 @@ Build.build_static = function(packages, target = Build.detect_host_target(), out
|
||||
var seen_flags = {}
|
||||
|
||||
// Compile all packages
|
||||
for (var i = 0; i < packages.length; i++) {
|
||||
var pkg = packages[i]
|
||||
arrfor(packages, function(pkg) {
|
||||
var is_core = (pkg == 'core')
|
||||
|
||||
// For core, include main.c; for others, exclude it
|
||||
var objects = Build.build_package(pkg, target, !is_core, buildtype)
|
||||
|
||||
for (var j = 0; j < objects.length; j++) {
|
||||
all_objects.push(objects[j])
|
||||
}
|
||||
arrfor(objects, function(obj) {
|
||||
push(all_objects, obj)
|
||||
})
|
||||
|
||||
// Collect LDFLAGS (with sigil replacement)
|
||||
var ldflags = replace_sigils_array(pkg_tools.get_flags(pkg, 'LDFLAGS', target))
|
||||
var pkg_dir = shop.get_package_dir(pkg)
|
||||
|
||||
// Deduplicate based on the entire LDFLAGS string for this package
|
||||
var ldflags_key = pkg + ':' + ldflags.join(' ')
|
||||
var ldflags_key = pkg + ':' + text(ldflags, ' ')
|
||||
if (!seen_flags[ldflags_key]) {
|
||||
seen_flags[ldflags_key] = true
|
||||
for (var j = 0; j < ldflags.length; j++) {
|
||||
var flag = ldflags[j]
|
||||
arrfor(ldflags, function(flag) {
|
||||
// Resolve relative -L paths
|
||||
if (flag.startsWith('-L') && !flag.startsWith('-L/')) {
|
||||
flag = '-L"' + pkg_dir + '/' + flag.substring(2) + '"'
|
||||
if (starts_with(flag, '-L') && !starts_with(flag, '-L/')) {
|
||||
flag = '-L"' + pkg_dir + '/' + text(flag, 2) + '"'
|
||||
}
|
||||
all_ldflags.push(flag)
|
||||
}
|
||||
push(all_ldflags, flag)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (all_objects.length == 0) {
|
||||
throw new Error('No object files to link')
|
||||
if (length(all_objects) == 0) {
|
||||
throw Error('No object files to link')
|
||||
}
|
||||
|
||||
// Link
|
||||
@@ -330,32 +380,32 @@ Build.build_static = function(packages, target = Build.detect_host_target(), out
|
||||
var target_ldflags = toolchains[target].c_link_args || []
|
||||
var exe_ext = toolchains[target].system == 'windows' ? '.exe' : ''
|
||||
|
||||
if (!output.endsWith(exe_ext) && exe_ext) {
|
||||
if (!ends_with(output, exe_ext) && exe_ext) {
|
||||
output = output + exe_ext
|
||||
}
|
||||
|
||||
var cmd_parts = [cc]
|
||||
|
||||
for (var i = 0; i < all_objects.length; i++) {
|
||||
cmd_parts.push('"' + all_objects[i] + '"')
|
||||
}
|
||||
arrfor(all_objects, function(obj) {
|
||||
push(cmd_parts, '"' + obj + '"')
|
||||
})
|
||||
|
||||
for (var i = 0; i < all_ldflags.length; i++) {
|
||||
cmd_parts.push(all_ldflags[i])
|
||||
}
|
||||
arrfor(all_ldflags, function(flag) {
|
||||
push(cmd_parts, flag)
|
||||
})
|
||||
|
||||
for (var i = 0; i < target_ldflags.length; i++) {
|
||||
cmd_parts.push(target_ldflags[i])
|
||||
}
|
||||
arrfor(target_ldflags, function(flag) {
|
||||
push(cmd_parts, flag)
|
||||
})
|
||||
|
||||
cmd_parts.push('-o', '"' + output + '"')
|
||||
push(cmd_parts, '-o', '"' + output + '"')
|
||||
|
||||
var cmd_str = cmd_parts.join(' ')
|
||||
var cmd_str = text(cmd_parts, ' ')
|
||||
|
||||
log.console('Linking ' + output)
|
||||
var ret = os.system(cmd_str)
|
||||
if (ret != 0) {
|
||||
throw new Error('Linking failed with command: ' + cmd_str)
|
||||
throw Error('Linking failed with command: ' + cmd_str)
|
||||
}
|
||||
|
||||
log.console('Built ' + output)
|
||||
@@ -374,30 +424,30 @@ Build.build_all_dynamic = function(target, buildtype = 'release') {
|
||||
var results = []
|
||||
|
||||
// Build core first
|
||||
if (packages.indexOf('core') >= 0) {
|
||||
if (find(packages, 'core') != null) {
|
||||
try {
|
||||
var lib = Build.build_dynamic('core', target, buildtype)
|
||||
results.push({ package: 'core', library: lib })
|
||||
push(results, { package: 'core', library: lib })
|
||||
} catch (e) {
|
||||
log.error('Failed to build core: ' + e)
|
||||
results.push({ package: 'core', error: e })
|
||||
log.error('Failed to build core: ' + text(e))
|
||||
push(results, { package: 'core', error: e })
|
||||
}
|
||||
}
|
||||
|
||||
// Build other packages
|
||||
for (var i = 0; i < packages.length; i++) {
|
||||
var pkg = packages[i]
|
||||
if (pkg == 'core') continue
|
||||
arrfor(packages, function(pkg) {
|
||||
if (pkg == 'core') return
|
||||
|
||||
try {
|
||||
var lib = Build.build_dynamic(pkg, target, buildtype)
|
||||
results.push({ package: pkg, library: lib })
|
||||
push(results, { package: pkg, library: lib })
|
||||
} catch (e) {
|
||||
log.error('Failed to build ' + pkg + ': ')
|
||||
log.error(e)
|
||||
results.push({ package: pkg, error: e })
|
||||
log.console(e.message)
|
||||
log.console(e.stack)
|
||||
push(results, { package: pkg, error: e })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
185
cellfs.cm
185
cellfs.cm
@@ -17,30 +17,7 @@ var writepath = "."
|
||||
function normalize_path(path) {
|
||||
if (!path) return ""
|
||||
// Remove leading/trailing slashes and normalize
|
||||
return path.replace(/^\/+|\/+$/g, "")
|
||||
}
|
||||
|
||||
// Helper to get directory from path
|
||||
function dirname(path) {
|
||||
var idx = path.lastIndexOf("/")
|
||||
if (idx == -1) return ""
|
||||
return path.substring(0, idx)
|
||||
}
|
||||
|
||||
// Helper to get basename from path
|
||||
function basename(path) {
|
||||
var idx = path.lastIndexOf("/")
|
||||
if (idx == -1) return path
|
||||
return path.substring(idx + 1)
|
||||
}
|
||||
|
||||
// Helper to join paths
|
||||
function join_paths(base, rel) {
|
||||
base = base.replace(/\/+$/, "")
|
||||
rel = rel.replace(/^\/+/, "")
|
||||
if (!base) return rel
|
||||
if (!rel) return base
|
||||
return base + "/" + rel
|
||||
return replace(path, /^\/+|\/+$/, "")
|
||||
}
|
||||
|
||||
// Check if a file exists in a specific mount
|
||||
@@ -59,7 +36,7 @@ function mount_exists(mount, path) {
|
||||
return false
|
||||
}
|
||||
} else { // fs
|
||||
var full_path = join_paths(mount.source, path)
|
||||
var full_path = fd.join_paths(mount.source, path)
|
||||
try {
|
||||
var st = fd.stat(full_path)
|
||||
return st.isFile || st.isDirectory
|
||||
@@ -86,7 +63,7 @@ function is_directory(path) {
|
||||
return false;
|
||||
}
|
||||
} else { // fs
|
||||
var full_path = join_paths(mount.source, path)
|
||||
var full_path = fd.join_paths(mount.source, path)
|
||||
try {
|
||||
var st = fd.stat(full_path)
|
||||
return st.isDirectory
|
||||
@@ -102,44 +79,50 @@ function resolve(path, must_exist) {
|
||||
path = normalize_path(path)
|
||||
|
||||
// Check for named mount
|
||||
if (path.startsWith("@")) {
|
||||
var idx = path.indexOf("/")
|
||||
if (starts_with(path, "@")) {
|
||||
var idx = search(path, "/")
|
||||
var mount_name = ""
|
||||
var rel_path = ""
|
||||
|
||||
if (idx == -1) {
|
||||
mount_name = path.substring(1)
|
||||
if (idx == null) {
|
||||
mount_name = text(path, 1)
|
||||
rel_path = ""
|
||||
} else {
|
||||
mount_name = path.substring(1, idx)
|
||||
rel_path = path.substring(idx + 1)
|
||||
mount_name = text(path, 1, idx)
|
||||
rel_path = text(path, idx + 1)
|
||||
}
|
||||
|
||||
// Find named mount
|
||||
var mount = null
|
||||
for (var m of mounts) {
|
||||
arrfor(mounts, function(m) {
|
||||
if (m.name == mount_name) {
|
||||
mount = m
|
||||
break
|
||||
return true
|
||||
}
|
||||
}
|
||||
}, false, true)
|
||||
|
||||
if (!mount) {
|
||||
throw new Error("Unknown mount point: @" + mount_name)
|
||||
throw Error("Unknown mount point: @" + mount_name)
|
||||
}
|
||||
|
||||
return { mount: mount, path: rel_path }
|
||||
}
|
||||
|
||||
// Search path
|
||||
for (var mount of mounts) {
|
||||
var found_mount = null
|
||||
arrfor(mounts, function(mount) {
|
||||
if (mount_exists(mount, path)) {
|
||||
return { mount: mount, path: path }
|
||||
found_mount = { mount: mount, path: path }
|
||||
return true
|
||||
}
|
||||
}, false, true)
|
||||
|
||||
if (found_mount) {
|
||||
return found_mount
|
||||
}
|
||||
|
||||
if (must_exist) {
|
||||
throw new Error("File not found in any mount: " + path)
|
||||
throw Error("File not found in any mount: " + path)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,8 +157,8 @@ function mount(source, name) {
|
||||
mount_info.zip_blob = blob // keep blob alive
|
||||
} else {
|
||||
var zip = miniz.read(blob)
|
||||
if (!zip || typeof zip.count != 'function') {
|
||||
throw new Error("Invalid archive file (not zip or qop): " + source)
|
||||
if (!is_object(zip) || !is_function(zip.count)) {
|
||||
throw Error("Invalid archive file (not zip or qop): " + source)
|
||||
}
|
||||
|
||||
mount_info.type = 'zip'
|
||||
@@ -183,36 +166,32 @@ function mount(source, name) {
|
||||
mount_info.zip_blob = blob // keep blob alive
|
||||
}
|
||||
} else {
|
||||
throw new Error("Unsupported mount source type: " + source)
|
||||
throw Error("Unsupported mount source type: " + source)
|
||||
}
|
||||
|
||||
mounts.push(mount_info)
|
||||
push(mounts, mount_info)
|
||||
}
|
||||
|
||||
// Unmount
|
||||
function unmount(name_or_source) {
|
||||
for (var i = 0; i < mounts.length; i++) {
|
||||
if (mounts[i].name == name_or_source || mounts[i].source == name_or_source) {
|
||||
mounts.splice(i, 1)
|
||||
return
|
||||
}
|
||||
}
|
||||
throw new Error("Mount not found: " + name_or_source)
|
||||
mounts = filter(mounts, function(mount) {
|
||||
return mount.name != name_or_source && mount.source != name_or_source
|
||||
})
|
||||
}
|
||||
|
||||
// Read file
|
||||
function slurp(path) {
|
||||
var res = resolve(path, true)
|
||||
if (!res) throw new Error("File not found: " + path)
|
||||
if (!res) throw Error("File not found: " + path)
|
||||
|
||||
if (res.mount.type == 'zip') {
|
||||
return res.mount.handle.slurp(res.path)
|
||||
} else if (res.mount.type == 'qop') {
|
||||
var data = res.mount.handle.read(res.path)
|
||||
if (!data) throw new Error("File not found in qop: " + path)
|
||||
if (!data) throw Error("File not found in qop: " + path)
|
||||
return data
|
||||
} else {
|
||||
var full_path = join_paths(res.mount.source, res.path)
|
||||
var full_path = fd.join_paths(res.mount.source, res.path)
|
||||
return fd.slurp(full_path)
|
||||
}
|
||||
}
|
||||
@@ -229,7 +208,7 @@ function slurpwrite(path, data) {
|
||||
// Check existence
|
||||
function exists(path) {
|
||||
var res = resolve(path, false)
|
||||
if (path.startsWith("@")) {
|
||||
if (starts_with(path, "@")) {
|
||||
return mount_exists(res.mount, res.path)
|
||||
}
|
||||
return res != null
|
||||
@@ -238,7 +217,7 @@ function exists(path) {
|
||||
// Stat
|
||||
function stat(path) {
|
||||
var res = resolve(path, true)
|
||||
if (!res) throw new Error("File not found: " + path)
|
||||
if (!res) throw Error("File not found: " + path)
|
||||
|
||||
if (res.mount.type == 'zip') {
|
||||
var mod = res.mount.handle.mod(res.path)
|
||||
@@ -249,14 +228,14 @@ function stat(path) {
|
||||
}
|
||||
} else if (res.mount.type == 'qop') {
|
||||
var s = res.mount.handle.stat(res.path)
|
||||
if (!s) throw new Error("File not found in qop: " + path)
|
||||
if (!s) throw Error("File not found in qop: " + path)
|
||||
return {
|
||||
filesize: s.size,
|
||||
modtime: s.modtime,
|
||||
isDirectory: s.isDirectory
|
||||
}
|
||||
} else {
|
||||
var full_path = join_paths(res.mount.source, res.path)
|
||||
var full_path = fd.join_paths(res.mount.source, res.path)
|
||||
var s = fd.stat(full_path)
|
||||
return {
|
||||
filesize: s.size,
|
||||
@@ -268,7 +247,7 @@ function stat(path) {
|
||||
|
||||
// Get search paths
|
||||
function searchpath() {
|
||||
return mounts.slice()
|
||||
return array(mounts)
|
||||
}
|
||||
|
||||
// Mount a package using the shop system
|
||||
@@ -282,7 +261,7 @@ function mount_package(name) {
|
||||
var dir = shop.get_package_dir(name)
|
||||
|
||||
if (!dir) {
|
||||
throw new Error("Package not found: " + name)
|
||||
throw Error("Package not found: " + name)
|
||||
}
|
||||
|
||||
mount(dir, name)
|
||||
@@ -296,16 +275,16 @@ function match(str, pattern) {
|
||||
|
||||
function rm(path) {
|
||||
var res = resolve(path, true)
|
||||
if (res.mount.type != 'fs') throw new Error("Cannot delete from non-fs mount")
|
||||
if (res.mount.type != 'fs') throw Error("Cannot delete from non-fs mount")
|
||||
|
||||
var full_path = join_paths(res.mount.source, res.path)
|
||||
var full_path = fd.join_paths(res.mount.source, res.path)
|
||||
var st = fd.stat(full_path)
|
||||
if (st.isDirectory) fd.rmdir(full_path)
|
||||
else fd.unlink(full_path)
|
||||
}
|
||||
|
||||
function mkdir(path) {
|
||||
var full = join_paths(writepath, path)
|
||||
var full = fd.join_paths(writepath, path)
|
||||
fd.mkdir(full)
|
||||
}
|
||||
|
||||
@@ -324,7 +303,7 @@ function prefdir(org, app) {
|
||||
function realdir(path) {
|
||||
var res = resolve(path, false)
|
||||
if (!res) return null
|
||||
return join_paths(res.mount.source, res.path)
|
||||
return fd.join_paths(res.mount.source, res.path)
|
||||
}
|
||||
|
||||
function enumerate(path, recurse) {
|
||||
@@ -337,21 +316,21 @@ function enumerate(path, recurse) {
|
||||
var list = fd.readdir(curr_full)
|
||||
if (!list) return
|
||||
|
||||
for (var item of list) {
|
||||
arrfor(list, function(item) {
|
||||
var item_rel = rel_prefix ? rel_prefix + "/" + item : item
|
||||
results.push(item_rel)
|
||||
push(results, item_rel)
|
||||
|
||||
if (recurse) {
|
||||
var st = fd.stat(join_paths(curr_full, item))
|
||||
var st = fd.stat(fd.join_paths(curr_full, item))
|
||||
if (st.isDirectory) {
|
||||
visit(join_paths(curr_full, item), item_rel)
|
||||
visit(fd.join_paths(curr_full, item), item_rel)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (res.mount.type == 'fs') {
|
||||
var full = join_paths(res.mount.source, res.path)
|
||||
var full = fd.join_paths(res.mount.source, res.path)
|
||||
var st = fd.stat(full)
|
||||
if (st && st.isDirectory) {
|
||||
visit(full, "")
|
||||
@@ -359,29 +338,29 @@ function enumerate(path, recurse) {
|
||||
} else if (res.mount.type == 'qop') {
|
||||
var all = res.mount.handle.list()
|
||||
var prefix = res.path ? res.path + "/" : ""
|
||||
var prefix_len = prefix.length
|
||||
var prefix_len = length(prefix)
|
||||
|
||||
// Use a set to avoid duplicates if we are simulating directories
|
||||
var seen = {}
|
||||
|
||||
for (var p of all) {
|
||||
if (p.startsWith(prefix)) {
|
||||
var rel = p.substring(prefix_len)
|
||||
if (rel.length == 0) continue
|
||||
arrfor(all, function(p) {
|
||||
if (starts_with(p, prefix)) {
|
||||
var rel = text(p, prefix_len)
|
||||
if (length(rel) == 0) return
|
||||
|
||||
if (!recurse) {
|
||||
var slash = rel.indexOf('/')
|
||||
if (slash != -1) {
|
||||
rel = rel.substring(0, slash)
|
||||
var slash = search(rel, '/')
|
||||
if (slash != null) {
|
||||
rel = text(rel, 0, slash)
|
||||
}
|
||||
}
|
||||
|
||||
if (!seen[rel]) {
|
||||
seen[rel] = true
|
||||
results.push(rel)
|
||||
push(results, rel)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return results
|
||||
@@ -393,17 +372,25 @@ function globfs(globs, dir) {
|
||||
var results = []
|
||||
|
||||
function check_neg(path) {
|
||||
for (var g of globs) {
|
||||
if (g.startsWith("!") && wildstar.match(g.substring(1), path, wildstar.WM_WILDSTAR)) return true;
|
||||
}
|
||||
return false;
|
||||
var result = false
|
||||
arrfor(globs, function(g) {
|
||||
if (starts_with(g, "!") && wildstar.match(text(g, 1), path, wildstar.WM_WILDSTAR)) {
|
||||
result = true
|
||||
return true
|
||||
}
|
||||
}, false, true)
|
||||
return result
|
||||
}
|
||||
|
||||
function check_pos(path) {
|
||||
for (var g of globs) {
|
||||
if (!g.startsWith("!") && wildstar.match(g, path, wildstar.WM_WILDSTAR)) return true;
|
||||
}
|
||||
return false;
|
||||
var result = false
|
||||
arrfor(globs, function(g) {
|
||||
if (!starts_with(g, "!") && wildstar.match(g, path, wildstar.WM_WILDSTAR)) {
|
||||
result = true
|
||||
return true
|
||||
}
|
||||
}, false, true)
|
||||
return result
|
||||
}
|
||||
|
||||
function visit(curr_full, rel_prefix) {
|
||||
@@ -412,10 +399,10 @@ function globfs(globs, dir) {
|
||||
var list = fd.readdir(curr_full)
|
||||
if (!list) return
|
||||
|
||||
for (var item of list) {
|
||||
arrfor(list, function(item) {
|
||||
var item_rel = rel_prefix ? rel_prefix + "/" + item : item
|
||||
|
||||
var child_full = join_paths(curr_full, item)
|
||||
var child_full = fd.join_paths(curr_full, item)
|
||||
var st = fd.stat(child_full)
|
||||
|
||||
if (st.isDirectory) {
|
||||
@@ -424,14 +411,14 @@ function globfs(globs, dir) {
|
||||
}
|
||||
} else {
|
||||
if (!check_neg(item_rel) && check_pos(item_rel)) {
|
||||
results.push(item_rel)
|
||||
push(results, item_rel)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (res.mount.type == 'fs') {
|
||||
var full = join_paths(res.mount.source, res.path)
|
||||
var full = fd.join_paths(res.mount.source, res.path)
|
||||
var st = fd.stat(full)
|
||||
if (st && st.isDirectory) {
|
||||
visit(full, "")
|
||||
@@ -439,18 +426,18 @@ function globfs(globs, dir) {
|
||||
} else if (res.mount.type == 'qop') {
|
||||
var all = res.mount.handle.list()
|
||||
var prefix = res.path ? res.path + "/" : ""
|
||||
var prefix_len = prefix.length
|
||||
var prefix_len = length(prefix)
|
||||
|
||||
for (var p of all) {
|
||||
if (p.startsWith(prefix)) {
|
||||
var rel = p.substring(prefix_len)
|
||||
if (rel.length == 0) continue
|
||||
arrfor(all, function(p) {
|
||||
if (starts_with(p, prefix)) {
|
||||
var rel = text(p, prefix_len)
|
||||
if (length(rel) == 0) return
|
||||
|
||||
if (!check_neg(rel) && check_pos(rel)) {
|
||||
results.push(rel)
|
||||
push(results, rel)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return results
|
||||
|
||||
224
clean.ce
224
clean.ce
@@ -1,26 +1,218 @@
|
||||
// cell clean - Remove build artifacts from global shop
|
||||
// cell clean [<scope>] - Remove cached material to force refetch/rebuild
|
||||
//
|
||||
// Usage:
|
||||
// cell clean Clean build outputs for current directory package
|
||||
// cell clean . Clean build outputs for current directory package
|
||||
// cell clean <locator> Clean build outputs for specific package
|
||||
// cell clean shop Clean entire shop
|
||||
// cell clean world Clean all world packages
|
||||
//
|
||||
// Options:
|
||||
// --build Remove build outputs only (default)
|
||||
// --fetch Remove fetched sources only
|
||||
// --all Remove both build outputs and fetched sources
|
||||
// --deep Apply to full dependency closure
|
||||
// --dry-run Show what would be deleted
|
||||
|
||||
var fd = use('fd')
|
||||
var shop = use('internal/shop')
|
||||
var pkg = use('package')
|
||||
var fd = use('fd')
|
||||
|
||||
var build_dir = shop.get_shop_path() + '/build'
|
||||
var scope = null
|
||||
var clean_build = false
|
||||
var clean_fetch = false
|
||||
var deep = false
|
||||
var dry_run = false
|
||||
|
||||
if (!fd.is_dir(build_dir)) {
|
||||
log.console("No build directory found at " + build_dir)
|
||||
$stop()
|
||||
return
|
||||
for (var i = 0; i < length(args); i++) {
|
||||
if (args[i] == '--build') {
|
||||
clean_build = true
|
||||
} else if (args[i] == '--fetch') {
|
||||
clean_fetch = true
|
||||
} else if (args[i] == '--all') {
|
||||
clean_build = true
|
||||
clean_fetch = true
|
||||
} else if (args[i] == '--deep') {
|
||||
deep = true
|
||||
} else if (args[i] == '--dry-run') {
|
||||
dry_run = true
|
||||
} else if (args[i] == '--help' || args[i] == '-h') {
|
||||
log.console("Usage: cell clean [<scope>] [options]")
|
||||
log.console("")
|
||||
log.console("Remove cached material to force refetch/rebuild.")
|
||||
log.console("")
|
||||
log.console("Scopes:")
|
||||
log.console(" <locator> Clean specific package")
|
||||
log.console(" shop Clean entire shop")
|
||||
log.console(" world Clean all world packages")
|
||||
log.console("")
|
||||
log.console("Options:")
|
||||
log.console(" --build Remove build outputs only (default)")
|
||||
log.console(" --fetch Remove fetched sources only")
|
||||
log.console(" --all Remove both build outputs and fetched sources")
|
||||
log.console(" --deep Apply to full dependency closure")
|
||||
log.console(" --dry-run Show what would be deleted")
|
||||
$stop()
|
||||
} else if (!starts_with(args[i], '-')) {
|
||||
scope = args[i]
|
||||
}
|
||||
}
|
||||
|
||||
log.console("Cleaning build artifacts...")
|
||||
|
||||
// Remove the build directory
|
||||
try {
|
||||
fd.rm(build_dir)
|
||||
log.console("Build directory removed: " + build_dir)
|
||||
} catch (e) {
|
||||
log.error(e)
|
||||
// Default to --build if nothing specified
|
||||
if (!clean_build && !clean_fetch) {
|
||||
clean_build = true
|
||||
}
|
||||
|
||||
log.console("Clean complete!")
|
||||
// Default scope to current directory
|
||||
if (!scope) {
|
||||
scope = '.'
|
||||
}
|
||||
|
||||
// Resolve local paths for single package scope
|
||||
var is_shop_scope = (scope == 'shop')
|
||||
var is_world_scope = (scope == 'world')
|
||||
|
||||
if (!is_shop_scope && !is_world_scope) {
|
||||
if (scope == '.' || starts_with(scope, './') || starts_with(scope, '../') || fd.is_dir(scope)) {
|
||||
var resolved = fd.realpath(scope)
|
||||
if (resolved) {
|
||||
scope = resolved
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var files_to_delete = []
|
||||
var dirs_to_delete = []
|
||||
|
||||
// Gather packages to clean
|
||||
var packages_to_clean = []
|
||||
|
||||
if (is_shop_scope) {
|
||||
packages_to_clean = shop.list_packages()
|
||||
} else if (is_world_scope) {
|
||||
// For now, world is the same as shop
|
||||
packages_to_clean = shop.list_packages()
|
||||
} else {
|
||||
// Single package
|
||||
push(packages_to_clean, scope)
|
||||
|
||||
if (deep) {
|
||||
try {
|
||||
var deps = pkg.gather_dependencies(scope)
|
||||
arrfor(deps, function(dep) {
|
||||
push(packages_to_clean, dep)
|
||||
})
|
||||
} catch (e) {
|
||||
// Skip if can't read dependencies
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Gather files to clean
|
||||
var lib_dir = shop.get_lib_dir()
|
||||
var build_dir = shop.get_build_dir()
|
||||
var packages_dir = replace(shop.get_package_dir(''), /\/$/, '') // Get base packages dir
|
||||
|
||||
if (clean_build) {
|
||||
if (is_shop_scope) {
|
||||
// Clean entire build and lib directories
|
||||
if (fd.is_dir(build_dir)) {
|
||||
push(dirs_to_delete, build_dir)
|
||||
}
|
||||
if (fd.is_dir(lib_dir)) {
|
||||
push(dirs_to_delete, lib_dir)
|
||||
}
|
||||
} else {
|
||||
// Clean specific package libraries
|
||||
arrfor(packages_to_clean, function(p) {
|
||||
if (p == 'core') return
|
||||
|
||||
var lib_name = shop.lib_name_for_package(p)
|
||||
var dylib_ext = '.dylib'
|
||||
var lib_path = lib_dir + '/' + lib_name + dylib_ext
|
||||
|
||||
if (fd.is_file(lib_path)) {
|
||||
push(files_to_delete, lib_path)
|
||||
}
|
||||
|
||||
// Also check for .so and .dll
|
||||
var so_path = lib_dir + '/' + lib_name + '.so'
|
||||
var dll_path = lib_dir + '/' + lib_name + '.dll'
|
||||
if (fd.is_file(so_path)) {
|
||||
push(files_to_delete, so_path)
|
||||
}
|
||||
if (fd.is_file(dll_path)) {
|
||||
push(files_to_delete, dll_path)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (clean_fetch) {
|
||||
if (is_shop_scope) {
|
||||
// Clean entire packages directory (dangerous!)
|
||||
if (fd.is_dir(packages_dir)) {
|
||||
push(dirs_to_delete, packages_dir)
|
||||
}
|
||||
} else {
|
||||
// Clean specific package directories
|
||||
arrfor(packages_to_clean, function(p) {
|
||||
if (p == 'core') return
|
||||
|
||||
var pkg_dir = shop.get_package_dir(p)
|
||||
if (fd.is_dir(pkg_dir) || fd.is_link(pkg_dir)) {
|
||||
push(dirs_to_delete, pkg_dir)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Execute or report
|
||||
if (dry_run) {
|
||||
log.console("Would delete:")
|
||||
if (length(files_to_delete) == 0 && length(dirs_to_delete) == 0) {
|
||||
log.console(" (nothing to clean)")
|
||||
} else {
|
||||
arrfor(files_to_delete, function(f) {
|
||||
log.console(" [file] " + f)
|
||||
})
|
||||
arrfor(dirs_to_delete, function(d) {
|
||||
log.console(" [dir] " + d)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
var deleted_count = 0
|
||||
|
||||
arrfor(files_to_delete, function(f) {
|
||||
try {
|
||||
fd.unlink(f)
|
||||
log.console("Deleted: " + f)
|
||||
deleted_count++
|
||||
} catch (e) {
|
||||
log.error("Failed to delete " + f + ": " + e)
|
||||
}
|
||||
})
|
||||
|
||||
arrfor(dirs_to_delete, function(d) {
|
||||
try {
|
||||
if (fd.is_link(d)) {
|
||||
fd.unlink(d)
|
||||
} else {
|
||||
fd.rmdir(d, 1) // recursive
|
||||
}
|
||||
log.console("Deleted: " + d)
|
||||
deleted_count++
|
||||
} catch (e) {
|
||||
log.error("Failed to delete " + d + ": " + e)
|
||||
}
|
||||
})
|
||||
|
||||
if (deleted_count == 0) {
|
||||
log.console("Nothing to clean.")
|
||||
} else {
|
||||
log.console("")
|
||||
log.console("Clean complete: " + text(deleted_count) + " item(s) deleted.")
|
||||
}
|
||||
}
|
||||
|
||||
$stop()
|
||||
25
clone.ce
25
clone.ce
@@ -7,7 +7,7 @@ var fd = use('fd')
|
||||
var http = use('http')
|
||||
var miniz = use('miniz')
|
||||
|
||||
if (args.length < 2) {
|
||||
if (length(args) < 2) {
|
||||
log.console("Usage: cell clone <origin> <path>")
|
||||
log.console("Clones a cell package to a local path and links it.")
|
||||
$stop()
|
||||
@@ -18,7 +18,7 @@ var origin = args[0]
|
||||
var target_path = args[1]
|
||||
|
||||
// Resolve target path to absolute
|
||||
if (target_path == '.' || target_path.startsWith('./') || target_path.startsWith('../')) {
|
||||
if (target_path == '.' || starts_with(target_path, './') || starts_with(target_path, '../')) {
|
||||
var resolved = fd.realpath(target_path)
|
||||
if (resolved) {
|
||||
target_path = resolved
|
||||
@@ -27,12 +27,12 @@ if (target_path == '.' || target_path.startsWith('./') || target_path.startsWith
|
||||
var cwd = fd.realpath('.')
|
||||
if (target_path == '.') {
|
||||
target_path = cwd
|
||||
} else if (target_path.startsWith('./')) {
|
||||
target_path = cwd + target_path.substring(1)
|
||||
} else if (target_path.startsWith('../')) {
|
||||
} 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
|
||||
var parent = cwd.substring(0, cwd.lastIndexOf('/'))
|
||||
target_path = parent + target_path.substring(2)
|
||||
var parent = fd.dirname(cwd)
|
||||
target_path = parent + text(target_path, 2)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,14 +92,13 @@ try {
|
||||
for (var i = 0; i < count; i++) {
|
||||
if (zip.is_directory(i)) continue
|
||||
var filename = zip.get_filename(i)
|
||||
var parts = filename.split('/')
|
||||
if (parts.length <= 1) continue
|
||||
var first_slash = search(filename, '/')
|
||||
if (first_slash == null) continue
|
||||
if (first_slash + 1 >= length(filename)) continue
|
||||
|
||||
// Skip the first directory (repo-commit prefix)
|
||||
parts.shift()
|
||||
var rel_path = parts.join('/')
|
||||
var rel_path = text(filename, first_slash + 1)
|
||||
var full_path = target_path + '/' + rel_path
|
||||
var dir_path = full_path.substring(0, full_path.lastIndexOf('/'))
|
||||
var dir_path = fd.dirname(full_path)
|
||||
|
||||
// Ensure directory exists
|
||||
if (!fd.is_dir(dir_path)) {
|
||||
|
||||
67
config.ce
67
config.ce
@@ -31,30 +31,30 @@ function print_help() {
|
||||
|
||||
// Parse a dot-notation key into path segments
|
||||
function parse_key(key) {
|
||||
return key.split('.')
|
||||
return array(key, '.')
|
||||
}
|
||||
|
||||
// Get a value from nested object using path
|
||||
function get_nested(obj, path) {
|
||||
var current = obj
|
||||
for (var segment of path) {
|
||||
if (!current || typeof current != 'object') return null
|
||||
arrfor(path, function(segment) {
|
||||
if (is_null(current) || !is_object(current)) return null
|
||||
current = current[segment]
|
||||
}
|
||||
})
|
||||
return current
|
||||
}
|
||||
|
||||
// Set a value in nested object using path
|
||||
function set_nested(obj, path, value) {
|
||||
var current = obj
|
||||
for (var i = 0; i < path.length - 1; i++) {
|
||||
for (var i = 0; i < length(path) - 1; i++) {
|
||||
var segment = path[i]
|
||||
if (!current[segment] || typeof current[segment] != 'object') {
|
||||
if (is_null(current[segment]) || !is_object(current[segment])) {
|
||||
current[segment] = {}
|
||||
}
|
||||
current = current[segment]
|
||||
}
|
||||
current[path[path.length - 1]] = value
|
||||
current[path[length(path) - 1]] = value
|
||||
}
|
||||
|
||||
// Parse value string into appropriate type
|
||||
@@ -64,7 +64,7 @@ function parse_value(str) {
|
||||
if (str == 'false') return false
|
||||
|
||||
// Number (including underscores)
|
||||
var num_str = str.replace(/_/g, '')
|
||||
var num_str = replace(str, /_/g, '')
|
||||
if (/^-?\d+$/.test(num_str)) return parseInt(num_str)
|
||||
if (/^-?\d*\.\d+$/.test(num_str)) return parseFloat(num_str)
|
||||
|
||||
@@ -74,29 +74,29 @@ function parse_value(str) {
|
||||
|
||||
// Format value for display
|
||||
function format_value(val) {
|
||||
if (typeof val == 'string') return '"' + val + '"'
|
||||
if (typeof val == 'number' && val >= 1000) {
|
||||
if (is_text(val)) return '"' + val + '"'
|
||||
if (is_number(val) && val >= 1000) {
|
||||
// Add underscores to large numbers
|
||||
return val.toString().replace(/\B(?=(\d{3})+(?!\d))/g, '_')
|
||||
return replace(val.toString(), /\B(?=(\d{3})+(?!\d))/g, '_')
|
||||
}
|
||||
return text(val)
|
||||
}
|
||||
|
||||
// Print configuration tree recursively
|
||||
function print_config(obj, prefix = '') {
|
||||
for (var key in obj) {
|
||||
arrfor(array(obj), function(key) {
|
||||
var val = obj[key]
|
||||
var full_key = prefix ? prefix + '.' + key : key
|
||||
|
||||
if (isa(val, object))
|
||||
if (is_object(val))
|
||||
print_config(val, full_key)
|
||||
else
|
||||
log.console(full_key + ' = ' + format_value(val))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Main command handling
|
||||
if (args.length == 0) {
|
||||
if (length(args) == 0) {
|
||||
print_help()
|
||||
$stop()
|
||||
return
|
||||
@@ -110,6 +110,9 @@ if (!config) {
|
||||
}
|
||||
|
||||
var command = args[0]
|
||||
var key
|
||||
var path
|
||||
var value
|
||||
|
||||
switch (command) {
|
||||
case 'help':
|
||||
@@ -125,14 +128,14 @@ switch (command) {
|
||||
break
|
||||
|
||||
case 'get':
|
||||
if (args.length < 2) {
|
||||
if (length(args) < 2) {
|
||||
log.error("Usage: cell config get <key>")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
var key = args[1]
|
||||
var path = parse_key(key)
|
||||
var value = get_nested(config, path)
|
||||
key = args[1]
|
||||
path = parse_key(key)
|
||||
value = get_nested(config, path)
|
||||
|
||||
if (value == null) {
|
||||
log.error("Key not found: " + key)
|
||||
@@ -145,7 +148,7 @@ switch (command) {
|
||||
break
|
||||
|
||||
case 'set':
|
||||
if (args.length < 3) {
|
||||
if (length(args) < 3) {
|
||||
log.error("Usage: cell config set <key> <value>")
|
||||
$stop()
|
||||
return
|
||||
@@ -161,8 +164,8 @@ switch (command) {
|
||||
'ar_timer', 'actor_memory', 'net_service',
|
||||
'reply_timeout', 'actor_max', 'stack_max'
|
||||
]
|
||||
if (!valid_system_keys.includes(path[1])) {
|
||||
log.error("Invalid system key. Valid keys: " + valid_system_keys.join(', '))
|
||||
if (find(valid_system_keys, path[1]) == null) {
|
||||
log.error("Invalid system key. Valid keys: " + text(valid_system_keys, ', '))
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
@@ -175,7 +178,7 @@ switch (command) {
|
||||
|
||||
case 'actor':
|
||||
// Handle actor-specific configuration
|
||||
if (args.length < 3) {
|
||||
if (length(args) < 3) {
|
||||
log.error("Usage: cell config actor <name> <command> [options]")
|
||||
$stop()
|
||||
return
|
||||
@@ -190,7 +193,7 @@ switch (command) {
|
||||
|
||||
switch (actor_cmd) {
|
||||
case 'list':
|
||||
if (array(config.actors[actor_name]).length == 0) {
|
||||
if (length(array(config.actors[actor_name])) == 0) {
|
||||
log.console("No configuration for actor: " + actor_name)
|
||||
} else {
|
||||
log.console("# Configuration for actor: " + actor_name)
|
||||
@@ -200,14 +203,14 @@ switch (command) {
|
||||
break
|
||||
|
||||
case 'get':
|
||||
if (args.length < 4) {
|
||||
if (length(args) < 4) {
|
||||
log.error("Usage: cell config actor <name> get <key>")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
var key = args[3]
|
||||
var path = parse_key(key)
|
||||
var value = get_nested(config.actors[actor_name], path)
|
||||
key = args[3]
|
||||
path = parse_key(key)
|
||||
value = get_nested(config.actors[actor_name], path)
|
||||
|
||||
if (value == null) {
|
||||
log.error("Key not found for actor " + actor_name + ": " + key)
|
||||
@@ -217,15 +220,15 @@ switch (command) {
|
||||
break
|
||||
|
||||
case 'set':
|
||||
if (args.length < 5) {
|
||||
if (length(args) < 5) {
|
||||
log.error("Usage: cell config actor <name> set <key> <value>")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
var key = args[3]
|
||||
key = args[3]
|
||||
var value_str = args[4]
|
||||
var path = parse_key(key)
|
||||
var value = parse_value(value_str)
|
||||
path = parse_key(key)
|
||||
value = parse_value(value_str)
|
||||
|
||||
set_nested(config.actors[actor_name], path, value)
|
||||
pkg.save_config(config)
|
||||
|
||||
2
crypto.c
2
crypto.c
@@ -231,7 +231,7 @@ JSValue js_crypto_unlock(JSContext *js, JSValue self, int argc, JSValue *argv) {
|
||||
|
||||
static const JSCFunctionListEntry js_crypto_funcs[] = {
|
||||
JS_CFUNC_DEF("shared", 2, js_crypto_shared),
|
||||
JS_CFUNC_DEF("blake2", 1, js_crypto_blake2),
|
||||
JS_CFUNC_DEF("blake2", 2, js_crypto_blake2),
|
||||
JS_CFUNC_DEF("sign", 2, js_crypto_sign),
|
||||
JS_CFUNC_DEF("verify", 3, js_crypto_verify),
|
||||
JS_CFUNC_DEF("lock", 3, js_crypto_lock),
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#include "cell.h"
|
||||
|
||||
JSC_CCALL(os_gc, JS_RunGC(JS_GetRuntime(js)) )
|
||||
JSC_CCALL(os_mem_limit, JS_SetMemoryLimit(JS_GetRuntime(js), js2number(js,argv[0])))
|
||||
JSC_CCALL(os_gc_threshold, JS_SetGCThreshold(JS_GetRuntime(js), js2number(js,argv[0])))
|
||||
JSC_CCALL(os_max_stacksize, JS_SetMaxStackSize(JS_GetRuntime(js), js2number(js,argv[0])))
|
||||
|
||||
// Compute the approximate size of a single JS value in memory.
|
||||
@@ -15,8 +13,7 @@ JSC_CCALL(os_calc_mem,
|
||||
JS_SetPropertyStr(js,ret,"memory_used_size",number2js(js,mu.memory_used_size));
|
||||
JS_SetPropertyStr(js,ret,"malloc_count",number2js(js,mu.malloc_count));
|
||||
JS_SetPropertyStr(js,ret,"memory_used_count",number2js(js,mu.memory_used_count));
|
||||
JS_SetPropertyStr(js,ret,"atom_count",number2js(js,mu.atom_count));
|
||||
JS_SetPropertyStr(js,ret,"atom_size",number2js(js,mu.atom_size));
|
||||
/* atom_count and atom_size removed - atoms are now just strings */
|
||||
JS_SetPropertyStr(js,ret,"str_count",number2js(js,mu.str_count));
|
||||
JS_SetPropertyStr(js,ret,"str_size",number2js(js,mu.str_size));
|
||||
JS_SetPropertyStr(js,ret,"obj_count",number2js(js,mu.obj_count));
|
||||
@@ -92,9 +89,7 @@ JSC_CCALL(js_fn_info,
|
||||
static const JSCFunctionListEntry js_js_funcs[] = {
|
||||
MIST_FUNC_DEF(os, calc_mem, 0),
|
||||
MIST_FUNC_DEF(os, mem_limit, 1),
|
||||
MIST_FUNC_DEF(os, gc_threshold, 1),
|
||||
MIST_FUNC_DEF(os, max_stacksize, 1),
|
||||
MIST_FUNC_DEF(os, gc, 0),
|
||||
MIST_FUNC_DEF(os, eval, 2),
|
||||
MIST_FUNC_DEF(js, compile, 2),
|
||||
MIST_FUNC_DEF(js, eval_compile, 1),
|
||||
|
||||
@@ -230,10 +230,10 @@ var json = use('json')
|
||||
Check type or prototype chain.
|
||||
|
||||
```javascript
|
||||
isa(42, number) // true
|
||||
isa("hi", text) // true
|
||||
isa([1,2], array) // true
|
||||
isa({}, object) // true
|
||||
is_number(42) // true
|
||||
is_text("hi") // true
|
||||
is_array([1,2]) // true
|
||||
is_object({}) // true
|
||||
isa(child, parent) // true if parent is in prototype chain
|
||||
```
|
||||
|
||||
@@ -270,7 +270,7 @@ Cell supports regex patterns in string functions, but not standalone regex objec
|
||||
|
||||
```javascript
|
||||
text.search("hello world", /world/)
|
||||
text.replace("hello", /l/g, "L")
|
||||
replace("hello", /l/g, "L")
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
921
docs/functions.md
Normal file
921
docs/functions.md
Normal file
@@ -0,0 +1,921 @@
|
||||
# Cell Functions
|
||||
|
||||
The intrinsics are constants and functions that are built into the language. The use statement is not needed to access them.
|
||||
|
||||
A programmer is not obliged to consult the list of intrinsics before naming a new variable or input.
|
||||
New intrinsics may be added to Misty without breaking existing programs.
|
||||
Constants
|
||||
|
||||
false
|
||||
|
||||
This is the value of 1 = 0. The false value is one of the two logical values.
|
||||
|
||||
true
|
||||
|
||||
This is the value of 1 = 1. The true value is one of the two logical values.
|
||||
|
||||
null
|
||||
|
||||
This is the value of 1 / 0. The null value is an empty immutable object. All attempts to obtain a value from null by refinement will produce null.
|
||||
|
||||
Any attempt to modify null will disrupt. Any attempt to call null as a function will disrupt.
|
||||
|
||||
null is the value of missing input values, missing fields in records, and invalid numbers. The |default operator can detect the presence of null and substitute another value.
|
||||
|
||||
pi
|
||||
|
||||
This is an approximation of the circle expression circumference / diameter, or to be precisely approximate, 3.1415926535897932.
|
||||
|
||||
Creator Functions
|
||||
|
||||
The creator functions are used to make new objects. Some of them can take various types. All of these functions can return null if their inputs are not suitable.
|
||||
|
||||
Array
|
||||
|
||||
array(number)
|
||||
|
||||
Make an array. All of the elements are initialized to null.
|
||||
|
||||
number is a non-negative integer, the intended length of the new array.
|
||||
|
||||
array(number, initial_value)
|
||||
|
||||
Make an array. All of the elements are initialized to initial_value.
|
||||
|
||||
number is a non-negative integer, the intended length of the new array.
|
||||
|
||||
If initial_value is a function, then the function is called for each element to produce initialization values. If the function has an arity of 1 or more, it is passed the element number.
|
||||
|
||||
array(array)
|
||||
|
||||
Copy. Make a mutable copy of the array.
|
||||
|
||||
array(array, function, reverse, exit)
|
||||
|
||||
Map. Call the function with each element of the array, collecting the return values in a new array. The function is passed each element and its element number.
|
||||
|
||||
function (element, element_nr)
|
||||
If reverse is true, then it starts with the last element and works backwards.
|
||||
|
||||
If exit is not null, then when the function returns the exit value, then the array function returns early. The exit value will not be stored into the new array. If the array was processed normally, then the returned array will be shorter than the input array. If the array was processed in reverse, then the returned array will have the same length as the input array, and the first elements will be null. The elements in the new array that were not set due to an early exit will be set to null.
|
||||
|
||||
This is like the for function except that the return values are collected into a new array.
|
||||
|
||||
array(array, another_array)
|
||||
|
||||
Concat. Produce a new array that concatenates the array and another_array.
|
||||
|
||||
array(array, from, to)
|
||||
|
||||
Slice. Make a mutable copy of all or part of an array.
|
||||
|
||||
array: the array to copy
|
||||
|
||||
from: the position at which to start copying. Default: 0, the beginning. If negative, add length(array).
|
||||
|
||||
to: the position at which to stop copying. Default: length(array), the end. If negative, add length(array).
|
||||
|
||||
If, after adjustment, from and to are not valid integers in the proper range, then it returns null. from must be positive and less than or equal to to. to must be less than or equal to length(array).
|
||||
|
||||
array(record)
|
||||
|
||||
Keys. Make an array containing all of the text keys in the record. The keys are not guaranteed to be in any particular order.
|
||||
|
||||
array(text)
|
||||
|
||||
Split the text into grapheme clusters. A grapheme cluster is a Unicode codepoint and its contributing combining characters, if any.
|
||||
|
||||
array(text, separator)
|
||||
|
||||
Split the text into an array of subtexts. The separator can be a text or pattern.
|
||||
|
||||
array(text, length)
|
||||
|
||||
Dice the text into an array of subtexts of a given length.
|
||||
|
||||
Logical
|
||||
|
||||
logical(value)
|
||||
|
||||
if value = 0 \/ value = false \/ value = "false" \/ value = null
|
||||
return false
|
||||
fi
|
||||
if value = 1 \/ value = true \/ value = "true"
|
||||
return true
|
||||
fi
|
||||
return null
|
||||
Number
|
||||
|
||||
number(logical)
|
||||
|
||||
The result is 1 or 0.
|
||||
|
||||
number(number)
|
||||
|
||||
The number is returned.
|
||||
|
||||
number(text, radix)
|
||||
|
||||
Convert a text to a number. The optional radix is an integer from 2 thru 37. (See Base 32.) The default radix is 10.
|
||||
|
||||
number(text, format)
|
||||
|
||||
number format
|
||||
format radix separator decimal point
|
||||
"" 10 .period
|
||||
"n"
|
||||
"u" _underbar
|
||||
"d" ,comma
|
||||
"s" space
|
||||
"v" .period ,comma
|
||||
"l" dependent on locale
|
||||
"i" _underbar
|
||||
"b" 2
|
||||
"o" 8
|
||||
"h" 16
|
||||
"t" 32
|
||||
"j" 0x- base 16
|
||||
0o- base 8
|
||||
0b- base 2
|
||||
otherwise base 10
|
||||
The number function converts a text into a number.
|
||||
|
||||
If it is unable to (possibly because of a formatting error), it returns null. The format character determines how the text is interpreted. If the format is not one of those listed, then null is returned.
|
||||
|
||||
Examples:
|
||||
|
||||
assign result: number("123,456,789.10", "d") # result is 123456789.1
|
||||
assign result: number("123.456.789,10", "v") # result is 123456789.1
|
||||
assign result: number("123.456.789,10", "d") # result is null
|
||||
assign result: number("123 456 789.10", "s") # result is 123456789.1
|
||||
assign result: number("12.350") # result is 12.35
|
||||
assign result: number("12.350", "v") # result is 12350
|
||||
assign result: number("12.350", "i") # result is null
|
||||
assign result: number("666") # result is 666
|
||||
assign result: number("666", "b") # result is null
|
||||
assign result: number("666", "o") # result is 438
|
||||
assign result: number("666", "h") # result is 1638
|
||||
assign result: number("666", "t") # result is 6342
|
||||
assign result: number("0666") # result is 666
|
||||
Record
|
||||
|
||||
record(record)
|
||||
|
||||
Copy. Make a mutable copy.
|
||||
|
||||
record(record, another_record)
|
||||
|
||||
Combine. Make a copy of a record, and then put all the fields of another_record into the copy.
|
||||
|
||||
record(record, array_of_keys)
|
||||
|
||||
Select. Make a new record containing only the fields that are named by the array_of_keys.
|
||||
|
||||
record(array_of_keys)
|
||||
|
||||
Set. Make a record using the array as the source of the keys. Each field value is true.
|
||||
|
||||
record(array_of_keys, value)
|
||||
|
||||
Value Set. Make a record using the array as the source of the keys. Each field value is value.
|
||||
|
||||
record(array_of_keys, function)
|
||||
|
||||
Functional Value Set. Make a record using the array as the source of the keys. The function is called for each key, yielding the field values.
|
||||
|
||||
Text
|
||||
|
||||
text(array)
|
||||
|
||||
Convert an array to text. The array can contain text and unicode codepoints. All are concatenated together to make a single text.
|
||||
|
||||
text(array, separator)
|
||||
|
||||
Convert an array to text. The array can contain text and unicode codepoints. All are concatenated together to make a single text. The separator text is inserted between each piece. The default separator is the empty text.
|
||||
|
||||
text(number, radix)
|
||||
|
||||
Convert a number to text. The optional radix is an integer from 2 thru 37. (See Base 32.) The default radix is 10.
|
||||
|
||||
text(number, format)
|
||||
|
||||
The format of the format text is
|
||||
|
||||
|
||||
format
|
||||
format_separation format_style format_places
|
||||
|
||||
|
||||
format_separation
|
||||
""
|
||||
digit
|
||||
|
||||
|
||||
format_style
|
||||
'b'
|
||||
'c'
|
||||
'e'
|
||||
'h'
|
||||
'i'
|
||||
'l'
|
||||
'n'
|
||||
'o'
|
||||
's'
|
||||
't'
|
||||
'u'
|
||||
|
||||
|
||||
format_places
|
||||
digit digit
|
||||
digit
|
||||
|
||||
Convert a number to formatted text. The text function converts a number to a text. It takes a format text input.
|
||||
|
||||
A format text contains a style letter that controls how a text is produced from the number. It is optionally preceded by a separation digit, and optionally followed by a places digit. There are real styles and integer styles. If the format input is not a proper format text, then null is returned.
|
||||
|
||||
Separation is a character that is placed between digits to improve readability. If separation is 0, then there is no separation. If separation is 3, then a character is inserted before the quadrillions, trillions, billions, millions, and thousands.
|
||||
|
||||
Places is the number of places to display after the decimal point (in real styles) or the minimum number of digits to display with zero-fill (in integer styles). If places is 0, then as many digits as necessary are displayed. Places can be zero or one or two digits.
|
||||
|
||||
real style base default
|
||||
separation default
|
||||
places decimal
|
||||
point separator
|
||||
e exponential 10 0 0 .period
|
||||
n number
|
||||
s space 3 space
|
||||
u underbar _underbar
|
||||
d decimal 2 ,comma
|
||||
c comma ,comma .period
|
||||
l locale determined by the locale
|
||||
The real format options are
|
||||
|
||||
"e" uses scientific notation. One digit is placed before the decimal point, and all of the remaining digits after, followed by e and the exponent.
|
||||
|
||||
"n" uses .period as the decimal point and no separator. It is the format used for numbers in Misty source programs and JSON. Scientific notation is used if the number value is extreme.
|
||||
|
||||
"s" uses .period as the decimal point and a space as the separator.
|
||||
|
||||
"u" uses .period as the decimal point and _underbar as the separator.
|
||||
|
||||
"d" uses .period as the decimal point and ,comma as the separator.
|
||||
|
||||
"c" uses ,comma as the decimal point and .period as the separator.
|
||||
|
||||
"l" depends on the locale to determine the characters to use as the decimal point and the separator.
|
||||
|
||||
The optional places determines the number of digits after the decimal point. The default is determined by the format, as seen in the table. If the places is 0, then the number of decimal places will be the fewest to exactly display the number without truncating. If the places is larger, then the field is padded if necessary with trailing 0.
|
||||
|
||||
The optional separation determines the spacing of the separator character. For example, to place a separator between billions, millions, and thousands (that is, every 3 digits) then separation should be 3. If separation is zero, then there is no separation. The default is determined by the style.
|
||||
|
||||
integer style base default
|
||||
separation minimum
|
||||
places separator
|
||||
i integer 10 0 1 _underbar
|
||||
b binary 2
|
||||
o octal 8
|
||||
h hexadecimal 16
|
||||
t Base32 32
|
||||
The integer styles first trunc the number. The fractional part of the number is ignored. The separation character is _underbar.
|
||||
|
||||
The optional places determines the minimum number of digits to show. More leading 0 may be shown if necessary. The default is determined by the format, as seen in the table.
|
||||
|
||||
The optional separation determines the spacing of the separator character. For example, to place a separator between billions, millions, and thousands (that is, every 3 digits) then separation should be 3. If separation is zero, then there is no separation. The default is determined by the format, as seen in the table.
|
||||
|
||||
Examples:
|
||||
|
||||
def data: 0123456789.1
|
||||
assign result: text(data) # result is "123456789.1"
|
||||
assign result: text(data, "n") # result is "123456789.1"
|
||||
assign result: text(data, "3s4") # result is "123 456 789.1000"
|
||||
assign result: text(data, "s") # result is "123 456 789.1"
|
||||
assign result: text(data, "d2") # result is "123,456,789.10"
|
||||
assign result: text(data, "4d0") # result is "1,2345,6789.1"
|
||||
assign result: text(data, "v2") # result is "123.456.789,10"
|
||||
assign result: text(data, "e") # result is "1.234567891e8"
|
||||
assign result: text(data, "e4") # result is "1.2345e8"
|
||||
assign result: text(data, "i") # result is "123456789"
|
||||
assign result: text(data, "8b") # result is "111_01011011_11001101_00010101"
|
||||
assign result: text(data, "o") # result is "726746425"
|
||||
assign result: text(data, "h") # result is "75BCD15"
|
||||
assign result: text(data, "t") # result is "3NQK8N"
|
||||
assign result: text(12) # result is "12"
|
||||
assign result: text(12, 8) # result is "14"
|
||||
assign result: text(12, 32) # result is "C"
|
||||
assign result: text(12, "4b8") # result is "0000_1100"
|
||||
assign result: text(12, "o3") # result is "014"
|
||||
assign result: text(12, "h4") # result is "000C"
|
||||
assign result: text(12, "t2") # result is "0C"
|
||||
text(text)
|
||||
|
||||
Return the text. The text is not altered.
|
||||
|
||||
text(text, from, to)
|
||||
|
||||
Make a copy of part of a text.
|
||||
|
||||
text: the text to copy.
|
||||
|
||||
from: the position at which to start copying. Default: 0, the beginning. If negative, add length(text).
|
||||
|
||||
to: the position at which to stop copying. Default: length(text), the end. If negative, add length(text).
|
||||
|
||||
If, after adjustment, from and to are not valid integers in the proper range, then it returns null. from must be positive and less than or equal to to. to must be less than or equal to length(text).
|
||||
|
||||
assign my_text: "miskatonic"
|
||||
text(my_text, 0, 3) # "mis" # the first 3
|
||||
text(my_text, 3, 6) # "kat" # from 3 to 6
|
||||
text(my_text, 5) # "tonic" # exclude the first 5
|
||||
text(my_text, 0, -4) # "miskat" # exclude the last 4
|
||||
text(my_text, -3) # "nic" # the last 3
|
||||
text(my_text, 0, 0) # ""
|
||||
text(my_text, 10) # ""
|
||||
text(my_text, 11) # null
|
||||
text(my_text, 2, 1) # null
|
||||
Sensory Functions
|
||||
|
||||
The sensory functions end with ?question mark. They always return a logical value.
|
||||
|
||||
actor?(value)
|
||||
|
||||
Is the value an actor address object?
|
||||
|
||||
actor?(me!) # true
|
||||
actor?("actor") # false
|
||||
actor?({actor: true}) # false
|
||||
array?(value)
|
||||
|
||||
Is the value an array? If the value is an array, the result is true. Otherwise, the result is false.
|
||||
|
||||
array?(0) # false
|
||||
array?({}) # false
|
||||
array?([]) # true
|
||||
not(array?([])) # false
|
||||
array?(pattern (1- {letter digit "_-%"})) # false
|
||||
array?(null) # false
|
||||
array?("array") # false
|
||||
blob?(value)
|
||||
|
||||
Is the value a blob? If the value is a blob, the result is true. Otherwise, the result is false.
|
||||
|
||||
blob?(0) # false
|
||||
blob?("blob") # false
|
||||
blob?(blob()) # true
|
||||
character?(value)
|
||||
|
||||
Is the value a character? If the value is a text with a length of 1, then the result is true. Otherwise, the result is false.
|
||||
|
||||
character?(1) # false
|
||||
character?("1") # true
|
||||
character?("character") # false
|
||||
character?("") # false
|
||||
character?("\u{FFFE}") # true
|
||||
character?() # false
|
||||
character?("/q") # false
|
||||
character?("\q") # true
|
||||
character?(<<">>) # true
|
||||
character?(null) # false
|
||||
data?(value)
|
||||
|
||||
Is the value data? If the value is a text, number, logical, array, blob, or record, then the result is true. If the value is a function, pattern, or null, the result is false.
|
||||
|
||||
data?(0) # true
|
||||
data?("") # true
|
||||
data?(["0"]) # true
|
||||
data?({}) # true
|
||||
data?(null) # false
|
||||
digit?(value)
|
||||
|
||||
Is the value a digit? If the value is a text with a length of 1 and is one of the 10 digit characters, then the result is true. Otherwise, the result is false.
|
||||
|
||||
digit?(0) # false
|
||||
digit?("0") # true
|
||||
digit?("9") # true
|
||||
digit?("09") # false
|
||||
digit?("digit") # false
|
||||
digit?("") # false
|
||||
digit?(1) # false
|
||||
digit?(["0"]) # false
|
||||
digit?("Z") # false
|
||||
false?(value)
|
||||
|
||||
Is the value false?
|
||||
|
||||
fit?(number)
|
||||
|
||||
Is the number a fit number? A number is a fit number if it is an integer that fits in 56 bits. All fit numbers are integers in the range -36028797018963968 thru 36028797018963967. Only fit numbers can be given to the fit functions. Misty has additional integers that are too big to fit.
|
||||
|
||||
function?(value)
|
||||
|
||||
Is the value a function? If the value is a function, then the result is true. Otherwise, the result is false.
|
||||
|
||||
function?(0) # false
|
||||
function?(function () (null)) # true
|
||||
function?("function") # false
|
||||
function?(null) # false
|
||||
function?(function?) # true
|
||||
integer?(value)
|
||||
|
||||
Is the value an integer? If the value is a number and if its fraction part is zero, then the result is true. Otherwise, the result is false.
|
||||
|
||||
integer?(0) # true
|
||||
integer?(13 / 4) # false
|
||||
integer?(16 / 4) # true
|
||||
integer?(65.0000000) # true
|
||||
integer?(65.0000001) # false
|
||||
integer?(null) # false
|
||||
integer?(true) # false
|
||||
integer?(1) # true
|
||||
integer?(36028797018963968) # true
|
||||
integer?(1.00001e100) # true
|
||||
letter?(value)
|
||||
|
||||
Is the value a letter? If the value is a text with a length of 1 and is a letter, then the result is true. Otherwise, the result is false.
|
||||
|
||||
letter?(0) # false
|
||||
letter?("0") # false
|
||||
letter?("letter") # false
|
||||
letter?("l") # true
|
||||
letter?("L") # true
|
||||
letter?("") # false
|
||||
letter?(null) # false
|
||||
logical?(value)
|
||||
|
||||
Is the value a logical? A logical is either a false or a true. All other values are not logical.
|
||||
|
||||
logical?(false) # true
|
||||
logical?(true) # true
|
||||
logical?(0) # false
|
||||
logical?() # false
|
||||
logical?(null) # false
|
||||
lower?(value)
|
||||
|
||||
Is the value a lower case letter? If the value is a text with a length of 1 and is a lower case letter, then the result is true. Otherwise, the result is false.
|
||||
|
||||
lower?(0) # false
|
||||
lower?("0") # false
|
||||
lower?("lower") # false
|
||||
lower?("l") # true
|
||||
lower?("L") # false
|
||||
lower?("") # false
|
||||
null?(value)
|
||||
|
||||
Is the value null? This does the same thing as value = null.
|
||||
|
||||
number?(value)
|
||||
|
||||
Is the value a number? If the value is a number, then the result is true. Otherwise, the result is false.
|
||||
|
||||
number?(0) # true
|
||||
number?((13 / 4)) # true
|
||||
number?((13 / 0)) # false
|
||||
number?(98.6) # true
|
||||
number?("0") # false
|
||||
number?(1) # true
|
||||
pattern?(value)
|
||||
|
||||
Is the value a pattern? If the value is a pattern, then the result is true. Otherwise, the result is false.
|
||||
|
||||
pattern?(pattern (1- {letter digit "_-%"})) # true
|
||||
record?(value)
|
||||
|
||||
Is the value a record? If the value is a record, then the result is true. Otherwise, the result is false.
|
||||
|
||||
record?(0) # false
|
||||
record?({}) # true
|
||||
record?([]) # false
|
||||
record?("record") # false
|
||||
record?("{}") # false
|
||||
record?(function () ({})) # false
|
||||
record?(pattern (1- {letter digit "_-%"})) # false
|
||||
record?(@) # true
|
||||
stone?(value)
|
||||
|
||||
Is the value stone?
|
||||
|
||||
stone?("false") # true
|
||||
stone?(9) # true
|
||||
stone?(null) # true
|
||||
stone?({}) # false
|
||||
stone?(stone({})) # true
|
||||
text?(value)
|
||||
|
||||
Is the value a text? If the value is a text, then the result is true. Otherwise, the result is false.
|
||||
|
||||
text?(0) # false
|
||||
text?("0") # true
|
||||
text?("number") # true
|
||||
text?("") # true
|
||||
text?(null) # false
|
||||
true?(value)
|
||||
|
||||
Is the value true?
|
||||
|
||||
upper?(value)
|
||||
|
||||
Is the value an upper case letter? If the value is a text with a length of 1 and is an upper case letter, then the result is true. Otherwise, the result is false.
|
||||
|
||||
upper?(0) # false
|
||||
upper?("0") # false
|
||||
upper?("UPPER") # false
|
||||
upper?("u") # false
|
||||
upper?("U") # true
|
||||
upper?("") # false
|
||||
whitespace?(value)
|
||||
|
||||
Is the value whitespace? If the value is a nonempty text containing only whitespace characters, then the result is true. Otherwise, the result is false.
|
||||
|
||||
whitespace?(0) # false
|
||||
whitespace?(32) # false
|
||||
whitespace?(char(32)) # true
|
||||
whitespace?("0") # false
|
||||
whitespace?(" ") # true
|
||||
whitespace?("\t") # true
|
||||
whitespace?("\r") # true
|
||||
whitespace?("\r\n") # true
|
||||
whitespace?("space") # false
|
||||
whitespace?(" ") # true
|
||||
whitespace?(" L") # false
|
||||
whitespace?("") # false
|
||||
Standard Functions
|
||||
|
||||
abs(number)
|
||||
|
||||
Absolute value. Return the positive form of the number. If the input value is not a number, the result is null.
|
||||
|
||||
apply(function, array)
|
||||
|
||||
Apply. Execute the function and return its return value. Pass the elements of the array as input values. See proxy.
|
||||
|
||||
If the first input value is not a function, apply returns its first input value.
|
||||
|
||||
If length(array) is greater than length(function), it disrupts.
|
||||
|
||||
If the second argument is not an array, it is used as a single input value.
|
||||
|
||||
ceiling(number, place)
|
||||
|
||||
If place is 0 or null, the number is rounded up to the smallest integer that is greater than or equal to the number. If place is a small positive integer, then the number is rounded up to that decimal place.
|
||||
|
||||
Examples:
|
||||
|
||||
assign result: ceiling(12.3775) # result is 13
|
||||
assign result: ceiling(12.3775, 0) # result is 13
|
||||
assign result: ceiling(12.3775, 1) # result is 20
|
||||
assign result: ceiling(12.3775, -2) # result is 12.38
|
||||
assign result: ceiling(-12.3775, -2) # result is -12.37
|
||||
assign result: ceiling(-12.3775) # result is -12
|
||||
character(value)
|
||||
|
||||
If the value is a text, it returns the first character. If the value is a non-negative 32-bit integer, it returns the character from that codepoint. Otherwise, it returns the empty string.
|
||||
|
||||
codepoint(text)
|
||||
|
||||
The codepoint function returns the codepoint number of the first character of the text. If the input value is not a text, or if it is the empty text, then it returns null.
|
||||
|
||||
extract(text, pattern, from, to)
|
||||
|
||||
The text is matched to the pattern. If it does not match, the result is null. If the pattern does match, then the result is a record containing the saved fields.
|
||||
|
||||
fallback(requestor_array)
|
||||
|
||||
The fallback requestor factory returns a requestor function that tries each of the requestors in the requstor_array until it gets a success. When the requestor is called, it calls the first requestor in requestor_array. If that is eventually successful, its value is passed to the callback. But if that requestor fails, the next requestor is called, and so on. If none of the requestors is successful, then the fallback fails. If any one succeeds, then the fallback succeeds.
|
||||
|
||||
The fallback requestor returns a cancel function that can be called when the result is no longer needed.
|
||||
|
||||
filter(array, function)
|
||||
|
||||
The filter function calls a function for every element in the array, passing each element and its element number.
|
||||
|
||||
(element, element_nr)
|
||||
When the function's return value is true, then the element is copied into a new array. If the function's return value is false, then the element is not copied into the new array. If the return value is not a logical, then the filter returns null.
|
||||
|
||||
It returns a new array. The length of the new array is between 0 thru length(array). It returns null if the function input is not a function.
|
||||
|
||||
Example:
|
||||
|
||||
def data: [0, 1.25, 2, 3.5, 4, 5.75]
|
||||
def integers: filter(data, integer?) # integers is [0, 2, 4]
|
||||
find(array, function, reverse, from)
|
||||
|
||||
Call the function for each element of the array, passing each element and its element number.
|
||||
|
||||
(element, element_nr)
|
||||
If the function returns true, then find returns the element number of the current element.
|
||||
|
||||
If the second input value is not a function, then it is compared exactly to the elements.
|
||||
|
||||
If the reverse input value is true, then search begins at the end of the array and works backward.
|
||||
|
||||
The from input value gives the element number to search first. The default is 0 unless reverse is true, when the default is length(array) - 1.
|
||||
|
||||
find returns the element number of the found value. If nothing is found, find returns null.
|
||||
|
||||
floor(number, place)
|
||||
|
||||
If place is 0 or null, the number is rounded down to the greatest integer that is less than or equal to the number. If place is a small positive integer, then the number is rounded down to that many decimal places. For positive numbers, this is like discarding decimal places.
|
||||
|
||||
Examples:
|
||||
|
||||
assign result: floor(12.3775) # result is 12
|
||||
assign result: floor(12.3775, -2) # result is 12.37
|
||||
assign result: floor(-12.3775, 0) # result is -13
|
||||
assign result: floor(-12.3775, -2) # result is -12.38
|
||||
for(array, function, reverse, exit)
|
||||
|
||||
For each. Call the function with each element of the array. The function is passed each element and its element number.
|
||||
|
||||
(element, element_nr)
|
||||
If reverse is true, then it starts with the last element and works backwards.
|
||||
|
||||
If exit is not null, then when the function returns the exit value, then the for function returns early. The exit value is usually true or false, but it may be anything. If exit is null, then every element is processed.
|
||||
|
||||
The for function returns null unless it did an early exit, when it returns the exit value.
|
||||
|
||||
format(text, collection, transformer)
|
||||
|
||||
The format function makes a new text with substitutions in an original text. A collection is either an array of texts or a record of texts.
|
||||
|
||||
A search is made for {left brace and }right brace in the text. If they are found, the middle text between them is examined. If the collection is an array, the middle text is used as a number, and then the matched {left brace and middle and }right brace are replaced with the text at that subscript in the array. If the collection is a record, and if the middle text is the key of a member of the collection with a text value, then the value of the member is used in the substitution. Unmatched text is not altered.
|
||||
|
||||
The text between {left brace and }right brace is broken on the :colon character. The left text will be used as a number or name to select a value from the collection. (The value need not be a text.)
|
||||
|
||||
If a transformer input is a function, then it is called with the collection[left text] and right text as inputs. If it returns a text, then the substitution is made.
|
||||
|
||||
If a transformer input is a record, then the right text is used to select a function from the transformer. That function is passed the value from the collection. If the return value is a text, that text will substitute. If there is no colon, then the empty text is used to select the function from the transformer. If the transformer does not produce a function, or if the function does not return a text, then no replacement occurs. If transformer[right text](collection[left text]) produces a text, then the substitution is made.
|
||||
|
||||
If the substitution is not made, and if collection[left text] is a number, then the right text is used as a format input in calling collection[left text].text(right text). If it returns a text, then the substitution is made.
|
||||
|
||||
Example:
|
||||
|
||||
var result: format("{0} in {1}!", ["Malmborg", "Plano"])
|
||||
# result is "Malmborg in Plano!"
|
||||
fraction(number)
|
||||
|
||||
The fraction function returns the fractional part of a number It returns null for non-numbers. Also see whole.
|
||||
|
||||
length(value)
|
||||
|
||||
The length function
|
||||
value result
|
||||
array number of elements
|
||||
blob number of bits
|
||||
logical null
|
||||
function number of named inputs
|
||||
null null
|
||||
number null
|
||||
record record.length()
|
||||
text number of codepoints
|
||||
Length. Find the length of an array in elements, a blob in bits, or a text in codepoints. For functions, it is the arity (or number of inputs).
|
||||
|
||||
If the value is a record containing a length field
|
||||
|
||||
If the length field contains a function, then length(my_record) has the same effect as my_record.length().
|
||||
|
||||
If the length field contains a number, return the number.
|
||||
|
||||
All other values produce null.
|
||||
|
||||
lower(text)
|
||||
|
||||
The lower function returns a text in which all uppercase characters are converted to lowercase.
|
||||
|
||||
Example:
|
||||
|
||||
assign result: lower("Carl Hollywood") # result is "carl hollywood"
|
||||
max(number, number)
|
||||
|
||||
Maximum. The result is the larger of the two numbers. If either input value is not a number, the result is null.
|
||||
|
||||
max(3, 4) # 4
|
||||
max can be used with min to constrain values within an acceptable range.
|
||||
|
||||
min(max(2, 3), 7) # 3
|
||||
min(max(4, 3), 7) # 4
|
||||
min(max(8, 3), 7) # 7
|
||||
max(1, null) # null
|
||||
max(null, 1) # null
|
||||
min(number,number)
|
||||
|
||||
Minimum. The result is the smaller of the two numbers. If either input value is not a number, the result is null.
|
||||
|
||||
min(3, 4) # 3
|
||||
modulo(dividend, divisor)
|
||||
|
||||
The result of modulo(dividend, divisor) is dividend - (divisor * floor(dividend / divisor)). The result has the sign of the divisor.
|
||||
|
||||
If dividend is 0, then the result is 0. If divisor is 0, or if either operand is not a number, then the result is null. dividend and divisor are not required to be integers.
|
||||
|
||||
If both input values are integers, and if the divisor is a positive power of two, then it is the same as and(dividend, divisor - 1).
|
||||
|
||||
neg(number)
|
||||
|
||||
Negate. Reverse the sign of a number. If the input value is not a number, the result is null. Note that the -minus sign is not used as a prefix negation operator.
|
||||
|
||||
normalize(text)
|
||||
|
||||
Unicode normalize. Return a text whose textual value is the same as text, but whose binary representation is in the specified Unicode normalization form. The two texts will display the same, but might not be equal.
|
||||
|
||||
not(logical)
|
||||
|
||||
Not. Return the opposite logical. If the input value is not a logical, it returns null.
|
||||
|
||||
parallel(requestor_array, throttle, need)
|
||||
|
||||
Parallel. The parallel requestor factory returns a resquestor function. When the requestor function is called with a callback function and a value, every requestor in the requestor_array is called with the value, and the results are placed in corresponding elements of the results array. This all happens in parallel. The value produced by the first element of the requestor_array provides the first element of the result. The completed results array is passed to the callback function.
|
||||
|
||||
By default, it starts all of the requestors in the requestor_array at once, each in its own turn so that they do not interfere with each other. This can shock some systems by unleashing a lot of demand all at once. To mitigate the shock, the optional throttle argument sets the maximum number of requestors running at a time. As requestors succeed or fail, waiting requestors can be started. The throttle is optional. If provided, the throttle is a number: the maximum number of requestors to have running at any time.
|
||||
|
||||
Ordinarily, the number of successes must be the same as the number of requestors in the requestor_array. If you need few successes, specify your need with the need argument. The need could be 1, meaning that 1 or more successes are needed. The need could be 0, meaning that no successes are needed. If the number of successes is greater than or equal to need, then the whole operation succeeds. The need must be between 0 and requestor_array.length.
|
||||
|
||||
The requestor function itself returns a cancel function that cancels all of the pending requestors from the requestor_array.
|
||||
|
||||
race(requestor_array, throttle, need)
|
||||
|
||||
Race. The race function is a requestor factory that takes an array of requestor functions and returns a requestor function that starts all of the requestors in the requestor_array at once.
|
||||
|
||||
By default, it starts all of the requestors in the requestor_array at once, each in its own turn so that they do not interfere with each other. This can shock some systems by unleashing a lot of demand at once. To mitigate the shock, the optional throttle argument sets the maximum number of requestors running at a time. As requestors succeed or fail, waiting requestors can be started.
|
||||
|
||||
By default, a single result is produced. If an array of results is need, specify the needed number of results in the need parameter. When the needed number of successful results is obtained, the operation ends. The results go into a sparce array aligned with the requestor_array, and unfinished requestors are cancelled. The need argument must be between 1 and requestor_array.length.
|
||||
|
||||
The returned requestor function returns a cancel function that cancels all of the pending requestors from the requestor_array.
|
||||
|
||||
reduce(array, function, initial, reverse)
|
||||
|
||||
Reduce. The reduce function takes a function that takes two input values and returns a value.
|
||||
|
||||
function (first, second) {
|
||||
return ...
|
||||
}
|
||||
The function is called for each element of the array, passing the result of the previous iteration to the next iteration.
|
||||
|
||||
The initial value is optional. If present, the function is called for every element of the array.
|
||||
|
||||
If initial is null:
|
||||
|
||||
If length(array) is 0, then it returns null.
|
||||
If length(array) is 1, then it returns array[0].
|
||||
If length(array) is 2, it returns function(array[0], array[1]).
|
||||
If length(array) is 3, it returns function(function(array[0], array[1]), array[2]).
|
||||
And so on.
|
||||
If initial is not null:
|
||||
|
||||
If length(array) is 0, then it returns initial.
|
||||
If length(array) is 1, then it returns function(initial, array[0]).
|
||||
If length(array) is 2, it returns function(function(initial, array[0]), array[1]).
|
||||
If length(array) is 3, it returns function(function(function(initial, array[0]), array[1]), array[2]).
|
||||
And so on.
|
||||
If reverse is true, then the work begins at the end of the array and works backward.
|
||||
|
||||
Example:
|
||||
|
||||
def data: [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
def total: reduce(data, '+) # total is 45
|
||||
def product: reduce(data, '*) # product is 362880
|
||||
remainder(dividend, divisor)
|
||||
|
||||
Remainder. For fit integers, the remainder is dividend - ((dividend // divisor) * divisor).
|
||||
|
||||
replace(text, target, replacement, limit)
|
||||
|
||||
Return a new text in which the target is replaced by the replacement.
|
||||
|
||||
text: the source text.
|
||||
|
||||
target: a text or pattern that should be replaced in the source.
|
||||
|
||||
replacement: text to replace the matched text, or a function that takes the matched text and the starting position, and returns the replacement text or null if it should be left alone.
|
||||
|
||||
limit: The maximum number of replacements. The default is all possible replacements. The limit includes null matches.
|
||||
|
||||
reverse(array)
|
||||
|
||||
The reverse function makes a new array or blob with the elements or bits in the opposite order.
|
||||
|
||||
It returns a new, reversed array or blob.
|
||||
|
||||
Example:
|
||||
|
||||
def data: ["I", "am", "Sam"]
|
||||
def result: reverse(data) # the result is ["Sam", "am", "I"]
|
||||
round(number, place)
|
||||
|
||||
If place is 0 or null, the number is rounded to the nearest integer. If place is a small integer, then the number is rounded to that many decimal places.
|
||||
|
||||
Examples:
|
||||
|
||||
round(12.3775) # 12
|
||||
round(12.3775, -2) # 12.38
|
||||
round(-12.3775, 0) # -12
|
||||
round(-12.3775, 1) # -10
|
||||
round(-12.3775, 2) # 0
|
||||
round(-12.3775, -2) # -12.38
|
||||
search(text, target, from)
|
||||
|
||||
Search the text for the target. If the target is found, return the character position of the left-most part of the match. If the search fails, return null.
|
||||
|
||||
text: the source text.
|
||||
|
||||
target: a text or pattern that should be found in the source.
|
||||
|
||||
from: The starting position for the search. The default is 0, the beginning of the text. If from is negative, it is added to length(text).
|
||||
|
||||
sequence(requestor_array)
|
||||
|
||||
Sequence. The sequence requestor factory that takes a requestor_array and returns a requestor function that starts all of the requestors in the requestor_array one at a time. The result of each becomes the input to the next. The last result is the result of the sequence.
|
||||
|
||||
sequence returns a requestor that returns a cancel function and processes each requestor in requestor_array one at a time. Each of those requestors is passed the result of the previous requestor as its value argument. If all succeed, then the sequence succeeds, giving the result of the last of the requestors. If any fail, then the sequence fails.The sequence succeeds if every requestor in the requestor_array succeeds
|
||||
|
||||
sign(number)
|
||||
|
||||
The sign function returns -1 if the number is negative, 0 if the number is exactly 0, 1 if the number is positive, and null if it is not a number.
|
||||
|
||||
sort(array, select)
|
||||
|
||||
The sort function produces a new array in which the values are sorted. Sort keys must be either all numbers or all texts. Any other type of key or any error in the key calculation will cause the sort to fail, returning null. The sort is ascending. The sort is stable, so the relative order of equal keys is preserved.
|
||||
|
||||
The optional select input determines how the sort key for each element is selected.
|
||||
|
||||
select type Sort key for array[index] Description
|
||||
null array[index] The sort key is the element itself. This is useful for sorting simple arrays of numbers or texts.
|
||||
text array[index][select] The sort key is the select field of each record element. This is useful for sorting arrays of records.
|
||||
number array[index][select] The sort key is the select element of each array element. This is useful for sorting arrays of arrays.
|
||||
array select[index] select is an array of the same length containing the sort keys.
|
||||
It returns a new, sorted array.
|
||||
|
||||
Examples:
|
||||
|
||||
def foods: ["oats", "peas", "beans", "barley"]
|
||||
def result: sort(foods) # result is ["barley", "beans", "oats", "peas"]
|
||||
|
||||
var stooges: [
|
||||
{first: "Moe", last: "Howard"}
|
||||
{first: "Joe", last: "DeRita"}
|
||||
{first: "Shemp", last: "Howard"}
|
||||
{first: "Larry", last: "Fine"}
|
||||
{first: "Joe", last: "Besser"}
|
||||
{first: "Curly", last: "Howard"}
|
||||
]
|
||||
assign stooges: sort(sort(stooges, "first"), "last")
|
||||
# stooges is now [
|
||||
# {first: "Joe", last: "Besser"}
|
||||
# {first: "Joe", last: "DeRita"}
|
||||
# {first: "Larry", last: "Fine"}
|
||||
# {first: "Curly", last: "Howard"}
|
||||
# {first: "Moe", last: "Howard"}
|
||||
# {first: "Shemp", last: "Howard"}
|
||||
# ]
|
||||
assign stooges: sort(stooges, [50, 60, 20, 40, 10, 30])
|
||||
# stooges is now [
|
||||
# {first: "Moe", last: "Howard"}
|
||||
# {first: "Larry", last: "Fine"}
|
||||
# {first: "Shemp", last: "Howard"}
|
||||
# {first: "Curly", last: "Howard"}
|
||||
# {first: "Joe", last: "Besser"}
|
||||
# {first: "Joe", last: "DeRita"}
|
||||
# ]
|
||||
stone(value)
|
||||
|
||||
Petrify the value, turning it into stone. Its contents are preserved, but it can no longer be modified by the assign statement or the blob.write functions. This is usually performed on arrays, records, and blobs. All other types are already stone. Attempting to turn a value that is stone to stone will have no effect.
|
||||
|
||||
The stone operation is deep. Any mutable objects in the value will also be turned to stone.
|
||||
|
||||
This can not be reversed. Immutable objects can never become mutable. A mutable copy can be made of an immutable object, but the original object remains immutable.
|
||||
|
||||
The stone function returns the value.
|
||||
|
||||
trim(text, reject)
|
||||
|
||||
The trim function removes selected characters from the ends of a text. The default is to remove control characters and spaces.
|
||||
|
||||
assign result: " Hello there ".trim() # result is "Hello there"
|
||||
trunc(number, place)
|
||||
|
||||
The number is truncated toward zero. If the number is positive, the result is the same as floor(place). If the number is negative, the result is the same as ceiling(place).
|
||||
|
||||
If place is a small integer, then the number is rounded down to that many decimal places. This is like discarding decimal places.
|
||||
|
||||
Examples:
|
||||
|
||||
assign result: trunc(12.3775, 0) # result is 12
|
||||
assign result: trunc(12.3775, 2) # result is 12.37
|
||||
assign result: trunc(-12.3775) # result is -12
|
||||
assign result: trunc(-12.3775, 2) # result is -12.37
|
||||
turkish_lower(text)
|
||||
|
||||
Similar to lower, except that I goes to ıdotless i.
|
||||
|
||||
turkish_upper(text)
|
||||
|
||||
Similar to upper, except that i goes to İI dot.
|
||||
|
||||
upper(text)
|
||||
|
||||
The upper function returns a text in which all lowercase characters are converted to uppercase.
|
||||
|
||||
Example:
|
||||
|
||||
assign result: upper("Carl Hollywood") # result is "CARL HOLLYWOOD"
|
||||
whole(number)
|
||||
|
||||
The whole function returns the whole part of a number. It returns null for non-numbers. Also see fraction.
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||

|
||||
|
||||
Cell is an actor-based scripting language for building concurrent applications. It combines a familiar JavaScript-like syntax with the actor model of computation.
|
||||
Cell is an actor-based scripting language for building concurrent applications. It combines a familiar C-like syntax with the actor model of computation, optimized for low memory usage and simplicity.
|
||||
|
||||
## Key Features
|
||||
|
||||
@@ -35,7 +35,7 @@ cell hello
|
||||
## Standard Library
|
||||
|
||||
- [text](library/text.md) — string manipulation
|
||||
- [number](library/number.md) — numeric operations
|
||||
- [number](library/number.md) — numeric operations (functions are global: `floor()`, `max()`, etc.)
|
||||
- [array](library/array.md) — array utilities
|
||||
- [object](library/object.md) — object utilities
|
||||
- [blob](library/blob.md) — binary data
|
||||
|
||||
@@ -46,98 +46,98 @@ number("0xff", "j") // 255
|
||||
|
||||
## Methods
|
||||
|
||||
### number.abs(n)
|
||||
### abs(n)
|
||||
|
||||
Absolute value.
|
||||
|
||||
```javascript
|
||||
number.abs(-5) // 5
|
||||
number.abs(5) // 5
|
||||
abs(-5) // 5
|
||||
abs(5) // 5
|
||||
```
|
||||
|
||||
### number.sign(n)
|
||||
### sign(n)
|
||||
|
||||
Returns -1, 0, or 1.
|
||||
|
||||
```javascript
|
||||
number.sign(-5) // -1
|
||||
number.sign(0) // 0
|
||||
number.sign(5) // 1
|
||||
sign(-5) // -1
|
||||
sign(0) // 0
|
||||
sign(5) // 1
|
||||
```
|
||||
|
||||
### number.floor(n, place)
|
||||
### floor(n, place)
|
||||
|
||||
Round down.
|
||||
|
||||
```javascript
|
||||
number.floor(4.9) // 4
|
||||
number.floor(4.567, 2) // 4.56
|
||||
floor(4.9) // 4
|
||||
floor(4.567, 2) // 4.56
|
||||
```
|
||||
|
||||
### number.ceiling(n, place)
|
||||
### ceiling(n, place)
|
||||
|
||||
Round up.
|
||||
|
||||
```javascript
|
||||
number.ceiling(4.1) // 5
|
||||
number.ceiling(4.123, 2) // 4.13
|
||||
ceiling(4.1) // 5
|
||||
ceiling(4.123, 2) // 4.13
|
||||
```
|
||||
|
||||
### number.round(n, place)
|
||||
### round(n, place)
|
||||
|
||||
Round to nearest.
|
||||
|
||||
```javascript
|
||||
number.round(4.5) // 5
|
||||
number.round(4.567, 2) // 4.57
|
||||
round(4.5) // 5
|
||||
round(4.567, 2) // 4.57
|
||||
```
|
||||
|
||||
### number.trunc(n, place)
|
||||
### trunc(n, place)
|
||||
|
||||
Truncate toward zero.
|
||||
|
||||
```javascript
|
||||
number.trunc(4.9) // 4
|
||||
number.trunc(-4.9) // -4
|
||||
trunc(4.9) // 4
|
||||
trunc(-4.9) // -4
|
||||
```
|
||||
|
||||
### number.whole(n)
|
||||
### whole(n)
|
||||
|
||||
Get the integer part.
|
||||
|
||||
```javascript
|
||||
number.whole(4.9) // 4
|
||||
number.whole(-4.9) // -4
|
||||
whole(4.9) // 4
|
||||
whole(-4.9) // -4
|
||||
```
|
||||
|
||||
### number.fraction(n)
|
||||
### fraction(n)
|
||||
|
||||
Get the fractional part.
|
||||
|
||||
```javascript
|
||||
number.fraction(4.75) // 0.75
|
||||
fraction(4.75) // 0.75
|
||||
```
|
||||
|
||||
### number.min(...values)
|
||||
### min(...values)
|
||||
|
||||
Return the smallest value.
|
||||
|
||||
```javascript
|
||||
number.min(3, 1, 4, 1, 5) // 1
|
||||
min(3, 1, 4, 1, 5) // 1
|
||||
```
|
||||
|
||||
### number.max(...values)
|
||||
### max(...values)
|
||||
|
||||
Return the largest value.
|
||||
|
||||
```javascript
|
||||
number.max(3, 1, 4, 1, 5) // 5
|
||||
max(3, 1, 4, 1, 5) // 5
|
||||
```
|
||||
|
||||
### number.remainder(dividend, divisor)
|
||||
### remainder(dividend, divisor)
|
||||
|
||||
Compute remainder.
|
||||
|
||||
```javascript
|
||||
number.remainder(17, 5) // 2
|
||||
remainder(17, 5) // 2
|
||||
```
|
||||
|
||||
@@ -70,18 +70,18 @@ text.search("hello world", "xyz") // null
|
||||
text.search("hello hello", "hello", 1) // 6
|
||||
```
|
||||
|
||||
### text.replace(text, target, replacement, limit)
|
||||
### text.replace(text, target, replacement, cap)
|
||||
|
||||
Replace occurrences of `target` with `replacement`.
|
||||
Replace occurrences of `target` with `replacement`. If `cap` is not specified, replaces all occurrences.
|
||||
|
||||
```javascript
|
||||
text.replace("hello", "l", "L") // "heLLo"
|
||||
text.replace("hello", "l", "L", 1) // "heLlo"
|
||||
text.replace("hello", "l", "L") // "heLLo" (replaces all)
|
||||
text.replace("hello", "l", "L", 1) // "heLlo" (replaces first only)
|
||||
|
||||
// With function
|
||||
text.replace("hello", "l", function(match, pos) {
|
||||
return pos == 2 ? "L" : match
|
||||
}) // "heLlo"
|
||||
}) // "heLLo" (replaces all by default)
|
||||
```
|
||||
|
||||
### text.format(text, collection, transformer)
|
||||
|
||||
248
docs/memory.md
Normal file
248
docs/memory.md
Normal file
@@ -0,0 +1,248 @@
|
||||
# Cell actor scripting language
|
||||
|
||||
Cell is a Misty [https://mistysystem.com](https://mistysystem.com) implementation.
|
||||
|
||||
## Memory
|
||||
Values are 32 bit for 32 bit builds and 64 bit for 64 bit builds.
|
||||
|
||||
### 32 bit value
|
||||
|
||||
LSB = 0
|
||||
payload is a 31 bit signed int
|
||||
|
||||
LSB = 01
|
||||
payload is a 30 bit pointer
|
||||
|
||||
LSB = 11
|
||||
next 3 bits = special tag. 27 bits of payload.
|
||||
|
||||
### 64 bit value
|
||||
LSB = 0
|
||||
payload is a 32 bit signed int, using high 32 bits
|
||||
|
||||
LSB = 01
|
||||
payload is a 61 bit pointer
|
||||
|
||||
LSB = 101
|
||||
Short float: a 61 bit double, with 3 less exponent bits
|
||||
|
||||
LSB = 11
|
||||
Special tag: next 3 bits. 5 bits total. 59 bits of payload. 8 total special tags.
|
||||
|
||||
Special tags:
|
||||
1: Bool. Payload is 0 or 1.
|
||||
2: null. payload is 0.
|
||||
3: exception.
|
||||
4: string.
|
||||
Immediate string. Next 3 low bits = length in bytes. Rest is string data. This allows for strings up to 7 ascii letters. Encoded in utf8.
|
||||
|
||||
## Numbers and math
|
||||
Cell can be compiled with different levels of exactness for numeracy. Any number which cannot be represented exactly becomes "null". Any numeric operation which includes "null" results in "null".
|
||||
|
||||
Using short floats in a 64 bit system means you have doubles in the range of +- 10^38, not the full range of double. If you create a number out of that range, it's null.
|
||||
|
||||
You can also compile a 64 bit system with full precision doubles, but this will use more memory and may be slower.
|
||||
|
||||
You can also compile a 64 bit system with 32 bit floats, stored as a 32 bit int is. Again, out of the 32 bit float range = null.
|
||||
|
||||
You can compile without floating point support at all; 32 bit ints are then used for fixed point calculations.
|
||||
|
||||
Or, you can compile using Dec64, which is a 64 bit decimal floating point format, for exact precision.
|
||||
|
||||
## Objects
|
||||
Objects are heap allocated, referenced by a pointer value. They are all preceded by an object header, the length of a word on the system.
|
||||
|
||||
### 64 bit build
|
||||
56 bits capacity
|
||||
1 bit memory reclamation flag: note that this obj has already been moved
|
||||
2 bit reserved (per object)
|
||||
1 bit stone: note that this obj is immutable
|
||||
3 bit type: note the type of the object
|
||||
1 bit: fwd: note that this obj is a forward linkage
|
||||
|
||||
Last bit ..1:
|
||||
The forward type indicates that the object (an array, blob, pretext, or record) has grown beyond its capacity and is now residing at a new address. The remaining 63 bits contain the address of the enlarged object. Forward linkages are cleaned up by the memory reclaimer.
|
||||
|
||||
Type 7: C light C object
|
||||
|
||||
Header
|
||||
Pointer
|
||||
|
||||
Capacity is an ID of a registered C type.
|
||||
Pointer is a pointer to the opaque C object.
|
||||
|
||||
Type 0: Array
|
||||
Header
|
||||
Length
|
||||
Element[]
|
||||
|
||||
Capacity is number of elements the array can hold. Length is number of elements in use. Number of words used by an array is capacity + 2.
|
||||
|
||||
Type 1: blob
|
||||
Header
|
||||
Length
|
||||
Bit[]
|
||||
Capacity is number of bits the blob can hold. Length is number of bits in use. Bits follow, from [0] to [capacity - 1], with [0] bit in the most significant position of word 2, and [63] in the least significant position of word 2. The last word is zero filled, if necessary.
|
||||
|
||||
Number of words used is (capacity + 63) // 64 + 2
|
||||
|
||||
Type 2: Text
|
||||
Text has two forms, depending on if it is stone or not, which changes the meaning of its length word.
|
||||
|
||||
Header
|
||||
Length(pretext) or Hash(text)
|
||||
Character[0] and character[1]
|
||||
|
||||
Capacity of pretex is the number of characters it can hold. During stoning and reclamation, capacity is set to the length.
|
||||
|
||||
The capacity of a text is its length.
|
||||
|
||||
The length of a pretext is the number of characters it contains; it is not greater than the capacity.
|
||||
|
||||
Hash of a text is used for organizing records. If the hash is zero, it's not been computed yet. All texts in the immutable memory have hashes.
|
||||
|
||||
A text object contains UTF32 characters, packed two per word. If the number of characters is odd, the least significant half of the last word is zero filled.
|
||||
|
||||
The number of words used by a text is (capacity + 1) // 2 + 2
|
||||
|
||||
Type 3: Record
|
||||
|
||||
A record is an array of fields represented as key/value pairs. Fields are located by hashes of texts, using open addressing with linear probing and lazy deletion. The load factor is less than 0.5.
|
||||
|
||||
Header
|
||||
Prototype
|
||||
Length
|
||||
Key[0]
|
||||
Value[0]
|
||||
Key[1]
|
||||
Value[1]
|
||||
...
|
||||
|
||||
The capacity is the number of fields the record can hold. It is a power of two minus one. It is at least twice the length.
|
||||
|
||||
The length is the number of fields that the record currently contains.
|
||||
|
||||
A field candidate number is identified by and(key.hash, capacity). In case of hash collision, advance to the next field. If this goes past the end, continue with field 1. Field 0 is reserved.
|
||||
|
||||
The "exception" special tag is used to mark deleted entries in the object map.
|
||||
|
||||
The number of words used by a record is (capacity + 1) * 2.
|
||||
|
||||
Prototypes are searched for for properties if one cannot be found on the record itself. Prototypes can have prototypes.
|
||||
|
||||
#### key[0] and value[0]
|
||||
These are reserved for internal use, and skipped over during key probing.
|
||||
|
||||
The first 32 bits of key are used as a 32 bit integer key, if this object has ever been used as a key itself.
|
||||
|
||||
The last 32 bits are used as an opaque C class key. C types can be registered with the system, and each are assigned a monotonically increasing number. In the case that this object has a C type, then the bottom 32 bits of key[0] are not 0. If that is the case, then a pointer to its C object is stored in value[0].
|
||||
|
||||
#### Valid keys & Hashing
|
||||
Keys are stored directly in object maps. There are three possibilities for a vaild key: an object text, an object record, or an immediate text.
|
||||
|
||||
In the case of an immediate text, the hash is computed on the fly using the fash64_hash_one function, before being used to look up the key in the object map. Direct value comparison is used to confirm the key.
|
||||
|
||||
For object texts (texts longer than 7 ascii chars), the hash is stored in the text object itself. When an object text is used as a key, a stone version is created and interned. Any program static texts reference this stoned, interned text. When looking up a heap text as a key, it is first discovered if it's in the interned table. If it's not, the key is not in the object (since all keys are interned). If it is, the interned version is returned to check against the object map. The hash of the interned text is used to look up the key in the object map, and then direct pointer comparison is used to confirm the key.
|
||||
|
||||
For record keys, these are unique; once a record is used as a key, it gets assigned a monotonically increasing 32 bit integer, stored in key[0]. When checking it in an object map, the integer is used directly as the key. If key[0] is 0, the record has not been used as a key yet. If it's not 0, fash64_hash_one is used to compute a hash of its ID, and then direct value pointer comparison is used to confirm.
|
||||
|
||||
### Text interning
|
||||
Texts that cannot fit in an immediate, and which are used as an object key, create a stoned and interned version (the pointer which is used as the key). Any text literals are also stoned and interned.
|
||||
|
||||
The interning table is an open addressed hash, with a load of 0.8, using a robin hood value. Probing is done using the text hash, confirmation is done using length, and then memcmp of the text.
|
||||
|
||||
When the GC run, a new interned text table is created. Each text literal, and each text used as a key, is added to the new table, as the live objects are copied. This keeps the interning table from becoming a graveyard. Interned values are never deleted until a GC.
|
||||
|
||||
Type 4: Function
|
||||
|
||||
Header
|
||||
Code
|
||||
Outer
|
||||
A function object has zero capacity and is always stone.
|
||||
|
||||
Code is a pointer to the code object that the function executes.
|
||||
|
||||
Outer is a pointer to the frame that created this function object.
|
||||
|
||||
Size is 3 words.
|
||||
|
||||
Type 5: Frame
|
||||
|
||||
Header
|
||||
Function
|
||||
Caller
|
||||
Return address
|
||||
|
||||
The activation frame is created when a function is invoked to hold its linkages and state.
|
||||
|
||||
The capacity is the number of slots, including the inputs, variables, temporaries, and the four words of overhead. A frame, unlike the other types, is never stone.
|
||||
|
||||
The function is the address of the function object being called.
|
||||
|
||||
The caller is the address of the frame that is invoking the function.
|
||||
|
||||
The return address is the address of the instruction in the code that should be executed upon return.
|
||||
|
||||
Next come the input arguments, if any.
|
||||
|
||||
Then the variables closed over by the inner functions.
|
||||
|
||||
Then the variables that are not closed over, followed by the temporaries.
|
||||
|
||||
When a function returns, the caller is set to zero. This is a signal to the memory reclaimer that the frame can be reduced.
|
||||
|
||||
Type 6: Code
|
||||
|
||||
Header
|
||||
Arity
|
||||
Size
|
||||
Closure size
|
||||
Entry point
|
||||
Disruption point
|
||||
|
||||
A code object exists in the actor's immutable memory. A code object never exists in mutable memory.
|
||||
|
||||
A code object has a zero capacity and is always stone.
|
||||
|
||||
The arity is the maximum number of inputs.
|
||||
|
||||
The size is the capacity of an activation frame that will execute this code.
|
||||
|
||||
The closure size is a reduced capacity for returned frames that survive memory reclamation.
|
||||
|
||||
The entry point is the address at which to begin execution.
|
||||
|
||||
The disruption point is the address of the disruption clause.
|
||||
|
||||
### opaque C objects
|
||||
Records can have opaque C data attached to them.
|
||||
|
||||
A C class can register a GC clean up, and a GC trace function. The trace function is called when the record is encountered in the live object graph; and it should mark any values it wants to keep alive in that function.
|
||||
|
||||
The system maintains an array of live opaque C objects. When such an object is encountered, it marks it as live in the array. When the GC completes, it iterates this array and calls the GC clean up function for each C object in the array with alive=0. Alive is then cleared for the next GC cycle.
|
||||
|
||||
## 32 bit build
|
||||
~3 bit type
|
||||
1 bit stone
|
||||
1 bit memory reclamation flag
|
||||
27 bit capacity
|
||||
|
||||
Key differences here are
|
||||
|
||||
blob max capacity is 2**27 bits = 2**24 bytes = 16 MB [this likely needs addressed]
|
||||
|
||||
fwd is type ...0, and the pointer is 31 bits
|
||||
other types are
|
||||
111 array
|
||||
101 object
|
||||
011 blob
|
||||
001
|
||||
|
||||
## Memory
|
||||
Cell uses a single block of memory that it doles out as needed to the actors in its system.
|
||||
|
||||
Actors are given a block of memory in standard sizes using a doubling buddy memory manager. An actor is given an immutable data section on birth, as well as a mutable data section. When its mutable data becomes full, it requests a new one. Actors utilize their mutable memory with a simple bump allocation. If there is not sufficient memory available, the actor suspends and its status changes to exhausted.
|
||||
|
||||
The smallest block size is determined per platform, but it can be as small as 4KB on 64 bit systems.
|
||||
|
||||
The actor is then given a new block of memory of the same size, and it runs a garbage collector to reclaim memory. It uses the cheney copying algorithm. If a disappointing amount of memory was reclaimed, it is noted, and the actor is given a larger block of memory on the next request.
|
||||
@@ -1,234 +0,0 @@
|
||||
// HTTP Download Actor
|
||||
// Handles download requests and progress queries
|
||||
var http = use('http');
|
||||
var os = use('os');
|
||||
|
||||
// Actor state
|
||||
var state = {
|
||||
downloading: false,
|
||||
current_url: null,
|
||||
total_bytes: 0,
|
||||
downloaded_bytes: 0,
|
||||
start_time: 0,
|
||||
error: null,
|
||||
connection: null,
|
||||
download_msg: null,
|
||||
chunks: []
|
||||
};
|
||||
|
||||
// Helper to calculate progress percentage
|
||||
function get_progress() {
|
||||
if (state.total_bytes == 0) {
|
||||
return 0;
|
||||
}
|
||||
return number.round((state.downloaded_bytes / state.total_bytes) * 100);
|
||||
}
|
||||
|
||||
// Helper to format status response
|
||||
function get_status() {
|
||||
if (!state.downloading) {
|
||||
return {
|
||||
status: 'idle',
|
||||
error: state.error
|
||||
};
|
||||
}
|
||||
|
||||
var elapsed = os.now() - state.start_time;
|
||||
var bytes_per_sec = elapsed > 0 ? state.downloaded_bytes / elapsed : 0;
|
||||
|
||||
return {
|
||||
status: 'downloading',
|
||||
url: state.current_url,
|
||||
progress: get_progress(),
|
||||
downloaded_bytes: state.downloaded_bytes,
|
||||
total_bytes: state.total_bytes,
|
||||
elapsed_seconds: elapsed,
|
||||
bytes_per_second: number.round(bytes_per_sec)
|
||||
};
|
||||
}
|
||||
|
||||
// Main message receiver
|
||||
$receiver(function(msg) {
|
||||
switch (msg.type) {
|
||||
case 'download':
|
||||
if (state.downloading) {
|
||||
send(msg, {
|
||||
type: 'error',
|
||||
error: 'Already downloading',
|
||||
current_url: state.current_url
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!msg.url) {
|
||||
send(msg, {
|
||||
type: 'error',
|
||||
error: 'No URL provided'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Start download
|
||||
state.downloading = true;
|
||||
state.current_url = msg.url;
|
||||
state.total_bytes = 0;
|
||||
state.downloaded_bytes = 0;
|
||||
state.start_time = os.now();
|
||||
state.error = null;
|
||||
state.download_msg = msg;
|
||||
state.chunks = [];
|
||||
|
||||
try {
|
||||
// Start the connection
|
||||
state.connection = http.fetch_start(msg.url, msg.options || {});
|
||||
if (!state.connection) {
|
||||
throw new Error('Failed to start download');
|
||||
}
|
||||
|
||||
// Schedule the first chunk read
|
||||
$delay(read_next_chunk, 0);
|
||||
|
||||
} catch (e) {
|
||||
state.error = e.toString();
|
||||
state.downloading = false;
|
||||
|
||||
send(msg, {
|
||||
type: 'error',
|
||||
error: state.error,
|
||||
url: msg.url
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
case 'status':
|
||||
log.console(`got status request. current is ${get_status()}`)
|
||||
send(msg, {
|
||||
type: 'status_response',
|
||||
...get_status()
|
||||
});
|
||||
break;
|
||||
|
||||
case 'cancel':
|
||||
if (state.downloading) {
|
||||
// Cancel the download
|
||||
if (state.connection) {
|
||||
http.fetch_close(state.connection);
|
||||
state.connection = null;
|
||||
}
|
||||
state.downloading = false;
|
||||
state.current_url = null;
|
||||
state.download_msg = null;
|
||||
state.chunks = [];
|
||||
|
||||
send(msg, {
|
||||
type: 'cancelled',
|
||||
message: 'Download cancelled',
|
||||
url: state.current_url
|
||||
});
|
||||
} else {
|
||||
send(msg, {
|
||||
type: 'error',
|
||||
error: 'No download in progress'
|
||||
});
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
send(msg, {
|
||||
type: 'error',
|
||||
error: 'Unknown message type: ' + msg.type
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Non-blocking chunk reader
|
||||
function read_next_chunk() {
|
||||
if (!state.downloading || !state.connection) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
var chunk = http.fetch_read_chunk(state.connection);
|
||||
|
||||
if (chunk == null) {
|
||||
// Download complete
|
||||
finish_download();
|
||||
return;
|
||||
}
|
||||
|
||||
// Store chunk
|
||||
state.chunks.push(chunk);
|
||||
|
||||
// Update progress
|
||||
var info = http.fetch_info(state.connection);
|
||||
state.downloaded_bytes = info.bytes_read;
|
||||
if (info.headers_complete && info.content_length > 0) {
|
||||
state.total_bytes = info.content_length;
|
||||
}
|
||||
|
||||
// Schedule next chunk read
|
||||
$delay(read_next_chunk, 0);
|
||||
|
||||
} catch (e) {
|
||||
// Error during download
|
||||
state.error = e.toString();
|
||||
if (state.connection) {
|
||||
http.fetch_close(state.connection);
|
||||
}
|
||||
|
||||
if (state.download_msg) {
|
||||
send(state.download_msg, {
|
||||
type: 'error',
|
||||
error: state.error,
|
||||
url: state.current_url
|
||||
});
|
||||
}
|
||||
|
||||
// Reset state
|
||||
state.downloading = false;
|
||||
state.connection = null;
|
||||
state.download_msg = null;
|
||||
state.chunks = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Complete the download and send result
|
||||
function finish_download() {
|
||||
if (state.connection) {
|
||||
http.fetch_close(state.connection);
|
||||
}
|
||||
|
||||
// Combine all chunks into single ArrayBuffer
|
||||
var total_size = 0;
|
||||
for (var i = 0; i < state.chunks.length; i++) {
|
||||
total_size += state.chunks[i].byteLength;
|
||||
}
|
||||
|
||||
var result = new ArrayBuffer(total_size);
|
||||
var view = new Uint8Array(result);
|
||||
var offset = 0;
|
||||
|
||||
for (var i = 0; i < state.chunks.length; i++) {
|
||||
var chunk_view = new Uint8Array(state.chunks[i]);
|
||||
view.set(chunk_view, offset);
|
||||
offset += state.chunks[i].byteLength;
|
||||
}
|
||||
|
||||
// Send complete message
|
||||
if (state.download_msg) {
|
||||
send(state.download_msg, {
|
||||
type: 'complete',
|
||||
url: state.current_url,
|
||||
data: result,
|
||||
size: result.byteLength,
|
||||
duration: os.now() - state.start_time
|
||||
});
|
||||
}
|
||||
|
||||
// Reset state
|
||||
state.downloading = false;
|
||||
state.connection = null;
|
||||
state.current_url = null;
|
||||
state.download_msg = null;
|
||||
state.chunks = [];
|
||||
}
|
||||
@@ -10,7 +10,7 @@ var match_id = 0;
|
||||
$portal(e => {
|
||||
log.console("NAT server: received connection request");
|
||||
|
||||
if (!isa(e.actor, actor))
|
||||
if (!is_actor(e.actor))
|
||||
send(e, {reason: "Must provide the actor you want to connect."});
|
||||
|
||||
if (waiting_client) {
|
||||
|
||||
93
fash.c
Normal file
93
fash.c
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
Fash64: Douglas Crockford (2017-02-02)
|
||||
64-bit hash that uses the high 64 bits of a 128-bit product for feedback.
|
||||
|
||||
Notes:
|
||||
- Requires a way to get the high half of a 64x64->128 multiply.
|
||||
- Uses __uint128_t when available; otherwise uses MSVC _umul128.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct fash64_state {
|
||||
uint64_t result;
|
||||
uint64_t sum;
|
||||
} fash64_state;
|
||||
|
||||
enum {
|
||||
FASH64_PRIME_11 = 11111111111111111027ull,
|
||||
FASH64_PRIME_8 = 8888888888888888881ull,
|
||||
FASH64_PRIME_3 = 3333333333333333271ull
|
||||
};
|
||||
|
||||
static inline void fash64_mul_hi_lo(uint64_t a, uint64_t b, uint64_t *hi, uint64_t *lo)
|
||||
{
|
||||
#if defined(__SIZEOF_INT128__)
|
||||
__uint128_t p = (__uint128_t)a * (__uint128_t)b;
|
||||
*lo = (uint64_t)p;
|
||||
*hi = (uint64_t)(p >> 64);
|
||||
#elif defined(_MSC_VER) && defined(_M_X64)
|
||||
*lo = _umul128(a, b, hi);
|
||||
#else
|
||||
/* Portable fallback (no 128-bit type, no _umul128). */
|
||||
uint64_t a0 = (uint32_t)a;
|
||||
uint64_t a1 = a >> 32;
|
||||
uint64_t b0 = (uint32_t)b;
|
||||
uint64_t b1 = b >> 32;
|
||||
|
||||
uint64_t p00 = a0 * b0;
|
||||
uint64_t p01 = a0 * b1;
|
||||
uint64_t p10 = a1 * b0;
|
||||
uint64_t p11 = a1 * b1;
|
||||
|
||||
uint64_t mid = (p00 >> 32) + (uint32_t)p01 + (uint32_t)p10;
|
||||
*lo = (p00 & 0xffffffffull) | (mid << 32);
|
||||
*hi = p11 + (p01 >> 32) + (p10 >> 32) + (mid >> 32);
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline void fash64_begin(fash64_state *s)
|
||||
{
|
||||
s->result = (uint64_t)FASH64_PRIME_8;
|
||||
s->sum = (uint64_t)FASH64_PRIME_3;
|
||||
}
|
||||
|
||||
static inline void fash64_word(fash64_state *s, uint64_t word)
|
||||
{
|
||||
uint64_t high, low;
|
||||
uint64_t mixed = s->result ^ word;
|
||||
|
||||
fash64_mul_hi_lo(mixed, (uint64_t)FASH64_PRIME_11, &high, &low);
|
||||
|
||||
s->sum += high;
|
||||
s->result = low ^ s->sum;
|
||||
}
|
||||
|
||||
static inline void fash64_block(fash64_state *s, const uint64_t *block, size_t word_count)
|
||||
{
|
||||
for (size_t i = 0; i < word_count; i++) fash64_word(s, block[i]);
|
||||
}
|
||||
|
||||
static inline uint64_t fash64_end(const fash64_state *s)
|
||||
{
|
||||
return s->result;
|
||||
}
|
||||
|
||||
/* Convenience one-shot helper */
|
||||
static inline uint64_t fash64_hash_words(const uint64_t *words, size_t word_count, uint64_t extra_word)
|
||||
{
|
||||
fash64_state s;
|
||||
fash64_begin(&s);
|
||||
fash64_block(&s, words, word_count);
|
||||
fash64_word(&s, extra_word);
|
||||
return fash64_end(&s);
|
||||
}
|
||||
|
||||
static inline uint64_t fash64_hash_one(uint64_t word)
|
||||
{
|
||||
uint64_t high, low;
|
||||
uint64_t mixed = (uint64_t)FASH64_PRIME_8 ^ word;
|
||||
fash64_mul_hi_lo(mixed, (uint64_t)FASH64_PRIME_11, &high, &low);
|
||||
return low ^ ((uint64_t)FASH64_PRIME_3 + high);
|
||||
}
|
||||
23
fd.c
23
fd.c
@@ -50,7 +50,7 @@ JSC_SCALL(fd_open,
|
||||
mode_t mode = 0644;
|
||||
|
||||
// Parse optional flags argument
|
||||
if (argc > 1 && JS_IsString(argv[1])) {
|
||||
if (argc > 1 && JS_IsText(argv[1])) {
|
||||
const char *flag_str = JS_ToCString(js, argv[1]);
|
||||
flags = 0;
|
||||
|
||||
@@ -78,7 +78,7 @@ JSC_CCALL(fd_write,
|
||||
|
||||
size_t len;
|
||||
ssize_t wrote;
|
||||
if (JS_IsString(argv[1])) {
|
||||
if (JS_IsText(argv[1])) {
|
||||
const char *data = JS_ToCStringLen(js, &len, argv[1]);
|
||||
if (!data) return JS_EXCEPTION;
|
||||
wrote = write(fd, data, len);
|
||||
@@ -276,7 +276,7 @@ JSC_SCALL(fd_mkdir,
|
||||
JSC_SCALL(fd_mv,
|
||||
if (argc < 2)
|
||||
ret = JS_ThrowTypeError(js, "fd.mv requires 2 arguments: old path and new path");
|
||||
else if (!JS_IsString(argv[1]))
|
||||
else if (!JS_IsText(argv[1]))
|
||||
ret = JS_ThrowTypeError(js, "second argument must be a string (new path)");
|
||||
else {
|
||||
const char *new_path = JS_ToCString(js, argv[1]);
|
||||
@@ -289,7 +289,7 @@ JSC_SCALL(fd_mv,
|
||||
JSC_SCALL(fd_symlink,
|
||||
if (argc < 2)
|
||||
ret = JS_ThrowTypeError(js, "fd.symlink requires 2 arguments: target and link path");
|
||||
else if (!JS_IsString(argv[1]))
|
||||
else if (!JS_IsText(argv[1]))
|
||||
ret = JS_ThrowTypeError(js, "second argument must be a string (link path)");
|
||||
else {
|
||||
const char *link_path = JS_ToCString(js, argv[1]);
|
||||
@@ -502,10 +502,9 @@ JSC_SCALL(fd_readdir,
|
||||
ret = JS_ThrowInternalError(js, "FindFirstFile failed for %s", path);
|
||||
} else {
|
||||
ret = JS_NewArray(js);
|
||||
int i = 0;
|
||||
do {
|
||||
if (strcmp(ffd.cFileName, ".") == 0 || strcmp(ffd.cFileName, "..") == 0) continue;
|
||||
JS_SetPropertyUint32(js, ret, i++, JS_NewString(js, ffd.cFileName));
|
||||
JS_ArrayPush(js, ret,JS_NewString(js, ffd.cFileName));
|
||||
} while (FindNextFile(hFind, &ffd) != 0);
|
||||
FindClose(hFind);
|
||||
}
|
||||
@@ -515,10 +514,9 @@ JSC_SCALL(fd_readdir,
|
||||
d = opendir(str);
|
||||
if (d) {
|
||||
ret = JS_NewArray(js);
|
||||
int i = 0;
|
||||
while ((dir = readdir(d)) != NULL) {
|
||||
if (strcmp(dir->d_name, ".") == 0 || strcmp(dir->d_name, "..") == 0) continue;
|
||||
JS_SetPropertyUint32(js, ret, i++, JS_NewString(js, dir->d_name));
|
||||
JS_ArrayPush(js, ret, JS_NewString(js, dir->d_name));
|
||||
}
|
||||
closedir(d);
|
||||
} else {
|
||||
@@ -559,20 +557,23 @@ JSC_CCALL(fd_slurpwrite,
|
||||
size_t len;
|
||||
const char *data = js_get_blob_data(js, &len, argv[1]);
|
||||
|
||||
if (data == (const char *)-1)
|
||||
if (!data && len > 0)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
const char *str = JS_ToCString(js, argv[0]);
|
||||
|
||||
if (!str) return JS_EXCEPTION;
|
||||
int fd = open(str, O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
JS_FreeCString(js, str);
|
||||
if (fd < 0)
|
||||
if (fd < 0) {
|
||||
JS_FreeCString(js, str);
|
||||
return JS_ThrowInternalError(js, "open failed for %s: %s", str, strerror(errno));
|
||||
}
|
||||
|
||||
ssize_t written = write(fd, data, len);
|
||||
close(fd);
|
||||
|
||||
JS_FreeCString(js, str);
|
||||
|
||||
if (written != (ssize_t)len)
|
||||
return JS_ThrowInternalError(js, "write failed for %s: %s", str, strerror(errno));
|
||||
|
||||
|
||||
62
fd.cm
62
fd.cm
@@ -1,31 +1,67 @@
|
||||
var fd = this
|
||||
var wildstar = use('wildstar')
|
||||
|
||||
function last_pos(str, sep) {
|
||||
var last = null
|
||||
replace(str, sep, function(m, pos) {
|
||||
last = pos
|
||||
return m
|
||||
})
|
||||
return last
|
||||
}
|
||||
|
||||
// Helper to join paths
|
||||
function join_paths(base, rel) {
|
||||
base = base.replace(/\/+$/, "")
|
||||
rel = rel.replace(/^\/+/, "")
|
||||
base = replace(base, /\/+$/, "")
|
||||
rel = replace(rel, /^\/+/, "")
|
||||
if (!base) return rel
|
||||
if (!rel) return base
|
||||
return base + "/" + rel
|
||||
}
|
||||
|
||||
fd.join_paths = join_paths
|
||||
fd.basename = function basename(path) {
|
||||
var last = last_pos(path, '/')
|
||||
if (last == null) return path
|
||||
return text(path, last+1)
|
||||
}
|
||||
|
||||
fd.dirname = function dirname(path) {
|
||||
var last = last_pos(path, '/')
|
||||
if (last == null) return ""
|
||||
return text(path,0,last)
|
||||
}
|
||||
|
||||
fd.stem = function stem(path) {
|
||||
var last = last_pos(path, '.')
|
||||
if (last == null) return path
|
||||
return text(path,0,last)
|
||||
}
|
||||
|
||||
fd.globfs = function(globs, dir) {
|
||||
if (dir == null) dir = "."
|
||||
var results = []
|
||||
|
||||
function check_neg(path) {
|
||||
for (var g of globs) {
|
||||
if (g.startsWith("!") && wildstar.match(g.substring(1), path, wildstar.WM_WILDSTAR)) return true;
|
||||
}
|
||||
return false;
|
||||
var found = false;
|
||||
arrfor(globs, function(g) {
|
||||
if (starts_with(g, "!") && wildstar.match(text(g, 1), path, wildstar.WM_WILDSTAR)) {
|
||||
found = true;
|
||||
return true;
|
||||
}
|
||||
}, null, true);
|
||||
return found;
|
||||
}
|
||||
|
||||
function check_pos(path) {
|
||||
for (var g of globs) {
|
||||
if (!g.startsWith("!") && wildstar.match(g, path, wildstar.WM_WILDSTAR)) return true;
|
||||
}
|
||||
return false;
|
||||
var found = false;
|
||||
arrfor(globs, function(g) {
|
||||
if (!starts_with(g, "!") && wildstar.match(g, path, wildstar.WM_WILDSTAR)) {
|
||||
found = true;
|
||||
return true;
|
||||
}
|
||||
}, null, true);
|
||||
return found;
|
||||
}
|
||||
|
||||
function visit(curr_full, rel_prefix) {
|
||||
@@ -34,7 +70,7 @@ fd.globfs = function(globs, dir) {
|
||||
var list = fd.readdir(curr_full)
|
||||
if (!list) return
|
||||
|
||||
for (var item of list) {
|
||||
arrfor(list, function(item) {
|
||||
var item_rel = rel_prefix ? rel_prefix + "/" + item : item
|
||||
|
||||
var child_full = join_paths(curr_full, item)
|
||||
@@ -46,10 +82,10 @@ fd.globfs = function(globs, dir) {
|
||||
}
|
||||
} else {
|
||||
if (!check_neg(item_rel) && check_pos(item_rel)) {
|
||||
results.push(item_rel)
|
||||
push(results, item_rel)
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
var st = fd.stat(dir)
|
||||
|
||||
@@ -39,7 +39,7 @@ JSC_SCALL(fd_open,
|
||||
FileOptions flags = kFileRead;
|
||||
|
||||
// Parse optional flags argument
|
||||
if (argc > 1 && JS_IsString(argv[1])) {
|
||||
if (argc > 1 && JS_IsText(argv[1])) {
|
||||
const char *flag_str = JS_ToCString(js, argv[1]);
|
||||
flags = 0;
|
||||
|
||||
@@ -70,7 +70,7 @@ JSC_CCALL(fd_write,
|
||||
|
||||
size_t len;
|
||||
int wrote;
|
||||
if (JS_IsString(argv[1])) {
|
||||
if (JS_IsText(argv[1])) {
|
||||
const char *data = JS_ToCStringLen(js, &len, argv[1]);
|
||||
if (!data) return JS_EXCEPTION;
|
||||
wrote = pd_file->write(fd, data, (unsigned int)len);
|
||||
@@ -202,7 +202,7 @@ JSC_SCALL(fd_mkdir,
|
||||
JSC_SCALL(fd_mv,
|
||||
if (argc < 2)
|
||||
ret = JS_ThrowTypeError(js, "fd.mv requires 2 arguments: old path and new path");
|
||||
else if (!JS_IsString(argv[1]))
|
||||
else if (!JS_IsText(argv[1]))
|
||||
ret = JS_ThrowTypeError(js, "second argument must be a string (new path)");
|
||||
else {
|
||||
const char *new_path = JS_ToCString(js, argv[1]);
|
||||
@@ -216,7 +216,7 @@ JSC_SCALL(fd_mv,
|
||||
|
||||
JSC_SCALL(fd_symlink,
|
||||
// Not supported
|
||||
if (argc >= 2 && JS_IsString(argv[1])) {
|
||||
if (argc >= 2 && JS_IsText(argv[1])) {
|
||||
// consume arg
|
||||
JS_FreeCString(js, JS_ToCString(js, argv[1]));
|
||||
}
|
||||
|
||||
65
fetch.ce
65
fetch.ce
@@ -13,7 +13,7 @@ var shop = use('internal/shop')
|
||||
// Parse arguments
|
||||
var target_pkg = null
|
||||
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
for (var i = 0; i < length(args); i++) {
|
||||
if (args[i] == '--help' || args[i] == '-h') {
|
||||
log.console("Usage: cell fetch [package]")
|
||||
log.console("Fetch package zips from remote sources.")
|
||||
@@ -24,7 +24,7 @@ for (var i = 0; i < args.length; i++) {
|
||||
log.console("This command ensures that the zip files on disk match what's in")
|
||||
log.console("the lock file. For local packages, this is a no-op.")
|
||||
$stop()
|
||||
} else if (!args[i].startsWith('-')) {
|
||||
} else if (!starts_with(args[i], '-')) {
|
||||
target_pkg = args[i]
|
||||
}
|
||||
}
|
||||
@@ -35,52 +35,55 @@ var packages_to_fetch = []
|
||||
|
||||
if (target_pkg) {
|
||||
// Fetch specific package
|
||||
if (!all_packages.includes(target_pkg)) {
|
||||
if (find(all_packages, target_pkg) == null) {
|
||||
log.error("Package not found: " + target_pkg)
|
||||
$stop()
|
||||
}
|
||||
packages_to_fetch.push(target_pkg)
|
||||
push(packages_to_fetch, target_pkg)
|
||||
} else {
|
||||
// Fetch all packages
|
||||
packages_to_fetch = all_packages
|
||||
}
|
||||
|
||||
log.console("Fetching " + text(packages_to_fetch.length) + " package(s)...")
|
||||
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)
|
||||
|
||||
var success_count = 0
|
||||
var skip_count = 0
|
||||
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
|
||||
|
||||
for (var pkg of packages_to_fetch) {
|
||||
var entry = lock[pkg]
|
||||
|
||||
// Skip local packages
|
||||
if (entry && entry.type == 'local') {
|
||||
skip_count++
|
||||
continue
|
||||
}
|
||||
|
||||
arrfor(packages_to_fetch, function(pkg) {
|
||||
// Skip core (handled separately)
|
||||
if (pkg == 'core') {
|
||||
skip_count++
|
||||
continue
|
||||
}
|
||||
if (pkg == 'core') return
|
||||
|
||||
var result = shop.fetch(pkg)
|
||||
if (result) {
|
||||
if (result.zip_blob) {
|
||||
log.console("Fetched: " + pkg)
|
||||
success_count++
|
||||
} else {
|
||||
skip_count++
|
||||
}
|
||||
} else {
|
||||
log.error("Failed to 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("")
|
||||
log.console("Fetch complete: " + text(success_count) + " fetched, " + text(skip_count) + " skipped, " + text(fail_count) + " failed")
|
||||
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()
|
||||
|
||||
403
gc_plan.md
Normal file
403
gc_plan.md
Normal file
@@ -0,0 +1,403 @@
|
||||
# Plan: Complete Copying GC Implementation
|
||||
|
||||
## Overview
|
||||
|
||||
Remove reference counting (DupValue/FreeValue) entirely and complete the Cheney copying garbage collector. Each JSContext will use bump allocation from a heap block, and when out of memory, request a new heap from JSRuntime's buddy allocator and copy live objects to the new heap.
|
||||
|
||||
## Target Architecture (from docs/memory.md)
|
||||
|
||||
### Object Types (simplified from current):
|
||||
|
||||
**Type 0 - Array**: `{ header, length, elements[] }`
|
||||
**Type 1 - Blob**: `{ header, length, bits[] }`
|
||||
**Type 2 - Text**: `{ header, length_or_hash, packed_chars[] }`
|
||||
**Type 3 - Record**: `{ header, prototype, length, key_value_pairs[] }`
|
||||
**Type 4 - Function**: `{ header, code_ptr, outer_frame_ptr }` - 3 words only, always stone
|
||||
**Type 5 - Frame**: `{ header, function_ptr, caller_ptr, ret_addr, args[], closure_vars[], local_vars[], temps[] }`
|
||||
**Type 6 - Code**: Lives in immutable memory only, never copied
|
||||
**Type 7 - Forward**: Object has moved; cap56 contains new address
|
||||
|
||||
### Key Design Points:
|
||||
- **JSFunction** is just a pointer to code and a pointer to the frame that created it (3 words)
|
||||
- **Closure variables live in frames** - when a function returns, its frame is "reduced" to just the closure variables
|
||||
- **Code objects are immutable** - stored in stone memory, never copied during GC
|
||||
- **Frame reduction**: When a function returns, `caller` is set to zero, signaling the frame can be shrunk
|
||||
|
||||
## Current State (needs refactoring)
|
||||
|
||||
1. **Partial Cheney GC exists** at `source/quickjs.c:1844-2030`: `ctx_gc`, `gc_copy_value`, `gc_scan_object`
|
||||
2. **744 calls to JS_DupValue/JS_FreeValue** scattered throughout (currently undefined, causing compilation errors)
|
||||
3. **Current JSFunction** is bloated (has kind, name, union of cfunc/bytecode/bound) - needs simplification
|
||||
4. **Current JSVarRef** is a separate object - should be eliminated, closures live in frames
|
||||
5. **Bump allocator** in `js_malloc` (line 1495) with `heap_base`/`heap_free`/`heap_end`
|
||||
6. **Buddy allocator** for memory blocks (lines 1727-1837)
|
||||
7. **Header offset inconsistency** - some structs have header at offset 0, some at offset 8
|
||||
|
||||
## Implementation Steps
|
||||
|
||||
### Phase 1: Define No-Op DupValue/FreeValue (To Enable Compilation)
|
||||
|
||||
Add these near line 100 in `source/quickjs.c`:
|
||||
|
||||
```c
|
||||
/* Copying GC - no reference counting needed */
|
||||
#define JS_DupValue(ctx, v) (v)
|
||||
#define JS_FreeValue(ctx, v) ((void)0)
|
||||
#define JS_DupValueRT(rt, v) (v)
|
||||
#define JS_FreeValueRT(rt, v) ((void)0)
|
||||
```
|
||||
|
||||
This makes the code compile while keeping existing call sites (they become no-ops).
|
||||
|
||||
### Phase 2: Standardize Object Headers (offset 0)
|
||||
|
||||
Remove `JSGCObjectHeader` (ref counting remnant) and put `objhdr_t` at offset 0:
|
||||
|
||||
```c
|
||||
typedef struct JSArray {
|
||||
objhdr_t hdr; // offset 0
|
||||
word_t length;
|
||||
JSValue values[];
|
||||
} JSArray;
|
||||
|
||||
typedef struct JSRecord {
|
||||
objhdr_t hdr; // offset 0
|
||||
JSRecord *proto;
|
||||
word_t length;
|
||||
slot slots[];
|
||||
} JSRecord;
|
||||
|
||||
typedef struct JSText {
|
||||
objhdr_t hdr; // offset 0
|
||||
word_t length; // pretext: length, text: hash
|
||||
word_t packed[];
|
||||
} JSText;
|
||||
|
||||
typedef struct JSBlob {
|
||||
objhdr_t hdr; // offset 0
|
||||
word_t length;
|
||||
uint8_t bits[];
|
||||
} JSBlob;
|
||||
|
||||
/* Simplified JSFunction per memory.md - 3 words */
|
||||
typedef struct JSFunction {
|
||||
objhdr_t hdr; // offset 0, always stone
|
||||
JSCode *code; // pointer to immutable code object
|
||||
struct JSFrame *outer; // frame that created this function
|
||||
} JSFunction;
|
||||
|
||||
/* JSFrame per memory.md */
|
||||
typedef struct JSFrame {
|
||||
objhdr_t hdr; // offset 0
|
||||
JSFunction *function; // function being executed
|
||||
struct JSFrame *caller; // calling frame (NULL = reduced/returned)
|
||||
word_t ret_addr; // return instruction address
|
||||
JSValue slots[]; // args, closure vars, locals, temps
|
||||
} JSFrame;
|
||||
|
||||
/* JSCode - always in immutable (stone) memory */
|
||||
typedef struct JSCode {
|
||||
objhdr_t hdr; // offset 0, always stone
|
||||
word_t arity; // max number of inputs
|
||||
word_t frame_size; // capacity of activation frame
|
||||
word_t closure_size; // reduced capacity for returned frames
|
||||
word_t entry_point; // address to begin execution
|
||||
word_t disruption_point;// address of disruption clause
|
||||
uint8_t bytecode[]; // actual bytecode
|
||||
} JSCode;
|
||||
```
|
||||
|
||||
### Phase 3: Complete gc_object_size for All Types
|
||||
|
||||
Update `gc_object_size` (line 1850) to read header at offset 0:
|
||||
|
||||
```c
|
||||
static size_t gc_object_size(void *ptr) {
|
||||
objhdr_t hdr = *(objhdr_t*)ptr; // Header at offset 0
|
||||
uint8_t type = objhdr_type(hdr);
|
||||
uint64_t cap = objhdr_cap56(hdr);
|
||||
|
||||
switch (type) {
|
||||
case OBJ_ARRAY:
|
||||
return sizeof(JSArray) + cap * sizeof(JSValue);
|
||||
case OBJ_BLOB:
|
||||
return sizeof(JSBlob) + (cap + 7) / 8; // cap is bits
|
||||
case OBJ_TEXT:
|
||||
return sizeof(JSText) + ((cap + 1) / 2) * sizeof(uint64_t);
|
||||
case OBJ_RECORD:
|
||||
return sizeof(JSRecord) + (cap + 1) * sizeof(slot); // cap is mask
|
||||
case OBJ_FUNCTION:
|
||||
return sizeof(JSFunction); // 3 words
|
||||
case OBJ_FRAME:
|
||||
return sizeof(JSFrame) + cap * sizeof(JSValue); // cap is slot count
|
||||
case OBJ_CODE:
|
||||
return 0; // Code is never copied (immutable)
|
||||
default:
|
||||
return 64; // Conservative fallback
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 4: Complete gc_scan_object for All Types
|
||||
|
||||
Update `gc_scan_object` (line 1924):
|
||||
|
||||
```c
|
||||
static void gc_scan_object(JSContext *ctx, void *ptr, uint8_t **to_free, uint8_t *to_end) {
|
||||
objhdr_t hdr = *(objhdr_t*)ptr;
|
||||
uint8_t type = objhdr_type(hdr);
|
||||
|
||||
switch (type) {
|
||||
case OBJ_ARRAY: {
|
||||
JSArray *arr = (JSArray*)ptr;
|
||||
for (uint32_t i = 0; i < arr->length; i++) {
|
||||
arr->values[i] = gc_copy_value(ctx, arr->values[i], to_free, to_end);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OBJ_RECORD: {
|
||||
JSRecord *rec = (JSRecord*)ptr;
|
||||
// Copy prototype
|
||||
if (rec->proto) {
|
||||
JSValue proto_val = JS_MKPTR(rec->proto);
|
||||
proto_val = gc_copy_value(ctx, proto_val, to_free, to_end);
|
||||
rec->proto = (JSRecord*)JS_VALUE_GET_PTR(proto_val);
|
||||
}
|
||||
// Copy table entries
|
||||
uint32_t mask = objhdr_cap56(rec->hdr);
|
||||
for (uint32_t i = 1; i <= mask; i++) { // Skip slot 0
|
||||
JSValue k = rec->slots[i].key;
|
||||
if (!rec_key_is_empty(k) && !rec_key_is_tomb(k)) {
|
||||
rec->slots[i].key = gc_copy_value(ctx, k, to_free, to_end);
|
||||
rec->slots[i].value = gc_copy_value(ctx, rec->slots[i].value, to_free, to_end);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OBJ_FUNCTION: {
|
||||
JSFunction *func = (JSFunction*)ptr;
|
||||
// Code is immutable, don't copy - but outer frame needs copying
|
||||
if (func->outer) {
|
||||
JSValue outer_val = JS_MKPTR(func->outer);
|
||||
outer_val = gc_copy_value(ctx, outer_val, to_free, to_end);
|
||||
func->outer = (JSFrame*)JS_VALUE_GET_PTR(outer_val);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OBJ_FRAME: {
|
||||
JSFrame *frame = (JSFrame*)ptr;
|
||||
// Copy function pointer
|
||||
if (frame->function) {
|
||||
JSValue func_val = JS_MKPTR(frame->function);
|
||||
func_val = gc_copy_value(ctx, func_val, to_free, to_end);
|
||||
frame->function = (JSFunction*)JS_VALUE_GET_PTR(func_val);
|
||||
}
|
||||
// Copy caller (unless NULL = reduced frame)
|
||||
if (frame->caller) {
|
||||
JSValue caller_val = JS_MKPTR(frame->caller);
|
||||
caller_val = gc_copy_value(ctx, caller_val, to_free, to_end);
|
||||
frame->caller = (JSFrame*)JS_VALUE_GET_PTR(caller_val);
|
||||
}
|
||||
// Copy all slots (args, closure vars, locals, temps)
|
||||
uint32_t slot_count = objhdr_cap56(frame->hdr);
|
||||
for (uint32_t i = 0; i < slot_count; i++) {
|
||||
frame->slots[i] = gc_copy_value(ctx, frame->slots[i], to_free, to_end);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OBJ_TEXT:
|
||||
case OBJ_BLOB:
|
||||
case OBJ_CODE:
|
||||
// No internal references to scan
|
||||
break;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 5: Fix gc_copy_value Forwarding
|
||||
|
||||
Update `gc_copy_value` (line 1883) for offset 0 headers:
|
||||
|
||||
```c
|
||||
static JSValue gc_copy_value(JSContext *ctx, JSValue v, uint8_t **to_free, uint8_t *to_end) {
|
||||
if (!JS_IsPtr(v)) return v; // Immediate value
|
||||
|
||||
void *ptr = JS_VALUE_GET_PTR(v);
|
||||
|
||||
// Stone memory - don't copy (includes Code objects)
|
||||
objhdr_t hdr = *(objhdr_t*)ptr;
|
||||
if (objhdr_s(hdr)) return v;
|
||||
|
||||
// Check if in current heap
|
||||
if ((uint8_t*)ptr < ctx->heap_base || (uint8_t*)ptr >= ctx->heap_end)
|
||||
return v; // External allocation
|
||||
|
||||
// Already forwarded?
|
||||
if (objhdr_type(hdr) == OBJ_FORWARD) {
|
||||
void *new_ptr = (void*)(uintptr_t)objhdr_cap56(hdr);
|
||||
return JS_MKPTR(new_ptr);
|
||||
}
|
||||
|
||||
// Copy object to new space
|
||||
size_t size = gc_object_size(ptr);
|
||||
void *new_ptr = *to_free;
|
||||
*to_free += size;
|
||||
memcpy(new_ptr, ptr, size);
|
||||
|
||||
// Leave forwarding pointer in old location
|
||||
*(objhdr_t*)ptr = objhdr_make((uint64_t)(uintptr_t)new_ptr, OBJ_FORWARD, 0, 0, 0, 0);
|
||||
|
||||
return JS_MKPTR(new_ptr);
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 6: Complete GC Root Tracing
|
||||
|
||||
Update `ctx_gc` (line 1966) to trace all roots including JSGCRef:
|
||||
|
||||
```c
|
||||
static int ctx_gc(JSContext *ctx) {
|
||||
// ... existing setup code ...
|
||||
|
||||
// Copy roots: global object, class prototypes, etc. (existing)
|
||||
ctx->global_obj = gc_copy_value(ctx, ctx->global_obj, &to_free, to_end);
|
||||
ctx->global_var_obj = gc_copy_value(ctx, ctx->global_var_obj, &to_free, to_end);
|
||||
// ... other existing root copying ...
|
||||
|
||||
// Copy GC root stack (JS_PUSH_VALUE/JS_POP_VALUE)
|
||||
for (JSGCRef *ref = ctx->top_gc_ref; ref; ref = ref->prev) {
|
||||
ref->val = gc_copy_value(ctx, ref->val, &to_free, to_end);
|
||||
}
|
||||
|
||||
// Copy GC root list (JS_AddGCRef/JS_DeleteGCRef)
|
||||
for (JSGCRef *ref = ctx->last_gc_ref; ref; ref = ref->prev) {
|
||||
ref->val = gc_copy_value(ctx, ref->val, &to_free, to_end);
|
||||
}
|
||||
|
||||
// Copy current exception
|
||||
ctx->current_exception = gc_copy_value(ctx, ctx->current_exception, &to_free, to_end);
|
||||
|
||||
// Cheney scan (existing)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 7: Trigger GC on Allocation Failure
|
||||
|
||||
Update `js_malloc` (line 1495):
|
||||
|
||||
```c
|
||||
void *js_malloc(JSContext *ctx, size_t size) {
|
||||
size = (size + 7) & ~7; // Align to 8 bytes
|
||||
|
||||
if ((uint8_t*)ctx->heap_free + size > (uint8_t*)ctx->heap_end) {
|
||||
if (ctx_gc(ctx) < 0) {
|
||||
JS_ThrowOutOfMemory(ctx);
|
||||
return NULL;
|
||||
}
|
||||
// Retry after GC
|
||||
if ((uint8_t*)ctx->heap_free + size > (uint8_t*)ctx->heap_end) {
|
||||
JS_ThrowOutOfMemory(ctx);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void *ptr = ctx->heap_free;
|
||||
ctx->heap_free = (uint8_t*)ctx->heap_free + size;
|
||||
return ptr;
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 8: Frame Reduction (for closures)
|
||||
|
||||
When a function returns, "reduce" its frame to just closure variables:
|
||||
|
||||
```c
|
||||
static void reduce_frame(JSContext *ctx, JSFrame *frame) {
|
||||
if (frame->caller == NULL) return; // Already reduced
|
||||
|
||||
JSCode *code = frame->function->code;
|
||||
uint32_t closure_size = code->closure_size;
|
||||
|
||||
// Shrink capacity to just closure variables
|
||||
frame->hdr = objhdr_make(closure_size, OBJ_FRAME, 0, 0, 0, 0);
|
||||
frame->caller = NULL; // Signal: frame is reduced
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 9: Remove Unused Reference Counting Code
|
||||
|
||||
Delete:
|
||||
- `gc_decref`, `gc_decref_child` functions
|
||||
- `gc_scan_incref_child`, `gc_scan_incref_child2` functions
|
||||
- `JS_GCPhaseEnum`, `gc_phase` fields
|
||||
- `JSGCObjectHeader` struct (merge into objhdr_t)
|
||||
- `ref_count` fields from any remaining structs
|
||||
- `mark_function_children_decref` function
|
||||
- All `free_*` functions that rely on ref counting
|
||||
|
||||
## Files to Modify
|
||||
|
||||
1. **source/quickjs.c** - Main implementation:
|
||||
- Add DupValue/FreeValue no-op macros (~line 100)
|
||||
- Restructure JSArray, JSBlob, JSText, JSRecord (lines 468-499)
|
||||
- Simplify JSFunction to 3-word struct (line 1205)
|
||||
- Add JSFrame as heap object (new)
|
||||
- Restructure JSCode/JSFunctionBytecode (line 1293)
|
||||
- Fix gc_object_size (line 1850)
|
||||
- Fix gc_copy_value (line 1883)
|
||||
- Complete gc_scan_object (line 1924)
|
||||
- Update ctx_gc for all roots (line 1966)
|
||||
- Update js_malloc to trigger GC (line 1495)
|
||||
- Delete ref counting code throughout
|
||||
|
||||
2. **source/quickjs.h** - Public API:
|
||||
- Remove JSGCObjectHeader
|
||||
- Update JSValue type checks if needed
|
||||
- Ensure JS_IsStone works with offset 0 headers
|
||||
|
||||
## Execution Order
|
||||
|
||||
1. **First**: Add DupValue/FreeValue macros (enables compilation)
|
||||
2. **Second**: Standardize struct layouts (header at offset 0)
|
||||
3. **Third**: Fix gc_object_size and gc_copy_value
|
||||
4. **Fourth**: Complete gc_scan_object for all types
|
||||
5. **Fifth**: Update ctx_gc with complete root tracing
|
||||
6. **Sixth**: Wire js_malloc to trigger GC
|
||||
7. **Seventh**: Add frame reduction for closures
|
||||
8. **Finally**: Remove ref counting dead code
|
||||
|
||||
## Verification
|
||||
|
||||
1. **Compile test**: `make` should succeed without errors
|
||||
2. **Basic test**: Run simple scripts:
|
||||
```js
|
||||
var a = [1, 2, 3]
|
||||
log.console(a[1])
|
||||
```
|
||||
3. **Stress test**: Allocate many objects to trigger GC:
|
||||
```js
|
||||
for (var i = 0; i < 100000; i++) {
|
||||
var x = { value: i }
|
||||
}
|
||||
log.console("done")
|
||||
```
|
||||
4. **Closure test**: Test functions with closures survive GC:
|
||||
```js
|
||||
fn make_counter() {
|
||||
var count = 0
|
||||
fn inc() { count = count + 1; return count }
|
||||
return inc
|
||||
}
|
||||
var c = make_counter()
|
||||
log.console(c()) // 1
|
||||
log.console(c()) // 2
|
||||
```
|
||||
5. **GC stress with closures**: Create many closures, trigger GC, verify they still work
|
||||
|
||||
## Key Design Decisions (Resolved)
|
||||
|
||||
1. **JSCode storage**: Lives in stone (immutable) memory, never copied during GC ✓
|
||||
2. **Header offset**: Standardized to offset 0 for all heap objects ✓
|
||||
3. **Closure variables**: Live in JSFrame objects; frames are "reduced" when functions return ✓
|
||||
4. **JSVarRef**: Eliminated - closures reference their outer frame directly ✓
|
||||
236
graph.ce
Normal file
236
graph.ce
Normal file
@@ -0,0 +1,236 @@
|
||||
// cell graph [<locator>] - Emit dependency graph
|
||||
//
|
||||
// Usage:
|
||||
// cell graph Graph current directory package
|
||||
// cell graph . Graph current directory package
|
||||
// cell graph <locator> Graph specific package
|
||||
// cell graph --world Graph all packages in shop (world set)
|
||||
//
|
||||
// Options:
|
||||
// --format <fmt> Output format: tree (default), dot, json
|
||||
// --resolved Show resolved view with links applied (default)
|
||||
// --locked Show lock view without links
|
||||
// --world Graph all packages in shop
|
||||
|
||||
var shop = use('internal/shop')
|
||||
var pkg = use('package')
|
||||
var link = use('link')
|
||||
var fd = use('fd')
|
||||
var json = use('json')
|
||||
|
||||
var target_locator = null
|
||||
var format = 'tree'
|
||||
var show_locked = false
|
||||
var show_world = false
|
||||
|
||||
for (var i = 0; i < length(args); i++) {
|
||||
if (args[i] == '--format' || args[i] == '-f') {
|
||||
if (i + 1 < length(args)) {
|
||||
format = args[++i]
|
||||
if (format != 'tree' && format != 'dot' && format != 'json') {
|
||||
log.error('Invalid format: ' + format + '. Must be tree, dot, or json')
|
||||
$stop()
|
||||
}
|
||||
} else {
|
||||
log.error('--format requires a format type')
|
||||
$stop()
|
||||
}
|
||||
} 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()
|
||||
|
||||
// Get effective locator (after links)
|
||||
function get_effective(locator) {
|
||||
return links[locator] || locator
|
||||
}
|
||||
|
||||
// Build graph data structure
|
||||
var nodes = {}
|
||||
var edges = []
|
||||
|
||||
function add_node(locator) {
|
||||
if (nodes[locator]) return
|
||||
|
||||
var lock = shop.load_lock()
|
||||
var lock_entry = lock[locator]
|
||||
var link_target = links[locator]
|
||||
var info = shop.resolve_package_info(locator)
|
||||
|
||||
nodes[locator] = {
|
||||
id: locator,
|
||||
effective: get_effective(locator),
|
||||
linked: link_target != null,
|
||||
local: info == 'local',
|
||||
commit: lock_entry && lock_entry.commit ? text(lock_entry.commit, 0, 8) : null
|
||||
}
|
||||
}
|
||||
|
||||
function gather_graph(locator, visited) {
|
||||
if (visited[locator]) return
|
||||
visited[locator] = true
|
||||
|
||||
add_node(locator)
|
||||
|
||||
try {
|
||||
var deps = pkg.dependencies(locator)
|
||||
if (deps) {
|
||||
arrfor(array(deps), function(alias) {
|
||||
var dep_locator = deps[alias]
|
||||
add_node(dep_locator)
|
||||
push(edges, { from: locator, to: dep_locator, alias: alias })
|
||||
gather_graph(dep_locator, visited)
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
// Package might not have dependencies
|
||||
}
|
||||
}
|
||||
|
||||
// Gather graph from roots
|
||||
var roots = []
|
||||
|
||||
if (show_world) {
|
||||
// Use all packages in shop as roots
|
||||
var packages = shop.list_packages()
|
||||
arrfor(packages, function(p) {
|
||||
if (p != 'core') {
|
||||
push(roots, p)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Default to current directory
|
||||
if (!target_locator) {
|
||||
target_locator = '.'
|
||||
}
|
||||
|
||||
// Resolve local paths
|
||||
if (target_locator == '.' || starts_with(target_locator, './') || starts_with(target_locator, '../') || fd.is_dir(target_locator)) {
|
||||
var resolved = fd.realpath(target_locator)
|
||||
if (resolved) {
|
||||
target_locator = resolved
|
||||
}
|
||||
}
|
||||
|
||||
push(roots, target_locator)
|
||||
}
|
||||
|
||||
arrfor(roots, function(root) {
|
||||
gather_graph(root, {})
|
||||
})
|
||||
|
||||
// Output based on format
|
||||
if (format == 'tree') {
|
||||
function print_tree(locator, prefix, is_last, visited) {
|
||||
if (visited[locator]) {
|
||||
log.console(prefix + (is_last ? "\\-- " : "|-- ") + locator + " (circular)")
|
||||
return
|
||||
}
|
||||
visited[locator] = true
|
||||
|
||||
var node = nodes[locator]
|
||||
var suffix = ""
|
||||
if (node.linked) suffix += " -> " + node.effective
|
||||
if (node.commit) suffix += " @" + node.commit
|
||||
if (node.local) suffix += " (local)"
|
||||
|
||||
log.console(prefix + (is_last ? "\\-- " : "|-- ") + locator + suffix)
|
||||
|
||||
// Get children
|
||||
var children = []
|
||||
arrfor(edges, function(e) {
|
||||
if (e.from == locator) {
|
||||
push(children, e)
|
||||
}
|
||||
})
|
||||
|
||||
for (var i = 0; i < length(children); i++) {
|
||||
var child_prefix = prefix + (is_last ? " " : "| ")
|
||||
print_tree(children[i].to, child_prefix, i == length(children) - 1, visited)
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < length(roots); i++) {
|
||||
log.console(roots[i])
|
||||
|
||||
var children = []
|
||||
arrfor(edges, function(e) {
|
||||
if (e.from == roots[i]) {
|
||||
push(children, e)
|
||||
}
|
||||
})
|
||||
|
||||
for (var j = 0; j < length(children); j++) {
|
||||
print_tree(children[j].to, "", j == length(children) - 1, {})
|
||||
}
|
||||
|
||||
if (i < length(roots) - 1) log.console("")
|
||||
}
|
||||
|
||||
} else if (format == 'dot') {
|
||||
log.console("digraph dependencies {")
|
||||
log.console(" rankdir=TB;")
|
||||
log.console(" node [shape=box];")
|
||||
log.console("")
|
||||
|
||||
// Node definitions
|
||||
arrfor(array(nodes), function(id) {
|
||||
var node = nodes[id]
|
||||
var label = id
|
||||
if (node.commit) label += "\\n@" + node.commit
|
||||
var attrs = 'label="' + label + '"'
|
||||
if (node.linked) attrs += ', style=dashed'
|
||||
if (node.local) attrs += ', color=blue'
|
||||
|
||||
// Safe node ID for dot
|
||||
var safe_id = replace(id, /[^a-zA-Z0-9]/g, '_')
|
||||
log.console(' ' + safe_id + ' [' + attrs + '];')
|
||||
})
|
||||
|
||||
log.console("")
|
||||
|
||||
// Edges
|
||||
arrfor(edges, function(e) {
|
||||
var from_id = replace(e.from, /[^a-zA-Z0-9]/g, '_')
|
||||
var to_id = replace(e.to, /[^a-zA-Z0-9]/g, '_')
|
||||
var label = e.alias != e.to ? 'label="' + e.alias + '"' : ''
|
||||
log.console(' ' + from_id + ' -> ' + to_id + (label ? ' [' + label + ']' : '') + ';')
|
||||
})
|
||||
|
||||
log.console("}")
|
||||
|
||||
} else if (format == 'json') {
|
||||
var output = {
|
||||
nodes: [],
|
||||
edges: []
|
||||
}
|
||||
|
||||
arrfor(array(nodes), function(id) {
|
||||
push(output.nodes, nodes[id])
|
||||
})
|
||||
|
||||
output.edges = edges
|
||||
|
||||
log.console(json.encode(output))
|
||||
}
|
||||
|
||||
$stop()
|
||||
44
help.ce
44
help.ce
@@ -2,7 +2,7 @@
|
||||
|
||||
var fd = use('fd')
|
||||
|
||||
var command = args.length > 0 ? args[0] : null
|
||||
var command = length(args) > 0 ? args[0] : null
|
||||
|
||||
// Display specific command help
|
||||
if (command) {
|
||||
@@ -27,21 +27,41 @@ if (stat && stat.isFile) {
|
||||
log.console(content)
|
||||
} else {
|
||||
// Fallback if man file doesn't exist
|
||||
log.console("cell - The Cell module system for Prosperon")
|
||||
log.console("cell - The Cell package manager")
|
||||
log.console("")
|
||||
log.console("Usage: cell <command> [arguments]")
|
||||
log.console("")
|
||||
log.console("Commands:")
|
||||
log.console(" init Initialize a new Cell project")
|
||||
log.console(" get Fetch and add a module dependency")
|
||||
log.console(" update Update a dependency to a new version")
|
||||
log.console(" vendor Copy all dependencies locally")
|
||||
log.console(" build Compile all modules to bytecode")
|
||||
log.console(" patch Create a patch for a module")
|
||||
log.console(" config Manage system and actor configurations")
|
||||
log.console(" help Show this help message")
|
||||
log.console("Package Management:")
|
||||
log.console(" install <locator> Install a package and its dependencies")
|
||||
log.console(" update [locator] Update packages from remote sources")
|
||||
log.console(" remove <locator> Remove a package from the shop")
|
||||
log.console(" add <locator> Add a dependency to current package")
|
||||
log.console("")
|
||||
log.console("Run 'cell help <command>' for more information on a command.")
|
||||
log.console("Building:")
|
||||
log.console(" build [locator] Build dynamic libraries for packages")
|
||||
log.console(" clean [scope] Remove build artifacts")
|
||||
log.console("")
|
||||
log.console("Linking (Local Development):")
|
||||
log.console(" link <origin> <target> Link a package to a local path")
|
||||
log.console(" unlink <origin> Remove a package link")
|
||||
log.console(" clone <origin> <path> Clone and link a package locally")
|
||||
log.console("")
|
||||
log.console("Information:")
|
||||
log.console(" list [scope] List packages and dependencies")
|
||||
log.console(" ls [locator] List modules and actors in a package")
|
||||
log.console(" why <locator> Show reverse dependencies")
|
||||
log.console(" search <query> Search for packages, modules, or actors")
|
||||
log.console("")
|
||||
log.console("Diagnostics:")
|
||||
log.console(" resolve [locator] Print fully resolved dependency closure")
|
||||
log.console(" graph [locator] Emit dependency graph (tree, dot, json)")
|
||||
log.console(" verify [scope] Verify integrity and consistency")
|
||||
log.console("")
|
||||
log.console("Other:")
|
||||
log.console(" help [command] Show help for a command")
|
||||
log.console(" version Show cell version")
|
||||
log.console("")
|
||||
log.console("Run 'cell <command> --help' for more information on a command.")
|
||||
}
|
||||
|
||||
$stop()
|
||||
171
install.ce
171
install.ce
@@ -1,62 +1,185 @@
|
||||
// cell install <locator> - Install a package to the shop
|
||||
// Does not modify the current project's cell.toml
|
||||
//
|
||||
// Usage:
|
||||
// cell install <locator> Install a package and its dependencies
|
||||
// cell install . Install current directory package
|
||||
//
|
||||
// Options:
|
||||
// --target <triple> Build for target platform
|
||||
// --refresh Refresh floating refs before locking
|
||||
// --dry-run Show what would be installed
|
||||
|
||||
var shop = use('internal/shop')
|
||||
var build = use('build')
|
||||
var pkg = use('package')
|
||||
var fd = use('fd')
|
||||
|
||||
if (args.length < 1) {
|
||||
log.console("Usage: cell install <locator>")
|
||||
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")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
|
||||
var locator = args[0]
|
||||
var locator = null
|
||||
var target_triple = null
|
||||
var refresh = false
|
||||
var dry_run = false
|
||||
|
||||
for (var i = 0; i < length(args); i++) {
|
||||
if (args[i] == '--target' || args[i] == '-t') {
|
||||
if (i + 1 < length(args)) {
|
||||
target_triple = args[++i]
|
||||
} else {
|
||||
log.error('--target requires a triple')
|
||||
$stop()
|
||||
}
|
||||
} else if (args[i] == '--refresh') {
|
||||
refresh = true
|
||||
} else if (args[i] == '--dry-run') {
|
||||
dry_run = 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")
|
||||
$stop()
|
||||
} else if (!starts_with(args[i], '-')) {
|
||||
locator = args[i]
|
||||
}
|
||||
}
|
||||
|
||||
if (!locator) {
|
||||
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.startsWith('./') || locator.startsWith('../') || fd.is_dir(locator)) {
|
||||
if (locator == '.' || starts_with(locator, './') || starts_with(locator, '../') || fd.is_dir(locator)) {
|
||||
var resolved = fd.realpath(locator)
|
||||
if (resolved) {
|
||||
locator = resolved
|
||||
}
|
||||
}
|
||||
|
||||
// Default target
|
||||
if (!target_triple) {
|
||||
target_triple = build.detect_host_target()
|
||||
}
|
||||
|
||||
log.console("Installing " + locator + "...")
|
||||
|
||||
var pkg = use('package')
|
||||
// Gather all packages that will be installed
|
||||
var packages_to_install = []
|
||||
var skipped_packages = []
|
||||
var visited = {}
|
||||
|
||||
// Recursive install function that handles dependencies
|
||||
function install_package(pkg_locator, visited) {
|
||||
function gather_packages(pkg_locator) {
|
||||
if (visited[pkg_locator]) return
|
||||
visited[pkg_locator] = true
|
||||
|
||||
// First, add to lock.toml
|
||||
shop.update(pkg_locator)
|
||||
// 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
|
||||
}
|
||||
|
||||
// Extract/symlink the package so we can read its cell.toml
|
||||
shop.extract(pkg_locator)
|
||||
push(packages_to_install, pkg_locator)
|
||||
|
||||
// Now get direct dependencies and install them first
|
||||
// Try to read dependencies
|
||||
try {
|
||||
// For packages not yet extracted, we need to update and extract first to read deps
|
||||
var lock = shop.load_lock()
|
||||
if (!lock[pkg_locator]) {
|
||||
if (!dry_run) {
|
||||
var update_result = shop.update(pkg_locator)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
var deps = pkg.dependencies(pkg_locator)
|
||||
if (deps) {
|
||||
for (var alias in deps) {
|
||||
arrfor(array(deps), function(alias) {
|
||||
var dep_locator = deps[alias]
|
||||
log.console("Installing dependency " + dep_locator)
|
||||
install_package(dep_locator, visited)
|
||||
}
|
||||
gather_packages(dep_locator)
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
// Package might not have dependencies or cell.toml issue
|
||||
log.console("Warning: Could not read dependencies for " + pkg_locator + ": " + e.message)
|
||||
if (!dry_run) {
|
||||
log.console(`Warning: Could not read dependencies for ${pkg_locator}: ${e.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
// Build the package after all dependencies are installed
|
||||
build.build_package(pkg_locator)
|
||||
}
|
||||
|
||||
install_package(locator, {})
|
||||
log.console("Installed " + locator)
|
||||
// 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) {
|
||||
log.console("")
|
||||
log.console("Would skip (missing local paths):")
|
||||
arrfor(skipped_packages, function(p) {
|
||||
log.console(" " + p)
|
||||
})
|
||||
}
|
||||
$stop()
|
||||
}
|
||||
|
||||
// Install each package
|
||||
function install_package(pkg_locator) {
|
||||
// Update lock entry
|
||||
shop.update(pkg_locator)
|
||||
|
||||
// Extract/symlink the package
|
||||
shop.extract(pkg_locator)
|
||||
|
||||
// Build scripts
|
||||
shop.build_package_scripts(pkg_locator)
|
||||
|
||||
// Build C code
|
||||
try {
|
||||
build.build_dynamic(pkg_locator, target_triple, 'release')
|
||||
} catch (e) {
|
||||
// Not all packages have C code
|
||||
}
|
||||
}
|
||||
|
||||
arrfor(packages_to_install, function(p) {
|
||||
log.console(" Installing " + p + "...")
|
||||
install_package(p)
|
||||
})
|
||||
|
||||
var 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)
|
||||
|
||||
$stop()
|
||||
|
||||
@@ -1,303 +0,0 @@
|
||||
/* array.cm - array creation and manipulation utilities */
|
||||
|
||||
var _isArray = Array.isArray
|
||||
var _slice = Array.prototype.slice
|
||||
var _push = Array.prototype.push
|
||||
var _sort = Array.prototype.sort
|
||||
var _keys = Object.keys
|
||||
var _from = Array.from
|
||||
|
||||
function array(arg, arg2, arg3, arg4) {
|
||||
// array(number) - create array of size with nulls
|
||||
// array(number, initial_value) - create array with initial values
|
||||
if (typeof arg == 'number') {
|
||||
if (arg < 0) return null
|
||||
var len = number.floor(arg)
|
||||
var result = []
|
||||
|
||||
if (arg2 == null) {
|
||||
result.length = 100
|
||||
} else if (typeof arg2 == 'function') {
|
||||
var arity = arg2.length
|
||||
for (var i = 0; i < len; i++) {
|
||||
result[i] = arity >= 1 ? arg2(i) : arg2()
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < len; i++) result[i] = arg2
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// array(array) - copy
|
||||
// array(array, function, reverse, exit) - map
|
||||
// array(array, another_array) - concat
|
||||
// array(array, from, to) - slice
|
||||
if (_isArray(arg)) {
|
||||
if (arg2 == null) {
|
||||
// Copy
|
||||
return _slice.call(arg)
|
||||
}
|
||||
|
||||
if (typeof arg2 == 'function') {
|
||||
// Map
|
||||
var fn = arg2
|
||||
var reverse = arg3 == true
|
||||
var exit = arg4
|
||||
var result = []
|
||||
|
||||
if (reverse) {
|
||||
for (var i = arg.length - 1; i >= 0; i--) {
|
||||
var val = fn(arg[i], i)
|
||||
if (exit != null && val == exit) break
|
||||
result[i] = val
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < arg.length; i++) {
|
||||
var val = fn(arg[i], i)
|
||||
if (exit != null && val == exit) break
|
||||
_push.call(result, val)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
if (_isArray(arg2)) {
|
||||
// Concat
|
||||
var result = _slice.call(arg)
|
||||
for (var i = 0; i < arg2.length; i++) {
|
||||
_push.call(result, arg2[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
if (typeof arg2 == 'number') {
|
||||
// Slice
|
||||
var from = arg2
|
||||
var to = arg3
|
||||
var len = arg.length
|
||||
|
||||
if (from < 0) from += len
|
||||
if (to == null) to = len
|
||||
if (to < 0) to += len
|
||||
|
||||
if (from < 0 || from > to || to > len) return null
|
||||
|
||||
return _slice.call(arg, from, to)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
// array(object) - keys
|
||||
if (typeof arg == 'object' && arg != null && !_isArray(arg)) {
|
||||
if (arg instanceof Set) {
|
||||
return _from(arg)
|
||||
}
|
||||
return _keys(arg)
|
||||
}
|
||||
|
||||
// array(text) - split into grapheme clusters
|
||||
// array(text, separator) - split by separator
|
||||
// array(text, length) - dice into chunks
|
||||
if (typeof arg == 'string') {
|
||||
if (arg2 == null) {
|
||||
// Split into grapheme clusters (simplified: split into characters)
|
||||
var result = []
|
||||
for (var i = 0; i < arg.length; i++) {
|
||||
_push.call(result, arg[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
if (typeof arg2 == 'string') {
|
||||
// Split by separator
|
||||
return arg.split(arg2)
|
||||
}
|
||||
|
||||
if (typeof arg2 == 'number') {
|
||||
// Dice into chunks
|
||||
var len = number.floor(arg2)
|
||||
if (len <= 0) return null
|
||||
var result = []
|
||||
for (var i = 0; i < arg.length; i += len) {
|
||||
_push.call(result, arg.substring(i, i + len))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
array.reduce = function(arr, fn, initial, reverse) {
|
||||
if (!_isArray(arr)) return null
|
||||
if (typeof fn != 'function') return null
|
||||
|
||||
var len = arr.length
|
||||
|
||||
if (initial == null) {
|
||||
if (len == 0) return null
|
||||
if (len == 1) return arr[0]
|
||||
|
||||
if (reverse == true) {
|
||||
var acc = arr[len - 1]
|
||||
for (var i = len - 2; i >= 0; i--) {
|
||||
acc = fn(acc, arr[i])
|
||||
}
|
||||
return acc
|
||||
} else {
|
||||
var acc = arr[0]
|
||||
for (var i = 1; i < len; i++) {
|
||||
acc = fn(acc, arr[i])
|
||||
}
|
||||
return acc
|
||||
}
|
||||
} else {
|
||||
if (len == 0) return initial
|
||||
|
||||
if (reverse == true) {
|
||||
var acc = initial
|
||||
for (var i = len - 1; i >= 0; i--) {
|
||||
acc = fn(acc, arr[i])
|
||||
}
|
||||
return acc
|
||||
} else {
|
||||
var acc = initial
|
||||
for (var i = 0; i < len; i++) {
|
||||
acc = fn(acc, arr[i])
|
||||
}
|
||||
return acc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
array.for = function(arr, fn, reverse, exit) {
|
||||
if (!_isArray(arr)) return null
|
||||
if (arr.length == 0) return null
|
||||
if (typeof fn != 'function') return null
|
||||
|
||||
if (reverse == true) {
|
||||
for (var i = arr.length - 1; i >= 0; i--) {
|
||||
var result = fn(arr[i], i)
|
||||
if (exit != null && result == exit) return exit
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var result = fn(arr[i], i)
|
||||
if (exit != null && result == exit) return exit
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
array.find = function(arr, fn, reverse, from) {
|
||||
if (!_isArray(arr)) return null
|
||||
|
||||
var len = arr.length
|
||||
|
||||
if (typeof fn != 'function') {
|
||||
// Compare exactly
|
||||
var target = fn
|
||||
if (reverse == true) {
|
||||
var start = from != null ? from : len - 1
|
||||
for (var i = start; i >= 0; i--) {
|
||||
if (arr[i] == target) return i
|
||||
}
|
||||
} else {
|
||||
var start = from != null ? from : 0
|
||||
for (var i = start; i < len; i++) {
|
||||
if (arr[i] == target) return i
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
if (reverse == true) {
|
||||
var start = from != null ? from : len - 1
|
||||
for (var i = start; i >= 0; i--) {
|
||||
if (fn(arr[i], i) == true) return i
|
||||
}
|
||||
} else {
|
||||
var start = from != null ? from : 0
|
||||
for (var i = start; i < len; i++) {
|
||||
if (fn(arr[i], i) == true) return i
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
array.filter = function(arr, fn) {
|
||||
if (!_isArray(arr)) return null
|
||||
if (typeof fn != 'function') return null
|
||||
|
||||
var result = []
|
||||
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var val = fn(arr[i], i)
|
||||
if (val == true) {
|
||||
_push.call(result, arr[i])
|
||||
} else if (val != false) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
array.sort = function(arr, select) {
|
||||
if (!_isArray(arr)) return null
|
||||
|
||||
var result = _slice.call(arr)
|
||||
var keys = []
|
||||
|
||||
// Extract keys
|
||||
for (var i = 0; i < result.length; i++) {
|
||||
var key
|
||||
if (select == null) {
|
||||
key = result[i]
|
||||
} else if (typeof select == 'string' || typeof select == 'number') {
|
||||
key = result[i][select]
|
||||
} else if (_isArray(select)) {
|
||||
key = select[i]
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
||||
if (typeof key != 'number' && typeof key != 'string') return null
|
||||
keys[i] = key
|
||||
}
|
||||
|
||||
// Check all keys are same type
|
||||
if (keys.length > 0) {
|
||||
var keyType = typeof keys[0]
|
||||
for (var i = 1; i < keys.length; i++) {
|
||||
if (typeof keys[i] != keyType) return null
|
||||
}
|
||||
}
|
||||
|
||||
// Create index array and sort
|
||||
var indices = []
|
||||
for (var i = 0; i < result.length; i++) indices[i] = i
|
||||
|
||||
// Stable sort using indices
|
||||
_sort.call(indices, function(a, b) {
|
||||
if (keys[a] < keys[b]) return -1
|
||||
if (keys[a] > keys[b]) return 1
|
||||
return a - b // stable
|
||||
})
|
||||
|
||||
var sorted = []
|
||||
for (var i = 0; i < indices.length; i++) {
|
||||
sorted[i] = result[indices[i]]
|
||||
}
|
||||
|
||||
return sorted
|
||||
}
|
||||
|
||||
return array
|
||||
@@ -7,6 +7,7 @@ var SYSYM = '__SYSTEM__'
|
||||
var hidden = _cell.hidden
|
||||
|
||||
var os = hidden.os;
|
||||
|
||||
_cell.os = null
|
||||
|
||||
var dylib_ext
|
||||
@@ -24,19 +25,7 @@ var ACTOR_EXT = '.ce'
|
||||
|
||||
var load_internal = os.load_internal
|
||||
function use_embed(name) {
|
||||
return load_internal(`js_${name}_use`)
|
||||
}
|
||||
|
||||
globalThis.meme = function(obj, ...mixins) {
|
||||
var result = _ObjectCreate(obj)
|
||||
|
||||
array.for(mixins, mix => {
|
||||
if (isa(mix, object)) {
|
||||
for (var key in mix)
|
||||
result[key] = mix[key]
|
||||
}
|
||||
})
|
||||
return result
|
||||
return load_internal("js_" + name + "_use")
|
||||
}
|
||||
|
||||
globalThis.logical = function(val1)
|
||||
@@ -48,50 +37,58 @@ globalThis.logical = function(val1)
|
||||
return null;
|
||||
}
|
||||
|
||||
var utf8 = use_embed('utf8')
|
||||
globalThis.some = function(arr, pred) {
|
||||
return find(arr, pred) != null
|
||||
}
|
||||
|
||||
globalThis.every = function(arr, pred) {
|
||||
return find(arr, x => not(pred(x))) == null
|
||||
}
|
||||
|
||||
globalThis.starts_with = function(str, prefix) {
|
||||
return search(str, prefix) == 0
|
||||
}
|
||||
|
||||
globalThis.ends_with = function(str, suffix) {
|
||||
return search(str, suffix, -length(suffix)) != null
|
||||
}
|
||||
|
||||
var js = use_embed('js')
|
||||
var fd = use_embed('fd')
|
||||
|
||||
// Get the shop path from HOME environment
|
||||
var home = os.getenv('HOME') || os.getenv('USERPROFILE')
|
||||
if (!home) {
|
||||
throw new Error('Could not determine home directory')
|
||||
throw Error('Could not determine home directory')
|
||||
}
|
||||
var shop_path = home + '/.cell'
|
||||
var packages_path = shop_path + '/packages'
|
||||
var core_path = packages_path + '/core'
|
||||
|
||||
if (!fd.is_dir(core_path)) {
|
||||
throw new Error('Cell shop not found at ' + shop_path + '. Run "cell install" to set up.')
|
||||
throw Error('Cell shop not found at ' + shop_path + '. Run "cell install" to set up.')
|
||||
}
|
||||
|
||||
var use_cache = {}
|
||||
use_cache['core/os'] = os
|
||||
|
||||
var _Symbol = Symbol
|
||||
|
||||
globalThis.key = function()
|
||||
{
|
||||
return _Symbol()
|
||||
}
|
||||
|
||||
// Load a core module from the file system
|
||||
function use_core(path) {
|
||||
var cache_key = 'core/' + path
|
||||
if (use_cache[cache_key])
|
||||
return use_cache[cache_key];
|
||||
|
||||
var sym = use_embed(path.replace('/','_'))
|
||||
var sym = use_embed(replace(path, '/', '_'))
|
||||
|
||||
// Core scripts are in packages/core/
|
||||
var file_path = core_path + '/' + path + MOD_EXT
|
||||
|
||||
if (fd.is_file(file_path)) {
|
||||
var script_blob = fd.slurp(file_path)
|
||||
var script = utf8.decode(script_blob)
|
||||
var script = text(script_blob)
|
||||
var mod = `(function setup_module(use){${script}})`
|
||||
var fn = js.eval('core:' + path, mod)
|
||||
var result = fn.call(sym, use_core);
|
||||
var result = call(fn,sym, [use_core])
|
||||
use_cache[cache_key] = result;
|
||||
return result;
|
||||
}
|
||||
@@ -101,226 +98,34 @@ function use_core(path) {
|
||||
}
|
||||
|
||||
var blob = use_core('blob')
|
||||
var blob_stone = blob.prototype.stone
|
||||
var blob_stonep = blob.prototype.stonep;
|
||||
delete blob.prototype.stone;
|
||||
delete blob.prototype.stonep;
|
||||
|
||||
// Capture Object and Array methods before they're deleted
|
||||
var _Object = Object
|
||||
var _ObjectKeys = Object.keys
|
||||
var _ObjectFreeze = Object.freeze
|
||||
var _ObjectIsFrozen = Object.isFrozen
|
||||
var _ObjectDefineProperty = Object.defineProperty
|
||||
var _ObjectGetPrototypeOf = Object.getPrototypeOf
|
||||
var _ObjectCreate = Object.create
|
||||
var _ArrayIsArray = Array.isArray
|
||||
|
||||
Object.prototype.toString = function()
|
||||
{
|
||||
return json.encode(this)
|
||||
}
|
||||
|
||||
function deepFreeze(object) {
|
||||
if (object instanceof blob)
|
||||
blob_stone.call(object);
|
||||
|
||||
var propNames = _ObjectKeys(object);
|
||||
|
||||
for (var name of propNames) {
|
||||
var value = object[name];
|
||||
|
||||
if ((value && typeof value == "object") || typeof value == "function")
|
||||
deepFreeze(value);
|
||||
}
|
||||
|
||||
return _ObjectFreeze(object);
|
||||
}
|
||||
|
||||
globalThis.actor = function()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
globalThis.stone = deepFreeze
|
||||
stone.p = function(object)
|
||||
{
|
||||
if (object instanceof blob)
|
||||
return blob_stonep.call(object)
|
||||
|
||||
return _ObjectIsFrozen(object)
|
||||
}
|
||||
|
||||
var actor_mod = use_core('actor')
|
||||
var wota = use_core('wota')
|
||||
var nota = use_core('nota')
|
||||
|
||||
// Load internal modules for global functions
|
||||
globalThis.text = use_core('internal/text')
|
||||
globalThis.number = use_core('internal/number')
|
||||
globalThis.array = use_core('internal/array')
|
||||
globalThis.object = use_core('internal/object')
|
||||
globalThis.fn = use_core('internal/fn')
|
||||
|
||||
// Global utility functions (use already-captured references)
|
||||
var _isArray = _ArrayIsArray
|
||||
var _keys = _ObjectKeys
|
||||
var _getPrototypeOf = _ObjectGetPrototypeOf
|
||||
var _create = _ObjectCreate
|
||||
|
||||
globalThis.length = function(value) {
|
||||
if (value == null) return null
|
||||
|
||||
// For functions, return arity
|
||||
if (typeof value == 'function') return value.length
|
||||
|
||||
// For strings, return codepoint count
|
||||
if (typeof value == 'string') return value.length
|
||||
|
||||
// For arrays, return element count
|
||||
if (_isArray(value)) return value.length
|
||||
|
||||
// For blobs, return bit count
|
||||
if (value instanceof blob && typeof value.length == 'number') return value.length
|
||||
|
||||
// For records with length field
|
||||
if (typeof value == 'object' && value != null) {
|
||||
if ('length' in value) {
|
||||
var len = value.length
|
||||
if (typeof len == 'function') return len.call(value)
|
||||
if (typeof len == 'number') return len
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
globalThis.reverse = function(value) {
|
||||
if (_isArray(value)) {
|
||||
var result = []
|
||||
for (var i = value.length - 1; i >= 0; i--) {
|
||||
result.push(value[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// For blobs, would need blob module support
|
||||
if (isa(value, blob)) {
|
||||
// Simplified: return null for now, would need proper blob reversal
|
||||
return null
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
globalThis.isa = function(value, master) {
|
||||
if (master == null) return false
|
||||
|
||||
// isa(value, array) - check if object has all keys
|
||||
if (_isArray(master)) {
|
||||
if (typeof value != 'object' || value == null) return false
|
||||
for (var i = 0; i < master.length; i++) {
|
||||
if (!(master[i] in value)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// isa(value, function) - check if function.prototype is in chain
|
||||
if (typeof master == 'function') {
|
||||
// Special type checks
|
||||
if (master == stone) return _ObjectIsFrozen(value) || typeof value != 'object'
|
||||
if (master == number) return typeof value == 'number'
|
||||
if (master == text) return typeof value == 'string'
|
||||
if (master == logical) return typeof value == 'boolean'
|
||||
if (master == array) return _isArray(value)
|
||||
if (master == object) return typeof value == 'object' && value != null && !_isArray(value)
|
||||
if (master == fn) return typeof value == 'function'
|
||||
if (master == actor) return isa(value, object) && value[ACTORDATA]
|
||||
|
||||
// Check prototype chain
|
||||
if (master.prototype) {
|
||||
var proto = _getPrototypeOf(value)
|
||||
while (proto != null) {
|
||||
if (proto == master.prototype) return true
|
||||
proto = _getPrototypeOf(proto)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isa(object, master_object) - check prototype chain
|
||||
if (typeof master == 'object') {
|
||||
var proto = _getPrototypeOf(value)
|
||||
while (proto != null) {
|
||||
if (proto == master) return true
|
||||
proto = _getPrototypeOf(proto)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
globalThis.proto = function(obj) {
|
||||
if (!isa(obj, object)) return null
|
||||
var p = _getPrototypeOf(obj)
|
||||
if (p == _Object.prototype) return null
|
||||
return p
|
||||
}
|
||||
|
||||
globalThis.splat = function(obj) {
|
||||
if (typeof obj != 'object' || obj == null) return null
|
||||
|
||||
var result = {}
|
||||
var current = obj
|
||||
|
||||
// Walk prototype chain and collect text keys
|
||||
while (current != null) {
|
||||
var keys = _keys(current)
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
var k = keys[i]
|
||||
if (!(k in result)) {
|
||||
var val = current[k]
|
||||
// Only include serializable types
|
||||
if (typeof val == 'object' || typeof val == 'number' ||
|
||||
typeof val == 'string' || typeof val == 'boolean') {
|
||||
result[k] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
current = _getPrototypeOf(current)
|
||||
}
|
||||
|
||||
// Call to_data if present
|
||||
if (typeof obj.to_data == 'function') {
|
||||
var extra = obj.to_data(result)
|
||||
if (typeof extra == 'object' && extra != null) {
|
||||
var extraKeys = _keys(extra)
|
||||
for (var i = 0; i < extraKeys.length; i++) {
|
||||
result[extraKeys[i]] = extra[extraKeys[i]]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
globalThis.is_actor = function(value) {
|
||||
return is_object(value) && value[ACTORDATA]
|
||||
}
|
||||
|
||||
var ENETSERVICE = 0.1
|
||||
var REPLYTIMEOUT = 60 // seconds before replies are ignored
|
||||
|
||||
globalThis.pi = 3.14159265358979323846264338327950288419716939937510
|
||||
|
||||
function caller_data(depth = 0)
|
||||
{
|
||||
var file = "nofile"
|
||||
var line = 0
|
||||
|
||||
var caller = new Error().stack.split("\n")[1+depth]
|
||||
var caller = array(Error().stack, "\n")[1+depth]
|
||||
if (caller) {
|
||||
var md = caller.match(/\((.*)\:/)
|
||||
var md = extract(caller, /\((.*)\:/)
|
||||
var m = md ? md[1] : "SCRIPT"
|
||||
if (m) file = m
|
||||
md = caller.match(/\:(\d*)\)/)
|
||||
md = extract(caller, /\:(\d*)\)/)
|
||||
m = md ? md[1] : 0
|
||||
if (m) line = m
|
||||
}
|
||||
@@ -329,52 +134,62 @@ function caller_data(depth = 0)
|
||||
}
|
||||
|
||||
function console_rec(line, file, msg) {
|
||||
return `[${_cell.id.slice(0,5)}] [${file}:${line}]: ${msg}\n`
|
||||
return `[${text(_cell.id, 0, 5)}] [${file}:${line}]: ${msg}\n`
|
||||
// time: [${time.text("mb d yyyy h:nn:ss")}]
|
||||
}
|
||||
|
||||
globalThis.log = {}
|
||||
log.console = function(msg)
|
||||
{
|
||||
globalThis.log = function(name, args) {
|
||||
var caller = caller_data(1)
|
||||
os.print(console_rec(caller.line, caller.file, msg))
|
||||
}
|
||||
var msg = args[0]
|
||||
|
||||
log.error = function(msg = new Error())
|
||||
{
|
||||
var caller = caller_data(1)
|
||||
|
||||
if (msg instanceof Error)
|
||||
msg = msg.name + ": " + msg.message + "\n" + msg.stack
|
||||
|
||||
os.print(console_rec(caller.line,caller.file,msg))
|
||||
}
|
||||
|
||||
log.system = function(msg) {
|
||||
msg = "[SYSTEM] " + msg
|
||||
log.console(msg)
|
||||
switch(name) {
|
||||
case 'console':
|
||||
os.print(console_rec(caller.line, caller.file, msg))
|
||||
break
|
||||
case 'error':
|
||||
msg = msg ?? Error()
|
||||
if (is_proto(msg, Error))
|
||||
msg = msg.name + ": " + msg.message + "\n" + msg.stack
|
||||
os.print(console_rec(caller.line, caller.file, msg))
|
||||
break
|
||||
case 'system':
|
||||
msg = "[SYSTEM] " + msg
|
||||
os.print(console_rec(caller.line, caller.file, msg))
|
||||
break
|
||||
default:
|
||||
log.console(`unknown log type: ${name}`)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function disrupt(err)
|
||||
{
|
||||
if (is_function(err.toString)) {
|
||||
os.print(err.toString())
|
||||
os.print("\n")
|
||||
os.print(err.stack)
|
||||
}
|
||||
|
||||
if (overling) {
|
||||
if (err) {
|
||||
// with an err, this is a forceful disrupt
|
||||
var reason = (err instanceof Error) ? err.stack : err
|
||||
var reason = (is_proto(err, Error)) ? err.stack : err
|
||||
report_to_overling({type:'disrupt', reason})
|
||||
} else
|
||||
report_to_overling({type:'stop'})
|
||||
}
|
||||
|
||||
if (underlings) {
|
||||
for (var id of underlings) {
|
||||
var unders = array(underlings)
|
||||
arrfor(unders, function(id, index) {
|
||||
log.console(`calling on ${id} to disrupt too`)
|
||||
$_.stop(create_actor({id}))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (err) {
|
||||
log.console(err);
|
||||
if (err.message)
|
||||
log.console(err.message)
|
||||
if (err.stack)
|
||||
log.console(err.stack)
|
||||
}
|
||||
@@ -382,6 +197,8 @@ function disrupt(err)
|
||||
actor_mod.disrupt()
|
||||
}
|
||||
|
||||
|
||||
|
||||
actor_mod.on_exception(disrupt)
|
||||
|
||||
_cell.args = _cell.hidden.init
|
||||
@@ -415,9 +232,9 @@ globalThis.sequence = pronto.sequence
|
||||
$_.time_limit = function(requestor, seconds)
|
||||
{
|
||||
if (!pronto.is_requestor(requestor))
|
||||
throw new Error('time_limit: first argument must be a requestor');
|
||||
if (!isa(seconds, number) || seconds <= 0)
|
||||
throw new Error('time_limit: seconds must be a positive number');
|
||||
throw Error('time_limit: first argument must be a requestor');
|
||||
if (!is_number(seconds) || seconds <= 0)
|
||||
throw Error('time_limit: seconds must be a positive number');
|
||||
|
||||
return function time_limit_requestor(callback, value) {
|
||||
pronto.check_callback(callback, 'time_limit')
|
||||
@@ -486,6 +303,8 @@ _cell.config = config
|
||||
ENETSERVICE = config.net_service
|
||||
REPLYTIMEOUT = config.reply_timeout
|
||||
|
||||
|
||||
|
||||
/*
|
||||
When handling a message, the message appears like this:
|
||||
{
|
||||
@@ -511,14 +330,12 @@ REPLYTIMEOUT = config.reply_timeout
|
||||
|
||||
function guid(bits = 256)
|
||||
{
|
||||
var guid = new blob(bits, os.random)
|
||||
var guid = blob(bits, os.random)
|
||||
stone(guid)
|
||||
return text(guid,'h')
|
||||
}
|
||||
|
||||
var _Symbol = Symbol
|
||||
|
||||
var HEADER = _Symbol()
|
||||
var HEADER = {}
|
||||
|
||||
// takes a function input value that will eventually be called with the current time in number form.
|
||||
$_.clock = function(fn) {
|
||||
@@ -528,7 +345,7 @@ $_.clock = function(fn) {
|
||||
})
|
||||
}
|
||||
|
||||
var underlings = new Set() // this is more like "all actors that are notified when we die"
|
||||
var underlings = {} // this is more like "all actors that are notified when we die"
|
||||
var overling = null
|
||||
var root = null
|
||||
|
||||
@@ -581,8 +398,8 @@ var portal_fn = null
|
||||
|
||||
// takes a function input value that will eventually be called with the current time in number form.
|
||||
$_.portal = function(fn, port) {
|
||||
if (portal) throw new Error(`Already started a portal listening on ${portal.port}`)
|
||||
if (!port) throw new Error("Requires a valid port.")
|
||||
if (portal) throw Error(`Already started a portal listening on ${portal.port}`)
|
||||
if (!port) throw Error("Requires a valid port.")
|
||||
log.system(`starting a portal on port ${port}`)
|
||||
portal = enet.create_host({address: "any", port})
|
||||
portal_fn = fn
|
||||
@@ -595,14 +412,16 @@ function handle_host(e) {
|
||||
peers[`${e.peer.address}:${e.peer.port}`] = e.peer
|
||||
var queue = peer_queue.get(e.peer)
|
||||
if (queue) {
|
||||
for (var msg of queue) e.peer.send(nota.encode(msg))
|
||||
arrfor(queue, (msg, index) => e.peer.send(nota.encode(msg)))
|
||||
log.system(`sent ${msg} out of queue`)
|
||||
peer_queue.delete(e.peer)
|
||||
}
|
||||
break
|
||||
case "disconnect":
|
||||
peer_queue.delete(e.peer)
|
||||
for (var id in peers) if (peers[id] == e.peer) delete peers[id]
|
||||
arrfor(array(peers), function(id, index) {
|
||||
if (peers[id] == e.peer) delete peers[id]
|
||||
})
|
||||
log.system('portal got disconnect from ' + e.peer.address + ":" + e.peer.port)
|
||||
break
|
||||
case "receive":
|
||||
@@ -612,16 +431,15 @@ function handle_host(e) {
|
||||
data.replycc[ACTORDATA].port = e.peer.port
|
||||
}
|
||||
function populate_actor_addresses(obj) {
|
||||
if (!isa(obj, object)) return
|
||||
if (!is_object(obj)) return
|
||||
if (obj[ACTORDATA] && !obj[ACTORDATA].address) {
|
||||
obj[ACTORDATA].address = e.peer.address
|
||||
obj[ACTORDATA].port = e.peer.port
|
||||
}
|
||||
for (var key in obj) {
|
||||
if (object.has(obj, key)) {
|
||||
arrfor(array(obj), function(key, index) {
|
||||
if (key in obj)
|
||||
populate_actor_addresses(obj[key])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
if (data.data) populate_actor_addresses(data.data)
|
||||
turn(data)
|
||||
@@ -639,20 +457,18 @@ $_.receiver = function receiver(fn) {
|
||||
receive_fn = fn
|
||||
}
|
||||
|
||||
$_.start = function start(cb, program, ...args) {
|
||||
$_.start = function start(cb, program) {
|
||||
if (!program) return
|
||||
|
||||
var id = guid()
|
||||
if (args.length == 1 && Array.isArray(args[0])) args = args[0]
|
||||
var startup = {
|
||||
id,
|
||||
overling: $_.self,
|
||||
root,
|
||||
arg: args,
|
||||
program,
|
||||
}
|
||||
greeters[id] = cb
|
||||
message_queue.push({ startup })
|
||||
push(message_queue, { startup })
|
||||
}
|
||||
|
||||
// stops an underling or self.
|
||||
@@ -661,10 +477,10 @@ $_.stop = function stop(actor) {
|
||||
need_stop = true
|
||||
return
|
||||
}
|
||||
if (!isa(actor, actor))
|
||||
throw new Error('Can only call stop on an actor.')
|
||||
if (!underlings.has(actor[ACTORDATA].id))
|
||||
throw new Error('Can only call stop on an underling or self.')
|
||||
if (!is_actor(actor))
|
||||
throw Error('Can only call stop on an actor.')
|
||||
if (is_null(underlings[actor[ACTORDATA].id]))
|
||||
throw Error('Can only call stop on an underling or self.')
|
||||
|
||||
sys_msg(actor, {kind:"stop"})
|
||||
}
|
||||
@@ -676,11 +492,6 @@ $_.unneeded = function unneeded(fn, seconds) {
|
||||
|
||||
// schedules the invocation of a function after a specified amount of time.
|
||||
$_.delay = function delay(fn, seconds = 0) {
|
||||
if (seconds <= 0) {
|
||||
$_.clock(fn)
|
||||
return
|
||||
}
|
||||
|
||||
function delay_turn() {
|
||||
fn()
|
||||
send_messages()
|
||||
@@ -692,16 +503,16 @@ $_.delay = function delay(fn, seconds = 0) {
|
||||
var enet = use_core('enet')
|
||||
|
||||
// causes this actor to stop when another actor stops.
|
||||
var couplings = new Set()
|
||||
var couplings = {}
|
||||
$_.couple = function couple(actor) {
|
||||
if (actor == $_.self) return // can't couple to self
|
||||
couplings.add(actor[ACTORDATA].id)
|
||||
couplings[actor[ACTORDATA].id] = true
|
||||
sys_msg(actor, {kind:'couple', from: $_.self})
|
||||
log.system(`coupled to ${actor}`)
|
||||
}
|
||||
|
||||
function actor_prep(actor, send) {
|
||||
message_queue.push({actor,send});
|
||||
push(message_queue, {actor,send});
|
||||
}
|
||||
|
||||
// Send a message immediately without queuing
|
||||
@@ -717,9 +528,9 @@ function actor_send(actor, message) {
|
||||
if (actor[HEADER] && !actor[HEADER].replycc) // attempting to respond to a message but sender is not expecting; silently drop
|
||||
return
|
||||
|
||||
if (!isa(actor, actor) && !isa(actor.replycc, actor)) throw new Error(`Must send to an actor object. Attempted send to ${actor}`)
|
||||
if (!is_actor(actor) && !is_actor(actor.replycc)) throw Error(`Must send to an actor object. Attempted send to ${actor}`)
|
||||
|
||||
if (typeof message != 'object') throw new Error('Must send an object record.')
|
||||
if (!is_object(message)) throw Error('Must send an object record.')
|
||||
|
||||
// message to self
|
||||
if (actor[ACTORDATA].id == _cell.id) {
|
||||
@@ -730,7 +541,7 @@ function actor_send(actor, message) {
|
||||
// message to actor in same flock
|
||||
if (actor[ACTORDATA].id && actor_mod.mailbox_exist(actor[ACTORDATA].id)) {
|
||||
var wota_blob = wota.encode(message)
|
||||
// log.console(`sending wota blob of ${wota_blob.length/8} bytes`)
|
||||
// log.console(`sending wota blob of ${length(wota_blob)/8} bytes`)
|
||||
actor_mod.mailbox_push(actor[ACTORDATA].id, wota_blob)
|
||||
return
|
||||
}
|
||||
@@ -768,36 +579,36 @@ var need_stop = false
|
||||
// if we've been flagged to stop, bail out before doing anything
|
||||
if (need_stop) {
|
||||
disrupt()
|
||||
message_queue.length = 0
|
||||
message_queue = []
|
||||
return
|
||||
}
|
||||
|
||||
for (var msg of message_queue) {
|
||||
arrfor(message_queue, function(msg, index) {
|
||||
if (msg.startup) {
|
||||
// now is the time to actually spin up the actor
|
||||
actor_mod.createactor(msg.startup)
|
||||
} else {
|
||||
actor_send(msg.actor, msg.send)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
message_queue.length = 0
|
||||
message_queue = []
|
||||
}
|
||||
|
||||
var replies = {}
|
||||
|
||||
globalThis.send = function send(actor, message, reply) {
|
||||
if (typeof actor != 'object')
|
||||
throw new Error(`Must send to an actor object. Provided: ${actor}`);
|
||||
if (!is_object(actor))
|
||||
throw Error(`Must send to an actor object. Provided: ${actor}`);
|
||||
|
||||
if (typeof message != 'object')
|
||||
throw new Error('Message must be an object')
|
||||
if (!is_object(message))
|
||||
throw Error('Message must be an object')
|
||||
var send = {type:"user", data: message}
|
||||
|
||||
if (actor[HEADER] && actor[HEADER].replycc) {
|
||||
var header = actor[HEADER]
|
||||
if (!header.replycc || !isa(header.replycc, actor))
|
||||
throw new Error(`Supplied actor had a return, but it's not a valid actor! ${actor[HEADER]}`)
|
||||
if (!header.replycc || !is_actor(header.replycc))
|
||||
throw Error(`Supplied actor had a return, but it's not a valid actor! ${actor[HEADER]}`)
|
||||
|
||||
actor = header.replycc
|
||||
send.return = header.reply
|
||||
@@ -836,8 +647,6 @@ function turn(msg)
|
||||
}
|
||||
|
||||
//log.console(`FIXME: need to get main from config, not just set to true`)
|
||||
//log.console(`FIXME: remove global access (ie globalThis.use)`)
|
||||
//log.console(`FIXME: add freeze/unfreeze at this level, so we can do it (but scripts cannot)`)
|
||||
actor_mod.register_actor(_cell.id, turn, true, config.ar_timer)
|
||||
|
||||
if (config.actor_memory)
|
||||
@@ -886,21 +695,22 @@ function handle_actor_disconnect(id) {
|
||||
delete greeters[id]
|
||||
}
|
||||
log.system(`actor ${id} disconnected`)
|
||||
if (couplings.has(id)) disrupt("coupled actor died") // couplings now disrupts instead of stop
|
||||
if (!is_null(couplings[id])) disrupt("coupled actor died") // couplings now disrupts instead of stop
|
||||
}
|
||||
|
||||
function handle_sysym(msg)
|
||||
{
|
||||
var from
|
||||
switch(msg.kind) {
|
||||
case 'stop':
|
||||
disrupt("got stop message")
|
||||
break
|
||||
case 'underling':
|
||||
var from = msg.from
|
||||
from = msg.from
|
||||
var greeter = greeters[from[ACTORDATA].id]
|
||||
if (greeter) greeter(msg.message)
|
||||
if (msg.message.type == 'disrupt')
|
||||
underlings.delete(from[ACTORDATA].id)
|
||||
delete underlings[from[ACTORDATA].id]
|
||||
break
|
||||
case 'contact':
|
||||
if (portal_fn) {
|
||||
@@ -908,11 +718,11 @@ function handle_sysym(msg)
|
||||
letter2[HEADER] = msg
|
||||
delete msg.data
|
||||
portal_fn(letter2)
|
||||
} else throw new Error('Got a contact message, but no portal is established.')
|
||||
} else throw Error('Got a contact message, but no portal is established.')
|
||||
break
|
||||
case 'couple': // from must be notified when we die
|
||||
var from = msg.from
|
||||
underlings.add(from[ACTORDATA].id)
|
||||
from = msg.from
|
||||
underlings[from[ACTORDATA].id] = true
|
||||
log.system(`actor ${from} is coupled to me`)
|
||||
break
|
||||
}
|
||||
@@ -973,99 +783,34 @@ if (!locator) {
|
||||
}
|
||||
|
||||
if (!locator)
|
||||
throw new Error(`Main program ${_cell.args.program} could not be found`)
|
||||
throw Error(`Main program ${_cell.args.program} could not be found`)
|
||||
|
||||
// Hide JavaScript built-ins - make them inaccessible
|
||||
// Store references we need internally before deleting
|
||||
var _Object = Object
|
||||
var _Array = Array
|
||||
var _String = String
|
||||
var _Number = Number
|
||||
var _Boolean = Boolean
|
||||
var _Math = Math
|
||||
var _Function = Function
|
||||
|
||||
var _Error = Error
|
||||
var _JSON = JSON
|
||||
|
||||
// juicing these before Math is gone
|
||||
|
||||
use_core('math/radians')
|
||||
use_core('math/cycles')
|
||||
use_core('math/degrees')
|
||||
|
||||
// Delete from globalThis
|
||||
delete globalThis.Object
|
||||
delete globalThis.Math
|
||||
delete globalThis.Number
|
||||
delete globalThis.String
|
||||
delete globalThis.Array
|
||||
delete globalThis.Boolean
|
||||
delete globalThis.Date
|
||||
delete globalThis.Function
|
||||
delete globalThis.Reflect
|
||||
delete globalThis.Proxy
|
||||
delete globalThis.WeakMap
|
||||
delete globalThis.WeakSet
|
||||
delete globalThis.WeakRef
|
||||
delete globalThis.BigInt
|
||||
delete globalThis.Symbol
|
||||
//delete globalThis.Map
|
||||
//delete globalThis.Set
|
||||
delete globalThis.Promise
|
||||
delete globalThis.ArrayBuffer
|
||||
delete globalThis.DataView
|
||||
delete globalThis.Int8Array
|
||||
delete globalThis.Uint8Array
|
||||
delete globalThis.Uint8ClampedArray
|
||||
delete globalThis.Int16Array
|
||||
delete globalThis.Uint16Array
|
||||
delete globalThis.Int32Array
|
||||
delete globalThis.Uint32Array
|
||||
delete globalThis.Float32Array
|
||||
delete globalThis.Float64Array
|
||||
delete globalThis.BigInt64Array
|
||||
delete globalThis.BigUint64Array
|
||||
delete globalThis.eval
|
||||
delete globalThis.parseInt
|
||||
delete globalThis.parseFloat
|
||||
delete globalThis.isNaN
|
||||
delete globalThis.isFinite
|
||||
delete globalThis.decodeURI
|
||||
delete globalThis.decodeURIComponent
|
||||
delete globalThis.encodeURI
|
||||
delete globalThis.encodeURIComponent
|
||||
delete globalThis.escape
|
||||
delete globalThis.unescape
|
||||
delete globalThis.Intl
|
||||
delete globalThis.RegExp
|
||||
|
||||
_ObjectFreeze(globalThis)
|
||||
stone(globalThis)
|
||||
|
||||
$_.clock(_ => {
|
||||
// Get capabilities for the main program
|
||||
var file_info = shop.file_info ? shop.file_info(locator.path) : null
|
||||
var inject = shop.script_inject_for ? shop.script_inject_for(file_info) : []
|
||||
|
||||
// Build values array for injection
|
||||
var vals = []
|
||||
for (var i = 0; i < inject.length; i++) {
|
||||
// Build env object for injection
|
||||
var env = {}
|
||||
for (var i = 0; i < length(inject); i++) {
|
||||
var key = inject[i]
|
||||
if (key && key[0] == '$') key = key.substring(1)
|
||||
if (key == 'fd') vals.push(fd)
|
||||
else vals.push($_[key])
|
||||
if (key && key[0] == '$') key = text(key, 1)
|
||||
if (key == 'fd') env[key] = fd
|
||||
else env[key] = $_[key]
|
||||
}
|
||||
|
||||
// Create use function bound to the program's package
|
||||
var pkg = file_info ? file_info.package : null
|
||||
var use_fn = function(path) { return shop.use(path, pkg) }
|
||||
|
||||
// Call with signature: setup_module(args, use, ...capabilities)
|
||||
// The script wrapper builds $_ from the injected capabilities for backward compatibility
|
||||
var val = locator.symbol.call(null, _cell.args.arg, use_fn, ...vals)
|
||||
// Call with signature: setup_module(args, use, env)
|
||||
// The script wrapper binds $delay, $start, etc. from env
|
||||
var val = call(locator.symbol, null, [_cell.args.arg, use_fn, env])
|
||||
|
||||
if (val)
|
||||
throw new Error('Program must not return anything');
|
||||
throw Error('Program must not return anything');
|
||||
})
|
||||
|
||||
})()
|
||||
@@ -1,22 +0,0 @@
|
||||
/* fn.cm - function utilities */
|
||||
|
||||
var _apply = Function.prototype.apply
|
||||
var _isArray = Array.isArray
|
||||
|
||||
var fn = {}
|
||||
|
||||
fn.apply = function(func, args) {
|
||||
if (typeof func != 'function') return func
|
||||
|
||||
if (!_isArray(args)) {
|
||||
args = [args]
|
||||
}
|
||||
|
||||
if (args.length > func.length) {
|
||||
throw new Error("fn.apply: too many arguments")
|
||||
}
|
||||
|
||||
return _apply.call(func, null, args)
|
||||
}
|
||||
|
||||
return fn
|
||||
@@ -1,63 +0,0 @@
|
||||
#include "cell.h"
|
||||
|
||||
static JSValue js_json_encode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
|
||||
if (argc < 1) return JS_ThrowTypeError(ctx, "json.encode requires at least 1 argument");
|
||||
|
||||
JSValue global = JS_GetGlobalObject(ctx);
|
||||
JSValue json = JS_GetPropertyStr(ctx, global, "JSON");
|
||||
JSValue stringify = JS_GetPropertyStr(ctx, json, "stringify");
|
||||
|
||||
JSValue args[3];
|
||||
args[0] = argv[0]; // value
|
||||
args[1] = (argc > 1) ? argv[1] : JS_NULL; // replacer
|
||||
args[2] = (argc > 2) ? argv[2] : JS_NewInt32(ctx, 1); // space, default 1
|
||||
|
||||
JSValue result = JS_Call(ctx, stringify, json, 3, args);
|
||||
|
||||
JS_FreeValue(ctx, stringify);
|
||||
JS_FreeValue(ctx, json);
|
||||
JS_FreeValue(ctx, global);
|
||||
|
||||
if (argc <= 2) JS_FreeValue(ctx, args[2]);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static JSValue js_json_decode(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) {
|
||||
if (argc < 1) return JS_ThrowTypeError(ctx, "json.decode requires at least 1 argument");
|
||||
|
||||
if (!JS_IsString(argv[0])) {
|
||||
JSValue err = JS_NewError(ctx);
|
||||
JS_DefinePropertyValueStr(ctx, err, "message",
|
||||
JS_NewString(ctx, "couldn't parse text: not a string"),
|
||||
JS_PROP_WRITABLE | JS_PROP_CONFIGURABLE);
|
||||
return JS_Throw(ctx, err);
|
||||
}
|
||||
|
||||
JSValue global = JS_GetGlobalObject(ctx);
|
||||
JSValue json = JS_GetPropertyStr(ctx, global, "JSON");
|
||||
JSValue parse = JS_GetPropertyStr(ctx, json, "parse");
|
||||
|
||||
JSValue args[2];
|
||||
args[0] = argv[0]; // text
|
||||
args[1] = (argc > 1) ? argv[1] : JS_NULL; // reviver
|
||||
|
||||
JSValue result = JS_Call(ctx, parse, json, argc > 1 ? 2 : 1, args);
|
||||
|
||||
JS_FreeValue(ctx, parse);
|
||||
JS_FreeValue(ctx, json);
|
||||
JS_FreeValue(ctx, global);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static const JSCFunctionListEntry js_json_funcs[] = {
|
||||
JS_CFUNC_DEF("encode", 1, js_json_encode),
|
||||
JS_CFUNC_DEF("decode", 1, js_json_decode),
|
||||
};
|
||||
|
||||
JSValue js_json_use(JSContext *js) {
|
||||
JSValue export = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, export, js_json_funcs, sizeof(js_json_funcs)/sizeof(JSCFunctionListEntry));
|
||||
return export;
|
||||
}
|
||||
@@ -106,7 +106,7 @@ char *js_do_nota_decode(JSContext *js, JSValue *tmp, char *nota, JSValue holder,
|
||||
nota = js_do_nota_decode(js, &inner, nota, holder, JS_NULL, reviver);
|
||||
JSValue obj = JS_NewObject(js);
|
||||
cell_rt *crt = JS_GetContextOpaque(js);
|
||||
JS_SetProperty(js, obj, crt->actor_sym, inner);
|
||||
// JS_SetProperty(js, obj, crt->actor_sym, inner);
|
||||
*tmp = obj;
|
||||
} else {
|
||||
switch(b) {
|
||||
@@ -166,7 +166,7 @@ static void nota_encode_value(NotaEncodeContext *enc, JSValueConst val, JSValueC
|
||||
case JS_TAG_NULL:
|
||||
nota_write_sym(&enc->nb, NOTA_NULL);
|
||||
break;
|
||||
case JS_TAG_OBJECT: {
|
||||
case JS_TAG_PTR: {
|
||||
if (js_is_blob(ctx, replaced)) {
|
||||
size_t buf_len;
|
||||
void *buf_data = js_get_blob_data(ctx, &buf_len, replaced);
|
||||
@@ -178,7 +178,7 @@ static void nota_encode_value(NotaEncodeContext *enc, JSValueConst val, JSValueC
|
||||
break;
|
||||
}
|
||||
|
||||
if (JS_IsArray(ctx, replaced)) {
|
||||
if (JS_IsArray(replaced)) {
|
||||
if (nota_stack_has(enc, replaced)) {
|
||||
enc->cycle = 1;
|
||||
break;
|
||||
@@ -198,7 +198,8 @@ static void nota_encode_value(NotaEncodeContext *enc, JSValueConst val, JSValueC
|
||||
}
|
||||
|
||||
cell_rt *crt = JS_GetContextOpaque(ctx);
|
||||
JSValue adata = JS_GetProperty(ctx, replaced, crt->actor_sym);
|
||||
// JSValue adata = JS_GetProperty(ctx, replaced, crt->actor_sym);
|
||||
JSValue adata = JS_NULL;
|
||||
if (!JS_IsNull(adata)) {
|
||||
nota_write_sym(&enc->nb, NOTA_PRIVATE);
|
||||
nota_encode_value(enc, adata, replaced, JS_NULL);
|
||||
@@ -213,7 +214,7 @@ static void nota_encode_value(NotaEncodeContext *enc, JSValueConst val, JSValueC
|
||||
nota_stack_push(enc, replaced);
|
||||
|
||||
JSValue to_json = JS_GetPropertyStr(ctx, replaced, "toJSON");
|
||||
if (JS_IsFunction(ctx, to_json)) {
|
||||
if (JS_IsFunction(to_json)) {
|
||||
JSValue result = JS_Call(ctx, to_json, replaced, 0, NULL);
|
||||
JS_FreeValue(ctx, to_json);
|
||||
if (!JS_IsException(result)) {
|
||||
@@ -227,36 +228,44 @@ static void nota_encode_value(NotaEncodeContext *enc, JSValueConst val, JSValueC
|
||||
}
|
||||
JS_FreeValue(ctx, to_json);
|
||||
|
||||
JSPropertyEnum *ptab;
|
||||
uint32_t plen;
|
||||
if (JS_GetOwnPropertyNames(ctx, &ptab, &plen, replaced, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK) < 0) {
|
||||
JSValue keys = JS_GetOwnPropertyNames(ctx, replaced);
|
||||
if (JS_IsException(keys)) {
|
||||
nota_write_sym(&enc->nb, NOTA_NULL);
|
||||
nota_stack_pop(enc);
|
||||
break;
|
||||
}
|
||||
int64_t plen64;
|
||||
if (JS_GetLength(ctx, keys, &plen64) < 0) {
|
||||
JS_FreeValue(ctx, keys);
|
||||
nota_write_sym(&enc->nb, NOTA_NULL);
|
||||
nota_stack_pop(enc);
|
||||
break;
|
||||
}
|
||||
uint32_t plen = (uint32_t)plen64;
|
||||
|
||||
uint32_t non_function_count = 0;
|
||||
for (uint32_t i = 0; i < plen; i++) {
|
||||
JSValue prop_val = JS_GetProperty(ctx, replaced, ptab[i].atom);
|
||||
if (!JS_IsFunction(ctx, prop_val)) non_function_count++;
|
||||
JSValue key = JS_GetPropertyUint32(ctx, keys, i);
|
||||
JSValue prop_val = JS_GetProperty(ctx, replaced, key);
|
||||
if (!JS_IsFunction(prop_val)) non_function_count++;
|
||||
JS_FreeValue(ctx, prop_val);
|
||||
JS_FreeValue(ctx, key);
|
||||
}
|
||||
|
||||
nota_write_record(&enc->nb, non_function_count);
|
||||
for (uint32_t i = 0; i < plen; i++) {
|
||||
JSValue prop_val = JS_GetProperty(ctx, replaced, ptab[i].atom);
|
||||
if (!JS_IsFunction(ctx, prop_val)) {
|
||||
const char *prop_name = JS_AtomToCString(ctx, ptab[i].atom);
|
||||
JSValue prop_key = JS_AtomToValue(ctx, ptab[i].atom);
|
||||
nota_write_text(&enc->nb, prop_name);
|
||||
nota_encode_value(enc, prop_val, replaced, prop_key);
|
||||
JSValue key = JS_GetPropertyUint32(ctx, keys, i);
|
||||
JSValue prop_val = JS_GetProperty(ctx, replaced, key);
|
||||
if (!JS_IsFunction(prop_val)) {
|
||||
const char *prop_name = JS_ToCString(ctx, key);
|
||||
nota_write_text(&enc->nb, prop_name ? prop_name : "");
|
||||
nota_encode_value(enc, prop_val, replaced, key);
|
||||
JS_FreeCString(ctx, prop_name);
|
||||
JS_FreeValue(ctx, prop_key);
|
||||
}
|
||||
JS_FreeValue(ctx, prop_val);
|
||||
JS_FreeAtom(ctx, ptab[i].atom);
|
||||
JS_FreeValue(ctx, key);
|
||||
}
|
||||
js_free(ctx, ptab);
|
||||
JS_FreeValue(ctx, keys);
|
||||
nota_stack_pop(enc);
|
||||
break;
|
||||
}
|
||||
@@ -337,7 +346,7 @@ static JSValue js_nota_encode(JSContext *ctx, JSValueConst this_val, int argc, J
|
||||
enc->ctx = ctx;
|
||||
enc->visitedStack = JS_NewArray(ctx);
|
||||
enc->cycle = 0;
|
||||
enc->replacer = (argc > 1 && JS_IsFunction(ctx, argv[1])) ? argv[1] : JS_NULL;
|
||||
enc->replacer = (argc > 1 && JS_IsFunction(argv[1])) ? argv[1] : JS_NULL;
|
||||
|
||||
nota_buffer_init(&enc->nb, 128);
|
||||
nota_encode_value(enc, argv[0], JS_NULL, JS_NewString(ctx, ""));
|
||||
@@ -365,7 +374,7 @@ static JSValue js_nota_decode(JSContext *js, JSValueConst self, int argc, JSValu
|
||||
if (nota == -1) return JS_EXCEPTION;
|
||||
if (!nota) return JS_NULL;
|
||||
|
||||
JSValue reviver = (argc > 1 && JS_IsFunction(js, argv[1])) ? argv[1] : JS_NULL;
|
||||
JSValue reviver = (argc > 1 && JS_IsFunction(argv[1])) ? argv[1] : JS_NULL;
|
||||
JSValue ret;
|
||||
JSValue holder = JS_NewObject(js);
|
||||
js_do_nota_decode(js, &ret, (char*)nota, holder, JS_NewString(js, ""), reviver);
|
||||
|
||||
@@ -1,169 +0,0 @@
|
||||
/* number.cm - number conversion and math utilities */
|
||||
var _floor = Math.floor
|
||||
var _ceil = Math.ceil
|
||||
var _round = Math.round
|
||||
var _abs = Math.abs
|
||||
var _trunc = Math.trunc
|
||||
var _min = Math.min
|
||||
var _max = Math.max
|
||||
var _pow = Math.pow
|
||||
var _parseInt = parseInt
|
||||
var _parseFloat = parseFloat
|
||||
|
||||
function number(val, format) {
|
||||
if (val == true) return 1
|
||||
if (val == false) return 0
|
||||
|
||||
if (typeof val == 'number') return val
|
||||
|
||||
if (typeof val == 'string') {
|
||||
if (typeof format == 'number') {
|
||||
// radix conversion
|
||||
if (format < 2 || format > 36) return null
|
||||
var result = _parseInt(val, format)
|
||||
if (isNaN(result)) return null
|
||||
return result
|
||||
}
|
||||
|
||||
if (typeof format == 'string') {
|
||||
return parse_formatted(val, format)
|
||||
}
|
||||
|
||||
// default: parse as decimal
|
||||
var result = _parseFloat(val)
|
||||
if (!isa(result, number)) return null
|
||||
return result
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
function parse_formatted(str, format) {
|
||||
if (!format || format == "" || format == "n") {
|
||||
var result = _parseFloat(str)
|
||||
if (isNaN(result)) return null
|
||||
return result
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case "u": // underbar separator
|
||||
str = str.split('_').join('')
|
||||
break
|
||||
case "d": // comma separator
|
||||
str = str.split(',').join('')
|
||||
break
|
||||
case "s": // space separator
|
||||
str = str.split(' ').join('')
|
||||
break
|
||||
case "v": // European style: period separator, comma decimal
|
||||
str = str.split('.').join('')
|
||||
str = str.replace(',', '.')
|
||||
break
|
||||
case "l": // locale - treat like 'd' for now
|
||||
str = str.split(',').join('')
|
||||
break
|
||||
case "i": // integer with underbar
|
||||
str = str.split('_').join('')
|
||||
break
|
||||
case "b": // binary
|
||||
return _parseInt(str, 2)
|
||||
case "o": // octal
|
||||
return _parseInt(str, 8)
|
||||
case "h": // hex
|
||||
return _parseInt(str, 16)
|
||||
case "t": // base32
|
||||
return _parseInt(str, 32)
|
||||
case "j": // JavaScript style prefix
|
||||
if (str.startsWith('0x') || str.startsWith('0X'))
|
||||
return _parseInt(str.slice(2), 16)
|
||||
if (str.startsWith('0o') || str.startsWith('0O'))
|
||||
return _parseInt(str.slice(2), 8)
|
||||
if (str.startsWith('0b') || str.startsWith('0B'))
|
||||
return _parseInt(str.slice(2), 2)
|
||||
return _parseFloat(str)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
||||
var result = _parseFloat(str)
|
||||
if (isNaN(result)) return null
|
||||
return result
|
||||
}
|
||||
|
||||
number.whole = function(n) {
|
||||
if (typeof n != 'number') return null
|
||||
return _trunc(n)
|
||||
}
|
||||
|
||||
number.fraction = function(n) {
|
||||
if (typeof n != 'number') return null
|
||||
return n - _trunc(n)
|
||||
}
|
||||
|
||||
number.floor = function(n, place) {
|
||||
if (typeof n != 'number') return null
|
||||
if (place == null || place == 0) return _floor(n)
|
||||
var mult = _pow(10, place)
|
||||
return _floor(n * mult) / mult
|
||||
}
|
||||
|
||||
number.ceiling = function(n, place) {
|
||||
if (typeof n != 'number') return null
|
||||
if (place == null || place == 0) return _ceil(n)
|
||||
var mult = _pow(10, place)
|
||||
return _ceil(n * mult) / mult
|
||||
}
|
||||
|
||||
number.abs = function(n) {
|
||||
if (typeof n != 'number') return null
|
||||
return _abs(n)
|
||||
}
|
||||
|
||||
number.round = function(n, place) {
|
||||
if (typeof n != 'number') return null
|
||||
if (place == null || place == 0) return _round(n)
|
||||
var mult = _pow(10, place)
|
||||
return _round(n * mult) / mult
|
||||
}
|
||||
|
||||
number.sign = function(n) {
|
||||
if (typeof n != 'number') return null
|
||||
if (n < 0) return -1
|
||||
if (n > 0) return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
number.trunc = function(n, place) {
|
||||
if (typeof n != 'number') return null
|
||||
if (place == null || place == 0) return _trunc(n)
|
||||
var mult = _pow(10, place)
|
||||
return _trunc(n * mult) / mult
|
||||
}
|
||||
|
||||
number.min = function(...vals) {
|
||||
if (vals.length == 0) return null
|
||||
var result = vals[0]
|
||||
for (var i = 1; i < vals.length; i++) {
|
||||
if (typeof vals[i] != 'number') return null
|
||||
if (vals[i] < result) result = vals[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
number.max = function(...vals) {
|
||||
if (vals.length == 0) return null
|
||||
var result = vals[0]
|
||||
for (var i = 1; i < vals.length; i++) {
|
||||
if (typeof vals[i] != 'number') return null
|
||||
if (vals[i] > result) result = vals[i]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
number.remainder = function(dividend, divisor) {
|
||||
if (typeof dividend != 'number' || typeof divisor != 'number') return null
|
||||
if (divisor == 0) return null
|
||||
return dividend - (_trunc(dividend / divisor) * divisor)
|
||||
}
|
||||
|
||||
return number
|
||||
@@ -1,93 +0,0 @@
|
||||
/* object.cm - object creation and manipulation utilities */
|
||||
|
||||
var _keys = array
|
||||
var _create = meme
|
||||
var _assign = Object.assign
|
||||
var _isArray = function(val) { return isa(val, array) }
|
||||
var _values = Object.values
|
||||
|
||||
function object(arg, arg2) {
|
||||
// object(object) - shallow mutable copy
|
||||
if (isa(arg, object) && arg2 == null) {
|
||||
var result = {}
|
||||
var keys = _keys(arg)
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
result[keys[i]] = arg[keys[i]]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// object(object, another_object) - combine
|
||||
if (isa(arg, object) && isa(arg2, object)) {
|
||||
var result = {}
|
||||
var keys = _keys(arg)
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
result[keys[i]] = arg[keys[i]]
|
||||
}
|
||||
keys = _keys(arg2)
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
result[keys[i]] = arg2[keys[i]]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// object(object, array_of_keys) - select
|
||||
if (isa(arg, object) && _isArray(arg2)) {
|
||||
var result = {}
|
||||
for (var i = 0; i < arg2.length; i++) {
|
||||
var key = arg2[i]
|
||||
if (typeof key == 'string' && key in arg) {
|
||||
result[key] = arg[key]
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// object(array_of_keys) - set with true values
|
||||
if (_isArray(arg) && arg2 == null) {
|
||||
var result = {}
|
||||
for (var i = 0; i < arg.length; i++) {
|
||||
var key = arg[i]
|
||||
if (typeof key == 'string') {
|
||||
result[key] = true
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// object(array_of_keys, value) - value set
|
||||
// object(array_of_keys, function) - functional value set
|
||||
if (_isArray(arg) && arg2 != null) {
|
||||
var result = {}
|
||||
if (typeof arg2 == 'function') {
|
||||
for (var i = 0; i < arg.length; i++) {
|
||||
var key = arg[i]
|
||||
if (typeof key == 'string') {
|
||||
result[key] = arg2(key)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (var i = 0; i < arg.length; i++) {
|
||||
var key = arg[i]
|
||||
if (typeof key == 'string') {
|
||||
result[key] = arg2
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
object.values = function(obj)
|
||||
{
|
||||
return _values(obj)
|
||||
}
|
||||
|
||||
object.assign = function(obj, ...args)
|
||||
{
|
||||
return _assign(obj, ...args)
|
||||
}
|
||||
|
||||
return object
|
||||
430
internal/shop.cm
430
internal/shop.cm
@@ -1,4 +1,5 @@
|
||||
var toml = use('toml')
|
||||
|
||||
var json = use('json')
|
||||
var fd = use('fd')
|
||||
var http = use('http')
|
||||
@@ -6,8 +7,8 @@ var miniz = use('miniz')
|
||||
var time = use('time')
|
||||
var js = use('js')
|
||||
var crypto = use('crypto')
|
||||
var utf8 = use('utf8')
|
||||
var blob = use('blob')
|
||||
|
||||
var pkg_tools = use('package')
|
||||
var os = use('os')
|
||||
var link = use('link')
|
||||
@@ -29,9 +30,9 @@ function put_into_cache(content, obj)
|
||||
|
||||
function ensure_dir(path) {
|
||||
if (fd.stat(path).isDirectory) return
|
||||
var parts = path.split('/')
|
||||
var current = path.startsWith('/') ? '/' : ''
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var parts = array(path, '/')
|
||||
var current = starts_with(path, '/') ? '/' : ''
|
||||
for (var i = 0; i < length(parts); i++) {
|
||||
if (parts[i] == '') continue
|
||||
current += parts[i] + '/'
|
||||
if (!fd.stat(current).isDirectory) {
|
||||
@@ -63,7 +64,7 @@ var dylib_ext = '.dylib' // Default extension
|
||||
|
||||
var use_cache = os.use_cache
|
||||
var global_shop_path = os.global_shop_path
|
||||
var $_ = os.$_
|
||||
var my$_ = os.$_
|
||||
|
||||
Shop.get_package_dir = function(name) {
|
||||
return global_shop_path + '/packages/' + name
|
||||
@@ -92,8 +93,8 @@ Shop.get_reports_dir = function() {
|
||||
}
|
||||
|
||||
function get_import_package(name) {
|
||||
var parts = name.split('/')
|
||||
if (parts.length > 1)
|
||||
var parts = array(name, '/')
|
||||
if (length(parts) > 1)
|
||||
return parts[0]
|
||||
|
||||
return null
|
||||
@@ -101,24 +102,24 @@ function get_import_package(name) {
|
||||
|
||||
function is_internal_path(path)
|
||||
{
|
||||
return path && path.startsWith('internal/')
|
||||
return path && starts_with(path, 'internal/')
|
||||
}
|
||||
|
||||
function split_explicit_package_import(path)
|
||||
{
|
||||
if (!path) return null
|
||||
var parts = path.split('/')
|
||||
var parts = array(path, '/')
|
||||
|
||||
if (parts.length < 2) return null
|
||||
if (length(parts) < 2) return null
|
||||
|
||||
var looks_explicit = path.startsWith('/') || (parts[0] && parts[0].includes('.'))
|
||||
var looks_explicit = starts_with(path, '/') || (parts[0] && search(parts[0], '.') != null)
|
||||
if (!looks_explicit) return null
|
||||
|
||||
// Find the longest prefix that is an installed package
|
||||
for (var i = parts.length - 1; i >= 1; i--) {
|
||||
var pkg_candidate = parts.slice(0, i).join('/')
|
||||
var mod_path = parts.slice(i).join('/')
|
||||
if (!mod_path || mod_path.length == 0) continue
|
||||
for (var i = length(parts) - 1; i >= 1; i--) {
|
||||
var pkg_candidate = text(array(parts, 0, i), '/')
|
||||
var mod_path = text(array(parts, i), '/')
|
||||
if (!mod_path || length(mod_path) == 0) continue
|
||||
|
||||
var candidate_dir = get_packages_dir() + '/' + safe_package_path(pkg_candidate)
|
||||
if (fd.is_file(candidate_dir + '/cell.toml'))
|
||||
@@ -142,7 +143,7 @@ function package_in_shop(package) {
|
||||
function abs_path_to_package(package_dir)
|
||||
{
|
||||
if (!fd.is_file(package_dir + '/cell.toml'))
|
||||
throw new Error('Not a valid package directory (no cell.toml): ' + package_dir)
|
||||
throw Error('Not a valid package directory (no cell.toml): ' + package_dir)
|
||||
|
||||
var packages_prefix = get_packages_dir() + '/'
|
||||
var core_dir = packages_prefix + core_package
|
||||
@@ -159,8 +160,15 @@ function abs_path_to_package(package_dir)
|
||||
}
|
||||
}
|
||||
|
||||
if (package_dir.startsWith(packages_prefix))
|
||||
return package_dir.substring(packages_prefix.length)
|
||||
if (starts_with(package_dir, packages_prefix))
|
||||
return text(package_dir, length(packages_prefix))
|
||||
|
||||
// Check if this local path is the target of a link
|
||||
// If so, return the canonical package name (link origin) instead
|
||||
var link_origin = link.get_origin(package_dir)
|
||||
if (link_origin) {
|
||||
return link_origin
|
||||
}
|
||||
|
||||
// in this case, the dir is the package
|
||||
if (package_in_shop(package_dir))
|
||||
@@ -189,9 +197,9 @@ Shop.file_info = function(file) {
|
||||
name: null
|
||||
}
|
||||
|
||||
if (file.endsWith(MOD_EXT))
|
||||
if (ends_with(file, MOD_EXT))
|
||||
info.is_module = true
|
||||
else if (file.endsWith(ACTOR_EXT))
|
||||
else if (ends_with(file, ACTOR_EXT))
|
||||
info.is_actor = true
|
||||
|
||||
// Find package directory and determine package name
|
||||
@@ -200,11 +208,11 @@ Shop.file_info = function(file) {
|
||||
info.package = abs_path_to_package(pkg_dir)
|
||||
|
||||
if (info.is_actor)
|
||||
info.name = file.substring(pkg_dir.length + 1, file.length - ACTOR_EXT.length)
|
||||
info.name = text(file, length(pkg_dir) + 1, length(file) - length(ACTOR_EXT))
|
||||
else if (info.is_module)
|
||||
info.name = file.substring(pkg_dir.length + 1, file.length - MOD_EXT.length)
|
||||
info.name = text(file, length(pkg_dir) + 1, length(file) - length(MOD_EXT))
|
||||
else
|
||||
info.name = file.substring(pkg_dir.length + 1)
|
||||
info.name = text(file, length(pkg_dir) + 1)
|
||||
}
|
||||
|
||||
return info
|
||||
@@ -212,9 +220,9 @@ Shop.file_info = function(file) {
|
||||
|
||||
function get_import_name(path)
|
||||
{
|
||||
var parts = path.split('/')
|
||||
if (parts.length < 2) return null
|
||||
return parts.slice(1).join('/')
|
||||
var parts = array(path, '/')
|
||||
if (length(parts) < 2) return null
|
||||
return text(array(parts, 1), '/')
|
||||
}
|
||||
|
||||
// Given a path like 'prosperon/sprite' and a package context,
|
||||
@@ -240,14 +248,14 @@ function safe_package_path(pkg)
|
||||
{
|
||||
// For absolute paths, replace / with _ to create a valid directory name
|
||||
// Also replace @ with _
|
||||
if (pkg && pkg.startsWith('/'))
|
||||
return pkg.replaceAll('/', '_').replaceAll('@', '_')
|
||||
return pkg.replaceAll('@', '_')
|
||||
if (pkg && starts_with(pkg, '/'))
|
||||
return replace(replace(pkg, '/', '_'), '@', '_')
|
||||
return replace(pkg, '@', '_')
|
||||
}
|
||||
|
||||
function package_cache_path(pkg)
|
||||
{
|
||||
return global_shop_path + '/cache/' + pkg.replaceAll('/', '_').replaceAll('@', '_')
|
||||
return global_shop_path + '/cache/' + replace(replace(pkg, '/', '_'), '@', '_')
|
||||
}
|
||||
|
||||
function get_shared_lib_path()
|
||||
@@ -267,7 +275,7 @@ Shop.load_lock = function() {
|
||||
return {}
|
||||
|
||||
var content = text(fd.slurp(path))
|
||||
if (!content.length) return {}
|
||||
if (!length(content)) return {}
|
||||
|
||||
_lock = toml.decode(content)
|
||||
|
||||
@@ -277,26 +285,26 @@ Shop.load_lock = function() {
|
||||
// Save lock.toml configuration (to global shop)
|
||||
Shop.save_lock = function(lock) {
|
||||
var path = global_shop_path + '/lock.toml'
|
||||
fd.slurpwrite(path, utf8.encode(toml.encode(lock)));
|
||||
fd.slurpwrite(path, stone(blob(toml.encode(lock))));
|
||||
}
|
||||
|
||||
|
||||
// Get information about how to resolve a package
|
||||
// Local packages always start with /
|
||||
Shop.resolve_package_info = function(pkg) {
|
||||
if (pkg.startsWith('/')) return 'local'
|
||||
if (pkg.includes('gitea')) return 'gitea'
|
||||
if (starts_with(pkg, '/')) return 'local'
|
||||
if (search(pkg, 'gitea') != null) return 'gitea'
|
||||
return null
|
||||
}
|
||||
|
||||
// Verify if a package name is valid and return status
|
||||
Shop.verify_package_name = function(pkg) {
|
||||
if (!pkg) throw new Error("Empty package name")
|
||||
if (pkg == 'local') throw new Error("local is not a valid package name")
|
||||
if (pkg == 'core') throw new Error("core is not a valid package name")
|
||||
if (!pkg) throw Error("Empty package name")
|
||||
if (pkg == 'local') throw Error("local is not a valid package name")
|
||||
if (pkg == 'core') throw Error("core is not a valid package name")
|
||||
|
||||
if (pkg.includes('://'))
|
||||
throw new Error(`Invalid package name: ${pkg}; did you mean ${pkg.split('://')[1]}?`)
|
||||
if (search(pkg, '://') != null)
|
||||
throw Error(`Invalid package name: ${pkg}; did you mean ${array(pkg, '://')[1]}?`)
|
||||
}
|
||||
|
||||
// Convert module package to download URL
|
||||
@@ -304,7 +312,7 @@ Shop.get_download_url = function(pkg, commit_hash) {
|
||||
var info = Shop.resolve_package_info(pkg)
|
||||
|
||||
if (info == 'gitea') {
|
||||
var parts = pkg.split('/')
|
||||
var parts = array(pkg, '/')
|
||||
var host = parts[0]
|
||||
var user = parts[1]
|
||||
var repo = parts[2]
|
||||
@@ -320,7 +328,7 @@ Shop.get_api_url = function(pkg) {
|
||||
var info = Shop.resolve_package_info(pkg)
|
||||
|
||||
if (info == 'gitea') {
|
||||
var parts = pkg.split('/')
|
||||
var parts = array(pkg, '/')
|
||||
var host = parts[0]
|
||||
var user = parts[1]
|
||||
var repo = parts[2]
|
||||
@@ -339,7 +347,7 @@ Shop.extract_commit_hash = function(pkg, response) {
|
||||
var data = json.decode(response)
|
||||
|
||||
if (info == 'gitea') {
|
||||
if (isa(data, array))
|
||||
if (is_array(data))
|
||||
data = data[0]
|
||||
return data.commit && data.commit.id
|
||||
}
|
||||
@@ -354,11 +362,6 @@ var open_dls = {}
|
||||
// These map to $_ properties in engine.cm
|
||||
var SHOP_DEFAULT_INJECT = ['$self', '$overling', '$clock', '$delay', '$start', '$receiver', '$contact', '$portal', '$time_limit', '$couple', '$stop', '$unneeded', '$connection', '$fd']
|
||||
|
||||
function strip_dollar(name) {
|
||||
if (name && name[0] == '$') return name.substring(1)
|
||||
return name
|
||||
}
|
||||
|
||||
// Decide what a given module is allowed to see.
|
||||
// This is the capability gate - tweak as needed.
|
||||
Shop.script_inject_for = function(file_info) {
|
||||
@@ -375,19 +378,25 @@ Shop.get_script_capabilities = function(path) {
|
||||
return Shop.script_inject_for(file_info)
|
||||
}
|
||||
|
||||
function inject_params(inject) {
|
||||
if (!inject || !inject.length) return ''
|
||||
return ', ' + inject.join(', ')
|
||||
function inject_env(inject) {
|
||||
var env = {}
|
||||
for (var i = 0; i < length(inject); i++) {
|
||||
var inj = inject[i]
|
||||
var key = trim(inj, '$')
|
||||
if (key == 'fd') env[key] = fd
|
||||
else env[key] = my$_[key]
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
function inject_values(inject) {
|
||||
var vals = []
|
||||
for (var i = 0; i < inject.length; i++) {
|
||||
var key = strip_dollar(inject[i])
|
||||
if (key == 'fd') vals.push(fd)
|
||||
else vals.push($_[key])
|
||||
function inject_bindings_code(inject) {
|
||||
var lines = []
|
||||
for (var i = 0; i < length(inject); i++) {
|
||||
var inj = inject[i]
|
||||
var key = trim(inj, '$')
|
||||
push(lines, `var $${key} = env["${key}"];`)
|
||||
}
|
||||
return vals
|
||||
return text(lines, '\n')
|
||||
}
|
||||
|
||||
// Build the use function for a specific package context
|
||||
@@ -398,32 +407,21 @@ function make_use_fn_code(pkg_arg) {
|
||||
// for script forms, path is the canonical path of the module
|
||||
var script_form = function(path, script, pkg, inject) {
|
||||
var pkg_arg = pkg ? `'${pkg}'` : 'null'
|
||||
var params = inject_params(inject)
|
||||
var binds = inject_bindings_code(inject)
|
||||
|
||||
// Build $_ object from injected capabilities for backward compatibility
|
||||
var build_compat = ''
|
||||
if (inject && inject.length) {
|
||||
var compat_props = []
|
||||
for (var i = 0; i < inject.length; i++) {
|
||||
var name = inject[i]
|
||||
var key = name
|
||||
if (key && key[0] == '$') key = key.substring(1)
|
||||
compat_props.push(key + ': ' + name)
|
||||
}
|
||||
build_compat = 'var $_ = {' + compat_props.join(', ') + '};'
|
||||
}
|
||||
|
||||
// use is passed as a parameter, not on globalThis for the script
|
||||
// $fd is injected as a capability, but we still allow use('fd') for now
|
||||
// $_ is built from injected capabilities for backward compatibility
|
||||
var fn = `(function setup_module(args, use${params}){ def arg = args; def PACKAGE = ${pkg_arg}; ${build_compat} ${script}})`
|
||||
var fn = `(function setup_module(args, use, env){
|
||||
def arg = args;
|
||||
def PACKAGE = ${pkg_arg};
|
||||
${binds}
|
||||
${script}
|
||||
})`
|
||||
return fn
|
||||
}
|
||||
|
||||
// Resolve module function, hashing it in the process
|
||||
// path is the exact path to the script file
|
||||
function resolve_mod_fn(path, pkg) {
|
||||
if (!fd.is_file(path)) throw new Error(`path ${path} is not a file`)
|
||||
if (!fd.is_file(path)) throw Error(`path ${path} is not a file`)
|
||||
|
||||
var file_info = Shop.file_info(path)
|
||||
var file_pkg = file_info.package
|
||||
@@ -431,7 +429,7 @@ function resolve_mod_fn(path, pkg) {
|
||||
var content = text(fd.slurp(path))
|
||||
var script = script_form(path, content, file_pkg, inject);
|
||||
|
||||
var obj = pull_from_cache(utf8.encode(script))
|
||||
var obj = pull_from_cache(stone(blob(script)))
|
||||
if (obj) {
|
||||
var fn = js.compile_unblob(obj)
|
||||
return js.eval_compile(fn)
|
||||
@@ -443,7 +441,7 @@ function resolve_mod_fn(path, pkg) {
|
||||
|
||||
var fn = js.compile(compile_name, script)
|
||||
|
||||
put_into_cache(utf8.encode(script), js.compile_blob(fn))
|
||||
put_into_cache(stone(blob(script)), js.compile_blob(fn))
|
||||
|
||||
return js.eval_compile(fn)
|
||||
}
|
||||
@@ -480,7 +478,7 @@ function resolve_locator(path, ctx)
|
||||
// If ctx is an absolute path (starts with /), use it directly
|
||||
// Otherwise, look it up in the packages directory
|
||||
var ctx_dir
|
||||
if (ctx.startsWith('/')) {
|
||||
if (starts_with(ctx, '/')) {
|
||||
ctx_dir = ctx
|
||||
} else {
|
||||
ctx_dir = get_packages_dir() + '/' + safe_package_path(ctx)
|
||||
@@ -527,25 +525,17 @@ function resolve_locator(path, ctx)
|
||||
|
||||
// Generate symbol name for a C module file
|
||||
// Uses the same format as Shop.c_symbol_for_file
|
||||
// Resolves linked packages to their actual target first
|
||||
// Symbol names are based on canonical package names, not link targets
|
||||
function make_c_symbol(pkg, file) {
|
||||
// Check if this package is linked - if so, use the link target for symbol name
|
||||
var link_target = link.get_target(pkg)
|
||||
var resolved_pkg = link_target ? link_target : pkg
|
||||
|
||||
var pkg_safe = resolved_pkg.replace(/\//g, '_').replace(/\./g, '_').replace(/-/g, '_')
|
||||
var file_safe = file.replace(/\//g, '_').replace(/\./g, '_').replace(/-/g, '_')
|
||||
var pkg_safe = replace(replace(replace(pkg, '/', '_'), '.', '_'), '-', '_')
|
||||
var file_safe = replace(replace(replace(file, '/', '_'), '.', '_'), '-', '_')
|
||||
return 'js_' + pkg_safe + '_' + file_safe + '_use'
|
||||
}
|
||||
|
||||
// Get the library path for a package in .cell/lib
|
||||
// Resolves linked packages to their actual target first
|
||||
// Library names are based on canonical package names, not link targets
|
||||
function get_lib_path(pkg) {
|
||||
// Check if this package is linked - if so, use the link target
|
||||
var link_target = link.get_target(pkg)
|
||||
var resolved_pkg = link_target ? link_target : pkg
|
||||
|
||||
var lib_name = resolved_pkg.replace(/\//g, '_').replace(/\./g, '_').replace(/-/g, '_')
|
||||
var lib_name = replace(replace(replace(pkg, '/', '_'), '.', '_'), '-', '_')
|
||||
return global_shop_path + '/lib/' + lib_name + dylib_ext
|
||||
}
|
||||
|
||||
@@ -559,7 +549,7 @@ Shop.open_package_dylib = function(pkg) {
|
||||
var resolved_pkg = link_target ? link_target : pkg
|
||||
|
||||
var pkg_dir;
|
||||
if (resolved_pkg.startsWith('/')) {
|
||||
if (starts_with(resolved_pkg, '/')) {
|
||||
pkg_dir = resolved_pkg
|
||||
} else {
|
||||
pkg_dir = get_packages_dir() + '/' + safe_package_path(resolved_pkg)
|
||||
@@ -571,20 +561,29 @@ Shop.open_package_dylib = function(pkg) {
|
||||
var content = text(fd.slurp(toml_path))
|
||||
var cfg = toml.decode(content)
|
||||
if (cfg.dependencies) {
|
||||
for (var alias in cfg.dependencies) {
|
||||
arrfor(array(cfg.dependencies), function(alias, i) {
|
||||
var dep_pkg = cfg.dependencies[alias]
|
||||
Shop.open_package_dylib(dep_pkg)
|
||||
}
|
||||
try {
|
||||
Shop.open_package_dylib(dep_pkg)
|
||||
} catch (dep_e) {
|
||||
// Dependency dylib load failed, continue with others
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore errors reading cell.toml
|
||||
// Error reading toml, continue
|
||||
}
|
||||
}
|
||||
|
||||
var dl_path = get_lib_path(pkg)
|
||||
if (fd.is_file(dl_path)) {
|
||||
if (!open_dls[dl_path]) {
|
||||
open_dls[dl_path] = os.dylib_open(dl_path)
|
||||
try {
|
||||
open_dls[dl_path] = os.dylib_open(dl_path)
|
||||
} catch (e) {
|
||||
dylib_visited[pkg] = false
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -592,20 +591,19 @@ Shop.open_package_dylib = function(pkg) {
|
||||
// Resolve a C symbol by searching:
|
||||
// 1. If package_context is null, only check core internal symbols
|
||||
// 2. Otherwise: own package (internal then dylib) -> other packages (internal then dylib) -> core (internal only)
|
||||
// Core is never loaded as a dynamic library via dlopen
|
||||
function resolve_c_symbol(path, package_context)
|
||||
{
|
||||
var explicit = split_explicit_package_import(path)
|
||||
if (explicit) {
|
||||
if (is_internal_path(explicit.path) && package_context && explicit.package != package_context)
|
||||
explicit = null
|
||||
}
|
||||
if (explicit) {
|
||||
var sym = make_c_symbol(explicit.package, explicit.path)
|
||||
if (os.internal_exists(sym)) {
|
||||
return {
|
||||
symbol: function() { return os.load_internal(sym) },
|
||||
scope: SCOPE_PACKAGE,
|
||||
// Core is never loaded as a dynamic library via dlopen
|
||||
function resolve_c_symbol(path, package_context) {
|
||||
var explicit = split_explicit_package_import(path)
|
||||
if (explicit) {
|
||||
if (is_internal_path(explicit.path) && package_context && explicit.package != package_context)
|
||||
explicit = null
|
||||
}
|
||||
if (explicit) {
|
||||
var sym = make_c_symbol(explicit.package, explicit.path)
|
||||
if (os.internal_exists(sym)) {
|
||||
return {
|
||||
symbol: function() { return os.load_internal(sym) },
|
||||
scope: SCOPE_PACKAGE,
|
||||
package: explicit.package,
|
||||
path: sym
|
||||
}
|
||||
@@ -624,7 +622,8 @@ Shop.open_package_dylib = function(pkg) {
|
||||
}
|
||||
|
||||
// If no package context, only check core internal symbols
|
||||
if (!package_context) {
|
||||
if (!package_context || package_context == 'core') {
|
||||
path = replace(path, '/', '_')
|
||||
var core_sym = `js_${path}_use`
|
||||
if (os.internal_exists(core_sym)) {
|
||||
return {
|
||||
@@ -693,7 +692,7 @@ Shop.open_package_dylib = function(pkg) {
|
||||
}
|
||||
|
||||
// 3. Check core internal symbols (core is never a dynamic library)
|
||||
var core_sym = `js_${path}_use`
|
||||
var core_sym = `js_${replace(path, '/', '_')}_use`
|
||||
if (os.internal_exists(core_sym)) {
|
||||
return {
|
||||
symbol: function() { return os.load_internal(core_sym) },
|
||||
@@ -716,7 +715,7 @@ function resolve_module_info(path, package_context) {
|
||||
|
||||
var c_resolve = resolve_c_symbol(path, package_context) || {scope:999}
|
||||
var mod_resolve = resolve_locator(path + '.cm', package_context) || {scope:999}
|
||||
var min_scope = number.min(c_resolve.scope, mod_resolve.scope)
|
||||
var min_scope = min(c_resolve.scope, mod_resolve.scope)
|
||||
|
||||
if (min_scope == 999)
|
||||
return null
|
||||
@@ -783,6 +782,14 @@ function make_use_fn(pkg) {
|
||||
}
|
||||
}
|
||||
|
||||
// Call a C module loader and execute the entrypoint
|
||||
function call_c_module(c_resolve) {
|
||||
var mod = c_resolve.symbol()
|
||||
// if (is_function(mod))
|
||||
// return mod()
|
||||
return mod
|
||||
}
|
||||
|
||||
function execute_module(info)
|
||||
{
|
||||
var c_resolve = info.c_resolve
|
||||
@@ -793,26 +800,31 @@ function execute_module(info)
|
||||
if (mod_resolve.scope < 900) {
|
||||
var context = null
|
||||
if (c_resolve.scope < 900) {
|
||||
context = c_resolve.symbol(null, $_)
|
||||
context = call_c_module(c_resolve)
|
||||
}
|
||||
|
||||
// Get file info to determine inject list
|
||||
var file_info = Shop.file_info(mod_resolve.path)
|
||||
var inject = Shop.script_inject_for(file_info)
|
||||
var vals = inject_values(inject)
|
||||
var env = inject_env(inject)
|
||||
var pkg = file_info.package
|
||||
var use_fn = make_use_fn(pkg)
|
||||
|
||||
// Call with signature: setup_module(args, use, ...capabilities)
|
||||
// Call with signature: setup_module(args, use, env)
|
||||
// args is null for module loading
|
||||
used = mod_resolve.symbol.call(context, null, use_fn, ...vals)
|
||||
used = call(mod_resolve.symbol, context, [null, use_fn, env])
|
||||
} else if (c_resolve.scope < 900) {
|
||||
// C only
|
||||
used = c_resolve.symbol(null, $_)
|
||||
used = call_c_module(c_resolve)
|
||||
} else {
|
||||
throw new Error(`Module ${info.path} could not be found`)
|
||||
} if (!used)
|
||||
throw new Error(`Module ${info} returned null`)
|
||||
throw Error(`Module ${info.path} could not be found`)
|
||||
}
|
||||
|
||||
// if (is_function(used))
|
||||
// throw Error('C module loader returned a function; did you forget to call it?')
|
||||
|
||||
if (!used)
|
||||
throw Error(`Module ${info} returned null`)
|
||||
|
||||
// stone(used)
|
||||
return used
|
||||
@@ -822,7 +834,7 @@ function get_module(path, package_context) {
|
||||
var info = resolve_module_info(path, package_context)
|
||||
|
||||
if (!info)
|
||||
throw new Error(`Module ${path} could not be found in ${package_context}`)
|
||||
throw Error(`Module ${path} could not be found in ${package_context}`)
|
||||
|
||||
return execute_module(info)
|
||||
}
|
||||
@@ -830,7 +842,7 @@ function get_module(path, package_context) {
|
||||
Shop.use = function use(path, package_context) {
|
||||
var info = resolve_module_info(path, package_context)
|
||||
if (!info)
|
||||
throw new Error(`Module ${path} could not be found in ${package_context}`)
|
||||
throw Error(`Module ${path} could not be found in ${package_context}`)
|
||||
|
||||
if (use_cache[info.cache_key])
|
||||
return use_cache[info.cache_key]
|
||||
@@ -842,7 +854,7 @@ Shop.resolve_locator = resolve_locator
|
||||
|
||||
// Get cache path for a package and commit
|
||||
function get_cache_path(pkg, commit) {
|
||||
return global_shop_path + '/cache/' + pkg.replaceAll('@','_').replaceAll('/','_') + '_' + commit + '.zip'
|
||||
return global_shop_path + '/cache/' + replace(replace(pkg, '@','_'), '/','_') + '_' + commit + '.zip'
|
||||
}
|
||||
|
||||
function get_package_abs_dir(package)
|
||||
@@ -878,15 +890,12 @@ function download_zip(pkg, commit_hash) {
|
||||
return null
|
||||
}
|
||||
|
||||
log.console("Downloading from " + download_url)
|
||||
try {
|
||||
var zip_blob = http.fetch(download_url)
|
||||
log.console(`putting to ${cache_path}`)
|
||||
fd.slurpwrite(cache_path, zip_blob)
|
||||
log.console("Cached to " + cache_path)
|
||||
return zip_blob
|
||||
} catch (e) {
|
||||
log.error(e)
|
||||
log.error("Download failed for " + pkg + ": " + e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -901,19 +910,22 @@ function get_cached_zip(pkg, commit_hash) {
|
||||
}
|
||||
|
||||
// Fetch: Ensure the zip on disk matches what's in the lock file
|
||||
// For local packages, this is a no-op (returns true)
|
||||
// For local packages, this is a no-op
|
||||
// For remote packages, downloads the zip if not present or hash mismatch
|
||||
// Returns true on success
|
||||
// Returns: { status: 'local'|'cached'|'downloaded'|'error', message: string }
|
||||
Shop.fetch = function(pkg) {
|
||||
var lock = Shop.load_lock()
|
||||
var lock_entry = lock[pkg]
|
||||
var info = Shop.resolve_package_info(pkg)
|
||||
|
||||
if (info == 'local') return null
|
||||
if (info == 'local') {
|
||||
return { status: 'local' }
|
||||
}
|
||||
|
||||
// No lock entry - can't fetch without knowing what commit
|
||||
if (!lock_entry || !lock_entry.commit)
|
||||
throw new Error("No lock entry for " + pkg + " - run update first")
|
||||
if (!lock_entry || !lock_entry.commit) {
|
||||
return { status: 'error', message: "No lock entry for " + pkg + " - run update first" }
|
||||
}
|
||||
|
||||
var commit = lock_entry.commit
|
||||
var expected_hash = lock_entry.zip_hash
|
||||
@@ -922,18 +934,34 @@ Shop.fetch = function(pkg) {
|
||||
var zip_blob = get_cached_zip(pkg, commit)
|
||||
|
||||
if (zip_blob) {
|
||||
// Verify hash matches
|
||||
var actual_hash = text(crypto.blake2(zip_blob), 'h')
|
||||
if (actual_hash == expected_hash)
|
||||
return true
|
||||
|
||||
log.console("Zip hash mismatch for " + pkg + ", re-fetching...")
|
||||
// If we have a hash on record, verify it
|
||||
if (expected_hash) {
|
||||
var actual_hash = text(crypto.blake2(zip_blob), 'h')
|
||||
if (actual_hash == expected_hash) {
|
||||
return { status: 'cached' }
|
||||
}
|
||||
log.console("Zip hash mismatch for " + pkg + ", re-fetching...")
|
||||
} else {
|
||||
// No hash stored yet - compute and store it
|
||||
var actual_hash = text(crypto.blake2(zip_blob), 'h')
|
||||
lock_entry.zip_hash = actual_hash
|
||||
Shop.save_lock(lock)
|
||||
return { status: 'cached' }
|
||||
}
|
||||
}
|
||||
|
||||
// Download the zip
|
||||
download_zip(pkg, commit)
|
||||
var new_zip = download_zip(pkg, commit)
|
||||
if (!new_zip) {
|
||||
return { status: 'error', message: "Failed to download " + pkg }
|
||||
}
|
||||
|
||||
return true
|
||||
// Store the hash
|
||||
var new_hash = text(crypto.blake2(new_zip), 'h')
|
||||
lock_entry.zip_hash = new_hash
|
||||
Shop.save_lock(lock)
|
||||
|
||||
return { status: 'downloaded' }
|
||||
}
|
||||
|
||||
// Extract: Extract a package to its target directory
|
||||
@@ -964,13 +992,33 @@ Shop.extract = function(pkg) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if already extracted at correct commit
|
||||
var lock = Shop.load_lock()
|
||||
var lock_entry = lock[pkg]
|
||||
if (lock_entry && lock_entry.commit) {
|
||||
var extracted_commit_file = target_dir + '/.cell_commit'
|
||||
if (fd.is_file(extracted_commit_file)) {
|
||||
var extracted_commit = trim(text(fd.slurp(extracted_commit_file)))
|
||||
if (extracted_commit == lock_entry.commit) {
|
||||
// Already extracted at this commit, skip
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var zip_blob = get_package_zip(pkg)
|
||||
|
||||
if (!zip_blob)
|
||||
throw new Error("No zip blob available for " + pkg)
|
||||
throw Error("No zip blob available for " + pkg)
|
||||
|
||||
// Extract zip for remote package
|
||||
install_zip(zip_blob, target_dir)
|
||||
|
||||
// Write marker file with the extracted commit
|
||||
if (lock_entry && lock_entry.commit) {
|
||||
fd.slurpwrite(target_dir + '/.cell_commit', stone(blob(lock_entry.commit)))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1002,8 +1050,20 @@ Shop.update = function(pkg) {
|
||||
|
||||
log.console(`checking ${pkg}`)
|
||||
|
||||
if (info == 'local') return {
|
||||
updated: time.number()
|
||||
if (info == 'local') {
|
||||
// Check if local path exists
|
||||
if (!fd.is_dir(pkg)) {
|
||||
log.console(` Local path does not exist: ${pkg}`)
|
||||
return null
|
||||
}
|
||||
// Local packages always get a lock entry
|
||||
var new_entry = {
|
||||
type: 'local',
|
||||
updated: time.number()
|
||||
}
|
||||
lock[pkg] = new_entry
|
||||
Shop.save_lock(lock)
|
||||
return new_entry
|
||||
}
|
||||
|
||||
var local_commit = lock_entry ? lock_entry.commit : null
|
||||
@@ -1012,14 +1072,14 @@ Shop.update = function(pkg) {
|
||||
log.console(`local commit: ${local_commit}`)
|
||||
log.console(`remote commit: ${remote_commit}`)
|
||||
|
||||
if (local_commit == remote_commit)
|
||||
return null
|
||||
|
||||
if (!remote_commit) {
|
||||
log.error("Could not resolve commit for " + pkg)
|
||||
return null
|
||||
}
|
||||
|
||||
if (local_commit == remote_commit)
|
||||
return null
|
||||
|
||||
var new_entry = {
|
||||
type: info,
|
||||
commit: remote_commit,
|
||||
@@ -1034,7 +1094,7 @@ Shop.update = function(pkg) {
|
||||
|
||||
function install_zip(zip_blob, target_dir) {
|
||||
var zip = miniz.read(zip_blob)
|
||||
if (!zip) throw new Error("Failed to read zip archive")
|
||||
if (!zip) throw Error("Failed to read zip archive")
|
||||
|
||||
if (fd.is_link(target_dir)) fd.unlink(target_dir)
|
||||
if (fd.is_dir(target_dir)) fd.rmdir(target_dir, 1)
|
||||
@@ -1043,20 +1103,27 @@ function install_zip(zip_blob, target_dir) {
|
||||
ensure_dir(target_dir)
|
||||
|
||||
var count = zip.count()
|
||||
var created_dirs = {}
|
||||
|
||||
for (var i = 0; i < count; i++) {
|
||||
if (zip.is_directory(i)) continue
|
||||
var filename = zip.get_filename(i)
|
||||
var parts = filename.split('/')
|
||||
if (parts.length <= 1) continue
|
||||
|
||||
parts.shift()
|
||||
var rel_path = parts.join('/')
|
||||
var slash_pos = search(filename, '/')
|
||||
if (slash_pos == null) continue
|
||||
if (slash_pos + 1 >= length(filename)) continue
|
||||
var rel_path = text(filename, slash_pos + 1)
|
||||
var full_path = target_dir + '/' + rel_path
|
||||
var dir_path = full_path.substring(0, full_path.lastIndexOf('/'))
|
||||
var dir_path = fd.dirname(full_path)
|
||||
|
||||
ensure_dir(dir_path)
|
||||
fd.slurpwrite(full_path, zip.slurp(filename))
|
||||
if (!created_dirs[dir_path]) {
|
||||
ensure_dir(dir_path)
|
||||
created_dirs[dir_path] = true
|
||||
}
|
||||
var file_data = zip.slurp(filename)
|
||||
|
||||
stone(file_data)
|
||||
|
||||
fd.slurpwrite(full_path, file_data)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1079,14 +1146,14 @@ Shop.get = function(pkg) {
|
||||
if (!lock[pkg]) {
|
||||
var info = Shop.resolve_package_info(pkg)
|
||||
if (!info) {
|
||||
throw new Error("Invalid package: " + pkg)
|
||||
throw Error("Invalid package: " + pkg)
|
||||
}
|
||||
|
||||
var commit = null
|
||||
if (info != 'local') {
|
||||
commit = fetch_remote_hash(pkg)
|
||||
if (!commit) {
|
||||
throw new Error("Could not resolve commit for " + pkg)
|
||||
throw Error("Could not resolve commit for " + pkg)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1128,12 +1195,14 @@ Shop.module_reload = function(path, package) {
|
||||
var old = use_cache[cache_key]
|
||||
var newmod = get_module(path, package)
|
||||
|
||||
for (var i in newmod)
|
||||
arrfor(array(newmod), function(i, idx) {
|
||||
old[i] = newmod[i]
|
||||
})
|
||||
|
||||
for (var i in old)
|
||||
arrfor(array(old), function(i, idx) {
|
||||
if (!(i in newmod))
|
||||
old[i] = null
|
||||
})
|
||||
}
|
||||
|
||||
function get_package_scripts(package)
|
||||
@@ -1141,10 +1210,10 @@ function get_package_scripts(package)
|
||||
var files = pkg_tools.list_files(package)
|
||||
var scripts = []
|
||||
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
for (var i = 0; i < length(files); i++) {
|
||||
var file = files[i]
|
||||
if (file.endsWith('.cm') || file.endsWith('.ce')) {
|
||||
scripts.push(file)
|
||||
if (ends_with(file, '.cm') || ends_with(file, '.ce')) {
|
||||
push(scripts, file)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1157,8 +1226,9 @@ Shop.build_package_scripts = function(package)
|
||||
var scripts = get_package_scripts(package)
|
||||
var pkg_dir = get_package_abs_dir(package)
|
||||
|
||||
for (var script of scripts)
|
||||
arrfor(scripts, function(script, i) {
|
||||
resolve_mod_fn(pkg_dir + '/' + script, package)
|
||||
})
|
||||
}
|
||||
|
||||
Shop.list_packages = function()
|
||||
@@ -1190,22 +1260,22 @@ Shop.get_package_dir = function(pkg) {
|
||||
// e.g., c_symbol_for_file('gitea.pockle.world/john/prosperon', 'sprite.c')
|
||||
// -> 'js_gitea_pockle_world_john_prosperon_sprite_use'
|
||||
Shop.c_symbol_for_file = function(pkg, file) {
|
||||
var pkg_safe = pkg.replace(/\//g, '_').replace(/\./g, '_').replace(/-/g, '_')
|
||||
var file_safe = file.substring(0, file.lastIndexOf('.')).replace(/\//g, '_').replace(/\./g, '_').replace(/-/g, '_')
|
||||
var pkg_safe = replace(replace(replace(pkg, '/', '_'), '.', '_'), '-', '_')
|
||||
var file_safe = replace(replace(fd.stem(file), '/', '_'), '.', '_')
|
||||
return 'js_' + pkg_safe + '_' + file_safe + '_use'
|
||||
}
|
||||
|
||||
// Generate C symbol prefix for a package
|
||||
// e.g., c_symbol_prefix('gitea.pockle.world/john/prosperon') -> 'js_gitea_pockle_world_john_prosperon_'
|
||||
Shop.c_symbol_prefix = function(pkg) {
|
||||
var pkg_safe = pkg.replace(/\//g, '_').replace(/\./g, '_').replace(/-/g, '_')
|
||||
var pkg_safe = replace(replace(replace(pkg, '/', '_'), '.', '_'), '-', '_')
|
||||
return 'js_' + pkg_safe + '_'
|
||||
}
|
||||
|
||||
// Get the library name for a package (without extension)
|
||||
// e.g., 'gitea.pockle.world/john/prosperon' -> 'gitea_pockle_world_john_prosperon'
|
||||
Shop.lib_name_for_package = function(pkg) {
|
||||
return pkg.replace(/\//g, '_').replace(/\./g, '_').replace(/-/g, '_')
|
||||
return replace(replace(replace(pkg, '/', '_'), '.', '_'), '-', '_')
|
||||
}
|
||||
|
||||
// Returns { ok: bool, results: [{pkg, ok, error}] }
|
||||
@@ -1214,12 +1284,12 @@ Shop.audit_packages = function() {
|
||||
|
||||
var bad = []
|
||||
|
||||
for (var package of packages) {
|
||||
if (package == 'core') continue
|
||||
if (fd.is_dir(package)) continue
|
||||
if (fetch_remote_hash(package)) continue
|
||||
bad.push(package)
|
||||
}
|
||||
arrfor(packages, function(package, i) {
|
||||
if (package == 'core') return
|
||||
if (fd.is_dir(package)) return
|
||||
if (fetch_remote_hash(package)) return
|
||||
push(bad, package)
|
||||
})
|
||||
|
||||
return bad
|
||||
}
|
||||
@@ -1231,16 +1301,16 @@ Shop.parse_package = function(locator) {
|
||||
|
||||
// Strip version suffix if present
|
||||
var clean = locator
|
||||
if (locator.includes('@')) {
|
||||
clean = locator.split('@')[0]
|
||||
if (search(locator, '@') != null) {
|
||||
clean = array(locator, '@')[0]
|
||||
}
|
||||
|
||||
var info = Shop.resolve_package_info(clean)
|
||||
if (!info) return null
|
||||
|
||||
// Extract package name (last component of path)
|
||||
var parts = clean.split('/')
|
||||
var name = parts[parts.length - 1]
|
||||
var parts = array(clean, '/')
|
||||
var name = parts[length(parts) - 1]
|
||||
|
||||
return {
|
||||
path: clean,
|
||||
|
||||
@@ -24,7 +24,7 @@ function get_pkg_dir(package_name) {
|
||||
if (!package_name) {
|
||||
return fd.realpath('.')
|
||||
}
|
||||
if (package_name.startsWith('/')) {
|
||||
if (starts_with(package_name, '/')) {
|
||||
return package_name
|
||||
}
|
||||
var shop = use('internal/shop')
|
||||
@@ -35,9 +35,9 @@ function get_pkg_dir(package_name) {
|
||||
function ensure_dir(path) {
|
||||
if (fd.is_dir(path)) return true
|
||||
|
||||
var parts = path.split('/')
|
||||
var current = path.startsWith('/') ? '/' : ''
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var parts = array(path, '/')
|
||||
var current = starts_with(path, '/') ? '/' : ''
|
||||
for (var i = 0; i < length(parts); i++) {
|
||||
if (parts[i] == '') continue
|
||||
current += parts[i] + '/'
|
||||
if (!fd.is_dir(current)) {
|
||||
|
||||
306
internal/text.c
306
internal/text.c
@@ -1,306 +0,0 @@
|
||||
#include "cell.h"
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
JSC_CCALL(text_blob_to_hex,
|
||||
size_t blob_len;
|
||||
void *blob_data = js_get_blob_data(js, &blob_len, argv[0]);
|
||||
if (!blob_data) return JS_ThrowTypeError(js, "Expected stone blob");
|
||||
uint8_t *bytes = (uint8_t *)blob_data;
|
||||
size_t hex_len = blob_len * 2;
|
||||
char *hex_str = malloc(hex_len + 1);
|
||||
if (!hex_str) return JS_ThrowOutOfMemory(js);
|
||||
static const char hex_digits[] = "0123456789ABCDEF";
|
||||
for (size_t i = 0; i < blob_len; ++i) {
|
||||
hex_str[i * 2] = hex_digits[(bytes[i] >> 4) & 0xF];
|
||||
hex_str[i * 2 + 1] = hex_digits[bytes[i] & 0xF];
|
||||
}
|
||||
hex_str[hex_len] = '\0';
|
||||
JSValue val = JS_NewString(js, hex_str);
|
||||
free(hex_str);
|
||||
return val;
|
||||
)
|
||||
|
||||
JSC_CCALL(text_blob_to_base32,
|
||||
size_t blob_len;
|
||||
void *blob_data = js_get_blob_data(js, &blob_len, argv[0]);
|
||||
if (!blob_data) return JS_ThrowTypeError(js, "Expected stone blob");
|
||||
uint8_t *bytes = (uint8_t *)blob_data;
|
||||
static const char b32_digits[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
|
||||
// Calculate exact output length needed
|
||||
size_t groups = (blob_len + 4) / 5; // Round up to next group of 5
|
||||
size_t b32_len = groups * 8;
|
||||
char *b32_str = malloc(b32_len + 1);
|
||||
if (!b32_str) return JS_ThrowOutOfMemory(js);
|
||||
|
||||
size_t in_idx = 0;
|
||||
size_t out_idx = 0;
|
||||
|
||||
while (in_idx < blob_len) {
|
||||
// Read up to 5 bytes into a 40-bit buffer
|
||||
uint64_t buf = 0;
|
||||
int bytes_to_read = (blob_len - in_idx < 5) ? (blob_len - in_idx) : 5;
|
||||
|
||||
for (int i = 0; i < bytes_to_read; ++i) {
|
||||
buf = (buf << 8) | bytes[in_idx++];
|
||||
}
|
||||
|
||||
// Pad buffer to 40 bits if we read fewer than 5 bytes
|
||||
buf <<= 8 * (5 - bytes_to_read);
|
||||
|
||||
// Extract 8 groups of 5 bits each
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
b32_str[out_idx++] = b32_digits[(buf >> (35 - i * 5)) & 0x1F];
|
||||
}
|
||||
}
|
||||
|
||||
// Add padding if necessary
|
||||
if (blob_len % 5 != 0) {
|
||||
static const int pad_count[] = {0, 6, 4, 3, 1}; // padding for 0,1,2,3,4 bytes
|
||||
int padding = pad_count[blob_len % 5];
|
||||
for (int i = 0; i < padding; ++i) {
|
||||
b32_str[b32_len - 1 - i] = '=';
|
||||
}
|
||||
}
|
||||
|
||||
b32_str[b32_len] = '\0';
|
||||
JSValue val = JS_NewString(js, b32_str);
|
||||
free(b32_str);
|
||||
return val;
|
||||
)
|
||||
|
||||
static int base32_char_to_val(char c) {
|
||||
if (c >= 'A' && c <= 'Z') return c - 'A';
|
||||
if (c >= 'a' && c <= 'z') return c - 'a';
|
||||
if (c >= '2' && c <= '7') return c - '2' + 26;
|
||||
return -1;
|
||||
}
|
||||
|
||||
JSC_CCALL(text_base32_to_blob,
|
||||
const char *str = JS_ToCString(js, argv[0]);
|
||||
if (!str) return JS_ThrowTypeError(js, "Expected string");
|
||||
size_t str_len = strlen(str);
|
||||
if (str_len == 0) {
|
||||
JS_FreeCString(js, str);
|
||||
return JS_ThrowTypeError(js, "Empty base32 string");
|
||||
}
|
||||
|
||||
// Remove padding to get effective length
|
||||
size_t effective_len = str_len;
|
||||
while (effective_len > 0 && str[effective_len - 1] == '=') {
|
||||
effective_len--;
|
||||
}
|
||||
|
||||
// Calculate output length: each group of 8 base32 chars -> 5 bytes
|
||||
size_t output_len = (effective_len * 5) / 8;
|
||||
uint8_t *output = malloc(output_len);
|
||||
if (!output) {
|
||||
JS_FreeCString(js, str);
|
||||
return JS_ThrowOutOfMemory(js);
|
||||
}
|
||||
|
||||
size_t in_idx = 0;
|
||||
size_t out_idx = 0;
|
||||
|
||||
// Process in groups of 8 characters (40 bits -> 5 bytes)
|
||||
while (in_idx < effective_len) {
|
||||
uint64_t buf = 0;
|
||||
int chars_to_read = (effective_len - in_idx < 8) ? (effective_len - in_idx) : 8;
|
||||
|
||||
// Read up to 8 base32 characters into buffer
|
||||
for (int i = 0; i < chars_to_read; ++i) {
|
||||
int val = base32_char_to_val(str[in_idx++]);
|
||||
if (val < 0) {
|
||||
free(output);
|
||||
JS_FreeCString(js, str);
|
||||
return JS_ThrowTypeError(js, "Invalid base32 character");
|
||||
}
|
||||
buf = (buf << 5) | val;
|
||||
}
|
||||
|
||||
// Calculate how many bytes we can extract
|
||||
int bytes_to_extract = (chars_to_read * 5) / 8;
|
||||
|
||||
// Shift buffer to align the most significant bits
|
||||
buf <<= (40 - chars_to_read * 5);
|
||||
|
||||
// Extract bytes from most significant to least significant
|
||||
for (int i = 0; i < bytes_to_extract && out_idx < output_len; ++i) {
|
||||
output[out_idx++] = (buf >> (32 - i * 8)) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
JSValue val = js_new_blob_stoned_copy(js, output, output_len);
|
||||
free(output);
|
||||
JS_FreeCString(js, str);
|
||||
return val;
|
||||
)
|
||||
|
||||
static int base64_char_to_val_standard(char c) {
|
||||
if (c >= 'A' && c <= 'Z') return c - 'A';
|
||||
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
|
||||
if (c >= '0' && c <= '9') return c - '0' + 52;
|
||||
if (c == '+') return 62;
|
||||
if (c == '/') return 63;
|
||||
return -1;
|
||||
}
|
||||
static int base64_char_to_val_url(char c) {
|
||||
if (c >= 'A' && c <= 'Z') return c - 'A';
|
||||
if (c >= 'a' && c <= 'z') return c - 'a' + 26;
|
||||
if (c >= '0' && c <= '9') return c - '0' + 52;
|
||||
if (c == '-') return 62;
|
||||
if (c == '_') return 63;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*─── blob → Base64 (standard, with ‘+’ and ‘/’, padded) ───────────────────*/
|
||||
JSC_CCALL(text_blob_to_base64,
|
||||
size_t blob_len;
|
||||
void *blob_data = js_get_blob_data(js, &blob_len, argv[0]);
|
||||
if (!blob_data) return JS_ThrowTypeError(js, "Expected stone blob");
|
||||
const uint8_t *bytes = blob_data;
|
||||
static const char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789+/";
|
||||
size_t out_len = ((blob_len + 2) / 3) * 4;
|
||||
char *out = malloc(out_len + 1);
|
||||
if (!out) return JS_ThrowOutOfMemory(js);
|
||||
|
||||
size_t in_i = 0, out_i = 0;
|
||||
while (in_i < blob_len) {
|
||||
uint32_t buf = 0;
|
||||
int to_read = (blob_len - in_i < 3 ? blob_len - in_i : 3);
|
||||
for (int j = 0; j < to_read; ++j) {
|
||||
buf = (buf << 8) | bytes[in_i++];
|
||||
}
|
||||
buf <<= 8 * (3 - to_read);
|
||||
out[out_i++] = b64[(buf >> 18) & 0x3F];
|
||||
out[out_i++] = b64[(buf >> 12) & 0x3F];
|
||||
out[out_i++] = (to_read > 1 ? b64[(buf >> 6) & 0x3F] : '=');
|
||||
out[out_i++] = (to_read > 2 ? b64[ buf & 0x3F] : '=');
|
||||
}
|
||||
out[out_len] = '\0';
|
||||
JSValue v = JS_NewString(js, out);
|
||||
free(out);
|
||||
return v;
|
||||
)
|
||||
|
||||
/*─── Base64 → blob (standard, expects ‘+’ and ‘/’, pads allowed) ────────────*/
|
||||
JSC_CCALL(text_base64_to_blob,
|
||||
const char *str = JS_ToCString(js, argv[0]);
|
||||
if (!str) return JS_ThrowTypeError(js, "Expected string");
|
||||
size_t len = strlen(str);
|
||||
// strip padding for length calculation
|
||||
size_t eff = len;
|
||||
while (eff > 0 && str[eff-1] == '=') eff--;
|
||||
size_t out_len = (eff * 6) / 8;
|
||||
uint8_t *out = malloc(out_len);
|
||||
if (!out) { JS_FreeCString(js, str); return JS_ThrowOutOfMemory(js); }
|
||||
|
||||
size_t in_i = 0, out_i = 0;
|
||||
while (in_i < eff) {
|
||||
uint32_t buf = 0;
|
||||
int to_read = (eff - in_i < 4 ? eff - in_i : 4);
|
||||
for (int j = 0; j < to_read; ++j) {
|
||||
int v = base64_char_to_val_standard(str[in_i++]);
|
||||
if (v < 0) { free(out); JS_FreeCString(js, str);
|
||||
return JS_ThrowTypeError(js, "Invalid base64 character"); }
|
||||
buf = (buf << 6) | v;
|
||||
}
|
||||
buf <<= 6 * (4 - to_read);
|
||||
int bytes_out = (to_read * 6) / 8;
|
||||
for (int j = 0; j < bytes_out && out_i < out_len; ++j) {
|
||||
out[out_i++] = (buf >> (16 - 8*j)) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
JSValue v = js_new_blob_stoned_copy(js, out, out_len);
|
||||
free(out);
|
||||
JS_FreeCString(js, str);
|
||||
return v;
|
||||
)
|
||||
|
||||
/*─── blob → Base64URL (no padding, ‘-’ and ‘_’) ─────────────────────────────*/
|
||||
JSC_CCALL(text_blob_to_base64url,
|
||||
size_t blob_len;
|
||||
void *blob_data = js_get_blob_data(js, &blob_len, argv[0]);
|
||||
if (!blob_data) return JS_ThrowTypeError(js, "Expected stone blob");
|
||||
const uint8_t *bytes = blob_data;
|
||||
static const char b64url[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789-_";
|
||||
size_t raw_len = ((blob_len + 2) / 3) * 4;
|
||||
// we’ll drop any trailing '='
|
||||
char *out = malloc(raw_len + 1);
|
||||
if (!out) return JS_ThrowOutOfMemory(js);
|
||||
|
||||
size_t in_i = 0, out_i = 0;
|
||||
while (in_i < blob_len) {
|
||||
uint32_t buf = 0;
|
||||
int to_read = (blob_len - in_i < 3 ? blob_len - in_i : 3);
|
||||
for (int j = 0; j < to_read; ++j) {
|
||||
buf = (buf << 8) | bytes[in_i++];
|
||||
}
|
||||
buf <<= 8 * (3 - to_read);
|
||||
out[out_i++] = b64url[(buf >> 18) & 0x3F];
|
||||
out[out_i++] = b64url[(buf >> 12) & 0x3F];
|
||||
if (to_read > 1) out[out_i++] = b64url[(buf >> 6) & 0x3F];
|
||||
if (to_read > 2) out[out_i++] = b64url[ buf & 0x3F];
|
||||
}
|
||||
out[out_i] = '\0';
|
||||
JSValue v = JS_NewString(js, out);
|
||||
free(out);
|
||||
return v;
|
||||
)
|
||||
|
||||
/*─── Base64URL → blob (accepts ‘-’ / ‘_’, no padding needed) ─────────────────*/
|
||||
JSC_CCALL(text_base64url_to_blob,
|
||||
const char *str = JS_ToCString(js, argv[0]);
|
||||
if (!str) return JS_ThrowTypeError(js, "Expected string");
|
||||
size_t len = strlen(str);
|
||||
size_t eff = len; // no '=' in URL‐safe, but strip if present
|
||||
while (eff > 0 && str[eff-1] == '=') eff--;
|
||||
size_t out_len = (eff * 6) / 8;
|
||||
uint8_t *out = malloc(out_len);
|
||||
if (!out) { JS_FreeCString(js, str); return JS_ThrowOutOfMemory(js); }
|
||||
|
||||
size_t in_i = 0, out_i = 0;
|
||||
while (in_i < eff) {
|
||||
uint32_t buf = 0;
|
||||
int to_read = (eff - in_i < 4 ? eff - in_i : 4);
|
||||
for (int j = 0; j < to_read; ++j) {
|
||||
int v = base64_char_to_val_url(str[in_i++]);
|
||||
if (v < 0) { free(out); JS_FreeCString(js, str);
|
||||
return JS_ThrowTypeError(js, "Invalid base64url character"); }
|
||||
buf = (buf << 6) | v;
|
||||
}
|
||||
buf <<= 6 * (4 - to_read);
|
||||
int bytes_out = (to_read * 6) / 8;
|
||||
for (int j = 0; j < bytes_out && out_i < out_len; ++j) {
|
||||
out[out_i++] = (buf >> (16 - 8*j)) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
JSValue v = js_new_blob_stoned_copy(js, out, out_len);
|
||||
free(out);
|
||||
JS_FreeCString(js, str);
|
||||
return v;
|
||||
)
|
||||
|
||||
static const JSCFunctionListEntry js_text_funcs[] = {
|
||||
MIST_FUNC_DEF(text, blob_to_hex, 1),
|
||||
MIST_FUNC_DEF(text, blob_to_base32, 1),
|
||||
MIST_FUNC_DEF(text, base32_to_blob, 1),
|
||||
MIST_FUNC_DEF(text, blob_to_base64, 1),
|
||||
MIST_FUNC_DEF(text, base64_to_blob, 1),
|
||||
MIST_FUNC_DEF(text, blob_to_base64url, 1),
|
||||
MIST_FUNC_DEF(text, base64url_to_blob, 1),
|
||||
};
|
||||
|
||||
JSValue js_internal_text_use(JSContext *js)
|
||||
{
|
||||
JSValue mod = JS_NewObject(js);
|
||||
JS_SetPropertyFunctionList(js, mod, js_text_funcs, countof(js_text_funcs));
|
||||
return mod;
|
||||
}
|
||||
602
internal/text.cm
602
internal/text.cm
@@ -1,602 +0,0 @@
|
||||
/* text.cm - text conversion and formatting utilities */
|
||||
var blob = use('blob')
|
||||
var utf8 = use('utf8')
|
||||
|
||||
var _toLowerCase = String.prototype.toLowerCase
|
||||
var _toUpperCase = String.prototype.toUpperCase
|
||||
var _trim = String.prototype.trim
|
||||
var _indexOf = String.prototype.indexOf
|
||||
var _lastIndexOf = String.prototype.lastIndexOf
|
||||
var _replace = String.prototype.replace
|
||||
var _normalize = String.prototype.normalize
|
||||
var _substring = String.prototype.substring
|
||||
var _charCodeAt = String.prototype.charCodeAt
|
||||
var _codePointAt = String.prototype.codePointAt
|
||||
|
||||
var _String = String
|
||||
|
||||
var that = this
|
||||
|
||||
// Convert number to string with given radix
|
||||
function to_radix(num, radix) {
|
||||
if (radix < 2 || radix > 36) return null;
|
||||
|
||||
var digits = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||
var result = "";
|
||||
var n = number.whole(num);
|
||||
var negative = n < 0;
|
||||
n = number.abs(n);
|
||||
|
||||
if (n == 0) return "0";
|
||||
|
||||
while (n > 0) {
|
||||
result = digits[n % radix] + result;
|
||||
n = number.floor(n / radix);
|
||||
}
|
||||
|
||||
return negative ? "-" + result : result;
|
||||
}
|
||||
|
||||
// Insert separator every n digits from right
|
||||
function add_separator(str, sep, n) {
|
||||
if (!n || n == 0) return str;
|
||||
|
||||
var negative = str[0] == '-';
|
||||
if (negative) str = str.substring(1);
|
||||
|
||||
var parts = str.split('.');
|
||||
var integer = parts[0];
|
||||
var decimal = parts[1] || '';
|
||||
|
||||
// Add separators to integer part
|
||||
var result = "";
|
||||
for (var i = integer.length - 1, count = 0; i >= 0; i--) {
|
||||
if (count == n && i != integer.length - 1) {
|
||||
result = sep + result;
|
||||
count = 0;
|
||||
}
|
||||
result = integer[i] + result;
|
||||
count++;
|
||||
}
|
||||
|
||||
if (decimal) result += '.' + decimal;
|
||||
return negative ? '-' + result : result;
|
||||
}
|
||||
|
||||
// Format number with separator from left
|
||||
function add_separator_left(str, sep, n) {
|
||||
if (!n || n == 0) return str;
|
||||
|
||||
var negative = str[0] == '-';
|
||||
if (negative) str = str.substring(1);
|
||||
|
||||
var result = "";
|
||||
for (var i = 0, count = 0; i < str.length; i++) {
|
||||
if (count == n && i != 0) {
|
||||
result += sep;
|
||||
count = 0;
|
||||
}
|
||||
result += str[i];
|
||||
count++;
|
||||
}
|
||||
|
||||
return negative ? '-' + result : result;
|
||||
}
|
||||
|
||||
/* -------- main text function --------------------------------------- */
|
||||
|
||||
function text(...arguments) {
|
||||
var arg = arguments[0];
|
||||
|
||||
// Handle blob conversion
|
||||
if (arg instanceof blob) {
|
||||
if (!stone.p(arg))
|
||||
throw new Error("text: blob must be stone for reading");
|
||||
|
||||
var format = arguments[1];
|
||||
var bit_length = arg.length;
|
||||
var result = "";
|
||||
|
||||
if (typeof format == 'string') {
|
||||
// Extract style from format
|
||||
var style = '';
|
||||
for (var i = 0; i < format.length; i++) {
|
||||
if ((format[i] >= 'a' && format[i] <= 'z') || (format[i] >= 'A' && format[i] <= 'Z')) {
|
||||
style = format[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle blob encoding styles
|
||||
switch (style) {
|
||||
case 'h': // hexadecimal
|
||||
return that.blob_to_hex(arg);
|
||||
|
||||
case 't': // base32
|
||||
return that.blob_to_base32(arg);
|
||||
|
||||
case 'b': // binary
|
||||
for (var i = 0; i < bit_length; i++) {
|
||||
result += arg.read_logical(i) ? '1' : '0';
|
||||
}
|
||||
return result;
|
||||
|
||||
case 'o': // octal
|
||||
var bits = 0;
|
||||
var value = 0;
|
||||
|
||||
for (var i = 0; i < bit_length; i++) {
|
||||
var bit = arg.read_logical(i);
|
||||
value = (value << 1) | (bit ? 1 : 0);
|
||||
bits++;
|
||||
|
||||
if (bits == 3) {
|
||||
result += value.toString();
|
||||
bits = 0;
|
||||
value = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle remaining bits
|
||||
if (bits > 0) {
|
||||
value = value << (3 - bits);
|
||||
result += value.toString();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Default: interpret as UTF-8 text
|
||||
// Use the utf8 module to decode the blob
|
||||
if (arg.length == 0) return ""
|
||||
return utf8.decode(arg);
|
||||
}
|
||||
|
||||
// Handle array conversion
|
||||
if (isa(arg, array)) {
|
||||
var separator = arguments[1] || "";
|
||||
|
||||
// Check if all items are valid codepoints
|
||||
var all_codepoints = true;
|
||||
for (var i = 0; i < arg.length; i++) {
|
||||
var item = arg[i];
|
||||
if (!(typeof item == 'number' && item >= 0 && item <= 0x10FFFF && item == number.floor(item))) {
|
||||
all_codepoints = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (all_codepoints && separator == "") {
|
||||
// Use utf8 module to convert codepoints to string
|
||||
return utf8.from_codepoints(arg);
|
||||
} else {
|
||||
// General array to string conversion
|
||||
var result = "";
|
||||
for (var i = 0; i < arg.length; i++) {
|
||||
if (i > 0) result += separator;
|
||||
|
||||
var item = arg[i];
|
||||
if (typeof item == 'number' && item >= 0 && item <= 0x10FFFF && item == number.floor(item)) {
|
||||
// Single codepoint - use utf8 module
|
||||
result += utf8.from_codepoints([item]);
|
||||
} else {
|
||||
result += String(item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle number conversion
|
||||
if (typeof arg == 'number') {
|
||||
var format = arguments[1];
|
||||
|
||||
// Simple radix conversion
|
||||
if (typeof format == 'number') {
|
||||
return to_radix(arg, format);
|
||||
}
|
||||
|
||||
// Format string conversion
|
||||
if (typeof format == 'string') {
|
||||
return format_number(arg, format);
|
||||
}
|
||||
|
||||
// Default conversion
|
||||
return _String(arg);
|
||||
}
|
||||
|
||||
// Handle text operations
|
||||
if (typeof arg == 'string') {
|
||||
if (arguments.length == 1) return arg;
|
||||
|
||||
var from = arguments[1];
|
||||
var to = arguments[2];
|
||||
|
||||
if (typeof from != 'number' || typeof to != 'number') return arg;
|
||||
|
||||
var len = arg.length;
|
||||
|
||||
// Adjust negative indices
|
||||
if (from < 0) from += len;
|
||||
if (to < 0) to += len;
|
||||
|
||||
// Default values
|
||||
if (from == null) from = 0;
|
||||
if (to == null) to = len;
|
||||
|
||||
// Validate range
|
||||
if (from < 0 || from > to || to > len) return null;
|
||||
|
||||
return arg.substring(from, to);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/* -------- number formatting ---------------------------------------- */
|
||||
|
||||
function format_number(num, format) {
|
||||
// Parse format string
|
||||
var separation = 0;
|
||||
var style = '';
|
||||
var places = 0;
|
||||
|
||||
var i = 0;
|
||||
|
||||
// Parse separation digit
|
||||
if (i < format.length && format[i] >= '0' && format[i] <= '9') {
|
||||
separation = number(format[i]);
|
||||
i++;
|
||||
}
|
||||
|
||||
// Parse style letter
|
||||
if (i < format.length) {
|
||||
style = format[i];
|
||||
i++;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse places digits
|
||||
if (i < format.length && format[i] >= '0' && format[i] <= '9') {
|
||||
places = number(format[i]);
|
||||
i++;
|
||||
if (i < format.length && format[i] >= '0' && format[i] <= '9') {
|
||||
places = places * 10 + number(format[i]);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// Invalid format if there's more
|
||||
if (i < format.length) return null;
|
||||
|
||||
// Real number styles
|
||||
if (style == 'e' || style == 'n' || style == 's' ||
|
||||
style == 'u' || style == 'd' || style == 'v' || style == 'l') {
|
||||
|
||||
var decimal_point = '.';
|
||||
var separator = '';
|
||||
var default_separation = 0;
|
||||
var default_places = 0;
|
||||
|
||||
switch (style) {
|
||||
case 'e': // exponential
|
||||
decimal_point = '.';
|
||||
separator = '';
|
||||
default_separation = 0;
|
||||
default_places = 0;
|
||||
break;
|
||||
case 'n': // number
|
||||
decimal_point = '.';
|
||||
separator = '';
|
||||
default_separation = 0;
|
||||
default_places = 0;
|
||||
break;
|
||||
case 's': // space
|
||||
decimal_point = '.';
|
||||
separator = ' ';
|
||||
default_separation = 3;
|
||||
default_places = 0;
|
||||
break;
|
||||
case 'u': // underbar
|
||||
decimal_point = '.';
|
||||
separator = '_';
|
||||
default_separation = 0;
|
||||
default_places = 0;
|
||||
break;
|
||||
case 'd': // decimal
|
||||
decimal_point = '.';
|
||||
separator = ',';
|
||||
default_separation = 3;
|
||||
default_places = 2;
|
||||
break;
|
||||
case 'v': // comma (European style)
|
||||
decimal_point = ',';
|
||||
separator = '.';
|
||||
default_separation = 0;
|
||||
default_places = 0;
|
||||
break;
|
||||
case 'l': // locale (default to 'd' style for now)
|
||||
decimal_point = '.';
|
||||
separator = ',';
|
||||
default_separation = 3;
|
||||
default_places = 2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (separation == 0) separation = default_separation;
|
||||
if (places == 0 && style != 'e' && style != 'n') places = default_places;
|
||||
|
||||
// Format the number
|
||||
if (style == 'e') {
|
||||
// Scientific notation
|
||||
var str = places > 0 ? num.toExponential(places) : num.toExponential();
|
||||
return str;
|
||||
} else if (style == 'n' && (number.abs(num) >= 1e21 || (number.abs(num) < 1e-6 && num != 0))) {
|
||||
// Use scientific notation for extreme values
|
||||
return num.toExponential();
|
||||
} else {
|
||||
// Regular decimal formatting
|
||||
var str;
|
||||
if (places > 0) {
|
||||
str = num.toFixed(places);
|
||||
} else {
|
||||
str = num.toString();
|
||||
}
|
||||
|
||||
// Replace decimal point if needed
|
||||
if (decimal_point != '.') {
|
||||
str = str.replace('.', decimal_point);
|
||||
}
|
||||
|
||||
// Add separators
|
||||
if (separation > 0 && separator) {
|
||||
str = add_separator(str, separator, separation);
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
// Integer styles
|
||||
if (style == 'i' || style == 'b' || style == 'o' ||
|
||||
style == 'h' || style == 't') {
|
||||
|
||||
var radix = 10;
|
||||
var default_separation = 0;
|
||||
var default_places = 1;
|
||||
|
||||
switch (style) {
|
||||
case 'i': // integer
|
||||
radix = 10;
|
||||
default_separation = 0;
|
||||
default_places = 1;
|
||||
break;
|
||||
case 'b': // binary
|
||||
radix = 2;
|
||||
default_separation = 0;
|
||||
default_places = 1;
|
||||
break;
|
||||
case 'o': // octal
|
||||
radix = 8;
|
||||
default_separation = 0;
|
||||
default_places = 1;
|
||||
break;
|
||||
case 'h': // hexadecimal
|
||||
radix = 16;
|
||||
default_separation = 0;
|
||||
default_places = 1;
|
||||
break;
|
||||
case 't': // base32
|
||||
radix = 32;
|
||||
default_separation = 0;
|
||||
default_places = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (separation == 0) separation = default_separation;
|
||||
if (places == 0) places = default_places;
|
||||
|
||||
// Convert to integer
|
||||
var n = number.whole(num);
|
||||
var str = to_radix(n, radix).toUpperCase();
|
||||
|
||||
// Pad with zeros if needed
|
||||
var negative = str[0] == '-';
|
||||
if (negative) str = str.substring(1);
|
||||
|
||||
while (str.length < places) {
|
||||
str = '0' + str;
|
||||
}
|
||||
|
||||
// Add separators
|
||||
if (separation > 0) {
|
||||
str = add_separator_left(str, '_', separation);
|
||||
}
|
||||
|
||||
return negative ? '-' + str : str;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/* -------- text sub-functions --------------------------------------- */
|
||||
|
||||
text.lower = function(str) {
|
||||
if (typeof str != 'string') return null
|
||||
return _toLowerCase.call(str)
|
||||
}
|
||||
|
||||
text.upper = function(str) {
|
||||
if (typeof str != 'string') return null
|
||||
return _toUpperCase.call(str)
|
||||
}
|
||||
|
||||
text.trim = function(str, reject) {
|
||||
if (typeof str != 'string') return null
|
||||
if (reject == null) return _trim.call(str)
|
||||
|
||||
// Custom trim with reject characters
|
||||
var start = 0
|
||||
var end = str.length
|
||||
|
||||
while (start < end && reject.indexOf(str[start]) >= 0) start++
|
||||
while (end > start && reject.indexOf(str[end - 1]) >= 0) end--
|
||||
|
||||
return _substring.call(str, start, end)
|
||||
}
|
||||
|
||||
text.normalize = function(str) {
|
||||
if (typeof str != 'string') return null
|
||||
return _normalize.call(str, 'NFC')
|
||||
}
|
||||
|
||||
text.codepoint = function(str) {
|
||||
if (typeof str != 'string' || str.length == 0) return null
|
||||
return _codePointAt.call(str, 0)
|
||||
}
|
||||
|
||||
text.search = function(str, target, from) {
|
||||
if (typeof str != 'string') return null
|
||||
if (typeof target != 'string') return null
|
||||
|
||||
if (from == null) from = 0
|
||||
if (from < 0) from += str.length
|
||||
if (from < 0) from = 0
|
||||
|
||||
var result = _indexOf.call(str, target, from)
|
||||
if (result == -1) return null
|
||||
return result
|
||||
}
|
||||
|
||||
text.replace = function(str, target, replacement, limit) {
|
||||
if (typeof str != 'string') return null
|
||||
if (typeof target != 'string') return null
|
||||
|
||||
if (limit == null) {
|
||||
// Replace all
|
||||
var result = str
|
||||
var pos = 0
|
||||
while (true) {
|
||||
var idx = _indexOf.call(result, target, pos)
|
||||
if (idx == -1) break
|
||||
|
||||
var rep = replacement
|
||||
if (typeof replacement == 'function') {
|
||||
rep = replacement(target, idx)
|
||||
if (rep == null) {
|
||||
pos = idx + target.length
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
result = _substring.call(result, 0, idx) + rep + _substring.call(result, idx + target.length)
|
||||
pos = idx + rep.length
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Replace with limit
|
||||
var result = str
|
||||
var pos = 0
|
||||
var count = 0
|
||||
|
||||
while (count < limit) {
|
||||
var idx = _indexOf.call(result, target, pos)
|
||||
if (idx == -1) break
|
||||
|
||||
var rep = replacement
|
||||
if (typeof replacement == 'function') {
|
||||
rep = replacement(target, idx)
|
||||
if (rep == null) {
|
||||
pos = idx + target.length
|
||||
count++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
result = _substring.call(result, 0, idx) + rep + _substring.call(result, idx + target.length)
|
||||
pos = idx + rep.length
|
||||
count++
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
text.format = function(str, collection, transformer) {
|
||||
if (typeof str != 'string') return null
|
||||
|
||||
var result = ""
|
||||
var i = 0
|
||||
|
||||
while (i < str.length) {
|
||||
if (str[i] == '{') {
|
||||
var end = _indexOf.call(str, '}', i)
|
||||
if (end == -1) {
|
||||
result += str[i]
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
var middle = _substring.call(str, i + 1, end)
|
||||
var colonIdx = _indexOf.call(middle, ':')
|
||||
var key = colonIdx >= 0 ? _substring.call(middle, 0, colonIdx) : middle
|
||||
var formatSpec = colonIdx >= 0 ? _substring.call(middle, colonIdx + 1) : ""
|
||||
|
||||
var value = null
|
||||
if (isa(collection, array)) {
|
||||
var idx = number(key)
|
||||
if (!isNaN(idx) && idx >= 0 && idx < collection.length) {
|
||||
value = collection[idx]
|
||||
}
|
||||
} else if (isa(collection, object)) {
|
||||
value = collection[key]
|
||||
}
|
||||
|
||||
var substitution = null
|
||||
|
||||
if (transformer != null) {
|
||||
if (typeof transformer == 'function') {
|
||||
substitution = transformer(value, formatSpec)
|
||||
} else if (typeof transformer == 'object') {
|
||||
var fn = transformer[formatSpec]
|
||||
if (typeof fn == 'function') {
|
||||
substitution = fn(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (substitution == null && typeof value == 'number' && formatSpec) {
|
||||
// Try number formatting
|
||||
substitution = String(value) // simplified
|
||||
}
|
||||
|
||||
if (substitution == null && value != null) {
|
||||
substitution = String(value)
|
||||
}
|
||||
|
||||
if (substitution != null) {
|
||||
result += substitution
|
||||
} else {
|
||||
result += _substring.call(str, i, end + 1)
|
||||
}
|
||||
|
||||
i = end + 1
|
||||
} else {
|
||||
result += str[i]
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
text.extract = function(str, pattern, from, to) {
|
||||
// Simplified pattern matching - returns null for now
|
||||
// Full implementation would require regex or custom pattern language
|
||||
if (typeof str != 'string') return null
|
||||
return null
|
||||
}
|
||||
|
||||
return text
|
||||
27
link.ce
27
link.ce
@@ -17,7 +17,7 @@ var shop = use('internal/shop')
|
||||
var fd = use('fd')
|
||||
var toml = use('toml')
|
||||
|
||||
if (args.length < 1) {
|
||||
if (length(args) < 1) {
|
||||
log.console("Usage: link <command> [args] or link [package] <target>")
|
||||
log.console("Commands:")
|
||||
log.console(" list List all active links")
|
||||
@@ -35,25 +35,25 @@ var cmd = args[0]
|
||||
if (cmd == 'list') {
|
||||
var links = link.load()
|
||||
var count = 0
|
||||
for (var k in links) {
|
||||
arrfor(array(links), function(k) {
|
||||
log.console(k + " -> " + links[k])
|
||||
count++
|
||||
}
|
||||
})
|
||||
if (count == 0) log.console("No links.")
|
||||
|
||||
} else if (cmd == 'sync') {
|
||||
log.console("Syncing links...")
|
||||
var result = link.sync_all(shop)
|
||||
log.console("Synced " + result.synced + " link(s)")
|
||||
if (result.errors.length > 0) {
|
||||
if (length(result.errors) > 0) {
|
||||
log.console("Errors:")
|
||||
for (var i = 0; i < result.errors.length; i++) {
|
||||
for (var i = 0; i < length(result.errors); i++) {
|
||||
log.console(" " + result.errors[i])
|
||||
}
|
||||
}
|
||||
|
||||
} else if (cmd == 'delete' || cmd == 'rm') {
|
||||
if (args.length < 2) {
|
||||
if (length(args) < 2) {
|
||||
log.console("Usage: link delete <package>")
|
||||
$stop()
|
||||
return
|
||||
@@ -92,7 +92,7 @@ if (cmd == 'list') {
|
||||
}
|
||||
|
||||
var arg1 = args[start_idx]
|
||||
var arg2 = (args.length > start_idx + 1) ? args[start_idx + 1] : null
|
||||
var arg2 = (length(args) > start_idx + 1) ? args[start_idx + 1] : null
|
||||
|
||||
if (!arg1) {
|
||||
log.console("Error: target or package required")
|
||||
@@ -108,13 +108,13 @@ if (cmd == 'list') {
|
||||
// Resolve target if it's a local path
|
||||
if (target == '.' || fd.is_dir(target)) {
|
||||
target = fd.realpath(target)
|
||||
} else if (target.startsWith('./') || target.startsWith('../')) {
|
||||
} else if (starts_with(target, './') || starts_with(target, '../')) {
|
||||
// Relative path that doesn't exist yet - try to resolve anyway
|
||||
var cwd = fd.realpath('.')
|
||||
if (target.startsWith('./')) {
|
||||
target = cwd + target.substring(1)
|
||||
if (starts_with(target, './')) {
|
||||
target = cwd + text(target, 1)
|
||||
} else {
|
||||
// For ../ paths, let fd.realpath handle it if possible
|
||||
// For ../ paths, var fd.realpath handle it if possible
|
||||
target = fd.realpath(target) || target
|
||||
}
|
||||
}
|
||||
@@ -127,7 +127,7 @@ if (cmd == 'list') {
|
||||
// Resolve path
|
||||
if (target == '.' || fd.is_dir(target)) {
|
||||
target = fd.realpath(target)
|
||||
} else if (target.startsWith('./') || target.startsWith('../')) {
|
||||
} else if (starts_with(target, './') || starts_with(target, '../')) {
|
||||
target = fd.realpath(target) || target
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ if (cmd == 'list') {
|
||||
}
|
||||
|
||||
// Validate: if target is a local path, it must have cell.toml
|
||||
if (target.startsWith('/')) {
|
||||
if (starts_with(target, '/')) {
|
||||
if (!fd.is_file(target + '/cell.toml')) {
|
||||
log.console("Error: " + target + " is not a valid package (no cell.toml)")
|
||||
$stop()
|
||||
@@ -171,6 +171,7 @@ if (cmd == 'list') {
|
||||
link.add(pkg_name, target, shop)
|
||||
} catch (e) {
|
||||
log.console("Error: " + e.message)
|
||||
log.error(e)
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
|
||||
116
link.cm
116
link.cm
@@ -3,7 +3,7 @@
|
||||
|
||||
var toml = use('toml')
|
||||
var fd = use('fd')
|
||||
var utf8 = use('utf8')
|
||||
var blob = use('blob')
|
||||
var os = use('os')
|
||||
|
||||
var global_shop_path = os.global_shop_path
|
||||
@@ -21,9 +21,9 @@ function get_packages_dir() {
|
||||
// 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 && pkg.startsWith('/'))
|
||||
return pkg.replaceAll('/', '_').replaceAll('@', '_')
|
||||
return pkg.replaceAll('@', '_')
|
||||
if (pkg && starts_with(pkg, '/'))
|
||||
return replace(replace(pkg, '/', '_'), '@', '_')
|
||||
return replace(pkg, '@', '_')
|
||||
}
|
||||
|
||||
function get_package_abs_dir(package) {
|
||||
@@ -32,9 +32,9 @@ function get_package_abs_dir(package) {
|
||||
|
||||
function ensure_dir(path) {
|
||||
if (fd.stat(path).isDirectory) return
|
||||
var parts = path.split('/')
|
||||
var current = path.startsWith('/') ? '/' : ''
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var parts = array(path, '/')
|
||||
var current = starts_with(path, '/') ? '/' : ''
|
||||
for (var i = 0; i < length(parts); i++) {
|
||||
if (parts[i] == '') continue
|
||||
current += parts[i] + '/'
|
||||
if (!fd.stat(current).isDirectory) {
|
||||
@@ -47,7 +47,7 @@ function ensure_dir(path) {
|
||||
// If target is a local path (starts with /), return it directly
|
||||
// If target is a package name, return the package directory
|
||||
function resolve_link_target(target) {
|
||||
if (target.startsWith('/')) {
|
||||
if (starts_with(target, '/')) {
|
||||
return target
|
||||
}
|
||||
// Target is another package - resolve to its directory
|
||||
@@ -81,21 +81,23 @@ Link.save = function(links) {
|
||||
link_cache = links
|
||||
var cfg = { links: links }
|
||||
var path = get_links_path()
|
||||
fd.slurpwrite(path, utf8.encode(toml.encode(cfg)))
|
||||
var b = blob(toml.encode(cfg))
|
||||
stone(b)
|
||||
fd.slurpwrite(path, b)
|
||||
}
|
||||
|
||||
Link.add = function(canonical, target, shop) {
|
||||
// Validate canonical package exists in shop
|
||||
var lock = shop.load_lock()
|
||||
if (!lock[canonical]) {
|
||||
throw new Error('Package ' + canonical + ' is not installed. Install it first with: cell get ' + canonical)
|
||||
throw Error('Package ' + canonical + ' is not installed. Install it first with: cell get ' + canonical)
|
||||
}
|
||||
|
||||
// Validate target is a valid package
|
||||
if (target.startsWith('/')) {
|
||||
if (starts_with(target, '/')) {
|
||||
// Local path - must have cell.toml
|
||||
if (!fd.is_file(target + '/cell.toml')) {
|
||||
throw new Error('Target ' + target + ' is not a valid package (no cell.toml)')
|
||||
throw Error('Target ' + target + ' is not a valid package (no cell.toml)')
|
||||
}
|
||||
} else {
|
||||
// Remote package target - ensure it's installed
|
||||
@@ -109,6 +111,37 @@ Link.add = function(canonical, target, shop) {
|
||||
// Create the symlink immediately
|
||||
Link.sync_one(canonical, target, shop)
|
||||
|
||||
// Install dependencies of the linked package
|
||||
// Read the target's cell.toml to find its dependencies
|
||||
var target_path = starts_with(target, '/') ? target : get_package_abs_dir(target)
|
||||
var toml_path = target_path + '/cell.toml'
|
||||
if (fd.is_file(toml_path)) {
|
||||
try {
|
||||
var content = text(fd.slurp(toml_path))
|
||||
var cfg = toml.decode(content)
|
||||
if (cfg.dependencies) {
|
||||
arrfor(array(cfg.dependencies), function(alias) {
|
||||
var dep_locator = cfg.dependencies[alias]
|
||||
// Skip local dependencies that don't exist
|
||||
if (starts_with(dep_locator, '/') && !fd.is_dir(dep_locator)) {
|
||||
log.console(" Skipping missing local dependency: " + dep_locator)
|
||||
return
|
||||
}
|
||||
// Install the dependency if not already in shop
|
||||
try {
|
||||
shop.get(dep_locator)
|
||||
shop.extract(dep_locator)
|
||||
} catch (e) {
|
||||
log.console(` Warning: Could not install dependency ${dep_locator}: ${e.message}`)
|
||||
log.error(e)
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
log.console(` Warning: Could not read dependencies from ${toml_path}`)
|
||||
}
|
||||
}
|
||||
|
||||
log.console("Linked " + canonical + " -> " + target)
|
||||
return true
|
||||
}
|
||||
@@ -133,12 +166,12 @@ Link.remove = function(canonical) {
|
||||
Link.clear = function() {
|
||||
// Remove all symlinks first
|
||||
var links = Link.load()
|
||||
for (var canonical in links) {
|
||||
arrfor(array(links), function(canonical) {
|
||||
var target_dir = get_package_abs_dir(canonical)
|
||||
if (fd.is_link(target_dir)) {
|
||||
fd.unlink(target_dir)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
Link.save({})
|
||||
log.console("Cleared all links")
|
||||
@@ -151,7 +184,7 @@ Link.sync_one = function(canonical, target, shop) {
|
||||
var link_target = resolve_link_target(target)
|
||||
|
||||
// Ensure parent directories exist
|
||||
var parent = target_dir.substring(0, target_dir.lastIndexOf('/'))
|
||||
var parent = fd.dirname(target_dir)
|
||||
ensure_dir(parent)
|
||||
|
||||
// Check current state
|
||||
@@ -177,32 +210,58 @@ Link.sync_one = function(canonical, target, shop) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Sync all links - ensure all symlinks are in place
|
||||
// Sync all links - ensure all symlinks are in place and dependencies are installed
|
||||
Link.sync_all = function(shop) {
|
||||
var links = Link.load()
|
||||
var count = 0
|
||||
var errors = []
|
||||
|
||||
for (var canonical in links) {
|
||||
arrfor(array(links), function(canonical) {
|
||||
var target = links[canonical]
|
||||
try {
|
||||
// Validate target exists
|
||||
var link_target = resolve_link_target(target)
|
||||
if (!fd.is_dir(link_target)) {
|
||||
errors.push(canonical + ': target ' + link_target + ' does not exist')
|
||||
continue
|
||||
push(errors, canonical + ': target ' + link_target + ' does not exist')
|
||||
return
|
||||
}
|
||||
if (!fd.is_file(link_target + '/cell.toml')) {
|
||||
errors.push(canonical + ': target ' + link_target + ' is not a valid package')
|
||||
continue
|
||||
push(errors, canonical + ': target ' + link_target + ' is not a valid package')
|
||||
return
|
||||
}
|
||||
|
||||
Link.sync_one(canonical, target, shop)
|
||||
|
||||
// Install dependencies of the linked package
|
||||
var toml_path = link_target + '/cell.toml'
|
||||
try {
|
||||
var content = text(fd.slurp(toml_path))
|
||||
var cfg = toml.decode(content)
|
||||
if (cfg.dependencies) {
|
||||
arrfor(array(cfg.dependencies), function(alias) {
|
||||
var dep_locator = cfg.dependencies[alias]
|
||||
// Skip local dependencies that don't exist
|
||||
if (starts_with(dep_locator, '/') && !fd.is_dir(dep_locator)) {
|
||||
return
|
||||
}
|
||||
// Install the dependency if not already in shop
|
||||
try {
|
||||
shop.get(dep_locator)
|
||||
shop.extract(dep_locator)
|
||||
} catch (e) {
|
||||
// Silently continue - dependency may already be installed
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
// Could not read dependencies - continue anyway
|
||||
}
|
||||
|
||||
count++
|
||||
} catch (e) {
|
||||
errors.push(canonical + ': ' + e.message)
|
||||
push(errors, canonical + ': ' + e.message)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return { synced: count, errors: errors }
|
||||
}
|
||||
@@ -219,4 +278,15 @@ Link.get_target = function(canonical) {
|
||||
return links[canonical] || null
|
||||
}
|
||||
|
||||
// Get the canonical package name that links to this target (reverse lookup)
|
||||
// Returns null if no package links to this target
|
||||
Link.get_origin = function(target) {
|
||||
var links = Link.load()
|
||||
var found = null
|
||||
arrfor(array(links), function(origin) {
|
||||
if (links[origin] == target) found = origin
|
||||
})
|
||||
return found
|
||||
}
|
||||
|
||||
return Link
|
||||
|
||||
214
list.ce
214
list.ce
@@ -1,85 +1,169 @@
|
||||
// list installed packages
|
||||
// cell list -> list packages installed in this package
|
||||
// cell list all -> list all packages (including those that are there due to installed packages)
|
||||
// cell list package <name> -> list the packages for the package <name>
|
||||
// cell list [<scope>] - List packages and dependencies
|
||||
//
|
||||
// Usage:
|
||||
// cell list List dependencies of current package
|
||||
// cell list shop List all packages in shop with status
|
||||
// cell list <locator> List dependency tree for a package
|
||||
|
||||
var shop = use('internal/shop')
|
||||
var pkg = use('package')
|
||||
var link = use('link')
|
||||
var fd = use('fd')
|
||||
|
||||
var mode = 'local'
|
||||
var target_pkg = null
|
||||
|
||||
if (args && args.length > 0) {
|
||||
if (args[0] == 'all') {
|
||||
mode = 'all'
|
||||
} else if (args[0] == 'shop') {
|
||||
mode = 'shop'
|
||||
} else if (args[0] == 'package') {
|
||||
if (args.length < 2) {
|
||||
log.console("Usage: cell list package <name>")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
mode = 'package'
|
||||
target_pkg = args[1]
|
||||
} else {
|
||||
log.console("Usage:")
|
||||
log.console(" cell list : list local packages")
|
||||
log.console(" cell list all : list all recursive packages")
|
||||
log.console(" cell list package <name>: list dependencies of <name>")
|
||||
log.console(" cell list shop : list all packages in shop")
|
||||
$stop()
|
||||
return
|
||||
if (args && length(args) > 0) {
|
||||
if (args[0] == 'shop') {
|
||||
mode = 'shop'
|
||||
} else if (args[0] == '--help' || args[0] == '-h') {
|
||||
log.console("Usage: cell list [<scope>]")
|
||||
log.console("")
|
||||
log.console("List packages and dependencies.")
|
||||
log.console("")
|
||||
log.console("Scopes:")
|
||||
log.console(" (none) List dependencies of current package")
|
||||
log.console(" shop List all packages in shop with status")
|
||||
log.console(" <locator> List dependency tree for a package")
|
||||
$stop()
|
||||
} else {
|
||||
mode = 'package'
|
||||
target_pkg = args[0]
|
||||
|
||||
// Resolve local paths
|
||||
if (target_pkg == '.' || starts_with(target_pkg, './') || starts_with(target_pkg, '../') || fd.is_dir(target_pkg)) {
|
||||
var resolved = fd.realpath(target_pkg)
|
||||
if (resolved) {
|
||||
target_pkg = resolved
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var links = link.load()
|
||||
var lock = shop.load_lock()
|
||||
|
||||
function print_deps(ctx, indent) {
|
||||
indent = indent || ""
|
||||
var deps
|
||||
try {
|
||||
deps = pkg.dependencies(ctx)
|
||||
} catch (e) {
|
||||
log.console(indent + " (could not read dependencies)")
|
||||
return
|
||||
}
|
||||
|
||||
if (!deps) {
|
||||
log.console(indent + " (none)")
|
||||
return
|
||||
}
|
||||
|
||||
var aliases = array(deps)
|
||||
aliases = sort(aliases)
|
||||
|
||||
if (length(aliases) == 0) {
|
||||
log.console(indent + " (none)")
|
||||
return
|
||||
}
|
||||
|
||||
for (var i = 0; i < length(aliases); i++) {
|
||||
var alias = aliases[i]
|
||||
var locator = deps[alias]
|
||||
var link_target = links[locator]
|
||||
var lock_entry = lock[locator]
|
||||
|
||||
var line = indent + " " + alias
|
||||
if (alias != locator) {
|
||||
line += " -> " + locator
|
||||
}
|
||||
|
||||
// Add status indicators
|
||||
var status = []
|
||||
if (link_target) {
|
||||
push(status, "linked -> " + link_target)
|
||||
}
|
||||
if (lock_entry && lock_entry.commit) {
|
||||
push(status, "@" + text(lock_entry.commit, 0, 8))
|
||||
}
|
||||
if (lock_entry && lock_entry.type == 'local') {
|
||||
push(status, "local")
|
||||
}
|
||||
if (!lock_entry) {
|
||||
push(status, "not installed")
|
||||
}
|
||||
|
||||
if (length(status) > 0) {
|
||||
line += " [" + text(status, ", ") + "]"
|
||||
}
|
||||
|
||||
log.console(line)
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == 'local') {
|
||||
log.console("Installed Packages (Local):")
|
||||
print_deps(null)
|
||||
log.console("Dependencies:")
|
||||
print_deps(null)
|
||||
} else if (mode == 'package') {
|
||||
// Resolve alias to canonical package path
|
||||
var canon = shop.get_canonical_package(target_pkg, null)
|
||||
if (!canon) {
|
||||
log.console("Package '" + target_pkg + "' not found in local dependencies.")
|
||||
} else {
|
||||
log.console("Dependencies for " + target_pkg + " (" + canon + "):")
|
||||
print_deps(canon)
|
||||
}
|
||||
} else if (mode == 'all') {
|
||||
log.console("All Packages:")
|
||||
var all = shop.list_packages(null)
|
||||
// list_packages returns an array of package strings (locators)
|
||||
// We want to perhaps sort them
|
||||
all.sort()
|
||||
for (var i = 0; i < all.length; i++) {
|
||||
log.console(" " + all[i])
|
||||
}
|
||||
if (all.length == 0) log.console(" (none)")
|
||||
log.console("Dependencies for " + target_pkg + ":")
|
||||
print_deps(target_pkg)
|
||||
} else if (mode == 'shop') {
|
||||
log.console("Shop Packages:")
|
||||
var all = shop.list_packages()
|
||||
log.console("Shop packages:")
|
||||
log.console("")
|
||||
|
||||
if (all.length == 0)
|
||||
log.console(" (none)")
|
||||
else
|
||||
all.forEach(package => log.console(" " + package))
|
||||
}
|
||||
var packages = shop.list_packages()
|
||||
if (length(packages) == 0) {
|
||||
log.console(" (none)")
|
||||
} else {
|
||||
packages = sort(packages)
|
||||
|
||||
function print_deps(ctx) {
|
||||
var deps = pkg.dependencies(ctx)
|
||||
var aliases = []
|
||||
for (var k in deps) aliases.push(k)
|
||||
aliases.sort()
|
||||
// Group by type
|
||||
var local_pkgs = []
|
||||
var linked_pkgs = []
|
||||
var remote_pkgs = []
|
||||
|
||||
if (aliases.length == 0) {
|
||||
log.console(" (none)")
|
||||
} else {
|
||||
for (var i = 0; i < aliases.length; i++) {
|
||||
var alias = aliases[i]
|
||||
var locator = deps[alias]
|
||||
log.console(" " + alias + " -> " + locator)
|
||||
}
|
||||
arrfor(packages, function(p) {
|
||||
if (p == 'core') return
|
||||
var lock_entry = lock[p]
|
||||
var link_target = links[p]
|
||||
|
||||
if (link_target) {
|
||||
push(linked_pkgs, p)
|
||||
} else if (lock_entry && lock_entry.type == 'local') {
|
||||
push(local_pkgs, p)
|
||||
} else {
|
||||
push(remote_pkgs, p)
|
||||
}
|
||||
})
|
||||
|
||||
if (length(linked_pkgs) > 0) {
|
||||
log.console("Linked packages:")
|
||||
arrfor(linked_pkgs, function(p) {
|
||||
var target = links[p]
|
||||
log.console(" " + p + " -> " + target)
|
||||
})
|
||||
log.console("")
|
||||
}
|
||||
|
||||
if (length(local_pkgs) > 0) {
|
||||
log.console("Local packages:")
|
||||
arrfor(local_pkgs, function(p) {
|
||||
log.console(" " + p)
|
||||
})
|
||||
log.console("")
|
||||
}
|
||||
|
||||
if (length(remote_pkgs) > 0) {
|
||||
log.console("Remote packages:")
|
||||
arrfor(remote_pkgs, function(p) {
|
||||
var lock_entry = lock[p]
|
||||
var commit = lock_entry && lock_entry.commit ? " @" + text(lock_entry.commit, 0, 8) : ""
|
||||
log.console(" " + p + commit)
|
||||
})
|
||||
log.console("")
|
||||
}
|
||||
|
||||
log.console("Total: " + text(length(packages)) + " package(s)")
|
||||
}
|
||||
}
|
||||
|
||||
$stop()
|
||||
|
||||
12
ls.ce
12
ls.ce
@@ -11,22 +11,22 @@ var modules = package.list_modules(pkg)
|
||||
var programs = package.list_programs(pkg)
|
||||
|
||||
log.console("Modules in " + pkg + ":")
|
||||
modules.sort()
|
||||
if (modules.length == 0) {
|
||||
modules = sort(modules)
|
||||
if (length(modules) == 0) {
|
||||
log.console(" (none)")
|
||||
} else {
|
||||
for (var i = 0; i < modules.length; i++) {
|
||||
for (var i = 0; i < length(modules); i++) {
|
||||
log.console(" " + modules[i])
|
||||
}
|
||||
}
|
||||
|
||||
log.console("")
|
||||
log.console("Programs in " + pkg + ":")
|
||||
programs.sort()
|
||||
if (programs.length == 0) {
|
||||
programs = sort(programs)
|
||||
if (length(programs) == 0) {
|
||||
log.console(" (none)")
|
||||
} else {
|
||||
for (var i = 0; i < programs.length; i++) {
|
||||
for (var i = 0; i < length(programs); i++) {
|
||||
log.console(" " + programs[i])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
var cycles = {}
|
||||
var Math_obj = Math
|
||||
|
||||
cycles.arc_cosine = function(x) { return Math_obj.acos(x) / (2 * pi) }
|
||||
cycles.arc_sine = function(x) { return Math_obj.asin(x) / (2 * pi) }
|
||||
cycles.arc_tangent = function(x) { return Math_obj.atan(x) / (2 * pi) }
|
||||
cycles.cosine = function(x) { return Math_obj.cos(x * 2 * pi) }
|
||||
cycles.e = Math_obj.E
|
||||
cycles.ln = function(x) { return Math_obj.log(x) }
|
||||
cycles.log = function(x) { return Math_obj.log10(x) }
|
||||
cycles.log2 = function(x) { return Math_obj.log2(x) }
|
||||
cycles.power = function(x, y) { return Math_obj.pow(x, y) }
|
||||
cycles.root = function(x, y) { return Math_obj.pow(x, 1 / y) }
|
||||
cycles.sine = function(x) { return Math_obj.sin(x * 2 * pi) }
|
||||
cycles.sqrt = function(x) { return Math_obj.sqrt(x) }
|
||||
cycles.tangent = function(x) { return Math_obj.tan(x * 2 * pi) }
|
||||
|
||||
return cycles
|
||||
@@ -1,21 +0,0 @@
|
||||
var degrees = {}
|
||||
var Math_obj = Math
|
||||
|
||||
var deg2rad = pi / 180
|
||||
var rad2deg = 180 / pi
|
||||
|
||||
return {
|
||||
arc_cosine: function(x) { return Math_obj.acos(x) * rad2deg },
|
||||
arc_sine: function(x) { return Math_obj.asin(x) * rad2deg },
|
||||
arc_tangent: function(x) { return Math_obj.atan(x) * rad2deg },
|
||||
cosine: function(x) { return Math_obj.cos(x * deg2rad) },
|
||||
e: Math_obj.E,
|
||||
ln: function(x) { return Math_obj.log(x) },
|
||||
log: function(x) { return Math_obj.log10(x) },
|
||||
log2: function(x) { return Math_obj.log2(x) },
|
||||
power: function(x, y) { return Math_obj.pow(x, y) },
|
||||
root: function(x, y) { return Math_obj.pow(x, 1/y) },
|
||||
sine: function(x) { return Math_obj.sin(x * deg2rad) },
|
||||
sqrt: function(x) { return Math_obj.sqrt(x) },
|
||||
tangent: function(x) { return Math_obj.tan(x * deg2rad) }
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
var Math = globalThis.Math
|
||||
|
||||
return {
|
||||
arc_cosine: Math.acos,
|
||||
arc_sine: Math.asin,
|
||||
arc_tangent: Math.atan,
|
||||
cosine: Math.cos,
|
||||
e: Math.E,
|
||||
ln: Math.log,
|
||||
log: Math.log10,
|
||||
log2: Math.log2,
|
||||
power: Math.pow,
|
||||
root: function(x, n) { return Math.pow(x, 1/n) },
|
||||
sine: Math.sin,
|
||||
sqrt: Math.sqrt,
|
||||
tangent: Math.tan
|
||||
}
|
||||
@@ -38,9 +38,9 @@ endif
|
||||
link_args = link
|
||||
sources = []
|
||||
src += [ # core
|
||||
'qjs_blob.c',
|
||||
'monocypher.c',
|
||||
'cell.c',
|
||||
'suite.c',
|
||||
'wildmatch.c',
|
||||
'qjs_actor.c',
|
||||
'qjs_wota.c',
|
||||
@@ -58,8 +58,6 @@ scripts = [
|
||||
'wildstar.c',
|
||||
'fit.c',
|
||||
'crypto.c',
|
||||
'internal/text.c',
|
||||
'utf8.c',
|
||||
'internal/kim.c',
|
||||
'time.c',
|
||||
'internal/nota.c',
|
||||
@@ -70,7 +68,6 @@ scripts = [
|
||||
'net/enet.c',
|
||||
'wildstar.c',
|
||||
'archive/miniz.c',
|
||||
'internal/json.c',
|
||||
]
|
||||
|
||||
foreach file: scripts
|
||||
@@ -78,7 +75,7 @@ foreach file: scripts
|
||||
endforeach
|
||||
|
||||
srceng = 'source'
|
||||
includes = [srceng, 'internal', 'debug', 'net', 'archive', 'math']
|
||||
includes = [srceng, 'internal', 'debug', 'net', 'archive']
|
||||
|
||||
foreach file : src
|
||||
full_path = join_paths(srceng, file)
|
||||
|
||||
40
net/enet.c
40
net/enet.c
@@ -70,7 +70,7 @@ static JSValue js_enet_host_create(JSContext *ctx, JSValueConst this_val, int ar
|
||||
|
||||
JSValue config_obj = argv[0];
|
||||
JSValue addr_val = JS_GetPropertyStr(ctx, config_obj, "address");
|
||||
const char *addr_str = JS_IsString(addr_val) ? JS_ToCString(ctx, addr_val) : NULL;
|
||||
const char *addr_str = JS_IsText(addr_val) ? JS_ToCString(ctx, addr_val) : NULL;
|
||||
JS_FreeValue(ctx, addr_val);
|
||||
|
||||
if (!addr_str)
|
||||
@@ -145,7 +145,7 @@ static JSValue js_enet_host_service(JSContext *ctx, JSValueConst this_val, int a
|
||||
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
|
||||
if (!host) return JS_EXCEPTION;
|
||||
|
||||
if (argc < 1 || !JS_IsFunction(ctx, argv[0])) return JS_ThrowTypeError(ctx, "Expected a callback function as first argument");
|
||||
if (argc < 1 || !JS_IsFunction(argv[0])) return JS_ThrowTypeError(ctx, "Expected a callback function as first argument");
|
||||
JSValue callback = JS_DupValue(ctx, argv[0]);
|
||||
|
||||
double secs;
|
||||
@@ -245,7 +245,7 @@ static JSValue js_enet_host_broadcast(JSContext *ctx, JSValueConst this_val, int
|
||||
size_t data_len = 0;
|
||||
uint8_t *buf = NULL;
|
||||
|
||||
if (JS_IsString(argv[0])) {
|
||||
if (JS_IsText(argv[0])) {
|
||||
data_str = JS_ToCStringLen(ctx, &data_len, argv[0]);
|
||||
if (!data_str) return JS_EXCEPTION;
|
||||
} else if (js_is_blob(ctx,argv[0])) {
|
||||
@@ -310,7 +310,7 @@ static JSValue js_enet_peer_send(JSContext *ctx, JSValueConst this_val, int argc
|
||||
size_t data_len = 0;
|
||||
uint8_t *buf = NULL;
|
||||
|
||||
if (JS_IsString(argv[0])) {
|
||||
if (JS_IsText(argv[0])) {
|
||||
data_str = JS_ToCStringLen(ctx, &data_len, argv[0]);
|
||||
if (!data_str) return JS_EXCEPTION;
|
||||
} else if (js_is_blob(ctx,argv[0])) {
|
||||
@@ -437,8 +437,8 @@ static const JSCFunctionListEntry js_enet_host_funcs[] = {
|
||||
JS_CFUNC_DEF("connect", 2, js_enet_host_connect),
|
||||
JS_CFUNC_DEF("flush", 0, js_enet_host_flush),
|
||||
JS_CFUNC_DEF("broadcast", 1, js_enet_host_broadcast),
|
||||
JS_CGETSET_DEF("port", js_enet_host_get_port, NULL),
|
||||
JS_CGETSET_DEF("address", js_enet_host_get_address, NULL),
|
||||
// JS_CGETSET_DEF("port", js_enet_host_get_port, NULL),
|
||||
// JS_CGETSET_DEF("address", js_enet_host_get_address, NULL),
|
||||
};
|
||||
|
||||
static JSValue js_enet_peer_get_rtt(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
|
||||
@@ -552,20 +552,20 @@ static const JSCFunctionListEntry js_enet_peer_funcs[] = {
|
||||
JS_CFUNC_DEF("ping", 0, js_enet_peer_ping),
|
||||
JS_CFUNC_DEF("throttle_configure", 3, js_enet_peer_throttle_configure),
|
||||
JS_CFUNC_DEF("timeout", 3, js_enet_peer_timeout),
|
||||
JS_CGETSET_DEF("rtt", js_enet_peer_get_rtt, NULL),
|
||||
JS_CGETSET_DEF("incoming_bandwidth", js_enet_peer_get_incoming_bandwidth, NULL),
|
||||
JS_CGETSET_DEF("outgoing_bandwidth", js_enet_peer_get_outgoing_bandwidth, NULL),
|
||||
JS_CGETSET_DEF("last_send_time", js_enet_peer_get_last_send_time, NULL),
|
||||
JS_CGETSET_DEF("last_receive_time", js_enet_peer_get_last_receive_time, NULL),
|
||||
JS_CGETSET_DEF("mtu", js_enet_peer_get_mtu, NULL),
|
||||
JS_CGETSET_DEF("outgoing_data_total", js_enet_peer_get_outgoing_data_total, NULL),
|
||||
JS_CGETSET_DEF("incoming_data_total", js_enet_peer_get_incoming_data_total, NULL),
|
||||
JS_CGETSET_DEF("rtt_variance", js_enet_peer_get_rtt_variance, NULL),
|
||||
JS_CGETSET_DEF("packet_loss", js_enet_peer_get_packet_loss, NULL),
|
||||
JS_CGETSET_DEF("state", js_enet_peer_get_state, NULL),
|
||||
JS_CGETSET_DEF("reliable_data_in_transit", js_enet_peer_get_reliable_data_in_transit, NULL),
|
||||
JS_CGETSET_DEF("port", js_enet_peer_get_port, NULL),
|
||||
JS_CGETSET_DEF("address", js_enet_peer_get_address, NULL),
|
||||
// JS_CGETSET_DEF("rtt", js_enet_peer_get_rtt, NULL),
|
||||
// JS_CGETSET_DEF("incoming_bandwidth", js_enet_peer_get_incoming_bandwidth, NULL),
|
||||
// JS_CGETSET_DEF("outgoing_bandwidth", js_enet_peer_get_outgoing_bandwidth, NULL),
|
||||
// JS_CGETSET_DEF("last_send_time", js_enet_peer_get_last_send_time, NULL),
|
||||
// JS_CGETSET_DEF("last_receive_time", js_enet_peer_get_last_receive_time, NULL),
|
||||
// JS_CGETSET_DEF("mtu", js_enet_peer_get_mtu, NULL),
|
||||
// JS_CGETSET_DEF("outgoing_data_total", js_enet_peer_get_outgoing_data_total, NULL),
|
||||
// JS_CGETSET_DEF("incoming_data_total", js_enet_peer_get_incoming_data_total, NULL),
|
||||
// JS_CGETSET_DEF("rtt_variance", js_enet_peer_get_rtt_variance, NULL),
|
||||
// JS_CGETSET_DEF("packet_loss", js_enet_peer_get_packet_loss, NULL),
|
||||
// JS_CGETSET_DEF("state", js_enet_peer_get_state, NULL),
|
||||
// JS_CGETSET_DEF("reliable_data_in_transit", js_enet_peer_get_reliable_data_in_transit, NULL),
|
||||
// JS_CGETSET_DEF("port", js_enet_peer_get_port, NULL),
|
||||
// JS_CGETSET_DEF("address", js_enet_peer_get_address, NULL),
|
||||
};
|
||||
|
||||
JSValue js_enet_use(JSContext *ctx)
|
||||
|
||||
@@ -293,7 +293,7 @@ static int par_easycurl_to_memory(char const *url, par_byte **data, int *nbytes)
|
||||
// Performs a blocking HTTP GET and returns a QuickJS Blob of the body
|
||||
static JSValue js_fetch_picoparser(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
if (argc < 1 || !JS_IsString(argv[0]))
|
||||
if (argc < 1 || !JS_IsText(argv[0]))
|
||||
return JS_ThrowTypeError(ctx, "fetch: URL string required");
|
||||
|
||||
const char *url = JS_ToCString(ctx, argv[0]);
|
||||
|
||||
@@ -135,7 +135,7 @@ static int parse_url(const char *url, char **host, int *port, char **path, int *
|
||||
// Performs a blocking HTTP GET and returns a QuickJS Blob of the body
|
||||
static JSValue js_fetch_playdate(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
if (argc < 1 || !JS_IsString(argv[0]))
|
||||
if (argc < 1 || !JS_IsText(argv[0]))
|
||||
return JS_ThrowTypeError(ctx, "fetch: URL string required");
|
||||
|
||||
if (!pd_network || !pd_network->http) {
|
||||
|
||||
12
net/socket.c
12
net/socket.c
@@ -144,7 +144,7 @@ JSC_CCALL(socket_socket,
|
||||
int protocol = 0;
|
||||
|
||||
// Parse domain
|
||||
if (JS_IsString(argv[0])) {
|
||||
if (JS_IsText(argv[0])) {
|
||||
const char *domain_str = JS_ToCString(js, argv[0]);
|
||||
if (strcmp(domain_str, "AF_INET") == 0) domain = AF_INET;
|
||||
else if (strcmp(domain_str, "AF_INET6") == 0) domain = AF_INET6;
|
||||
@@ -156,7 +156,7 @@ JSC_CCALL(socket_socket,
|
||||
|
||||
// Parse type
|
||||
if (argc > 1) {
|
||||
if (JS_IsString(argv[1])) {
|
||||
if (JS_IsText(argv[1])) {
|
||||
const char *type_str = JS_ToCString(js, argv[1]);
|
||||
if (strcmp(type_str, "SOCK_STREAM") == 0) type = SOCK_STREAM;
|
||||
else if (strcmp(type_str, "SOCK_DGRAM") == 0) type = SOCK_DGRAM;
|
||||
@@ -297,7 +297,7 @@ JSC_CCALL(socket_send,
|
||||
flags = js2number(js, argv[2]);
|
||||
}
|
||||
|
||||
if (JS_IsString(argv[1])) {
|
||||
if (JS_IsText(argv[1])) {
|
||||
const char *data = JS_ToCStringLen(js, &len, argv[1]);
|
||||
sent = send(sockfd, data, len, flags);
|
||||
JS_FreeCString(js, data);
|
||||
@@ -385,7 +385,7 @@ JSC_CCALL(socket_sendto,
|
||||
to_len = sizeof(addr);
|
||||
}
|
||||
|
||||
if (JS_IsString(argv[1])) {
|
||||
if (JS_IsText(argv[1])) {
|
||||
const char *data = JS_ToCStringLen(js, &len, argv[1]);
|
||||
sent = sendto(sockfd, data, len, flags, to_addr, to_len);
|
||||
JS_FreeCString(js, data);
|
||||
@@ -525,7 +525,7 @@ JSC_CCALL(socket_setsockopt,
|
||||
int optname = 0;
|
||||
|
||||
// Parse level
|
||||
if (JS_IsString(argv[1])) {
|
||||
if (JS_IsText(argv[1])) {
|
||||
const char *level_str = JS_ToCString(js, argv[1]);
|
||||
if (strcmp(level_str, "SOL_SOCKET") == 0) level = SOL_SOCKET;
|
||||
else if (strcmp(level_str, "IPPROTO_TCP") == 0) level = IPPROTO_TCP;
|
||||
@@ -537,7 +537,7 @@ JSC_CCALL(socket_setsockopt,
|
||||
}
|
||||
|
||||
// Parse option name
|
||||
if (JS_IsString(argv[2])) {
|
||||
if (JS_IsText(argv[2])) {
|
||||
const char *opt_str = JS_ToCString(js, argv[2]);
|
||||
if (strcmp(opt_str, "SO_REUSEADDR") == 0) optname = SO_REUSEADDR;
|
||||
else if (strcmp(opt_str, "SO_KEEPALIVE") == 0) optname = SO_KEEPALIVE;
|
||||
|
||||
34
pack.ce
34
pack.ce
@@ -14,7 +14,7 @@ var output_name = 'app'
|
||||
var target_package = null
|
||||
var buildtype = 'debug'
|
||||
|
||||
if (args.length < 1) {
|
||||
if (length(args) < 1) {
|
||||
log.error('Usage: cell pack <package> [options]')
|
||||
log.error('')
|
||||
log.error('Options:')
|
||||
@@ -22,30 +22,30 @@ if (args.length < 1) {
|
||||
log.error(' -t, --target <target> Cross-compile for target platform')
|
||||
log.error(' -b, --buildtype <type> Build type: release, debug, minsize (default: release)')
|
||||
log.error('')
|
||||
log.error('Available targets: ' + build.list_targets().join(', '))
|
||||
log.error('Available targets: ' + text(build.list_targets(), ', '))
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
|
||||
target_package = args[0]
|
||||
|
||||
for (var i = 1; i < args.length; i++) {
|
||||
for (var i = 1; i < length(args); i++) {
|
||||
if (args[i] == '-t' || args[i] == '--target') {
|
||||
if (i + 1 < args.length) {
|
||||
if (i + 1 < length(args)) {
|
||||
target = args[++i]
|
||||
} else {
|
||||
log.error('-t requires a target')
|
||||
$stop()
|
||||
}
|
||||
} else if (args[i] == '-o' || args[i] == '--output') {
|
||||
if (i + 1 < args.length) {
|
||||
if (i + 1 < length(args)) {
|
||||
output_name = args[++i]
|
||||
} else {
|
||||
log.error('-o requires an output name')
|
||||
$stop()
|
||||
}
|
||||
} else if (args[i] == '-b' || args[i] == '--buildtype') {
|
||||
if (i + 1 < args.length) {
|
||||
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')
|
||||
@@ -63,7 +63,7 @@ for (var i = 1; i < args.length; i++) {
|
||||
log.console(' -t, --target <target> Cross-compile for target platform')
|
||||
log.console(' -b, --buildtype <type> Build type: release, debug, minsize (default: release)')
|
||||
log.console('')
|
||||
log.console('Available targets: ' + build.list_targets().join(', '))
|
||||
log.console('Available targets: ' + text(build.list_targets(), ', '))
|
||||
$stop()
|
||||
} else {
|
||||
log.error('Unknown option: ' + args[i])
|
||||
@@ -79,7 +79,7 @@ if (!target) {
|
||||
|
||||
if (target && !build.has_target(target)) {
|
||||
log.error('Invalid target: ' + target)
|
||||
log.console('Available targets: ' + build.list_targets().join(', '))
|
||||
log.console('Available targets: ' + text(build.list_targets(), ', '))
|
||||
$stop()
|
||||
}
|
||||
|
||||
@@ -87,29 +87,29 @@ if (target && !build.has_target(target)) {
|
||||
var packages = ['core']
|
||||
var deps = pkg_tools.gather_dependencies(target_package)
|
||||
|
||||
for (var i = 0; i < deps.length; i++) {
|
||||
packages.push(deps[i])
|
||||
for (var i = 0; i < length(deps); i++) {
|
||||
push(packages, deps[i])
|
||||
}
|
||||
packages.push(target_package)
|
||||
push(packages, target_package)
|
||||
|
||||
// Remove duplicates
|
||||
var unique_packages = []
|
||||
var seen = {}
|
||||
for (var i = 0; i < packages.length; i++) {
|
||||
for (var i = 0; i < length(packages); i++) {
|
||||
if (!seen[packages[i]]) {
|
||||
seen[packages[i]] = true
|
||||
unique_packages.push(packages[i])
|
||||
push(unique_packages, packages[i])
|
||||
}
|
||||
}
|
||||
packages = unique_packages
|
||||
|
||||
log.console('Preparing packages...')
|
||||
for (var package of packages) {
|
||||
if (package == 'core') continue
|
||||
arrfor(packages, function(package) {
|
||||
if (package == 'core') return
|
||||
shop.extract(package)
|
||||
}
|
||||
})
|
||||
|
||||
log.console('Building static binary from ' + text(packages.length) + ' packages: ' + packages.join(', '))
|
||||
log.console('Building static binary from ' + text(length(packages)) + ' packages: ' + text(packages, ', '))
|
||||
|
||||
try {
|
||||
var result = build.build_static(packages, target, output_name, buildtype)
|
||||
|
||||
158
package.cm
158
package.cm
@@ -1,17 +1,21 @@
|
||||
var package = {}
|
||||
|
||||
var fd = use('fd')
|
||||
var toml = use('toml')
|
||||
var json = use('json')
|
||||
var os = use('os')
|
||||
var link = use('link')
|
||||
|
||||
// Cache for loaded configs to avoid toml re-parsing corruption
|
||||
var config_cache = {}
|
||||
|
||||
// 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 (pkg.startsWith('/'))
|
||||
return pkg.replaceAll('/', '_').replaceAll('@', '_')
|
||||
return pkg.replaceAll('@', '_')
|
||||
if (starts_with(pkg, '/'))
|
||||
return replace(replace(pkg, '/', '_'), '@', '_')
|
||||
return replace(pkg, '@', '_')
|
||||
}
|
||||
|
||||
function get_path(name)
|
||||
@@ -20,19 +24,50 @@ function get_path(name)
|
||||
if (!name)
|
||||
return fd.realpath('.')
|
||||
// If name is already an absolute path, use it directly
|
||||
if (name.startsWith('/'))
|
||||
if (starts_with(name, '/'))
|
||||
return name
|
||||
|
||||
// Check if this package is linked - if so, use the link target directly
|
||||
// This avoids symlink-related issues with file reading
|
||||
var link_target = link.get_target(name)
|
||||
if (link_target) {
|
||||
// If link target is a local path, use it directly
|
||||
if (starts_with(link_target, '/'))
|
||||
return link_target
|
||||
// Otherwise it's another package name, resolve that
|
||||
return os.global_shop_path + '/packages/' + replace(replace(link_target, '/', '_'), '@', '_')
|
||||
}
|
||||
|
||||
// Remote packages use nested directories, so don't transform slashes
|
||||
return os.global_shop_path + '/packages/' + name.replaceAll('@', '_')
|
||||
return os.global_shop_path + '/packages/' + replace(name, '@', '_')
|
||||
}
|
||||
|
||||
package.load_config = function(name)
|
||||
{
|
||||
var config_path = get_path(name) + '/cell.toml'
|
||||
if (!fd.is_file(config_path))
|
||||
throw new Error(`${config_path} isn't a path`)
|
||||
|
||||
return toml.decode(text(fd.slurp(config_path)))
|
||||
// Return cached config if available
|
||||
if (config_cache[config_path])
|
||||
return config_cache[config_path]
|
||||
|
||||
if (!fd.is_file(config_path)) {
|
||||
throw Error(`${config_path} does not exist`)
|
||||
}
|
||||
|
||||
var content = text(fd.slurp(config_path))
|
||||
if (!content || length(trim(content)) == 0)
|
||||
return {}
|
||||
|
||||
var result = toml.decode(content)
|
||||
if (!result) {
|
||||
return {}
|
||||
}
|
||||
|
||||
// Deep copy to avoid toml module's shared state bug and cache it
|
||||
result = json.decode(json.encode(result))
|
||||
config_cache[config_path] = result
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
package.save_config = function(name, config)
|
||||
@@ -51,11 +86,11 @@ package.find_alias = function(name, locator)
|
||||
var config = package.load_config(name)
|
||||
if (!config.dependencies) return null
|
||||
|
||||
for (var alias in config.dependencies)
|
||||
if (config.dependencies[alias] == locator)
|
||||
return alias
|
||||
|
||||
return null
|
||||
var found = null
|
||||
arrfor(array(config.dependencies), function(alias) {
|
||||
if (config.dependencies[alias] == locator) found = alias
|
||||
})
|
||||
return found
|
||||
}
|
||||
|
||||
package.alias_to_package = function(name, alias)
|
||||
@@ -95,19 +130,15 @@ package.find_package_dir = function(file)
|
||||
var absolute = fd.realpath(file)
|
||||
|
||||
var dir = absolute
|
||||
if (fd.is_file(dir)) {
|
||||
var last_slash = dir.lastIndexOf('/')
|
||||
if (last_slash > 0) dir = dir.substring(0, last_slash)
|
||||
}
|
||||
if (fd.is_file(dir))
|
||||
dir = fd.dirname(dir)
|
||||
|
||||
while (dir && dir.length > 0) {
|
||||
while (dir && length(dir) > 0) {
|
||||
var toml_path = dir + '/cell.toml'
|
||||
if (fd.is_file(toml_path)) {
|
||||
return dir
|
||||
}
|
||||
var last_slash = dir.lastIndexOf('/')
|
||||
if (last_slash <= 0) break
|
||||
dir = dir.substring(0, last_slash)
|
||||
dir = fd.dirname(dir)
|
||||
}
|
||||
|
||||
return null
|
||||
@@ -120,18 +151,25 @@ package.find_package_dir = function(file)
|
||||
// Returns null if no alias is found for the given path
|
||||
package.split_alias = function(name, path)
|
||||
{
|
||||
if (!path || path.length == 0) {
|
||||
if (!path || length(path) == 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
var parts = path.split('/')
|
||||
var parts = array(path, '/')
|
||||
var first_part = parts[0]
|
||||
|
||||
var config = package.load_config(name)
|
||||
if (config.dependencies && config.dependencies[first_part]) {
|
||||
var dep_locator = config.dependencies[first_part]
|
||||
var remaining_path = parts.slice(1).join('/')
|
||||
return { package: dep_locator, path: remaining_path }
|
||||
try {
|
||||
var config = package.load_config(name)
|
||||
if (!config) return null
|
||||
|
||||
var deps = config.dependencies
|
||||
if (deps && deps[first_part]) {
|
||||
var dep_locator = deps[first_part]
|
||||
var remaining_path = text(array(parts, 1), '/')
|
||||
return { package: dep_locator, path: remaining_path }
|
||||
}
|
||||
} catch (e) {
|
||||
// Config doesn't exist or couldn't be loaded
|
||||
}
|
||||
|
||||
return null
|
||||
@@ -149,13 +187,13 @@ package.gather_dependencies = function(name)
|
||||
var deps = package.dependencies(pkg_name)
|
||||
if (!deps) return
|
||||
|
||||
for (var alias in deps) {
|
||||
arrfor(array(deps), function(alias) {
|
||||
var locator = deps[alias]
|
||||
if (!all_deps[locator]) {
|
||||
all_deps[locator] = true
|
||||
gather_recursive(locator)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
gather_recursive(name)
|
||||
@@ -171,10 +209,10 @@ package.list_files = function(pkg) {
|
||||
var list = fd.readdir(current_dir)
|
||||
if (!list) return
|
||||
|
||||
for (var i = 0; i < list.length; i++) {
|
||||
for (var i = 0; i < length(list); i++) {
|
||||
var item = list[i]
|
||||
if (item == '.' || item == '..') continue
|
||||
if (item.startsWith('.')) continue
|
||||
if (starts_with(item, '.')) continue
|
||||
|
||||
// Skip build directories in root
|
||||
|
||||
@@ -185,7 +223,7 @@ package.list_files = function(pkg) {
|
||||
if (st.isDirectory) {
|
||||
walk(full_path, rel_path)
|
||||
} else {
|
||||
files.push(rel_path)
|
||||
push(files, rel_path)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,9 +237,9 @@ package.list_files = function(pkg) {
|
||||
package.list_modules = function(name) {
|
||||
var files = package.list_files(name)
|
||||
var modules = []
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
if (files[i].endsWith('.cm')) {
|
||||
modules.push(files[i].substring(0, files[i].length - 3))
|
||||
for (var i = 0; i < length(files); i++) {
|
||||
if (ends_with(files[i], '.cm')) {
|
||||
push(modules, text(files[i], 0, -3))
|
||||
}
|
||||
}
|
||||
return modules
|
||||
@@ -210,9 +248,9 @@ package.list_modules = function(name) {
|
||||
package.list_programs = function(name) {
|
||||
var files = package.list_files(name)
|
||||
var programs = []
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
if (files[i].endsWith('.ce')) {
|
||||
programs.push(files[i].substring(0, files[i].length - 3))
|
||||
for (var i = 0; i < length(files); i++) {
|
||||
if (ends_with(files[i], '.ce')) {
|
||||
push(programs, text(files[i], 0, -3))
|
||||
}
|
||||
}
|
||||
return programs
|
||||
@@ -229,13 +267,13 @@ package.get_flags = function(name, flag_type, target) {
|
||||
// Base flags
|
||||
if (config.compilation && config.compilation[flag_type]) {
|
||||
var base = config.compilation[flag_type]
|
||||
flags = flags.concat(base.split(/\s+/).filter(function(f) { return f.length > 0 }))
|
||||
flags = array(flags, filter(array(base, /\s+/), function(f) { return length(f) > 0 }))
|
||||
}
|
||||
|
||||
// Target-specific flags
|
||||
if (target && config.compilation && config.compilation[target] && config.compilation[target][flag_type]) {
|
||||
var target_flags = config.compilation[target][flag_type]
|
||||
flags = flags.concat(target_flags.split(/\s+/).filter(function(f) { return f.length > 0 }))
|
||||
flags = array(flags, filter(array(target_flags, /\s+/), function(f) { return length(f) > 0 }))
|
||||
}
|
||||
|
||||
return flags
|
||||
@@ -252,31 +290,27 @@ package.get_c_files = function(name, target, exclude_main) {
|
||||
// Group files by their base name (without target suffix)
|
||||
var groups = {} // base_key -> { generic: file, variants: { target: file } }
|
||||
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
for (var i = 0; i < length(files); i++) {
|
||||
var file = files[i]
|
||||
if (!file.endsWith('.c') && !file.endsWith('.cpp')) continue
|
||||
if (!ends_with(file, '.c') && !ends_with(file, '.cpp')) continue
|
||||
|
||||
var ext = file.endsWith('.cpp') ? '.cpp' : '.c'
|
||||
var base = file.substring(0, file.length - ext.length)
|
||||
var dir = ''
|
||||
var name_part = base
|
||||
var slash = base.lastIndexOf('/')
|
||||
if (slash >= 0) {
|
||||
dir = base.substring(0, slash + 1)
|
||||
name_part = base.substring(slash + 1)
|
||||
}
|
||||
var ext = ends_with(file, '.cpp') ? '.cpp' : '.c'
|
||||
var base = text(file, 0, -length(ext))
|
||||
var name_part = fd.basename(base)
|
||||
var dir_part = fd.dirname(base)
|
||||
var dir = (dir_part && dir_part != '.') ? dir_part + '/' : ''
|
||||
|
||||
// Check for target suffix
|
||||
var is_variant = false
|
||||
var variant_target = null
|
||||
var generic_name = name_part
|
||||
|
||||
for (var t = 0; t < known_targets.length; t++) {
|
||||
for (var t = 0; t < length(known_targets); t++) {
|
||||
var suffix = '_' + known_targets[t]
|
||||
if (name_part.endsWith(suffix)) {
|
||||
if (ends_with(name_part, suffix)) {
|
||||
is_variant = true
|
||||
variant_target = known_targets[t]
|
||||
generic_name = name_part.substring(0, name_part.length - suffix.length)
|
||||
generic_name = text(name_part, 0, -length(suffix))
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -295,7 +329,7 @@ package.get_c_files = function(name, target, exclude_main) {
|
||||
|
||||
// Select appropriate file from each group
|
||||
var result = []
|
||||
for (var key in groups) {
|
||||
arrfor(array(groups), function(key) {
|
||||
var group = groups[key]
|
||||
var selected = null
|
||||
|
||||
@@ -309,14 +343,12 @@ package.get_c_files = function(name, target, exclude_main) {
|
||||
if (selected) {
|
||||
// Skip main.c if requested
|
||||
if (exclude_main) {
|
||||
var basename = selected
|
||||
var s = selected.lastIndexOf('/')
|
||||
if (s >= 0) basename = selected.substring(s + 1)
|
||||
if (basename == 'main.c' || basename.startsWith('main_')) continue
|
||||
var basename = fd.basename(selected)
|
||||
if (basename == 'main.c' || starts_with(basename, 'main_')) return
|
||||
}
|
||||
result.push(selected)
|
||||
push(result, selected)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
224
parseq.cm
224
parseq.cm
@@ -1,224 +0,0 @@
|
||||
// parseq.js (Misty edition)
|
||||
// Douglas Crockford → adapted for Misty by ChatGPT, 2025‑05‑29
|
||||
// Better living thru eventuality!
|
||||
|
||||
/*
|
||||
The original parseq.js relied on the browser's setTimeout and ran in
|
||||
milliseconds. In Misty we may be given an optional @.delay capability
|
||||
(arguments[0]) and time limits are expressed in **seconds**. This rewrite
|
||||
removes the setTimeout dependency, uses the @.delay capability when it is
|
||||
present, and provides the factories described in the Misty specification:
|
||||
|
||||
fallback, par_all, par_any, race, sequence
|
||||
|
||||
Each factory returns a **requestor** function as described by the spec.
|
||||
*/
|
||||
|
||||
def delay = arg[0] // may be null
|
||||
|
||||
// ———————————————————————————————————————— helpers
|
||||
|
||||
function make_reason (factory, excuse, evidence) {
|
||||
def reason = new Error(`parseq.${factory}${excuse ? ': ' + excuse : ''}`)
|
||||
reason.evidence = evidence
|
||||
return reason
|
||||
}
|
||||
|
||||
function is_requestor (fn) {
|
||||
return typeof fn == 'function' && (fn.length == 1 || fn.length == 2)
|
||||
}
|
||||
|
||||
function check_requestors (list, factory) {
|
||||
if (!isa(list, array) || list.some(r => !is_requestor(r)))
|
||||
throw make_reason(factory, 'Bad requestor list.', list)
|
||||
}
|
||||
|
||||
function check_callback (cb, factory) {
|
||||
if (typeof cb != 'function' || cb.length != 2)
|
||||
throw make_reason(factory, 'Not a callback.', cb)
|
||||
}
|
||||
|
||||
function schedule (fn, seconds) {
|
||||
if (seconds == null || seconds <= 0) return fn()
|
||||
if (typeof delay == 'function') return delay(fn, seconds)
|
||||
throw make_reason('schedule', '@.delay capability required for timeouts.')
|
||||
}
|
||||
|
||||
// ———————————————————————————————————————— core runner
|
||||
|
||||
function run (factory, requestors, initial, action, time_limit, throttle = 0) {
|
||||
let cancel_list = new Array(requestors.length)
|
||||
let next = 0
|
||||
let timer_cancel
|
||||
|
||||
function cancel (reason = make_reason(factory, 'Cancel.')) {
|
||||
if (timer_cancel) timer_cancel(), timer_cancel = null
|
||||
if (!cancel_list) return
|
||||
cancel_list.forEach(c => { try { if (typeof c == 'function') c(reason) } catch (_) {} })
|
||||
cancel_list = null
|
||||
}
|
||||
|
||||
function start_requestor (value) {
|
||||
if (!cancel_list || next >= requestors.length) return
|
||||
let idx = next++
|
||||
def req = requestors[idx]
|
||||
|
||||
try {
|
||||
cancel_list[idx] = req(function req_cb (val, reason) {
|
||||
if (!cancel_list || idx == null) return
|
||||
cancel_list[idx] = null
|
||||
action(val, reason, idx)
|
||||
idx = null
|
||||
if (factory == 'sequence') start_requestor(val)
|
||||
else if (throttle) start_requestor(initial)
|
||||
}, value)
|
||||
} catch (ex) {
|
||||
action(null, ex, idx)
|
||||
idx = null
|
||||
if (factory == 'sequence') start_requestor(value)
|
||||
else if (throttle) start_requestor(initial)
|
||||
}
|
||||
}
|
||||
|
||||
if (time_limit != null) {
|
||||
if (typeof time_limit != 'number' || time_limit < 0)
|
||||
throw make_reason(factory, 'Bad time limit.', time_limit)
|
||||
if (time_limit > 0) timer_cancel = schedule(() => cancel(make_reason(factory, 'Timeout.', time_limit)), time_limit)
|
||||
}
|
||||
|
||||
def concurrent = throttle ? number.min(throttle, requestors.length) : requestors.length
|
||||
for (let i = 0; i < concurrent; i++) start_requestor(initial)
|
||||
|
||||
return cancel
|
||||
}
|
||||
|
||||
// ———————————————————————————————————————— factories
|
||||
|
||||
function _normalize (collection, factory) {
|
||||
if (isa(collection)) return { names: null, list: collection }
|
||||
if (collection && typeof collection == 'object') {
|
||||
def names = array(collection)
|
||||
def list = names.map(k => collection[k]).filter(is_requestor)
|
||||
return { names, list }
|
||||
}
|
||||
throw make_reason(factory, 'Expected array or record.', collection)
|
||||
}
|
||||
|
||||
function _denormalize (names, list) {
|
||||
if (!names) return list
|
||||
def obj = meme(null)
|
||||
names.forEach((k, i) => { obj[k] = list[i] })
|
||||
return obj
|
||||
}
|
||||
|
||||
function par_all (collection, time_limit, throttle) {
|
||||
def factory = 'par_all'
|
||||
def { names, list } = _normalize(collection, factory)
|
||||
if (list.length == 0) return (cb, v) => cb(names ? {} : [])
|
||||
check_requestors(list, factory)
|
||||
|
||||
return function par_all_req (cb, initial) {
|
||||
check_callback(cb, factory)
|
||||
let pending = list.length
|
||||
def results = new Array(list.length)
|
||||
|
||||
def cancel = run(factory, list, initial, (val, reason, idx) => {
|
||||
if (val == null) {
|
||||
cancel(reason)
|
||||
return cb(null, reason)
|
||||
}
|
||||
results[idx] = val
|
||||
if (--pending == 0) cb(_denormalize(names, results))
|
||||
}, time_limit, throttle)
|
||||
|
||||
return cancel
|
||||
}
|
||||
}
|
||||
|
||||
function par_any (collection, time_limit, throttle) {
|
||||
def factory = 'par_any'
|
||||
def { names, list } = _normalize(collection, factory)
|
||||
if (list.length == 0) return (cb, v) => cb(names ? {} : [])
|
||||
check_requestors(list, factory)
|
||||
|
||||
return function par_any_req (cb, initial) {
|
||||
check_callback(cb, factory)
|
||||
let pending = list.length
|
||||
def successes = new Array(list.length)
|
||||
|
||||
def cancel = run(factory, list, initial, (val, reason, idx) => {
|
||||
pending--
|
||||
if (val != null) successes[idx] = val
|
||||
if (successes.some(v => v != null)) {
|
||||
if (!pending) cancel(make_reason(factory, 'Finished.'))
|
||||
return cb(_denormalize(names, successes.filter(v => v != null)))
|
||||
}
|
||||
if (!pending) cb(null, make_reason(factory, 'No successes.'))
|
||||
}, time_limit, throttle)
|
||||
|
||||
return cancel
|
||||
}
|
||||
}
|
||||
|
||||
function race (list, time_limit, throttle) {
|
||||
def factory = throttle == 1 ? 'fallback' : 'race'
|
||||
if (!isa(list, array) || list.length == 0)
|
||||
throw make_reason(factory, 'No requestors.')
|
||||
check_requestors(list, factory)
|
||||
|
||||
return function race_req (cb, initial) {
|
||||
check_callback(cb, factory)
|
||||
let done = false
|
||||
def cancel = run(factory, list, initial, (val, reason, idx) => {
|
||||
if (done) return
|
||||
if (val != null) {
|
||||
done = true
|
||||
cancel(make_reason(factory, 'Loser.', idx))
|
||||
cb(val)
|
||||
} else if (--list.length == 0) {
|
||||
done = true
|
||||
cancel(reason)
|
||||
cb(null, reason)
|
||||
}
|
||||
}, time_limit, throttle)
|
||||
return cancel
|
||||
}
|
||||
}
|
||||
|
||||
function fallback (list, time_limit) {
|
||||
return race(list, time_limit, 1)
|
||||
}
|
||||
|
||||
function sequence (list, time_limit) {
|
||||
def factory = 'sequence'
|
||||
if (!isa(list, array)) throw make_reason(factory, 'Not an array.', list)
|
||||
check_requestors(list, factory)
|
||||
if (list.length == 0) return (cb, v) => cb(v)
|
||||
|
||||
return function sequence_req (cb, initial) {
|
||||
check_callback(cb, factory)
|
||||
let idx = 0
|
||||
|
||||
function next (value) {
|
||||
if (idx >= list.length) return cb(value)
|
||||
try {
|
||||
list[idx++](function seq_cb (val, reason) {
|
||||
if (val == null) return cb(null, reason)
|
||||
next(val)
|
||||
}, value)
|
||||
} catch (ex) {
|
||||
cb(null, ex)
|
||||
}
|
||||
}
|
||||
|
||||
next(initial)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
fallback,
|
||||
par_all,
|
||||
par_any,
|
||||
race,
|
||||
sequence
|
||||
}
|
||||
338
plan.md
Normal file
338
plan.md
Normal file
@@ -0,0 +1,338 @@
|
||||
# Cell/QuickJS Refactoring Plan: Remove Atoms, Shapes, and Dual-Encoding
|
||||
|
||||
## Overview
|
||||
|
||||
Refactor `source/quickjs.c` to match `docs/memory.md` specification:
|
||||
- Remove JSAtom system (171 references → ~41 remaining)
|
||||
- Remove JSShape system (94 references) ✓
|
||||
- Remove IC caches (shape-based inline caches) ✓
|
||||
- Remove `is_wide_char` dual-encoding (18 locations) ✓
|
||||
- Use JSValue texts directly as property keys
|
||||
- Reference: `mquickjs.c` shows the target pattern
|
||||
|
||||
## Completed Phases
|
||||
|
||||
### Phase 1: Remove is_wide_char Remnants ✓
|
||||
### Phase 2: Remove IC Caches ✓
|
||||
### Phase 3: Remove JSShape System ✓
|
||||
### Phase 4: Complete Property Access with JSValue Keys ✓
|
||||
|
||||
Completed:
|
||||
- Removed JS_GC_OBJ_TYPE_JS_OBJECT fallbacks from OP_get_field
|
||||
- Removed JS_GC_OBJ_TYPE_JS_OBJECT fallbacks from OP_put_field
|
||||
- Removed JS_GC_OBJ_TYPE_JS_OBJECT fallbacks from OP_define_field
|
||||
- Created emit_key() function that adds JSValue to cpool and emits index
|
||||
|
||||
---
|
||||
|
||||
## Phase 5: Convert JSAtom to JSValue Text (IN PROGRESS)
|
||||
|
||||
This is the core transformation. All identifier handling moves from atoms to JSValue.
|
||||
|
||||
### Completed Items
|
||||
|
||||
**Token and Parser Infrastructure:**
|
||||
- [x] Change JSToken.u.ident.atom to JSToken.u.ident.str (JSValue)
|
||||
- [x] Change parse_ident() to return JSValue
|
||||
- [x] Create emit_key() function (cpool-based)
|
||||
- [x] Create JS_KEY_* macros for common names (lines ~279-335 in quickjs.c)
|
||||
- [x] Update all token.u.ident.atom references to .str
|
||||
- [x] Create keyword lookup table (js_keywords[]) with string comparison
|
||||
- [x] Rewrite update_token_ident() to use js_keyword_lookup()
|
||||
- [x] Rewrite is_strict_future_keyword() to use JSValue
|
||||
- [x] Update token_is_pseudo_keyword() to use JSValue and js_key_equal()
|
||||
|
||||
**Function Declaration Parsing:**
|
||||
- [x] Update js_parse_function_decl() signature to use JSValue func_name
|
||||
- [x] Update js_parse_function_decl2() to use JSValue func_name throughout
|
||||
- [x] Update js_parse_function_check_names() to use JSValue
|
||||
- [x] Convert JS_DupAtom/JS_FreeAtom to JS_DupValue/JS_FreeValue in function parsing
|
||||
|
||||
**Variable Definition and Lookup:**
|
||||
- [x] Update find_global_var() to use JSValue and js_key_equal()
|
||||
- [x] Update find_lexical_global_var() to use JSValue
|
||||
- [x] Update find_lexical_decl() to use JSValue and js_key_equal()
|
||||
- [x] Update js_define_var() to use JSValue
|
||||
- [x] Update js_parse_check_duplicate_parameter() to use JSValue and js_key_equal()
|
||||
- [x] Update js_parse_destructuring_var() to return JSValue
|
||||
- [x] Update js_parse_var() to use JSValue for variable names
|
||||
|
||||
**Comparison Helpers:**
|
||||
- [x] Create js_key_equal_str() for comparing JSValue with C string literals
|
||||
- [x] Update is_var_in_arg_scope() to use js_key_equal/js_key_equal_str
|
||||
- [x] Update has_with_scope() to use js_key_equal_str
|
||||
- [x] Update closure variable comparisons (cv->var_name) to use js_key_equal_str
|
||||
|
||||
**Property Access:**
|
||||
- [x] Fix JS_GetPropertyStr to create proper JSValue keys
|
||||
- [x] Fix JS_SetPropertyInternal callers to use JS_KEY_* instead of JS_ATOM_*
|
||||
|
||||
### JS_KEY_* Macros Added
|
||||
|
||||
Compile-time immediate ASCII string constants (≤7 chars):
|
||||
```c
|
||||
JS_KEY_empty, JS_KEY_name, JS_KEY_message, JS_KEY_stack,
|
||||
JS_KEY_errors, JS_KEY_Error, JS_KEY_cause, JS_KEY_length,
|
||||
JS_KEY_value, JS_KEY_get, JS_KEY_set, JS_KEY_raw,
|
||||
JS_KEY_flags, JS_KEY_source, JS_KEY_exec, JS_KEY_toJSON,
|
||||
JS_KEY_eval, JS_KEY_this, JS_KEY_true, JS_KEY_false,
|
||||
JS_KEY_null, JS_KEY_NaN, JS_KEY_default, JS_KEY_index,
|
||||
JS_KEY_input, JS_KEY_groups, JS_KEY_indices, JS_KEY_let,
|
||||
JS_KEY_var, JS_KEY_new, JS_KEY_of, JS_KEY_yield,
|
||||
JS_KEY_async, JS_KEY_target, JS_KEY_from, JS_KEY_meta,
|
||||
JS_KEY_as, JS_KEY_with
|
||||
```
|
||||
|
||||
Runtime macro for strings >7 chars:
|
||||
```c
|
||||
#define JS_KEY_STR(ctx, str) JS_NewStringLen((ctx), (str), sizeof(str) - 1)
|
||||
```
|
||||
|
||||
Helper function for comparing JSValue with C string literals:
|
||||
```c
|
||||
static JS_BOOL js_key_equal_str(JSValue a, const char *str);
|
||||
```
|
||||
|
||||
### Remaining Work
|
||||
|
||||
#### 5.3 Update js_parse_property_name() ✓
|
||||
- [x] Change return type from JSAtom* to JSValue*
|
||||
- [x] Update all callers (js_parse_object_literal, etc.)
|
||||
- [x] Updated get_lvalue(), put_lvalue(), js_parse_destructuring_element()
|
||||
|
||||
#### 5.4 Replace remaining emit_atom() calls with emit_key() ✓
|
||||
- [x] Removed emit_atom wrapper function
|
||||
- [x] Changed last emit_atom(JS_ATOM_this) to emit_key(JS_KEY_this)
|
||||
|
||||
#### 5.5 Update Variable Opcode Format in quickjs-opcode.h
|
||||
- [ ] Change `atom` format opcodes to `key` format
|
||||
- [ ] Change `atom_u8` and `atom_u16` to `key_u8` and `key_u16`
|
||||
|
||||
#### 5.6 Update VM Opcode Handlers ✓
|
||||
These now read cpool indices and look up JSValue:
|
||||
- [x] OP_check_var, OP_get_var_undef, OP_get_var
|
||||
- [x] OP_put_var, OP_put_var_init, OP_put_var_strict
|
||||
- [x] OP_set_name, OP_make_var_ref, OP_delete_var
|
||||
- [x] OP_define_var, OP_define_func, OP_throw_error
|
||||
- [x] OP_make_loc_ref, OP_make_arg_ref
|
||||
- [x] OP_define_method, OP_define_method_computed
|
||||
|
||||
#### 5.7 Update resolve_scope_var() ✓
|
||||
- [x] Changed signature to use JSValue var_name
|
||||
- [x] Updated all comparisons to use js_key_equal()/js_key_equal_str()
|
||||
- [x] Updated var_object_test() to use JSValue
|
||||
- [x] Updated optimize_scope_make_global_ref() to use JSValue
|
||||
- [x] Updated resolve_variables() callers to read from cpool
|
||||
|
||||
#### 5.8 Convert Remaining JS_ATOM_* Usages
|
||||
Categories remaining:
|
||||
- Some debug/print functions still use JSAtom
|
||||
- Some function signatures not yet converted
|
||||
- Will be addressed in Phase 7 cleanup
|
||||
|
||||
---
|
||||
|
||||
## Phase 6: Update Bytecode Serialization ✓
|
||||
|
||||
### 6.1 JS_WriteObjectTag Changes ✓
|
||||
- [x] Changed JS_WriteObjectTag to use bc_put_key() directly for property keys
|
||||
- [x] Removed JS_ValueToAtom/bc_put_atom path (was broken anyway)
|
||||
- [x] cpool values serialized via JS_WriteObjectRec()
|
||||
|
||||
### 6.2 JS_ReadObject Changes ✓
|
||||
- [x] Changed JS_ReadObjectTag to use bc_get_key() for property keys
|
||||
- [x] Uses JS_SetPropertyInternal with JSValue keys
|
||||
|
||||
### 6.3 Opcode Format Updates ✓
|
||||
- [x] Added OP_FMT_key_u8, OP_FMT_key_u16, OP_FMT_key_label_u16 formats
|
||||
- [x] Updated variable opcodes to use key formats instead of atom formats
|
||||
- [x] Updated bc_byte_swap() to handle new key formats
|
||||
- [x] Updated JS_WriteFunctionBytecode() to skip key format opcodes
|
||||
- [x] Updated JS_ReadFunctionBytecode() to skip key format opcodes
|
||||
|
||||
### 6.4 Version Bump ✓
|
||||
- [x] Incremented BC_VERSION from 5 to 6
|
||||
|
||||
---
|
||||
|
||||
## Phase 7: Final Cleanup ✓
|
||||
|
||||
### 7.1 Additional Parser/Compiler Fixes ✓
|
||||
- [x] Fixed TOK_IDENT case to use JSValue name, JS_DupValue, emit_key
|
||||
- [x] Fixed TOK_TRY catch clause to use JSValue name
|
||||
- [x] Fixed js_parse_statement_or_decl label_name to use JSValue
|
||||
- [x] Fixed OP_scope_get_var "eval" check to use js_key_equal_str
|
||||
- [x] Fixed js_parse_delete to use JSValue for name comparison
|
||||
- [x] Fixed JSON parsing to use js_key_from_string for property names
|
||||
- [x] Added js_key_from_string() helper function
|
||||
- [x] Added JS_KEY__ret_, JS_KEY__eval_, JS_KEY__var_ for internal names
|
||||
- [x] Updated add_closure_var, get_closure_var2, get_closure_var to use JSValue var_name
|
||||
- [x] Updated set_closure_from_var to use JS_DupValue
|
||||
- [x] Updated add_closure_variables to use JS_DupValue
|
||||
- [x] Updated instantiate_hoisted_definitions to use fd_cpool_add for keys
|
||||
- [x] Updated resolve_variables to use js_key_equal and fd_cpool_add
|
||||
|
||||
### 7.1.1 Property Access and Runtime Fixes ✓
|
||||
- [x] Fixed JS_GetPropertyValue to use js_key_from_string instead of JS_ValueToAtom
|
||||
- [x] Fixed JS_GetPropertyKey to use js_key_from_string for string keys
|
||||
- [x] Fixed JS_SetPropertyKey to use js_key_from_string for string keys
|
||||
- [x] Fixed JS_HasPropertyKey to use js_key_from_string for string keys
|
||||
- [x] Fixed JS_DeletePropertyKey to use js_key_from_string for string keys
|
||||
- [x] Updated JS_HasProperty signature to take JSValue prop
|
||||
- [x] Fixed OP_get_ref_value handler to use JSValue key
|
||||
- [x] Fixed OP_put_ref_value handler to use JSValue key
|
||||
- [x] Updated free_func_def to use JS_FreeValue for JSValue fields
|
||||
|
||||
### 7.2 Remove JSAtom Type and Functions ✓
|
||||
- [x] Removed most JS_ATOM_* constants (kept JS_ATOM_NULL, JS_ATOM_END for BC compat)
|
||||
- [x] JS_NewAtomString now returns JSValue using js_key_new
|
||||
- [x] JS_FreeAtom, JS_DupAtom are stubs (no-op for backward compat)
|
||||
- [x] JS_AtomToValue, JS_ValueToAtom are stubs (minimal BC compat)
|
||||
- [x] Replaced JS_ATOM_* usages with JS_KEY_* or JS_GetPropertyStr
|
||||
|
||||
### 7.3 Additional Runtime Fixes ✓
|
||||
- [x] Fixed free_function_bytecode to use JS_FreeValueRT for JSValue fields
|
||||
- [x] Fixed JS_SetPropertyFunctionList to use JSValue keys via find_key()
|
||||
- [x] Fixed JS_InstantiateFunctionListItem to use JSValue keys
|
||||
- [x] Fixed internalize_json_property to use JSValue names
|
||||
- [x] Fixed emit_break and push_break_entry to use JSValue label_name
|
||||
- [x] Implemented JS_Invoke to use JSValue method key
|
||||
|
||||
### 7.4 Remaining Stubs (kept for bytecode backward compatibility)
|
||||
- JSAtom typedef (uint32_t) - used in BC serialization
|
||||
- JS_ATOM_NULL, JS_ATOM_END - bytecode format markers
|
||||
- JS_FreeAtom, JS_DupAtom - no-op stubs
|
||||
- JS_FreeAtomRT, JS_DupAtomRT - no-op stubs
|
||||
- Legacy BC reader (idx_to_atom array) - for reading old bytecode
|
||||
|
||||
---
|
||||
|
||||
## Current Build Status
|
||||
|
||||
**Build: SUCCEEDS** with warnings (unused variables, labels)
|
||||
|
||||
**Statistics:**
|
||||
- JS_ATOM_* usages: Minimal (only BC serialization compat)
|
||||
- Property access uses JS_KEY_* macros or JS_GetPropertyStr
|
||||
- BC_VERSION: 6 (updated for new key-based format)
|
||||
|
||||
**What Works:**
|
||||
- All property access via JSValue keys
|
||||
- Keyword detection via string comparison
|
||||
- Function declaration parsing with JSValue names
|
||||
- Variable definition with JSValue names
|
||||
- Closure variable tracking with JSValue names
|
||||
- VM opcode handlers read cpool indices and look up JSValue
|
||||
- resolve_scope_var() uses JSValue throughout
|
||||
- js_parse_property_name() returns JSValue
|
||||
- Bytecode serialization uses bc_put_key/bc_get_key for property keys
|
||||
- Variable opcodes use key format (cpool indices)
|
||||
- JSON parsing uses JSValue keys
|
||||
- Internal variable names use JS_KEY__ret_, JS_KEY__eval_, JS_KEY__var_
|
||||
- JS_SetPropertyFunctionList uses JSValue keys
|
||||
- JS_Invoke uses JSValue method keys
|
||||
- break/continue labels use JSValue
|
||||
|
||||
---
|
||||
|
||||
## Phase 8: Migrate to New Tagging System (IN PROGRESS)
|
||||
|
||||
**Problem**: `JS_VALUE_GET_TAG` returns `JS_TAG_PTR` for all pointers, but ~200 places check for obsolete tags like `JS_TAG_OBJECT`, `JS_TAG_STRING`, `JS_TAG_FUNCTION`, etc., which are never returned. This causes crashes.
|
||||
|
||||
**Target Design** (from memory.md):
|
||||
- JSValue tags: Only `JS_TAG_INT`, `JS_TAG_PTR`, `JS_TAG_SHORT_FLOAT`, `JS_TAG_SPECIAL`
|
||||
- Pointer types determined by `objhdr_t` header at offset 8 in heap objects
|
||||
- mist_obj_type: `OBJ_ARRAY(0)`, `OBJ_BLOB(1)`, `OBJ_TEXT(2)`, `OBJ_RECORD(3)`, `OBJ_FUNCTION(4)`, etc.
|
||||
|
||||
### 8.1 Unified Heap Object Layout ✓
|
||||
- [x] Updated mist_text structure to have objhdr_t at offset 8:
|
||||
```c
|
||||
typedef struct mist_text {
|
||||
JSRefCountHeader _dummy_header; /* unused, for offset alignment */
|
||||
uint32_t _pad; /* padding to align objhdr_t to offset 8 */
|
||||
objhdr_t hdr; /* NOW at offset 8, like JSString */
|
||||
word_t length;
|
||||
word_t packed[];
|
||||
} mist_text;
|
||||
```
|
||||
- [x] JSString already has objhdr_t at offset 8
|
||||
|
||||
### 8.2 Type-Checking Helper Functions ✓
|
||||
Added lowercase internal helpers (to avoid conflict with quickjs.h declarations):
|
||||
```c
|
||||
static inline JS_BOOL js_is_gc_object(JSValue v) { return JS_IsPtr(v); }
|
||||
static inline JSGCObjectTypeEnum js_get_gc_type(JSValue v) {
|
||||
return ((JSGCObjectHeader *)JS_VALUE_GET_PTR(v))->gc_obj_type;
|
||||
}
|
||||
static inline JS_BOOL js_is_record(JSValue v) {
|
||||
if (!JS_IsPtr(v)) return FALSE;
|
||||
return js_get_gc_type(v) == JS_GC_OBJ_TYPE_RECORD;
|
||||
}
|
||||
static inline JS_BOOL js_is_array(JSValue v) {
|
||||
if (!JS_IsPtr(v)) return FALSE;
|
||||
return js_get_gc_type(v) == JS_GC_OBJ_TYPE_ARRAY;
|
||||
}
|
||||
static inline JS_BOOL js_is_function(JSValue v) {
|
||||
if (!JS_IsPtr(v)) return FALSE;
|
||||
return js_get_gc_type(v) == JS_GC_OBJ_TYPE_FUNCTION;
|
||||
}
|
||||
static inline JS_BOOL js_is_object(JSValue v) {
|
||||
if (!JS_IsPtr(v)) return FALSE;
|
||||
JSGCObjectTypeEnum t = js_get_gc_type(v);
|
||||
return t == JS_GC_OBJ_TYPE_RECORD || t == JS_GC_OBJ_TYPE_ARRAY;
|
||||
}
|
||||
```
|
||||
|
||||
### 8.3 Updated Core Functions ✓
|
||||
- [x] Updated JS_IsString to read objhdr_t from offset 8
|
||||
- [x] Updated js_key_hash to read objhdr_t from offset 8
|
||||
- [x] Updated js_key_equal to read objhdr_t from offset 8
|
||||
- [x] Updated __JS_FreeValueRT to use objhdr_type for type dispatch
|
||||
- [x] Updated JS_MarkValue, JS_MarkValueEdgeEx for GC
|
||||
- [x] Added JS_SetPropertyValue function
|
||||
- [x] Changed quickjs.h JS_IsFunction/JS_IsObject from inline to extern declarations
|
||||
|
||||
### 8.4 Tag Check Migration (PARTIAL)
|
||||
Updated some critical tag checks:
|
||||
- [x] Some JS_TAG_OBJECT checks → js_is_object() or js_is_record()
|
||||
- [ ] Many more JS_TAG_OBJECT checks remain (~200 total)
|
||||
- [ ] JS_TAG_FUNCTION checks → js_is_function()
|
||||
- [ ] JS_TAG_STRING checks (some already use JS_IsString)
|
||||
|
||||
### 8.5 Remaining Work
|
||||
- [ ] Fix ASAN memory corruption error (attempting free on address not malloc'd)
|
||||
- Crash occurs in js_def_realloc during js_realloc_array
|
||||
- Address is 112 bytes inside a JSFunctionDef allocation
|
||||
- [ ] Complete remaining ~200 tag check migrations
|
||||
- [ ] Add mist_hdr to JSFunction (optional, gc_obj_type already works)
|
||||
- [ ] Remove obsolete tag definitions from quickjs.h:
|
||||
- JS_TAG_STRING = -8
|
||||
- JS_TAG_ARRAY = -6
|
||||
- JS_TAG_FUNCTION = -5
|
||||
- JS_TAG_FUNCTION_BYTECODE = -2
|
||||
- JS_TAG_OBJECT = -1
|
||||
|
||||
### Current Status
|
||||
|
||||
**Build: SUCCEEDS** with warnings
|
||||
|
||||
**Runtime: CRASHES** with ASAN error:
|
||||
```
|
||||
==16122==ERROR: AddressSanitizer: attempting free on address which was not malloc()-ed
|
||||
```
|
||||
The crash occurs during test execution in `js_def_realloc` called from `js_realloc_array`.
|
||||
Root cause not yet identified - likely a pointer being passed to realloc that wasn't allocated with malloc.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- JSVarDef.var_name is JSValue
|
||||
- JSClosureVar.var_name is JSValue
|
||||
- JSGlobalVar.var_name is JSValue
|
||||
- JSFunctionDef.func_name is JSValue
|
||||
- BlockEnv.label_name is JSValue
|
||||
- OP_get_field/put_field/define_field already use cpool index format
|
||||
- JSRecord with open addressing is fully implemented
|
||||
- js_key_hash and js_key_equal work with both immediate and heap text
|
||||
- js_key_equal_str enables comparison with C string literals for internal names
|
||||
@@ -145,12 +145,12 @@ static void encode_js_value(json_encoder *enc, JSContext *js, JSValue val) {
|
||||
double d = js2number(js, val);
|
||||
if (d == (int)d) enc->writeInt(enc, (int)d);
|
||||
else enc->writeDouble(enc, d);
|
||||
} else if (JS_IsString(val)) {
|
||||
} else if (JS_IsText(val)) {
|
||||
size_t len;
|
||||
const char *str = JS_ToCStringLen(js, &len, val);
|
||||
enc->writeString(enc, str, len);
|
||||
JS_FreeCString(js, str);
|
||||
} else if (JS_IsArray(js, val)) {
|
||||
} else if (JS_IsArray(val)) {
|
||||
encode_js_array(enc, js, val);
|
||||
} else if (JS_IsObject(val)) {
|
||||
encode_js_object(enc, js, val);
|
||||
|
||||
@@ -102,7 +102,7 @@ static void scores_cb(PDScoresList *scores, const char *errorMessage) {
|
||||
JSC_SCALL(scoreboards_addScore,
|
||||
if (!pd_scoreboards) return JS_ThrowInternalError(js, "scoreboards not initialized");
|
||||
uint32_t value = (uint32_t)js2number(js, argv[1]);
|
||||
if (argc > 2 && JS_IsFunction(js, argv[2])) {
|
||||
if (argc > 2 && JS_IsFunction(argv[2])) {
|
||||
g_scoreboard_js = js;
|
||||
JS_FreeValue(js, g_add_score_callback);
|
||||
g_add_score_callback = JS_DupValue(js, argv[2]);
|
||||
@@ -112,7 +112,7 @@ JSC_SCALL(scoreboards_addScore,
|
||||
|
||||
JSC_SCALL(scoreboards_getPersonalBest,
|
||||
if (!pd_scoreboards) return JS_ThrowInternalError(js, "scoreboards not initialized");
|
||||
if (argc > 1 && JS_IsFunction(js, argv[1])) {
|
||||
if (argc > 1 && JS_IsFunction(argv[1])) {
|
||||
g_scoreboard_js = js;
|
||||
JS_FreeValue(js, g_personal_best_callback);
|
||||
g_personal_best_callback = JS_DupValue(js, argv[1]);
|
||||
@@ -129,7 +129,7 @@ JSC_CCALL(scoreboards_freeScore,
|
||||
|
||||
JSC_CCALL(scoreboards_getScoreboards,
|
||||
if (!pd_scoreboards) return JS_ThrowInternalError(js, "scoreboards not initialized");
|
||||
if (argc > 0 && JS_IsFunction(js, argv[0])) {
|
||||
if (argc > 0 && JS_IsFunction(argv[0])) {
|
||||
g_scoreboard_js = js;
|
||||
JS_FreeValue(js, g_boards_list_callback);
|
||||
g_boards_list_callback = JS_DupValue(js, argv[0]);
|
||||
@@ -145,7 +145,7 @@ JSC_CCALL(scoreboards_freeBoardsList,
|
||||
|
||||
JSC_SCALL(scoreboards_getScores,
|
||||
if (!pd_scoreboards) return JS_ThrowInternalError(js, "scoreboards not initialized");
|
||||
if (argc > 1 && JS_IsFunction(js, argv[1])) {
|
||||
if (argc > 1 && JS_IsFunction(argv[1])) {
|
||||
g_scoreboard_js = js;
|
||||
JS_FreeValue(js, g_scores_callback);
|
||||
g_scores_callback = JS_DupValue(js, argv[1]);
|
||||
|
||||
122
pronto.cm
122
pronto.cm
@@ -4,22 +4,22 @@
|
||||
// Time is in seconds.
|
||||
|
||||
function make_reason(factory, excuse, evidence) {
|
||||
def reason = new Error(`pronto.${factory}${excuse ? ': ' + excuse : ''}`)
|
||||
def reason = Error(`pronto.${factory}${excuse ? ': ' + excuse : ''}`)
|
||||
reason.evidence = evidence
|
||||
return reason
|
||||
}
|
||||
|
||||
function is_requestor(fn) {
|
||||
return typeof fn == 'function' && (fn.length == 1 || fn.length == 2)
|
||||
return is_function(fn) && (length(fn) == 1 || length(fn) == 2)
|
||||
}
|
||||
|
||||
function check_requestors(list, factory) {
|
||||
if (!isa(list, array) || list.some(r => !is_requestor(r)))
|
||||
if (!is_array(list) || some(list, r => !is_requestor(r)))
|
||||
throw make_reason(factory, 'Bad requestor array.', list)
|
||||
}
|
||||
|
||||
function check_callback(cb, factory) {
|
||||
if (typeof cb != 'function' || cb.length != 2)
|
||||
if (!is_function(cb) || length(cb) != 2)
|
||||
throw make_reason(factory, 'Not a callback.', cb)
|
||||
}
|
||||
|
||||
@@ -27,15 +27,15 @@ function check_callback(cb, factory) {
|
||||
// Tries each requestor in order until one succeeds.
|
||||
function fallback(requestor_array) {
|
||||
def factory = 'fallback'
|
||||
if (!isa(requestor_array, array) || requestor_array.length == 0)
|
||||
if (!is_array(requestor_array) || length(requestor_array) == 0)
|
||||
throw make_reason(factory, 'Empty requestor array.')
|
||||
check_requestors(requestor_array, factory)
|
||||
|
||||
return function fallback_requestor(callback, value) {
|
||||
check_callback(callback, factory)
|
||||
let index = 0
|
||||
let current_cancel = null
|
||||
let cancelled = false
|
||||
var index = 0
|
||||
var current_cancel = null
|
||||
var cancelled = false
|
||||
|
||||
function cancel(reason) {
|
||||
cancelled = true
|
||||
@@ -47,7 +47,7 @@ function fallback(requestor_array) {
|
||||
|
||||
function try_next() {
|
||||
if (cancelled) return
|
||||
if (index >= requestor_array.length) {
|
||||
if (index >= length(requestor_array)) {
|
||||
callback(null, make_reason(factory, 'All requestors failed.'))
|
||||
return
|
||||
}
|
||||
@@ -79,35 +79,35 @@ function fallback(requestor_array) {
|
||||
// Runs requestors in parallel, collecting all results.
|
||||
function parallel(requestor_array, throttle, need) {
|
||||
def factory = 'parallel'
|
||||
if (!isa(requestor_array, array))
|
||||
if (!is_array(requestor_array))
|
||||
throw make_reason(factory, 'Not an array.', requestor_array)
|
||||
check_requestors(requestor_array, factory)
|
||||
|
||||
def length = requestor_array.length
|
||||
def length = length(requestor_array)
|
||||
if (length == 0)
|
||||
return function(callback, value) { callback([]) }
|
||||
|
||||
if (need == null) need = length
|
||||
if (typeof need != 'number' || need < 0 || need > length)
|
||||
if (!is_number(need) || need < 0 || need > length)
|
||||
throw make_reason(factory, 'Bad need.', need)
|
||||
|
||||
if (throttle != null && (typeof throttle != 'number' || throttle < 1))
|
||||
if (throttle != null && (!is_number(throttle) || throttle < 1))
|
||||
throw make_reason(factory, 'Bad throttle.', throttle)
|
||||
|
||||
return function parallel_requestor(callback, value) {
|
||||
check_callback(callback, factory)
|
||||
def results = new Array(length)
|
||||
def cancel_list = new Array(length)
|
||||
let next_index = 0
|
||||
let successes = 0
|
||||
let failures = 0
|
||||
let finished = false
|
||||
def results = array(length)
|
||||
def cancel_list = array(length)
|
||||
var next_index = 0
|
||||
var successes = 0
|
||||
var failures = 0
|
||||
var finished = false
|
||||
|
||||
function cancel(reason) {
|
||||
if (finished) return
|
||||
finished = true
|
||||
cancel_list.forEach(c => {
|
||||
try { if (typeof c == 'function') c(reason) } catch (_) {}
|
||||
arrfor(cancel_list, c => {
|
||||
try { if (is_function(c)) c(reason) } catch (_) {}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -153,8 +153,9 @@ function parallel(requestor_array, throttle, need) {
|
||||
}
|
||||
}
|
||||
|
||||
def concurrent = throttle ? number.min(throttle, length) : length
|
||||
for (let i = 0; i < concurrent; i++) start_one()
|
||||
|
||||
def concurrent = throttle ? min(throttle, length) : length
|
||||
for (var i = 0; i < concurrent; i++) start_one()
|
||||
|
||||
return cancel
|
||||
}
|
||||
@@ -164,32 +165,32 @@ function parallel(requestor_array, throttle, need) {
|
||||
// Runs requestors in parallel, returns first success(es).
|
||||
function race(requestor_array, throttle, need) {
|
||||
def factory = 'race'
|
||||
if (!isa(requestor_array, array) || requestor_array.length == 0)
|
||||
if (!is_array(requestor_array) || length(requestor_array) == 0)
|
||||
throw make_reason(factory, 'Empty requestor array.')
|
||||
check_requestors(requestor_array, factory)
|
||||
|
||||
def length = requestor_array.length
|
||||
def length = length(requestor_array)
|
||||
if (need == null) need = 1
|
||||
if (typeof need != 'number' || need < 1 || need > length)
|
||||
if (!is_number(need) || need < 1 || need > length)
|
||||
throw make_reason(factory, 'Bad need.', need)
|
||||
|
||||
if (throttle != null && (typeof throttle != 'number' || throttle < 1))
|
||||
if (throttle != null && (!is_number(throttle) || throttle < 1))
|
||||
throw make_reason(factory, 'Bad throttle.', throttle)
|
||||
|
||||
return function race_requestor(callback, value) {
|
||||
check_callback(callback, factory)
|
||||
def results = new Array(length)
|
||||
def cancel_list = new Array(length)
|
||||
let next_index = 0
|
||||
let successes = 0
|
||||
let failures = 0
|
||||
let finished = false
|
||||
def results = array(length)
|
||||
def cancel_list = array(length)
|
||||
var next_index = 0
|
||||
var successes = 0
|
||||
var failures = 0
|
||||
var finished = false
|
||||
|
||||
function cancel(reason) {
|
||||
if (finished) return
|
||||
finished = true
|
||||
cancel_list.forEach(c => {
|
||||
try { if (typeof c == 'function') c(reason) } catch (_) {}
|
||||
arrfor(cancel_list, c => {
|
||||
try { if (is_function(c)) c(reason) } catch (_) {}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -238,8 +239,8 @@ function race(requestor_array, throttle, need) {
|
||||
}
|
||||
}
|
||||
|
||||
def concurrent = throttle ? number.min(throttle, length) : length
|
||||
for (let i = 0; i < concurrent; i++) start_one()
|
||||
def concurrent = throttle ? min(throttle, length) : length
|
||||
for (var i = 0; i < concurrent; i++) start_one()
|
||||
|
||||
return cancel
|
||||
}
|
||||
@@ -249,18 +250,18 @@ function race(requestor_array, throttle, need) {
|
||||
// Runs requestors one at a time, passing result to next.
|
||||
function sequence(requestor_array) {
|
||||
def factory = 'sequence'
|
||||
if (!isa(requestor_array, array))
|
||||
if (!is_array(requestor_array))
|
||||
throw make_reason(factory, 'Not an array.', requestor_array)
|
||||
check_requestors(requestor_array, factory)
|
||||
|
||||
if (requestor_array.length == 0)
|
||||
if (length(requestor_array) == 0)
|
||||
return function(callback, value) { callback(value) }
|
||||
|
||||
return function sequence_requestor(callback, value) {
|
||||
check_callback(callback, factory)
|
||||
let index = 0
|
||||
let current_cancel = null
|
||||
let cancelled = false
|
||||
var index = 0
|
||||
var current_cancel = null
|
||||
var cancelled = false
|
||||
|
||||
function cancel(reason) {
|
||||
cancelled = true
|
||||
@@ -272,7 +273,7 @@ function sequence(requestor_array) {
|
||||
|
||||
function run_next(val) {
|
||||
if (cancelled) return
|
||||
if (index >= requestor_array.length) {
|
||||
if (index >= length(requestor_array)) {
|
||||
callback(val)
|
||||
return
|
||||
}
|
||||
@@ -304,7 +305,7 @@ function sequence(requestor_array) {
|
||||
// Converts a unary function into a requestor.
|
||||
function requestorize(unary) {
|
||||
def factory = 'requestorize'
|
||||
if (typeof unary != 'function')
|
||||
if (!is_function(unary))
|
||||
throw make_reason(factory, 'Not a function.', unary)
|
||||
|
||||
return function requestorized(callback, value) {
|
||||
@@ -318,45 +319,12 @@ function requestorize(unary) {
|
||||
}
|
||||
}
|
||||
|
||||
// objectify(factory_fn)
|
||||
// Converts a factory that takes arrays to one that takes objects.
|
||||
function objectify(factory_fn) {
|
||||
def factory = 'objectify'
|
||||
if (typeof factory_fn != 'function')
|
||||
throw make_reason(factory, 'Not a factory.', factory_fn)
|
||||
|
||||
return function objectified_factory(object_of_requestors, ...rest) {
|
||||
if (!isa(object_of_requestors, object))
|
||||
throw make_reason(factory, 'Expected an object.', object_of_requestors)
|
||||
|
||||
def keys = array(object_of_requestors)
|
||||
def requestor_array = keys.map(k => object_of_requestors[k])
|
||||
|
||||
def inner_requestor = factory_fn(requestor_array, ...rest)
|
||||
|
||||
return function(callback, value) {
|
||||
return inner_requestor(function(results, reason) {
|
||||
if (results == null) {
|
||||
callback(null, reason)
|
||||
} else if (isa(results, array)) {
|
||||
def obj = {}
|
||||
keys.forEach((k, i) => { obj[k] = results[i] })
|
||||
callback(obj, reason)
|
||||
} else {
|
||||
callback(results, reason)
|
||||
}
|
||||
}, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
fallback,
|
||||
parallel,
|
||||
race,
|
||||
sequence,
|
||||
requestorize,
|
||||
objectify,
|
||||
is_requestor,
|
||||
check_callback
|
||||
}
|
||||
|
||||
16
qop.c
16
qop.c
@@ -8,10 +8,10 @@ static void js_qop_archive_finalizer(JSRuntime *rt, JSValue val) {
|
||||
qop_desc *qop = JS_GetOpaque(val, js_qop_archive_class_id);
|
||||
if (qop) {
|
||||
if (qop->hashmap) {
|
||||
js_free_rt(rt, qop->hashmap);
|
||||
js_free_rt(qop->hashmap);
|
||||
}
|
||||
qop_close(qop);
|
||||
js_free_rt(rt, qop);
|
||||
js_free_rt(qop);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ static void js_qop_writer_finalizer(JSRuntime *rt, JSValue val) {
|
||||
qop_writer *w = JS_GetOpaque(val, js_qop_writer_class_id);
|
||||
if (w) {
|
||||
if (w->fh) fclose(w->fh);
|
||||
if (w->files) js_free_rt(rt, w->files);
|
||||
js_free_rt(rt, w);
|
||||
if (w->files) js_free_rt(w->files);
|
||||
js_free_rt(w);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -450,10 +450,10 @@ static const JSCFunctionListEntry js_qop_writer_funcs[] = {
|
||||
static const JSCFunctionListEntry js_qop_funcs[] = {
|
||||
MIST_FUNC_DEF(qop, open, 1),
|
||||
MIST_FUNC_DEF(qop, write, 1),
|
||||
JS_PROP_INT32_DEF("FLAG_NONE", QOP_FLAG_NONE, JS_PROP_ENUMERABLE),
|
||||
JS_PROP_INT32_DEF("FLAG_COMPRESSED_ZSTD", QOP_FLAG_COMPRESSED_ZSTD, JS_PROP_ENUMERABLE),
|
||||
JS_PROP_INT32_DEF("FLAG_COMPRESSED_DEFLATE", QOP_FLAG_COMPRESSED_DEFLATE, JS_PROP_ENUMERABLE),
|
||||
JS_PROP_INT32_DEF("FLAG_ENCRYPTED", QOP_FLAG_ENCRYPTED, JS_PROP_ENUMERABLE),
|
||||
JS_PROP_INT32_DEF("FLAG_NONE", QOP_FLAG_NONE, 0),
|
||||
JS_PROP_INT32_DEF("FLAG_COMPRESSED_ZSTD", QOP_FLAG_COMPRESSED_ZSTD, 0),
|
||||
JS_PROP_INT32_DEF("FLAG_COMPRESSED_DEFLATE", QOP_FLAG_COMPRESSED_DEFLATE, 0),
|
||||
JS_PROP_INT32_DEF("FLAG_ENCRYPTED", QOP_FLAG_ENCRYPTED, 0),
|
||||
};
|
||||
|
||||
JSValue js_qop_use(JSContext *js) {
|
||||
|
||||
46
qopconv.ce
46
qopconv.ce
@@ -3,10 +3,10 @@ var qop = use('qop')
|
||||
|
||||
function print_usage() {
|
||||
log.console("Usage: qopconv [OPTION...] FILE...")
|
||||
log.console(" -u <archive> ... unpack archive")
|
||||
log.console(" -l <archive> ... list contents of archive")
|
||||
log.console(" -u <archive> .. unpack archive")
|
||||
log.console(" -l <archive> .. list contents of archive")
|
||||
log.console(" -d <dir> ....... change read dir when creating archives")
|
||||
log.console(" <sources...> <archive> ... create archive from sources")
|
||||
log.console(" <sources...> <archive> .. create archive from sources")
|
||||
}
|
||||
|
||||
function list(archive_path) {
|
||||
@@ -24,12 +24,12 @@ function list(archive_path) {
|
||||
}
|
||||
|
||||
var files = archive.list()
|
||||
for (var f of files) {
|
||||
arrfor(files, function(f) {
|
||||
var s = archive.stat(f)
|
||||
// Format: index hash size path
|
||||
// We don't have index/hash easily available in JS binding yet, just size/path
|
||||
log.console(`${f} (${s.size} bytes)`)
|
||||
}
|
||||
})
|
||||
archive.close()
|
||||
}
|
||||
|
||||
@@ -48,26 +48,26 @@ function unpack(archive_path) {
|
||||
}
|
||||
|
||||
var files = archive.list()
|
||||
for (var f of files) {
|
||||
arrfor(files, function(f) {
|
||||
var data = archive.read(f)
|
||||
if (data) {
|
||||
// Ensure directory exists
|
||||
var dir = f.substring(0, f.lastIndexOf('/'))
|
||||
var dir = fd.dirname(f)
|
||||
if (dir) {
|
||||
// recursive mkdir
|
||||
var parts = dir.split('/')
|
||||
var parts = array(dir, '/')
|
||||
var curr = "."
|
||||
for (var p of parts) {
|
||||
arrfor(parts, function(p) {
|
||||
curr += "/" + p
|
||||
try { fd.mkdir(curr) } catch(e) {}
|
||||
}
|
||||
})
|
||||
}
|
||||
var fh = fd.open(f, "w")
|
||||
fd.write(fh, data)
|
||||
fd.close(fh)
|
||||
log.console("Extracted " + f)
|
||||
}
|
||||
}
|
||||
})
|
||||
archive.close()
|
||||
}
|
||||
|
||||
@@ -89,11 +89,11 @@ function pack(sources, archive_path, read_dir) {
|
||||
|
||||
if (st.isDirectory) {
|
||||
var list = fd.readdir(full_path)
|
||||
for (var item of list) {
|
||||
if (item == "." || item == "..") continue
|
||||
arrfor(list, function(item) {
|
||||
if (item == "." || item == "..") return
|
||||
var sub = path == "." ? item : path + "/" + item
|
||||
add_recursive(sub)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
var data = fd.slurp(full_path)
|
||||
if (data) {
|
||||
@@ -103,22 +103,22 @@ function pack(sources, archive_path, read_dir) {
|
||||
}
|
||||
}
|
||||
|
||||
for (var s of sources) {
|
||||
arrfor(sources, function(s) {
|
||||
add_recursive(s)
|
||||
}
|
||||
})
|
||||
|
||||
writer.finalize()
|
||||
log.console("Created " + archive_path)
|
||||
}
|
||||
|
||||
if (typeof arg == 'undefined' || arg.length < 1) {
|
||||
if (!is_array(arg) || length(arg) < 1) {
|
||||
print_usage()
|
||||
} else {
|
||||
if (arg[0] == "-l") {
|
||||
if (arg.length < 2) print_usage()
|
||||
if (length(arg) < 2) print_usage()
|
||||
else list(arg[1])
|
||||
} else if (arg[0] == "-u") {
|
||||
if (arg.length < 2) print_usage()
|
||||
if (length(arg) < 2) print_usage()
|
||||
else unpack(arg[1])
|
||||
} else {
|
||||
var sources = []
|
||||
@@ -130,12 +130,12 @@ if (typeof arg == 'undefined' || arg.length < 1) {
|
||||
i = 2
|
||||
}
|
||||
|
||||
for (; i < arg.length - 1; i++) {
|
||||
sources.push(arg[i])
|
||||
for (; i < length(arg) - 1; i++) {
|
||||
push(sources, arg[i])
|
||||
}
|
||||
archive = arg[arg.length - 1]
|
||||
archive = arg[length(arg) - 1]
|
||||
|
||||
if (sources.length == 0) {
|
||||
if (length(sources) == 0) {
|
||||
print_usage()
|
||||
} else {
|
||||
pack(sources, archive, read_dir)
|
||||
|
||||
@@ -14,7 +14,7 @@ rnd.random_fit = function()
|
||||
|
||||
rnd.random_whole = function(num)
|
||||
{
|
||||
return number.floor(rnd.random() * num)
|
||||
return floor(rnd.random() * num)
|
||||
}
|
||||
|
||||
rnd.random_range = function(min,max)
|
||||
|
||||
109
remove.ce
109
remove.ce
@@ -1,24 +1,105 @@
|
||||
// cell remove <alias|path> - Remove a package from dependencies or shop
|
||||
// cell remove <locator> - Remove a package from the shop
|
||||
//
|
||||
// Usage:
|
||||
// cell remove <locator> Remove a package from the shop
|
||||
// cell remove . Remove current directory package from shop
|
||||
//
|
||||
// Options:
|
||||
// --prune Also remove packages no longer needed by any root
|
||||
// --dry-run Show what would be removed
|
||||
|
||||
var shop = use('internal/shop')
|
||||
var pkg = use('package')
|
||||
var link = use('link')
|
||||
var fd = use('fd')
|
||||
|
||||
if (args.length < 1) {
|
||||
log.console("Usage: cell remove <alias|path>")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
var target_pkg = null
|
||||
var prune = false
|
||||
var dry_run = false
|
||||
|
||||
var pkg = args[0]
|
||||
|
||||
// Resolve relative paths to absolute paths
|
||||
if (pkg == '.' || pkg.startsWith('./') || pkg.startsWith('../') || fd.is_dir(pkg)) {
|
||||
var resolved = fd.realpath(pkg)
|
||||
if (resolved) {
|
||||
pkg = resolved
|
||||
for (var i = 0; i < length(args); i++) {
|
||||
if (args[i] == '--prune') {
|
||||
prune = true
|
||||
} else if (args[i] == '--dry-run') {
|
||||
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")
|
||||
$stop()
|
||||
} else if (!starts_with(args[i], '-')) {
|
||||
target_pkg = args[i]
|
||||
}
|
||||
}
|
||||
|
||||
shop.remove(pkg)
|
||||
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)) {
|
||||
var resolved = fd.realpath(target_pkg)
|
||||
if (resolved) {
|
||||
target_pkg = resolved
|
||||
}
|
||||
}
|
||||
|
||||
var packages_to_remove = [target_pkg]
|
||||
|
||||
if (prune) {
|
||||
// Find packages no longer needed
|
||||
// Get all dependencies of remaining packages
|
||||
var lock = shop.load_lock()
|
||||
var all_packages = shop.list_packages()
|
||||
|
||||
// Build set of all needed packages (excluding target)
|
||||
var needed = {}
|
||||
arrfor(all_packages, function(p) {
|
||||
if (p == target_pkg || p == 'core') return
|
||||
|
||||
// Mark this package and its deps as needed
|
||||
needed[p] = true
|
||||
try {
|
||||
var deps = pkg.gather_dependencies(p)
|
||||
arrfor(deps, function(dep) {
|
||||
needed[dep] = true
|
||||
})
|
||||
} catch (e) {
|
||||
// Skip if can't read deps
|
||||
}
|
||||
})
|
||||
|
||||
// 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()
|
||||
|
||||
196
resolve.ce
Normal file
196
resolve.ce
Normal file
@@ -0,0 +1,196 @@
|
||||
// cell resolve [<locator>] - Print fully resolved dependency closure
|
||||
//
|
||||
// Usage:
|
||||
// cell resolve Resolve current directory package
|
||||
// cell resolve . Resolve current directory package
|
||||
// cell resolve <locator> Resolve specific package
|
||||
//
|
||||
// Options:
|
||||
// --target <triple> Annotate builds for target platform
|
||||
// --locked Show lock state without applying links
|
||||
// --refresh Refresh floating refs before printing
|
||||
|
||||
var shop = use('internal/shop')
|
||||
var pkg = use('package')
|
||||
var link = use('link')
|
||||
var build = use('build')
|
||||
var fd = use('fd')
|
||||
|
||||
var target_locator = null
|
||||
var target_triple = null
|
||||
var show_locked = false
|
||||
var refresh_first = false
|
||||
|
||||
for (var i = 0; i < length(args); i++) {
|
||||
if (args[i] == '--target' || args[i] == '-t') {
|
||||
if (i + 1 < length(args)) {
|
||||
target_triple = args[++i]
|
||||
} else {
|
||||
log.error('--target requires a triple')
|
||||
$stop()
|
||||
}
|
||||
} 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
|
||||
if (!target_locator) {
|
||||
target_locator = '.'
|
||||
}
|
||||
|
||||
// Resolve local paths
|
||||
if (target_locator == '.' || starts_with(target_locator, './') || starts_with(target_locator, '../') || fd.is_dir(target_locator)) {
|
||||
var resolved = fd.realpath(target_locator)
|
||||
if (resolved) {
|
||||
target_locator = resolved
|
||||
}
|
||||
}
|
||||
|
||||
// Check if it's a valid package
|
||||
if (!fd.is_file(target_locator + '/cell.toml')) {
|
||||
// Try to find it in the shop
|
||||
var pkg_dir = shop.get_package_dir(target_locator)
|
||||
if (!fd.is_file(pkg_dir + '/cell.toml')) {
|
||||
log.error("Not a valid package: " + target_locator)
|
||||
$stop()
|
||||
}
|
||||
}
|
||||
|
||||
// Detect target if not specified
|
||||
if (!target_triple) {
|
||||
target_triple = build.detect_host_target()
|
||||
}
|
||||
|
||||
var lock = shop.load_lock()
|
||||
var links = link.load()
|
||||
|
||||
// Gather all dependencies recursively
|
||||
var all_deps = {}
|
||||
var visited = {}
|
||||
|
||||
function gather_deps(locator, depth) {
|
||||
if (visited[locator]) return
|
||||
visited[locator] = true
|
||||
|
||||
all_deps[locator] = { depth: depth }
|
||||
|
||||
try {
|
||||
var deps = pkg.dependencies(locator)
|
||||
if (deps) {
|
||||
arrfor(array(deps), function(alias) {
|
||||
var dep_locator = deps[alias]
|
||||
gather_deps(dep_locator, depth + 1)
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
// Package might not have dependencies
|
||||
}
|
||||
}
|
||||
|
||||
gather_deps(target_locator, 0)
|
||||
|
||||
// Print header
|
||||
log.console("Resolved dependency closure for: " + target_locator)
|
||||
log.console("Target: " + target_triple)
|
||||
log.console("")
|
||||
|
||||
// Sort by depth then alphabetically
|
||||
var sorted = array(array(all_deps), function(locator) { return { locator: locator, depth: all_deps[locator].depth } })
|
||||
sorted = sort(sorted, "locator")
|
||||
sorted = sort(sorted, "depth")
|
||||
|
||||
for (var i = 0; i < length(sorted); i++) {
|
||||
var locator = sorted[i].locator
|
||||
var depth = sorted[i].depth
|
||||
|
||||
var indent = ""
|
||||
for (var j = 0; j < depth; j++) indent += " "
|
||||
|
||||
// Get info about this package
|
||||
var info = shop.resolve_package_info(locator)
|
||||
var lock_entry = lock[locator]
|
||||
var link_target = show_locked ? null : links[locator]
|
||||
var effective_locator = link_target || locator
|
||||
|
||||
// Check status
|
||||
var is_linked = link_target != null
|
||||
var is_in_lock = lock_entry != null
|
||||
var is_local = info == 'local'
|
||||
|
||||
// Check if fetched (package directory exists)
|
||||
var pkg_dir = shop.get_package_dir(locator)
|
||||
var is_fetched = fd.is_dir(pkg_dir) || fd.is_link(pkg_dir)
|
||||
|
||||
// Check if built (library exists)
|
||||
var lib_dir = shop.get_lib_dir()
|
||||
var lib_name = shop.lib_name_for_package(locator)
|
||||
var dylib_ext = '.dylib' // TODO: detect from target
|
||||
var lib_path = lib_dir + '/' + lib_name + dylib_ext
|
||||
var is_built = fd.is_file(lib_path)
|
||||
|
||||
// Format output
|
||||
var status_parts = []
|
||||
if (is_linked) push(status_parts, "linked")
|
||||
if (is_local) push(status_parts, "local")
|
||||
if (!is_in_lock) push(status_parts, "not in lock")
|
||||
if (!is_fetched) push(status_parts, "not fetched")
|
||||
if (is_built) push(status_parts, "built")
|
||||
|
||||
var commit_str = ""
|
||||
if (lock_entry && lock_entry.commit) {
|
||||
commit_str = " @" + text(lock_entry.commit, 0, 8)
|
||||
} else if (lock_entry && lock_entry.type == 'local') {
|
||||
commit_str = " (local)"
|
||||
}
|
||||
|
||||
var line = indent + locator + commit_str
|
||||
|
||||
if (is_linked && !show_locked) {
|
||||
line += " -> " + link_target
|
||||
}
|
||||
|
||||
if (length(status_parts) > 0) {
|
||||
line += " [" + text(status_parts, ", ") + "]"
|
||||
}
|
||||
|
||||
log.console(line)
|
||||
|
||||
// Show compilation inputs if requested (verbose)
|
||||
if (depth == 0) {
|
||||
try {
|
||||
var cflags = pkg.get_flags(locator, 'CFLAGS', target_triple)
|
||||
var ldflags = pkg.get_flags(locator, 'LDFLAGS', target_triple)
|
||||
if (length(cflags) > 0 || length(ldflags) > 0) {
|
||||
log.console(indent + " Compilation inputs:")
|
||||
if (length(cflags) > 0) {
|
||||
log.console(indent + " CFLAGS: " + text(cflags, ' '))
|
||||
}
|
||||
if (length(ldflags) > 0) {
|
||||
log.console(indent + " LDFLAGS: " + text(ldflags, ' '))
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Skip if can't read config
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.console("")
|
||||
log.console("Total: " + text(length(sorted)) + " package(s)")
|
||||
|
||||
$stop()
|
||||
48
search.ce
48
search.ce
@@ -4,14 +4,14 @@
|
||||
var shop = use('internal/shop')
|
||||
var pkg = use('package')
|
||||
|
||||
if (args.length < 1) {
|
||||
if (length(args) < 1) {
|
||||
log.console("Usage: cell search <query>")
|
||||
log.console("Searches for packages, actors, or modules matching the query.")
|
||||
$stop()
|
||||
return
|
||||
}
|
||||
|
||||
var query = args[0].toLowerCase()
|
||||
var query = args[0]
|
||||
var found_packages = []
|
||||
var found_modules = []
|
||||
var found_actors = []
|
||||
@@ -19,34 +19,34 @@ var found_actors = []
|
||||
// Search through all installed packages
|
||||
var packages = shop.list_packages()
|
||||
|
||||
for (var package_name of packages) {
|
||||
arrfor(packages, function(package_name) {
|
||||
// Check if package name matches
|
||||
if (package_name.toLowerCase().includes(query)) {
|
||||
found_packages.push(package_name)
|
||||
if (search(package_name, query) != null) {
|
||||
push(found_packages, package_name)
|
||||
}
|
||||
|
||||
// Search modules and actors within the package
|
||||
try {
|
||||
var modules = pkg.list_modules(package_name)
|
||||
for (var mod of modules) {
|
||||
if (mod.toLowerCase().includes(query)) {
|
||||
found_modules.push(package_name + ':' + mod)
|
||||
arrfor(modules, function(mod) {
|
||||
if (search(mod, query) != null) {
|
||||
push(found_modules, package_name + ':' + mod)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
var actors = pkg.list_programs(package_name)
|
||||
for (var actor of actors) {
|
||||
if (actor.toLowerCase().includes(query)) {
|
||||
found_actors.push(package_name + ':' + actor)
|
||||
arrfor(actors, function(actor) {
|
||||
if (search(actor, query) != null) {
|
||||
push(found_actors, package_name + ':' + actor)
|
||||
}
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
// Skip packages that can't be read
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Print results
|
||||
var total = found_packages.length + found_modules.length + found_actors.length
|
||||
var total = length(found_packages) + length(found_modules) + length(found_actors)
|
||||
|
||||
if (total == 0) {
|
||||
log.console("No results found for '" + query + "'")
|
||||
@@ -54,27 +54,27 @@ if (total == 0) {
|
||||
log.console("Found " + text(total) + " result(s) for '" + query + "':")
|
||||
log.console("")
|
||||
|
||||
if (found_packages.length > 0) {
|
||||
if (length(found_packages) > 0) {
|
||||
log.console("Packages:")
|
||||
for (var p of found_packages) {
|
||||
arrfor(found_packages, function(p) {
|
||||
log.console(" " + p)
|
||||
}
|
||||
})
|
||||
log.console("")
|
||||
}
|
||||
|
||||
if (found_modules.length > 0) {
|
||||
if (length(found_modules) > 0) {
|
||||
log.console("Modules:")
|
||||
for (var m of found_modules) {
|
||||
arrfor(found_modules, function(m) {
|
||||
log.console(" " + m)
|
||||
}
|
||||
})
|
||||
log.console("")
|
||||
}
|
||||
|
||||
if (found_actors.length > 0) {
|
||||
if (length(found_actors) > 0) {
|
||||
log.console("Actors:")
|
||||
for (var a of found_actors) {
|
||||
arrfor(found_actors, function(a) {
|
||||
log.console(" " + a)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
111
source/blob.h
111
source/blob.h
@@ -138,72 +138,81 @@ void bitcpy(unsigned char *dst, size_t dst_bit_offset,
|
||||
}
|
||||
}
|
||||
|
||||
// Fast bit-copy for arbitrary bit ranges (inclusive) from src → dest
|
||||
void copy_bits_fast(const void *src, void *dest,
|
||||
size_t n, /* start bit in src (inclusive) */
|
||||
size_t m, /* end bit in src (inclusive) */
|
||||
size_t x) /* start bit in dest */
|
||||
static inline uint16_t load16_window(const uint8_t *s, size_t i, size_t last_byte)
|
||||
{
|
||||
const uint8_t *s = (const uint8_t*)src;
|
||||
uint8_t *d = (uint8_t*)dest;
|
||||
uint16_t lo = s[i];
|
||||
uint16_t hi = 0;
|
||||
if (i + 1 <= last_byte) hi = (uint16_t)s[i + 1] << 8;
|
||||
return lo | hi;
|
||||
}
|
||||
|
||||
size_t total_bits = m - n + 1;
|
||||
void copy_bits_fast(const void *src, void *dest, size_t n, size_t m, size_t x)
|
||||
{
|
||||
if (m < n) return;
|
||||
|
||||
size_t src_bit = n;
|
||||
size_t dst_bit = x;
|
||||
const uint8_t *s = (const uint8_t *)src;
|
||||
uint8_t *d = (uint8_t *)dest;
|
||||
|
||||
size_t total_bits = m - n + 1;
|
||||
size_t src_bit = n;
|
||||
size_t dst_bit = x;
|
||||
size_t src_byte = src_bit >> 3;
|
||||
size_t dst_byte = dst_bit >> 3;
|
||||
int src_off = src_bit & 7;
|
||||
int dst_off = dst_bit & 7;
|
||||
int src_off = src_bit & 7;
|
||||
int dst_off = dst_bit & 7;
|
||||
|
||||
// 1) Leading partial byte to align dest
|
||||
size_t last_src_byte = m >> 3;
|
||||
|
||||
/* Fast path: whole bytes, aligned */
|
||||
if (src_off == 0 && dst_off == 0 && (total_bits & 7) == 0) {
|
||||
memcpy(d + dst_byte, s + src_byte, total_bits >> 3);
|
||||
return;
|
||||
}
|
||||
|
||||
/* 1) Leading partial byte to align dest */
|
||||
if (dst_off != 0) {
|
||||
size_t chunk = 8 - dst_off;
|
||||
if (chunk > total_bits) chunk = total_bits;
|
||||
|
||||
uint8_t dst_mask = (((1u << chunk) - 1u) << dst_off);
|
||||
uint16_t wb = (uint16_t)s[src_byte] | ((uint16_t)s[src_byte + 1] << 8);
|
||||
uint8_t dst_mask = (uint8_t)(((1u << chunk) - 1u) << dst_off);
|
||||
uint16_t wb = load16_window(s, src_byte, last_src_byte);
|
||||
uint8_t bits = (uint8_t)((wb >> src_off) & ((1u << chunk) - 1u));
|
||||
bits <<= dst_off;
|
||||
d[dst_byte] = (d[dst_byte] & ~dst_mask) | (bits & dst_mask);
|
||||
d[dst_byte] = (d[dst_byte] & (uint8_t)~dst_mask) | (bits & dst_mask);
|
||||
|
||||
total_bits -= chunk;
|
||||
src_bit += chunk;
|
||||
dst_bit += chunk;
|
||||
src_byte = src_bit >> 3;
|
||||
dst_byte = dst_bit >> 3;
|
||||
src_off = src_bit & 7;
|
||||
dst_off = dst_bit & 7; // now zero
|
||||
src_bit += chunk;
|
||||
dst_bit += chunk;
|
||||
src_byte = src_bit >> 3;
|
||||
dst_byte = dst_bit >> 3;
|
||||
src_off = src_bit & 7;
|
||||
dst_off = dst_bit & 7;
|
||||
}
|
||||
|
||||
// 2) Copy full bytes
|
||||
/* 2) Copy full bytes */
|
||||
if (total_bits >= 8) {
|
||||
size_t num_bytes = total_bits >> 3;
|
||||
|
||||
if (src_off == 0) {
|
||||
size_t num_bytes = total_bits >> 3;
|
||||
memcpy(&d[dst_byte], &s[src_byte], num_bytes);
|
||||
total_bits -= num_bytes << 3;
|
||||
src_byte += num_bytes;
|
||||
dst_byte += num_bytes;
|
||||
memcpy(d + dst_byte, s + src_byte, num_bytes);
|
||||
} else {
|
||||
size_t num_bytes = total_bits >> 3;
|
||||
for (size_t i = 0; i < num_bytes; i++) {
|
||||
uint16_t wb = (uint16_t)s[src_byte + i] |
|
||||
((uint16_t)s[src_byte + i + 1] << 8);
|
||||
uint16_t wb = load16_window(s, src_byte + i, last_src_byte);
|
||||
d[dst_byte + i] = (uint8_t)((wb >> src_off) & 0xFFu);
|
||||
}
|
||||
total_bits -= num_bytes << 3;
|
||||
src_byte += num_bytes;
|
||||
dst_byte += num_bytes;
|
||||
}
|
||||
|
||||
total_bits -= num_bytes << 3;
|
||||
src_byte += num_bytes;
|
||||
dst_byte += num_bytes;
|
||||
}
|
||||
|
||||
// 3) Trailing bits (< 8)
|
||||
/* 3) Trailing bits (< 8), dest is byte-aligned here */
|
||||
if (total_bits > 0) {
|
||||
uint8_t dst_mask = (1u << total_bits) - 1u;
|
||||
uint16_t wb = (uint16_t)s[src_byte] | ((uint16_t)s[src_byte + 1] << 8);
|
||||
uint8_t dst_mask = (uint8_t)((1u << total_bits) - 1u);
|
||||
uint16_t wb = load16_window(s, src_byte, last_src_byte);
|
||||
uint8_t bits = (uint8_t)((wb >> src_off) & dst_mask);
|
||||
d[dst_byte] = (d[dst_byte] & ~dst_mask) | (bits & dst_mask);
|
||||
d[dst_byte] = (d[dst_byte] & (uint8_t)~dst_mask) | (bits & dst_mask);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,16 +387,30 @@ int blob_write_text(blob *b, const char *text) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int blob_write_bytes(blob *b, const void *data, size_t len_bytes) {
|
||||
int blob_write_bytes(blob *b, const void *data, size_t len_bytes)
|
||||
{
|
||||
if (!b || b->is_stone) return -1;
|
||||
if (len_bytes == 0) return 0;
|
||||
size_t bits = len_bytes * 8;
|
||||
if (blob_ensure_capacity(b, bits) < 0) return -1;
|
||||
copy_bits_fast(data, b->data, 0, bits - 1, b->length);
|
||||
b->length += bits;
|
||||
if (!len_bytes) return 0;
|
||||
|
||||
size_t bit_len = len_bytes << 3;
|
||||
size_t bit_off = b->length;
|
||||
size_t new_len = bit_off + bit_len;
|
||||
if (new_len < bit_off) return -1;
|
||||
|
||||
if (blob_ensure_capacity(b, new_len) < 0) return -1;
|
||||
|
||||
if ((bit_off & 7) == 0) {
|
||||
uint8_t *dst = (uint8_t *)b->data + (bit_off >> 3);
|
||||
memcpy(dst, data, len_bytes);
|
||||
} else {
|
||||
copy_bits_fast(data, b->data, 0, bit_len - 1, bit_off);
|
||||
}
|
||||
|
||||
b->length = new_len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int blob_read_bit(const blob *b, size_t pos, int *out_bit) {
|
||||
if (!b || !b->is_stone || !out_bit) return -1;
|
||||
if (pos >= b->length) return -1;
|
||||
|
||||
217
source/cell.c
217
source/cell.c
@@ -19,6 +19,10 @@
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
/* Test suite declarations */
|
||||
int run_c_test_suite(JSContext *ctx);
|
||||
static int run_test_suite(size_t heap_size);
|
||||
|
||||
cell_rt *root_cell = NULL;
|
||||
static char *core_path = NULL;
|
||||
|
||||
@@ -122,13 +126,12 @@ void script_startup(cell_rt *prt)
|
||||
rt = JS_NewRuntime();
|
||||
|
||||
JSContext *js = JS_NewContextRaw(rt);
|
||||
JS_SetInterruptHandler(rt, actor_interrupt_cb, prt);
|
||||
JS_SetInterruptHandler(rt, (JSInterruptHandler *)actor_interrupt_cb, prt);
|
||||
|
||||
JS_AddIntrinsicBaseObjects(js);
|
||||
JS_AddIntrinsicEval(js);
|
||||
JS_AddIntrinsicRegExp(js);
|
||||
JS_AddIntrinsicJSON(js);
|
||||
JS_AddIntrinsicMapSet(js);
|
||||
|
||||
JS_SetContextOpaque(js, prt);
|
||||
prt->context = js;
|
||||
@@ -146,12 +149,8 @@ void script_startup(cell_rt *prt)
|
||||
JS_SetPropertyStr(js, cell, "hidden", hidden_fn);
|
||||
JS_SetPropertyStr(js, hidden_fn, "os", js_os_use(js));
|
||||
|
||||
const char actorsym_script[] = "Symbol('actordata');";
|
||||
|
||||
JSValue actorsym = JS_Eval(js, actorsym_script, sizeof(actorsym_script)-1, "internal", 0);
|
||||
JS_SetPropertyStr(js, hidden_fn, "actorsym", actorsym);
|
||||
|
||||
crt->actor_sym = JS_ValueToAtom(js, actorsym);
|
||||
crt->actor_sym = JS_NewObject(js);
|
||||
JS_SetPropertyStr(js, hidden_fn, "actorsym", JS_DupValue(js,crt->actor_sym));
|
||||
|
||||
if (crt->init_wota) {
|
||||
JS_SetPropertyStr(js, hidden_fn, "init", wota2value(js, crt->init_wota));
|
||||
@@ -205,8 +204,170 @@ static void signal_handler(int sig)
|
||||
exit_handler();
|
||||
}
|
||||
|
||||
/* Run the C test suite with minimal runtime setup */
|
||||
static int run_test_suite(size_t heap_size)
|
||||
{
|
||||
JSRuntime *rt = JS_NewRuntime();
|
||||
if (!rt) {
|
||||
printf("Failed to create JS runtime\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
JSContext *ctx = JS_NewContextRawWithHeapSize(rt, heap_size);
|
||||
if (!ctx) {
|
||||
printf("Failed to create JS context\n");
|
||||
JS_FreeRuntime(rt);
|
||||
return 1;
|
||||
}
|
||||
|
||||
JS_AddIntrinsicBaseObjects(ctx);
|
||||
|
||||
int result = run_c_test_suite(ctx);
|
||||
|
||||
JS_FreeContext(ctx);
|
||||
JS_FreeRuntime(rt);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Run an immediate script string */
|
||||
static int run_eval(const char *script_or_file, int print_bytecode)
|
||||
{
|
||||
if (!find_cell_shop()) return 1;
|
||||
|
||||
/* Check if argument is a file path */
|
||||
struct stat st;
|
||||
char *script = NULL;
|
||||
char *allocated_script = NULL;
|
||||
const char *filename = "<eval>";
|
||||
|
||||
if (stat(script_or_file, &st) == 0 && S_ISREG(st.st_mode)) {
|
||||
/* It's a file, read its contents */
|
||||
FILE *f = fopen(script_or_file, "r");
|
||||
if (!f) {
|
||||
printf("Failed to open file: %s\n", script_or_file);
|
||||
return 1;
|
||||
}
|
||||
allocated_script = malloc(st.st_size + 1);
|
||||
if (!allocated_script) {
|
||||
fclose(f);
|
||||
printf("Failed to allocate memory for script\n");
|
||||
return 1;
|
||||
}
|
||||
size_t read_size = fread(allocated_script, 1, st.st_size, f);
|
||||
fclose(f);
|
||||
allocated_script[read_size] = '\0';
|
||||
script = allocated_script;
|
||||
filename = script_or_file;
|
||||
} else {
|
||||
/* Treat as inline script */
|
||||
script = (char *)script_or_file;
|
||||
}
|
||||
|
||||
JSRuntime *rt = JS_NewRuntime();
|
||||
if (!rt) {
|
||||
printf("Failed to create JS runtime\n");
|
||||
free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
|
||||
JSContext *ctx = JS_NewContextRaw(rt);
|
||||
if (!ctx) {
|
||||
printf("Failed to create JS context\n");
|
||||
JS_FreeRuntime(rt);
|
||||
free(allocated_script);
|
||||
return 1;
|
||||
}
|
||||
|
||||
JS_AddIntrinsicBaseObjects(ctx);
|
||||
JS_AddIntrinsicEval(ctx);
|
||||
JS_AddIntrinsicRegExp(ctx);
|
||||
JS_AddIntrinsicJSON(ctx);
|
||||
|
||||
int result = 0;
|
||||
|
||||
if (print_bytecode) {
|
||||
/* Compile only, then dump and optionally execute */
|
||||
JSValue func = JS_Eval(ctx, script, strlen(script), filename, JS_EVAL_FLAG_COMPILE_ONLY);
|
||||
if (JS_IsException(func)) {
|
||||
uncaught_exception(ctx, func);
|
||||
result = 1;
|
||||
} else {
|
||||
printf("=== Compiled Bytecode ===\n");
|
||||
JS_DumpFunctionBytecode(ctx, func);
|
||||
|
||||
/* Link - resolve global references */
|
||||
JSValue linked = JS_LinkFunction(ctx, func);
|
||||
if (JS_IsException(linked)) {
|
||||
uncaught_exception(ctx, linked);
|
||||
result = 1;
|
||||
} else {
|
||||
printf("\n=== Linked Bytecode ===\n");
|
||||
JS_DumpFunctionBytecode(ctx, linked);
|
||||
|
||||
/* Now execute the linked bytecode */
|
||||
JSValue v = JS_EvalFunction(ctx, linked);
|
||||
if (JS_IsException(v)) {
|
||||
uncaught_exception(ctx, v);
|
||||
result = 1;
|
||||
} else {
|
||||
JS_FreeValue(ctx, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Compile, link, execute */
|
||||
JSValue func = JS_Eval(ctx, script, strlen(script), filename, JS_EVAL_FLAG_COMPILE_ONLY);
|
||||
if (JS_IsException(func)) {
|
||||
uncaught_exception(ctx, func);
|
||||
result = 1;
|
||||
} else {
|
||||
JSValue linked = JS_LinkFunction(ctx, func);
|
||||
if (JS_IsException(linked)) {
|
||||
uncaught_exception(ctx, linked);
|
||||
result = 1;
|
||||
} else {
|
||||
JSValue v = JS_EvalFunction(ctx, linked);
|
||||
if (JS_IsException(v)) {
|
||||
uncaught_exception(ctx, v);
|
||||
result = 1;
|
||||
} else {
|
||||
JS_FreeValue(ctx, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JS_FreeContext(ctx);
|
||||
JS_FreeRuntime(rt);
|
||||
free(allocated_script);
|
||||
return result;
|
||||
}
|
||||
|
||||
int cell_init(int argc, char **argv)
|
||||
{
|
||||
/* Check for --test flag to run C test suite */
|
||||
if (argc >= 2 && strcmp(argv[1], "--test") == 0) {
|
||||
size_t heap_size = 64 * 1024; /* 64KB default */
|
||||
if (argc >= 3) {
|
||||
heap_size = strtoull(argv[2], NULL, 0);
|
||||
/* Round up to power of 2 for buddy allocator */
|
||||
size_t p = 1;
|
||||
while (p < heap_size) p <<= 1;
|
||||
heap_size = p;
|
||||
}
|
||||
return run_test_suite(heap_size);
|
||||
}
|
||||
|
||||
/* Check for -e or --eval flag to run immediate script */
|
||||
/* Also check for -p flag to print bytecode */
|
||||
if (argc >= 3 && (strcmp(argv[1], "-e") == 0 || strcmp(argv[1], "--eval") == 0)) {
|
||||
return run_eval(argv[2], 0);
|
||||
}
|
||||
if (argc >= 3 && (strcmp(argv[1], "-p") == 0 || strcmp(argv[1], "--print-bytecode") == 0)) {
|
||||
return run_eval(argv[2], 1);
|
||||
}
|
||||
|
||||
int script_start = 1;
|
||||
|
||||
/* Find the cell shop at ~/.cell */
|
||||
@@ -246,10 +407,8 @@ int cell_init(int argc, char **argv)
|
||||
|
||||
int JS_ArrayLength(JSContext *js, JSValue a)
|
||||
{
|
||||
JSValue length = JS_GetPropertyStr(js, a, "length");
|
||||
int len;
|
||||
JS_ToInt32(js,&len,length);
|
||||
JS_FreeValue(js,length);
|
||||
int64_t len;
|
||||
JS_GetLength(js, a, &len);
|
||||
return len;
|
||||
}
|
||||
|
||||
@@ -283,7 +442,6 @@ void cell_trace_sethook(cell_hook)
|
||||
int uncaught_exception(JSContext *js, JSValue v)
|
||||
{
|
||||
cell_rt *rt = JS_GetContextOpaque(js);
|
||||
|
||||
if (!JS_HasException(js)) {
|
||||
JS_FreeValue(js,v);
|
||||
return 1;
|
||||
@@ -291,26 +449,21 @@ int uncaught_exception(JSContext *js, JSValue v)
|
||||
|
||||
JSValue exp = JS_GetException(js);
|
||||
|
||||
if (JS_IsNull(rt->on_exception)) {
|
||||
const char *str = JS_ToCString(js, exp);
|
||||
if (str) {
|
||||
printf("Uncaught exception: %s\n", str);
|
||||
JS_FreeCString(js, str);
|
||||
}
|
||||
|
||||
JSValue stack = JS_GetPropertyStr(js, exp, "stack");
|
||||
if (!JS_IsNull(stack)) {
|
||||
const char *stack_str = JS_ToCString(js, stack);
|
||||
if (stack_str) {
|
||||
printf("Stack trace:\n%s\n", stack_str);
|
||||
JS_FreeCString(js, stack_str);
|
||||
}
|
||||
}
|
||||
JS_FreeValue(js, stack);
|
||||
} else {
|
||||
JSValue ret = JS_Call(js, rt->on_exception, JS_NULL, 1, &exp);
|
||||
JS_FreeValue(js, ret);
|
||||
JSValue message = JS_GetPropertyStr(js, exp, "message");
|
||||
const char *msg_str = JS_ToCString(js, message);
|
||||
if (msg_str) {
|
||||
printf("Exception: %s\n", msg_str);
|
||||
JS_FreeCString(js, msg_str);
|
||||
}
|
||||
JS_FreeValue(js, message);
|
||||
|
||||
JSValue stack = JS_GetPropertyStr(js, exp, "stack");
|
||||
const char *stack_str = JS_ToCString(js, stack);
|
||||
if (stack_str) {
|
||||
printf("Stack:\n%s\n", stack_str);
|
||||
JS_FreeCString(js, stack_str);
|
||||
}
|
||||
JS_FreeValue(js, stack);
|
||||
|
||||
JS_FreeValue(js, exp);
|
||||
JS_FreeValue(js, v);
|
||||
|
||||
@@ -36,7 +36,7 @@ void cell_trace_sethook(cell_hook);
|
||||
// Macros to help with creating scripts
|
||||
#define MIST_CFUNC_DEF(name, length, func1, props) { name, props, JS_DEF_CFUNC, 0, .u = { .func = { length, JS_CFUNC_generic, { .generic = func1 } } } }
|
||||
|
||||
#define MIST_FUNC_DEF(TYPE, FN, LEN) MIST_CFUNC_DEF(#FN, LEN, js_##TYPE##_##FN, JS_PROP_C_W_E)
|
||||
#define MIST_FUNC_DEF(TYPE, FN, LEN) MIST_CFUNC_DEF(#FN, LEN, js_##TYPE##_##FN, 0)
|
||||
#define PROTO_FUNC_DEF(TYPE, FN, LEN) MIST_CFUNC_DEF(#FN, LEN, js_##TYPE##_##FN, 0)
|
||||
|
||||
#define JS_SETSIG JSContext *js, JSValue self, JSValue val
|
||||
@@ -68,41 +68,6 @@ void cell_trace_sethook(cell_hook);
|
||||
JS_FreeCString(js,str); \
|
||||
) \
|
||||
|
||||
#define MIST_CGETSET_BASE(name, fgetter, fsetter, props) { name, props, JS_DEF_CGETSET, 0, .u = { .getset = { .get = { .getter = fgetter }, .set = { .setter = fsetter } } } }
|
||||
#define MIST_CGETSET_DEF(name, fgetter, fsetter) MIST_CGETSET_BASE(name, fgetter, fsetter, JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE)
|
||||
#define MIST_CGETET_HID(name, fgetter, fsetter) MIST_CGETSET_BASE(name, fgetter, fsetter, JS_PROP_CONFIGURABLE)
|
||||
#define MIST_GET(name, fgetter) { #fgetter , JS_PROP_CONFIGURABLE | JS_PROP_ENUMERABLE, JS_DEF_CGETSET, 0, .u = { .getset = { .get = { .getter = js_##name##_get_##fgetter } } } }
|
||||
|
||||
#define CGETSET_ADD_NAME(ID, ENTRY, NAME) MIST_CGETSET_DEF(#NAME, js_##ID##_get_##ENTRY, js_##ID##_set_##ENTRY)
|
||||
#define CGETSET_ADD(ID, ENTRY) MIST_CGETSET_DEF(#ENTRY, js_##ID##_get_##ENTRY, js_##ID##_set_##ENTRY)
|
||||
#define CGETSET_ADD_HID(ID, ENTRY) MIST_CGETSET_BASE(#ENTRY, js_##ID##_get_##ENTRY, js_##ID##_set_##ENTRY, JS_PROP_CONFIGURABLE)
|
||||
|
||||
#define GETSETPAIR(ID, ENTRY, TYPE, FN) \
|
||||
JSValue js_##ID##_set_##ENTRY (JS_SETSIG) { \
|
||||
js2##ID (js, self)->ENTRY = js2##TYPE (js,val); \
|
||||
{FN;} \
|
||||
return JS_NULL; \
|
||||
} \
|
||||
\
|
||||
JSValue js_##ID##_get_##ENTRY (JSContext *js, JSValue self) { \
|
||||
return TYPE##2js(js,js2##ID (js, self)->ENTRY); \
|
||||
} \
|
||||
|
||||
#define JSC_GETSET(ID, ENTRY, TYPE) GETSETPAIR( ID , ENTRY , TYPE , ; )
|
||||
#define JSC_GETSET_APPLY(ID, ENTRY, TYPE) GETSETPAIR(ID, ENTRY, TYPE, ID##_apply(js2##ID (js, self));)
|
||||
#define JSC_GETSET_CALLBACK(ID, ENTRY) \
|
||||
JSValue js_##ID##_set_##ENTRY (JS_SETSIG) { \
|
||||
JSValue fn = js2##ID (js, self)->ENTRY; \
|
||||
if (!JS_IsNull(fn)) JS_FreeValue(js, fn); \
|
||||
js2##ID (js, self)->ENTRY = JS_DupValue(js, val); \
|
||||
return JS_NULL; \
|
||||
}\
|
||||
JSValue js_##ID##_get_##ENTRY (JSContext *js, JSValue self) { return JS_DupValue(js, js2##ID (js, self)->ENTRY); } \
|
||||
|
||||
#define JSC_GET(ID, ENTRY, TYPE) \
|
||||
JSValue js_##ID##_get_##ENTRY (JSContext *js, JSValue self) { \
|
||||
return TYPE##2js(js,js2##ID (js, self)->ENTRY); } \
|
||||
|
||||
#define QJSCLASS(TYPE, ...)\
|
||||
JSClassID js_##TYPE##_id;\
|
||||
static void js_##TYPE##_finalizer(JSRuntime *rt, JSValue val){\
|
||||
@@ -179,15 +144,12 @@ JS_SetClassProto(js, js_##TYPE##_id, TYPE##_proto); \
|
||||
#define QJSCLASSPREP_FUNCS_CTOR(TYPE, CTOR_ARGC) \
|
||||
({ \
|
||||
QJSCLASSPREP_FUNCS(TYPE); \
|
||||
JSValue TYPE##_ctor = JS_NewCFunction2(js, js_##TYPE##_constructor, #TYPE, CTOR_ARGC, JS_CFUNC_constructor, 0); \
|
||||
JS_SetConstructor(js, TYPE##_ctor, TYPE##_proto); \
|
||||
JSValue TYPE##_ctor = JS_NewCFunction2(js, js_##TYPE##_constructor, #TYPE, CTOR_ARGC, JS_CFUNC_generic, 0); \
|
||||
TYPE##_ctor; \
|
||||
})
|
||||
|
||||
|
||||
#define countof(x) (sizeof(x)/sizeof((x)[0]))
|
||||
|
||||
|
||||
// Common macros for property access
|
||||
#define JS_GETPROP(JS, TARGET, VALUE, PROP, TYPE) {\
|
||||
JSValue __##PROP##__v = JS_GetPropertyStr(JS,VALUE,#PROP); \
|
||||
|
||||
@@ -53,7 +53,7 @@ typedef struct cell_rt {
|
||||
int main_thread_only;
|
||||
int affinity;
|
||||
|
||||
JSAtom actor_sym;
|
||||
JSValue actor_sym;
|
||||
|
||||
const char *name; // human friendly name
|
||||
cell_hook trace_hook;
|
||||
@@ -63,7 +63,7 @@ cell_rt *create_actor(void *wota);
|
||||
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
|
||||
void actor_disrupt(cell_rt *actor);
|
||||
|
||||
JSAtom actor_sym(cell_rt *actor);
|
||||
JSValue actor_sym(cell_rt *actor);
|
||||
|
||||
const char *send_message(const char *id, void *msg);
|
||||
const char *register_actor(const char *id, cell_rt *actor, int mainthread, double ar);
|
||||
|
||||
@@ -111,7 +111,7 @@ JSC_CCALL(actor_on_exception,
|
||||
)
|
||||
|
||||
JSC_CCALL(actor_clock,
|
||||
if (!JS_IsFunction(js, argv[0]))
|
||||
if (!JS_IsFunction(argv[0]))
|
||||
return JS_ThrowReferenceError(js, "Argument must be a function.");
|
||||
|
||||
cell_rt *actor = JS_GetContextOpaque(js);
|
||||
@@ -119,7 +119,7 @@ JSC_CCALL(actor_clock,
|
||||
)
|
||||
|
||||
JSC_CCALL(actor_delay,
|
||||
if (!JS_IsFunction(js, argv[0]))
|
||||
if (!JS_IsFunction(argv[0]))
|
||||
return JS_ThrowReferenceError(js, "Argument must be a function.");
|
||||
|
||||
cell_rt *actor = JS_GetContextOpaque(js);
|
||||
|
||||
@@ -1,606 +0,0 @@
|
||||
#define BLOB_IMPLEMENTATION
|
||||
#include "blob.h"
|
||||
#include "cell.h"
|
||||
|
||||
// Get countof from macros if not defined
|
||||
#ifndef countof
|
||||
#define countof(x) (sizeof(x)/sizeof((x)[0]))
|
||||
#endif
|
||||
|
||||
// Free function for blob
|
||||
void blob_free(JSRuntime *rt, blob *b)
|
||||
{
|
||||
blob_destroy(b);
|
||||
}
|
||||
|
||||
// Use QJSCLASS macro to generate class boilerplate
|
||||
QJSCLASS(blob,)
|
||||
|
||||
// Constructor function for blob
|
||||
static JSValue js_blob_constructor(JSContext *ctx, JSValueConst new_target,
|
||||
int argc, JSValueConst *argv) {
|
||||
blob *bd = NULL;
|
||||
|
||||
// new Blob()
|
||||
if (argc == 0) {
|
||||
// empty antestone blob
|
||||
bd = blob_new(0);
|
||||
}
|
||||
// new Blob(capacity)
|
||||
else if (argc == 1 && JS_IsNumber(argv[0])) {
|
||||
int64_t capacity_bits;
|
||||
if (JS_ToInt64(ctx, &capacity_bits, argv[0]) < 0) {
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
if (capacity_bits < 0) capacity_bits = 0;
|
||||
bd = blob_new((size_t)capacity_bits);
|
||||
}
|
||||
// new Blob(length, logical/random)
|
||||
else if (argc == 2 && JS_IsNumber(argv[0])) {
|
||||
int64_t length_bits;
|
||||
if (JS_ToInt64(ctx, &length_bits, argv[0]) < 0) {
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
if (length_bits < 0) length_bits = 0;
|
||||
|
||||
if (JS_IsBool(argv[1])) {
|
||||
// Fill with all 0s or all 1s
|
||||
int is_one = JS_ToBool(ctx, argv[1]);
|
||||
bd = blob_new_with_fill((size_t)length_bits, is_one);
|
||||
} else if (JS_IsFunction(ctx, argv[1])) {
|
||||
/* Random function provided – call it and use up to 56 bits at a time */
|
||||
size_t bytes = (length_bits + 7) / 8;
|
||||
bd = blob_new((size_t)length_bits);
|
||||
if (bd) {
|
||||
bd->length = length_bits;
|
||||
/* Ensure the backing storage starts out zeroed */
|
||||
memset(bd->data, 0, bytes);
|
||||
|
||||
size_t bits_written = 0;
|
||||
while (bits_written < length_bits) {
|
||||
JSValue randval = JS_Call(ctx, argv[1], JS_NULL, 0, NULL);
|
||||
if (JS_IsException(randval)) {
|
||||
blob_destroy(bd);
|
||||
return JS_EXCEPTION;
|
||||
}
|
||||
|
||||
int64_t fitval;
|
||||
JS_ToInt64(ctx, &fitval, randval);
|
||||
JS_FreeValue(ctx, randval);
|
||||
|
||||
/* Extract up to 56 bits from the random value */
|
||||
size_t bits_to_use = length_bits - bits_written;
|
||||
if (bits_to_use > 52) bits_to_use = 52;
|
||||
|
||||
/* Write bits from the random value */
|
||||
for (size_t j = 0; j < bits_to_use; j++) {
|
||||
size_t bit_pos = bits_written + j;
|
||||
size_t byte_idx = bit_pos / 8;
|
||||
size_t bit_idx = bit_pos % 8;
|
||||
|
||||
if (fitval & (1LL << j))
|
||||
bd->data[byte_idx] |= (uint8_t)(1 << bit_idx);
|
||||
else
|
||||
bd->data[byte_idx] &= (uint8_t)~(1 << bit_idx);
|
||||
}
|
||||
|
||||
bits_written += bits_to_use;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return JS_ThrowTypeError(ctx, "Second argument must be boolean or random function");
|
||||
}
|
||||
}
|
||||
// new Blob(blob, from, to)
|
||||
else if (argc >= 1 && JS_IsObject(argv[0])) {
|
||||
// we try copying from another blob if it's of the same class
|
||||
blob *src = js2blob(ctx, argv[0]);
|
||||
if (!src) {
|
||||
return JS_ThrowTypeError(ctx, "Blob constructor: argument 1 not a blob");
|
||||
}
|
||||
int64_t from = 0, to = (int64_t)src->length;
|
||||
if (argc >= 2 && JS_IsNumber(argv[1])) {
|
||||
JS_ToInt64(ctx, &from, argv[1]);
|
||||
if (from < 0) from = 0;
|
||||
}
|
||||
if (argc >= 3 && JS_IsNumber(argv[2])) {
|
||||
JS_ToInt64(ctx, &to, argv[2]);
|
||||
if (to < from) to = from;
|
||||
if (to > (int64_t)src->length) to = (int64_t)src->length;
|
||||
}
|
||||
bd = blob_new_from_blob(src, (size_t)from, (size_t)to);
|
||||
}
|
||||
// else fail
|
||||
else {
|
||||
return JS_ThrowTypeError(ctx, "Blob constructor: invalid arguments");
|
||||
}
|
||||
|
||||
if (!bd) {
|
||||
return JS_ThrowOutOfMemory(ctx);
|
||||
}
|
||||
|
||||
return blob2js(ctx, bd);
|
||||
}
|
||||
|
||||
// blob.write_bit(logical)
|
||||
static JSValue js_blob_write_bit(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
if (argc < 1) {
|
||||
return JS_ThrowTypeError(ctx, "write_bit(logical) requires 1 argument");
|
||||
}
|
||||
blob *bd = js2blob(ctx, this_val);
|
||||
if (!bd) {
|
||||
return JS_ThrowTypeError(ctx, "write_bit: not called on a blob");
|
||||
}
|
||||
|
||||
// Handle numeric 0/1 or boolean
|
||||
int bit_val;
|
||||
if (JS_IsNumber(argv[0])) {
|
||||
int32_t num;
|
||||
JS_ToInt32(ctx, &num, argv[0]);
|
||||
if (num != 0 && num != 1) {
|
||||
return JS_ThrowTypeError(ctx, "write_bit: value must be true, false, 0, or 1");
|
||||
}
|
||||
bit_val = num;
|
||||
} else {
|
||||
bit_val = JS_ToBool(ctx, argv[0]);
|
||||
}
|
||||
|
||||
if (blob_write_bit(bd, bit_val) < 0) {
|
||||
return JS_ThrowTypeError(ctx, "write_bit: cannot write (maybe stone or OOM)");
|
||||
}
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
// blob.write_blob(second_blob)
|
||||
static JSValue js_blob_write_blob(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
if (argc < 1) {
|
||||
return JS_ThrowTypeError(ctx, "write_blob(second_blob) requires 1 argument");
|
||||
}
|
||||
blob *bd = js2blob(ctx, this_val);
|
||||
if (!bd) {
|
||||
return JS_ThrowTypeError(ctx, "write_blob: not called on a blob");
|
||||
}
|
||||
blob *second = js2blob(ctx, argv[0]);
|
||||
if (!second) {
|
||||
return JS_ThrowTypeError(ctx, "write_blob: argument must be a blob");
|
||||
}
|
||||
|
||||
if (blob_write_blob(bd, second) < 0) {
|
||||
return JS_ThrowTypeError(ctx, "write_blob: cannot write to stone blob or OOM");
|
||||
}
|
||||
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
// blob.write_dec64(number)
|
||||
static JSValue js_blob_write_number(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
if (argc < 1) {
|
||||
return JS_ThrowTypeError(ctx, "write_dec64(number) requires 1 argument");
|
||||
}
|
||||
blob *bd = js2blob(ctx, this_val);
|
||||
if (!bd)
|
||||
return JS_ThrowTypeError(ctx, "write_dec64: not called on a blob");
|
||||
|
||||
// Get the number as a double and convert to DEC64
|
||||
double d;
|
||||
if (JS_ToFloat64(ctx, &d, argv[0]) < 0)
|
||||
return JS_EXCEPTION;
|
||||
|
||||
if (blob_write_dec64(bd, d) < 0)
|
||||
return JS_ThrowTypeError(ctx, "write_dec64: cannot write to stone blob or OOM");
|
||||
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
// blob.w16(value) - write a 16-bit value (short)
|
||||
static JSValue js_blob_w16(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
if (argc < 1)
|
||||
return JS_ThrowTypeError(ctx, "w16(value) requires 1 argument");
|
||||
|
||||
blob *bd = js2blob(ctx, this_val);
|
||||
if (!bd)
|
||||
return JS_ThrowTypeError(ctx, "w16: not called on a blob");
|
||||
|
||||
int32_t value;
|
||||
if (JS_ToInt32(ctx, &value, argv[0]) < 0) return JS_EXCEPTION;
|
||||
|
||||
int16_t short_val = (int16_t)value;
|
||||
if (blob_write_bytes(bd, &short_val, sizeof(int16_t)) < 0)
|
||||
return JS_ThrowTypeError(ctx, "w16: cannot write to stone blob or OOM");
|
||||
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
// blob.wf(value) - write a float (32-bit)
|
||||
static JSValue js_blob_wf(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
if (argc < 1)
|
||||
return JS_ThrowTypeError(ctx, "wf(value) requires 1 argument");
|
||||
|
||||
blob *bd = js2blob(ctx, this_val);
|
||||
if (!bd)
|
||||
return JS_ThrowTypeError(ctx, "wf: not called on a blob");
|
||||
|
||||
double d;
|
||||
if (JS_ToFloat64(ctx, &d, argv[0]) < 0) return JS_EXCEPTION;
|
||||
|
||||
float f = (float)d;
|
||||
if (blob_write_bytes(bd, &f, sizeof(float)) < 0)
|
||||
return JS_ThrowTypeError(ctx, "wf: cannot write to stone blob or OOM");
|
||||
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
// blob.write_fit(value, len)
|
||||
static JSValue js_blob_write_fit(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
if (argc < 2)
|
||||
return JS_ThrowTypeError(ctx, "write_fit(value, len) requires 2 arguments");
|
||||
|
||||
blob *bd = js2blob(ctx, this_val);
|
||||
if (!bd)
|
||||
return JS_ThrowTypeError(ctx, "write_fit: not called on a blob");
|
||||
|
||||
int64_t value;
|
||||
int32_t len;
|
||||
|
||||
if (JS_ToInt64(ctx, &value, argv[0]) < 0) return JS_EXCEPTION;
|
||||
if (JS_ToInt32(ctx, &len, argv[1]) < 0) return JS_EXCEPTION;
|
||||
|
||||
if (blob_write_fit(bd, value, len) < 0) {
|
||||
return JS_ThrowTypeError(ctx, "write_fit: value doesn't fit in specified bits, stone blob, or OOM");
|
||||
}
|
||||
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
// blob.write_kim(fit)
|
||||
static JSValue js_blob_write_text(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
if (argc < 1)
|
||||
return JS_ThrowTypeError(ctx, "write_kim(fit) requires 1 argument");
|
||||
|
||||
blob *bd = js2blob(ctx, this_val);
|
||||
if (!bd)
|
||||
return JS_ThrowTypeError(ctx, "write_kim: not called on a blob");
|
||||
|
||||
// Handle number or single character string
|
||||
const char *str = JS_ToCString(ctx, argv[0]);
|
||||
|
||||
if (blob_write_text(bd, str) < 0) {
|
||||
JS_FreeCString(ctx,str);
|
||||
return JS_ThrowTypeError(ctx, "write_kim: cannot write to stone blob or OOM");
|
||||
}
|
||||
|
||||
JS_FreeCString(ctx,str);
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
// blob.write_pad(block_size)
|
||||
static JSValue js_blob_write_pad(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
if (argc < 1)
|
||||
return JS_ThrowTypeError(ctx, "write_pad(block_size) requires 1 argument");
|
||||
|
||||
blob *bd = js2blob(ctx, this_val);
|
||||
if (!bd)
|
||||
return JS_ThrowTypeError(ctx, "write_pad: not called on a blob");
|
||||
|
||||
int32_t block_size;
|
||||
if (JS_ToInt32(ctx, &block_size, argv[0]) < 0) return JS_EXCEPTION;
|
||||
|
||||
if (blob_write_pad(bd, block_size) < 0)
|
||||
return JS_ThrowTypeError(ctx, "write_pad: cannot write (stone blob, OOM, or invalid block size)");
|
||||
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
// blob.read_logical(from)
|
||||
static JSValue js_blob_read_logical(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
if (argc < 1) {
|
||||
return JS_ThrowTypeError(ctx, "read_logical(from) requires 1 argument");
|
||||
}
|
||||
blob *bd = js2blob(ctx, this_val);
|
||||
if (!bd) {
|
||||
return JS_ThrowTypeError(ctx, "read_logical: not called on a blob");
|
||||
}
|
||||
int64_t pos;
|
||||
if (JS_ToInt64(ctx, &pos, argv[0]) < 0) {
|
||||
return JS_ThrowInternalError(ctx, "must provide a positive bit");
|
||||
}
|
||||
if (pos < 0) {
|
||||
return JS_ThrowRangeError(ctx, "read_logical: position must be non-negative");
|
||||
}
|
||||
int bit_val;
|
||||
if (blob_read_bit(bd, (size_t)pos, &bit_val) < 0) {
|
||||
return JS_ThrowTypeError(ctx, "read_logical: blob must be stone");
|
||||
}
|
||||
return JS_NewBool(ctx, bit_val);
|
||||
}
|
||||
|
||||
// blob.read_blob(from, to)
|
||||
static JSValue js_blob_read_blob(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
blob *bd = js2blob(ctx, this_val);
|
||||
if (!bd) {
|
||||
return JS_ThrowTypeError(ctx, "read_blob: not called on a blob");
|
||||
}
|
||||
|
||||
if (!bd->is_stone) {
|
||||
return JS_ThrowTypeError(ctx, "read_blob: blob must be stone");
|
||||
}
|
||||
|
||||
int64_t from = 0;
|
||||
int64_t to = bd->length;
|
||||
|
||||
if (argc >= 1) {
|
||||
if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION;
|
||||
if (from < 0) from = 0;
|
||||
}
|
||||
if (argc >= 2) {
|
||||
if (JS_ToInt64(ctx, &to, argv[1]) < 0) return JS_EXCEPTION;
|
||||
if (to > (int64_t)bd->length) to = bd->length;
|
||||
}
|
||||
|
||||
blob *new_bd = blob_read_blob(bd, from, to);
|
||||
if (!new_bd) {
|
||||
return JS_ThrowOutOfMemory(ctx);
|
||||
}
|
||||
|
||||
return blob2js(ctx, new_bd);
|
||||
}
|
||||
|
||||
// blob.read_dec64(from)
|
||||
static JSValue js_blob_read_number(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
if (argc < 1) {
|
||||
return JS_ThrowTypeError(ctx, "read_dec64(from) requires 1 argument");
|
||||
}
|
||||
blob *bd = js2blob(ctx, this_val);
|
||||
if (!bd) {
|
||||
return JS_ThrowTypeError(ctx, "read_number: not called on a blob");
|
||||
}
|
||||
|
||||
if (!bd->is_stone) {
|
||||
return JS_ThrowTypeError(ctx, "read_number: blob must be stone");
|
||||
}
|
||||
|
||||
double from;
|
||||
if (JS_ToFloat64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION;
|
||||
|
||||
if (from < 0) return JS_ThrowRangeError(ctx, "read_number: position must be non-negative");
|
||||
|
||||
double d;
|
||||
if (blob_read_dec64(bd, from, &d) < 0) {
|
||||
return JS_ThrowRangeError(ctx, "read_number: out of range");
|
||||
}
|
||||
|
||||
return JS_NewFloat64(ctx, d);
|
||||
}
|
||||
|
||||
// blob.read_fit(from, len)
|
||||
static JSValue js_blob_read_fit(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
if (argc < 2) {
|
||||
return JS_ThrowTypeError(ctx, "read_fit(from, len) requires 2 arguments");
|
||||
}
|
||||
|
||||
blob *bd = js2blob(ctx, this_val);
|
||||
if (!bd) {
|
||||
return JS_ThrowTypeError(ctx, "read_fit: not called on a blob");
|
||||
}
|
||||
|
||||
if (!bd->is_stone) {
|
||||
return JS_ThrowTypeError(ctx, "read_fit: blob must be stone");
|
||||
}
|
||||
|
||||
int64_t from;
|
||||
int32_t len;
|
||||
|
||||
if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION;
|
||||
if (JS_ToInt32(ctx, &len, argv[1]) < 0) return JS_EXCEPTION;
|
||||
|
||||
if (from < 0) {
|
||||
return JS_ThrowRangeError(ctx, "read_fit: position must be non-negative");
|
||||
}
|
||||
|
||||
int64_t value;
|
||||
if (blob_read_fit(bd, from, len, &value) < 0) {
|
||||
return JS_ThrowRangeError(ctx, "read_fit: out of range or invalid length");
|
||||
}
|
||||
|
||||
return JS_NewInt64(ctx, value);
|
||||
}
|
||||
|
||||
// blob.read_text(from)
|
||||
static JSValue js_blob_read_text(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
blob *bd = js2blob(ctx, this_val);
|
||||
if (!bd) {
|
||||
return JS_ThrowTypeError(ctx, "read_text: not called on a blob");
|
||||
}
|
||||
|
||||
if (!bd->is_stone) {
|
||||
return JS_ThrowTypeError(ctx, "read_text: blob must be stone");
|
||||
}
|
||||
|
||||
int64_t from;
|
||||
if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION;
|
||||
|
||||
char *text;
|
||||
size_t bits_read;
|
||||
if (blob_read_text(bd, from, &text, &bits_read) < 0) {
|
||||
return JS_ThrowRangeError(ctx, "read_text: out of range or invalid encoding");
|
||||
}
|
||||
|
||||
JSValue result = JS_NewString(ctx, text);
|
||||
free(text);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// blob.pad?(from, block_size)
|
||||
static JSValue js_blob_pad_q(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
if (argc < 2) {
|
||||
return JS_ThrowTypeError(ctx, "pad?(from, block_size) requires 2 arguments");
|
||||
}
|
||||
blob *bd = js2blob(ctx, this_val);
|
||||
if (!bd) {
|
||||
return JS_ThrowTypeError(ctx, "pad?: not called on a blob");
|
||||
}
|
||||
|
||||
if (!bd->is_stone) {
|
||||
return JS_ThrowTypeError(ctx, "pad?: blob must be stone");
|
||||
}
|
||||
|
||||
int64_t from;
|
||||
int32_t block_size;
|
||||
if (JS_ToInt64(ctx, &from, argv[0]) < 0) return JS_EXCEPTION;
|
||||
if (JS_ToInt32(ctx, &block_size, argv[1]) < 0) return JS_EXCEPTION;
|
||||
|
||||
return JS_NewBool(ctx, blob_pad_check(bd, from, block_size));
|
||||
}
|
||||
|
||||
// blob.stone()
|
||||
static JSValue js_blob_stone(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
blob *bd = js2blob(ctx, this_val);
|
||||
if (!bd) {
|
||||
return JS_ThrowTypeError(ctx, "stone: not called on a blob");
|
||||
}
|
||||
if (!bd->is_stone) {
|
||||
blob_make_stone(bd);
|
||||
}
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
static JSValue js_blob_stonep(JSContext *ctx, JSValueConst this_val,
|
||||
int argc, JSValueConst *argv) {
|
||||
blob *bd = js2blob(ctx, this_val);
|
||||
if (!bd) {
|
||||
return JS_ThrowTypeError(ctx, "stone: not called on a blob");
|
||||
}
|
||||
return JS_NewBool(ctx, bd->is_stone);
|
||||
}
|
||||
|
||||
// blob.length getter
|
||||
// Return number of bits in the blob
|
||||
static JSValue js_blob_get_length(JSContext *ctx, JSValueConst this_val, int magic) {
|
||||
blob *bd = js2blob(ctx, this_val);
|
||||
if (!bd) {
|
||||
return JS_ThrowTypeError(ctx, "length: not called on a blob");
|
||||
}
|
||||
return JS_NewInt64(ctx, bd->length);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Exports list
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
static const JSCFunctionListEntry js_blob_funcs[] = {
|
||||
// Write methods
|
||||
JS_CFUNC_DEF("write_bit", 1, js_blob_write_bit),
|
||||
JS_CFUNC_DEF("write_blob", 1, js_blob_write_blob),
|
||||
JS_CFUNC_DEF("write_number", 1, js_blob_write_number),
|
||||
JS_CFUNC_DEF("write_fit", 2, js_blob_write_fit),
|
||||
JS_CFUNC_DEF("write_text", 1, js_blob_write_text),
|
||||
JS_CFUNC_DEF("write_pad", 1, js_blob_write_pad),
|
||||
JS_CFUNC_DEF("wf", 1, js_blob_wf),
|
||||
JS_CFUNC_DEF("w16", 1, js_blob_w16),
|
||||
|
||||
// Read methods
|
||||
JS_CFUNC_DEF("read_logical", 1, js_blob_read_logical),
|
||||
JS_CFUNC_DEF("read_blob", 2, js_blob_read_blob),
|
||||
JS_CFUNC_DEF("read_number", 1, js_blob_read_number),
|
||||
JS_CFUNC_DEF("read_fit", 2, js_blob_read_fit),
|
||||
JS_CFUNC_DEF("read_text", 1, js_blob_read_text),
|
||||
JS_CFUNC_DEF("pad?", 2, js_blob_pad_q),
|
||||
|
||||
// Other methods
|
||||
JS_CFUNC_DEF("stone", 0, js_blob_stone),
|
||||
JS_CFUNC_DEF("stonep", 0, js_blob_stonep),
|
||||
|
||||
// Length property getter
|
||||
JS_CGETSET_DEF("length", js_blob_get_length, NULL),
|
||||
};
|
||||
|
||||
JSValue js_blob_use(JSContext *js) {
|
||||
return QJSCLASSPREP_FUNCS_CTOR(blob, 3);
|
||||
}
|
||||
|
||||
JSValue js_new_blob_stoned_copy(JSContext *js, void *data, size_t bytes)
|
||||
{
|
||||
blob *b = blob_new(bytes*8);
|
||||
memcpy(b->data, data, bytes);
|
||||
b->length = bytes * 8; // Set the actual length in bits
|
||||
blob_make_stone(b);
|
||||
|
||||
return blob2js(js, b);
|
||||
}
|
||||
|
||||
void *js_get_blob_data(JSContext *js, size_t *size, JSValue v)
|
||||
{
|
||||
blob *b = js2blob(js, v);
|
||||
if (!b) {
|
||||
JS_ThrowReferenceError(js, "get_blob_data: not called on a blob");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!b->is_stone) {
|
||||
JS_ThrowReferenceError(js, "attempted to read data from a non-stone blob");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (b->length % 8 != 0) {
|
||||
JS_ThrowReferenceError(js, "attempted to read data from a non-byte aligned blob [length is %d]", b->length);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*size = (b->length + 7) / 8; // Return actual byte size based on bit length
|
||||
|
||||
return b->data;
|
||||
}
|
||||
|
||||
void *js_get_blob_data_bits(JSContext *js, size_t *bits, JSValue v)
|
||||
{
|
||||
blob *b = js2blob(js, v);
|
||||
if (!b) {
|
||||
JS_ThrowReferenceError(js, "get_blob_data_bits: not called on a blob");
|
||||
return -1;
|
||||
}
|
||||
if (!b->is_stone) {
|
||||
JS_ThrowReferenceError(js, "attempted to read data from a non-stone blob");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!b->data) {
|
||||
JS_ThrowReferenceError(js, "attempted to read data from an empty blob");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (b->length % 8 != 0) {
|
||||
JS_ThrowReferenceError(js, "attempted to read data from a non-byte aligned blob");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (b->length == 0) {
|
||||
JS_ThrowReferenceError(js, "attempted to read data from an empty blob");
|
||||
return -1;
|
||||
}
|
||||
|
||||
*bits = b->length;
|
||||
return b->data;
|
||||
}
|
||||
|
||||
int js_is_blob(JSContext *js, JSValue v)
|
||||
{
|
||||
blob *b = js2blob(js,v);
|
||||
if (b) return 1;
|
||||
return 0;
|
||||
}
|
||||
@@ -19,14 +19,14 @@ typedef struct WotaEncodeContext {
|
||||
|
||||
static void wota_stack_push(WotaEncodeContext *enc, JSValueConst val)
|
||||
{
|
||||
if (!JS_IsObject(val)) return;
|
||||
/* if (!JS_IsObject(val)) return;
|
||||
|
||||
ObjectRef *ref = malloc(sizeof(ObjectRef));
|
||||
if (!ref) return;
|
||||
|
||||
ref->ptr = JS_VALUE_GET_PTR(val);
|
||||
ref->next = enc->visited_stack;
|
||||
enc->visited_stack = ref;
|
||||
enc->visited_stack = ref;*/
|
||||
}
|
||||
|
||||
static void wota_stack_pop(WotaEncodeContext *enc)
|
||||
@@ -40,7 +40,7 @@ static void wota_stack_pop(WotaEncodeContext *enc)
|
||||
|
||||
static int wota_stack_has(WotaEncodeContext *enc, JSValueConst val)
|
||||
{
|
||||
if (!JS_IsObject(val)) return 0;
|
||||
/* if (!JS_IsObject(val)) return 0;
|
||||
|
||||
void *ptr = JS_VALUE_GET_PTR(val);
|
||||
ObjectRef *current = enc->visited_stack;
|
||||
@@ -49,9 +49,10 @@ static int wota_stack_has(WotaEncodeContext *enc, JSValueConst val)
|
||||
if (current->ptr == ptr) return 1;
|
||||
current = current->next;
|
||||
}
|
||||
return 0;
|
||||
return 0;*/
|
||||
}
|
||||
|
||||
|
||||
static void wota_stack_free(WotaEncodeContext *enc)
|
||||
{
|
||||
while (enc->visited_stack) {
|
||||
@@ -59,10 +60,10 @@ static void wota_stack_free(WotaEncodeContext *enc)
|
||||
}
|
||||
}
|
||||
|
||||
static JSValue apply_replacer(WotaEncodeContext *enc, JSValueConst holder, JSAtom key, JSValueConst val)
|
||||
static JSValue apply_replacer(WotaEncodeContext *enc, JSValueConst holder, JSValue key, JSValueConst val)
|
||||
{
|
||||
if (JS_IsNull(enc->replacer)) return JS_DupValue(enc->ctx, val);
|
||||
JSValue key_val = (key == JS_ATOM_NULL) ? JS_NULL : JS_AtomToValue(enc->ctx, key);
|
||||
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]);
|
||||
@@ -71,51 +72,57 @@ static JSValue apply_replacer(WotaEncodeContext *enc, JSValueConst holder, JSAto
|
||||
return result;
|
||||
}
|
||||
|
||||
static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSAtom key);
|
||||
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;
|
||||
JSPropertyEnum *ptab;
|
||||
uint32_t plen;
|
||||
if (JS_GetOwnPropertyNames(ctx, &ptab, &plen, val, JS_GPN_ENUM_ONLY | JS_GPN_STRING_MASK) < 0) {
|
||||
JSValue keys = JS_GetOwnPropertyNames(ctx, val);
|
||||
if (JS_IsException(keys)) {
|
||||
wota_write_sym(&enc->wb, WOTA_NULL);
|
||||
return;
|
||||
}
|
||||
int64_t plen64;
|
||||
if (JS_GetLength(ctx, keys, &plen64) < 0) {
|
||||
JS_FreeValue(ctx, keys);
|
||||
wota_write_sym(&enc->wb, WOTA_NULL);
|
||||
return;
|
||||
}
|
||||
uint32_t plen = (uint32_t)plen64;
|
||||
uint32_t non_function_count = 0;
|
||||
JSValue props[plen];
|
||||
JSAtom atoms[plen];
|
||||
JSValue kept_keys[plen];
|
||||
|
||||
for (uint32_t i = 0; i < plen; i++) {
|
||||
JSValue prop_val = JS_GetProperty(ctx, val, ptab[i].atom);
|
||||
if (!JS_IsFunction(ctx, prop_val)) {
|
||||
atoms[non_function_count] = ptab[i].atom;
|
||||
JSValue key = JS_GetPropertyUint32(ctx, keys, i);
|
||||
JSValue prop_val = JS_GetProperty(ctx, val, key);
|
||||
if (!JS_IsFunction(prop_val)) {
|
||||
kept_keys[non_function_count] = key;
|
||||
props[non_function_count++] = prop_val;
|
||||
} else
|
||||
} else {
|
||||
JS_FreeValue(ctx, prop_val);
|
||||
JS_FreeValue(ctx, key);
|
||||
}
|
||||
}
|
||||
JS_FreeValue(ctx, keys);
|
||||
wota_write_record(&enc->wb, non_function_count);
|
||||
for (uint32_t i = 0; i < non_function_count; i++) {
|
||||
size_t plen;
|
||||
const char *prop_name = JS_AtomToCStringLen(ctx, &plen, atoms[i]);
|
||||
size_t klen;
|
||||
const char *prop_name = JS_ToCStringLen(ctx, &klen, kept_keys[i]);
|
||||
JSValue prop_val = props[i];
|
||||
wota_write_text_len(&enc->wb, prop_name, plen);
|
||||
wota_encode_value(enc, prop_val, val, atoms[i]);
|
||||
wota_write_text_len(&enc->wb, prop_name ? prop_name : "", prop_name ? klen : 0);
|
||||
wota_encode_value(enc, prop_val, val, kept_keys[i]);
|
||||
JS_FreeCString(ctx, prop_name);
|
||||
JS_FreeValue(ctx, prop_val);
|
||||
JS_FreeValue(ctx, kept_keys[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; i < plen; i++)
|
||||
JS_FreeAtom(ctx, ptab[i].atom);
|
||||
|
||||
js_free(ctx, ptab);
|
||||
}
|
||||
|
||||
static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSAtom key)
|
||||
static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValue key)
|
||||
{
|
||||
JSContext *ctx = enc->ctx;
|
||||
JSValue replaced;
|
||||
if (!JS_IsNull(enc->replacer) && key != JS_ATOM_NULL)
|
||||
if (!JS_IsNull(enc->replacer) && !JS_IsNull(key))
|
||||
replaced = apply_replacer(enc, holder, key, val);
|
||||
else
|
||||
replaced = JS_DupValue(enc->ctx, val);
|
||||
@@ -128,8 +135,7 @@ static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueC
|
||||
wota_write_int_word(&enc->wb, d);
|
||||
break;
|
||||
}
|
||||
case JS_TAG_FLOAT64:
|
||||
case JS_TAG_BIG_INT: {
|
||||
case JS_TAG_FLOAT64: {
|
||||
double d;
|
||||
if (JS_ToFloat64(ctx, &d, replaced) < 0) {
|
||||
wota_write_sym(&enc->wb, WOTA_NULL);
|
||||
@@ -151,7 +157,7 @@ static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueC
|
||||
case JS_TAG_NULL:
|
||||
wota_write_sym(&enc->wb, WOTA_NULL);
|
||||
break;
|
||||
case JS_TAG_OBJECT: {
|
||||
case JS_TAG_PTR: {
|
||||
if (js_is_blob(ctx, replaced)) {
|
||||
size_t buf_len;
|
||||
void *buf_data = js_get_blob_data(ctx, &buf_len, replaced);
|
||||
@@ -166,7 +172,7 @@ static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueC
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (JS_IsArray(ctx, replaced)) {
|
||||
if (JS_IsArray(replaced)) {
|
||||
if (wota_stack_has(enc, replaced)) {
|
||||
enc->cycle = 1;
|
||||
break;
|
||||
@@ -177,19 +183,19 @@ static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueC
|
||||
wota_write_array(&enc->wb, arr_len);
|
||||
for (int64_t i = 0; i < arr_len; i++) {
|
||||
JSValue elem_val = JS_GetPropertyUint32(ctx, replaced, i);
|
||||
JSAtom idx_atom = JS_NewAtomUInt32(ctx, (uint32_t)i);
|
||||
wota_encode_value(enc, elem_val, replaced, idx_atom);
|
||||
JS_FreeAtom(ctx, idx_atom);
|
||||
/* Use int index as key placeholder */
|
||||
wota_encode_value(enc, elem_val, replaced, JS_NewInt32(ctx, (int32_t)i));
|
||||
JS_FreeValue(ctx, elem_val);
|
||||
}
|
||||
wota_stack_pop(enc);
|
||||
break;
|
||||
}
|
||||
cell_rt *crt = JS_GetContextOpaque(ctx);
|
||||
JSValue adata = JS_GetProperty(ctx, replaced, crt->actor_sym);
|
||||
// JSValue adata = JS_GetProperty(ctx, replaced, crt->actor_sym);
|
||||
JSValue adata = JS_NULL;
|
||||
if (!JS_IsNull(adata)) {
|
||||
wota_write_sym(&enc->wb, WOTA_PRIVATE);
|
||||
wota_encode_value(enc, adata, replaced, JS_ATOM_NULL);
|
||||
wota_encode_value(enc, adata, replaced, JS_NULL);
|
||||
JS_FreeValue(ctx, adata);
|
||||
break;
|
||||
}
|
||||
@@ -200,7 +206,7 @@ static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueC
|
||||
}
|
||||
wota_stack_push(enc, replaced);
|
||||
JSValue to_json = JS_GetPropertyStr(ctx, replaced, "toJSON");
|
||||
if (JS_IsFunction(ctx, to_json)) {
|
||||
if (JS_IsFunction(to_json)) {
|
||||
JSValue result = JS_Call(ctx, to_json, replaced, 0, NULL);
|
||||
JS_FreeValue(ctx, to_json);
|
||||
if (!JS_IsException(result)) {
|
||||
@@ -223,7 +229,7 @@ static void wota_encode_value(WotaEncodeContext *enc, JSValueConst val, JSValueC
|
||||
JS_FreeValue(ctx, replaced);
|
||||
}
|
||||
|
||||
static char *decode_wota_value(JSContext *ctx, char *data_ptr, JSValue *out_val, JSValue holder, JSAtom key, JSValue reviver)
|
||||
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);
|
||||
@@ -245,10 +251,10 @@ static char *decode_wota_value(JSContext *ctx, char *data_ptr, JSValue *out_val,
|
||||
data_ptr = wota_read_sym(&scode, data_ptr);
|
||||
if (scode == WOTA_PRIVATE) {
|
||||
JSValue inner = JS_NULL;
|
||||
data_ptr = decode_wota_value(ctx, data_ptr, &inner, holder, JS_ATOM_NULL, reviver);
|
||||
data_ptr = decode_wota_value(ctx, data_ptr, &inner, holder, JS_NULL, reviver);
|
||||
JSValue obj = JS_NewObject(ctx);
|
||||
cell_rt *crt = JS_GetContextOpaque(ctx);
|
||||
JS_SetProperty(ctx, obj, crt->actor_sym, inner);
|
||||
// JS_SetProperty(ctx, obj, crt->actor_sym, inner);
|
||||
*out_val = obj;
|
||||
} else if (scode == WOTA_NULL) *out_val = JS_NULL;
|
||||
else if (scode == WOTA_FALSE) *out_val = JS_NewBool(ctx, 0);
|
||||
@@ -274,14 +280,12 @@ static char *decode_wota_value(JSContext *ctx, char *data_ptr, JSValue *out_val,
|
||||
case WOTA_ARR: {
|
||||
long long c;
|
||||
data_ptr = wota_read_array(&c, data_ptr);
|
||||
JSValue arr = JS_NewArray(ctx);
|
||||
JS_SetLength(ctx, arr, c);
|
||||
JSValue arr = JS_NewArrayLen(ctx, c);
|
||||
for (long long i = 0; i < c; i++) {
|
||||
JSValue elem_val = JS_NULL;
|
||||
JSAtom idx_atom = JS_NewAtomUInt32(ctx, (uint32_t)i);
|
||||
data_ptr = decode_wota_value(ctx, data_ptr, &elem_val, arr, idx_atom, reviver);
|
||||
JSValue idx_key = JS_NewInt32(ctx, (int32_t)i);
|
||||
data_ptr = decode_wota_value(ctx, data_ptr, &elem_val, arr, idx_key, reviver);
|
||||
JS_SetPropertyUint32(ctx, arr, i, elem_val);
|
||||
JS_FreeAtom(ctx, idx_atom);
|
||||
}
|
||||
*out_val = arr;
|
||||
break;
|
||||
@@ -295,11 +299,11 @@ static char *decode_wota_value(JSContext *ctx, char *data_ptr, JSValue *out_val,
|
||||
size_t key_len;
|
||||
data_ptr = wota_read_text_len(&key_len, &tkey, data_ptr);
|
||||
if (!tkey) continue; // invalid key
|
||||
JSAtom prop_key = JS_NewAtomLen(ctx, tkey, key_len);
|
||||
JSValue prop_key = JS_NewStringLen(ctx, tkey, key_len);
|
||||
JSValue sub_val = JS_NULL;
|
||||
data_ptr = decode_wota_value(ctx, data_ptr, &sub_val, obj, prop_key, reviver);
|
||||
JS_SetProperty(ctx, obj, prop_key, sub_val);
|
||||
JS_FreeAtom(ctx, prop_key);
|
||||
JS_FreeValue(ctx, prop_key);
|
||||
free(tkey);
|
||||
}
|
||||
*out_val = obj;
|
||||
@@ -311,7 +315,7 @@ static char *decode_wota_value(JSContext *ctx, char *data_ptr, JSValue *out_val,
|
||||
break;
|
||||
}
|
||||
if (!JS_IsNull(reviver)) {
|
||||
JSValue key_val = (key == JS_ATOM_NULL) ? JS_NULL : JS_AtomToValue(ctx, key);
|
||||
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]);
|
||||
@@ -334,7 +338,7 @@ void *value2wota(JSContext *ctx, JSValue v, JSValue replacer, size_t *bytes)
|
||||
enc->cycle = 0;
|
||||
enc->replacer = replacer;
|
||||
wota_buffer_init(&enc->wb, 16);
|
||||
wota_encode_value(enc, v, JS_NULL, JS_ATOM_NULL);
|
||||
wota_encode_value(enc, v, JS_NULL, JS_NULL);
|
||||
if (enc->cycle) {
|
||||
wota_stack_free(enc);
|
||||
wota_buffer_free(&enc->wb);
|
||||
@@ -351,7 +355,7 @@ JSValue wota2value(JSContext *ctx, void *wota)
|
||||
{
|
||||
JSValue result = JS_NULL;
|
||||
JSValue holder = JS_NewObject(ctx);
|
||||
decode_wota_value(ctx, wota, &result, holder, JS_ATOM_NULL, JS_NULL);
|
||||
decode_wota_value(ctx, wota, &result, holder, JS_NULL, JS_NULL);
|
||||
JS_FreeValue(ctx, holder);
|
||||
return result;
|
||||
}
|
||||
@@ -360,7 +364,7 @@ static JSValue js_wota_encode(JSContext *ctx, JSValueConst this_val, int argc, J
|
||||
{
|
||||
if (argc < 1) return JS_ThrowTypeError(ctx, "wota.encode requires at least 1 argument");
|
||||
size_t total_bytes;
|
||||
void *wota = value2wota(ctx, argv[0], JS_IsFunction(ctx,argv[1]) ? argv[1] : JS_NULL, &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);
|
||||
free(wota);
|
||||
return ret;
|
||||
@@ -373,13 +377,13 @@ static JSValue js_wota_decode(JSContext *ctx, JSValueConst this_val, int argc, J
|
||||
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_ThrowTypeError(ctx, "No blob data present");
|
||||
JSValue reviver = (argc > 1 && JS_IsFunction(ctx, argv[1])) ? argv[1] : JS_NULL;
|
||||
JSValue reviver = (argc > 1 && JS_IsFunction(argv[1])) ? argv[1] : JS_NULL;
|
||||
char *data_ptr = (char *)buf;
|
||||
JSValue result = JS_NULL;
|
||||
JSValue holder = JS_NewObject(ctx);
|
||||
JSAtom empty_atom = JS_NewAtom(ctx, "");
|
||||
decode_wota_value(ctx, data_ptr, &result, holder, empty_atom, reviver);
|
||||
JS_FreeAtom(ctx, empty_atom);
|
||||
JSValue empty_key = JS_NewString(ctx, "");
|
||||
decode_wota_value(ctx, data_ptr, &result, holder, empty_key, reviver);
|
||||
JS_FreeValue(ctx, empty_key);
|
||||
JS_FreeValue(ctx, holder);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -32,15 +32,14 @@ DEF(true, "true")
|
||||
DEF(if, "if")
|
||||
DEF(else, "else")
|
||||
DEF(return, "return")
|
||||
DEF(go, "go")
|
||||
DEF(var, "var")
|
||||
DEF(def, "def")
|
||||
DEF(this, "this")
|
||||
DEF(delete, "delete")
|
||||
DEF(void, "void")
|
||||
DEF(typeof, "typeof")
|
||||
DEF(new, "new")
|
||||
DEF(in, "in")
|
||||
DEF(instanceof, "instanceof")
|
||||
DEF(do, "do")
|
||||
DEF(while, "while")
|
||||
DEF(for, "for")
|
||||
|
||||
@@ -28,7 +28,6 @@ FMT(none)
|
||||
FMT(none_int)
|
||||
FMT(none_loc)
|
||||
FMT(none_arg)
|
||||
FMT(none_var_ref)
|
||||
FMT(u8)
|
||||
FMT(i8)
|
||||
FMT(loc8)
|
||||
@@ -42,17 +41,16 @@ FMT(npopx)
|
||||
FMT(npop_u16)
|
||||
FMT(loc)
|
||||
FMT(arg)
|
||||
FMT(var_ref)
|
||||
FMT(u32)
|
||||
FMT(i32)
|
||||
FMT(const)
|
||||
FMT(label)
|
||||
FMT(atom)
|
||||
FMT(atom_u8)
|
||||
FMT(atom_u16)
|
||||
FMT(atom_label_u8)
|
||||
FMT(atom_label_u16)
|
||||
FMT(label_u16)
|
||||
FMT(key)
|
||||
FMT(key_u8)
|
||||
FMT(key_u16)
|
||||
FMT(key_label_u16)
|
||||
FMT(u8_u16) /* 1 byte + 2 bytes for upvalue access */
|
||||
#undef FMT
|
||||
#endif /* FMT */
|
||||
|
||||
@@ -68,14 +66,12 @@ DEF(invalid, 1, 0, 0, none) /* never emitted */
|
||||
DEF( push_i32, 5, 0, 1, i32)
|
||||
DEF( push_const, 5, 0, 1, const)
|
||||
DEF( fclosure, 5, 0, 1, const) /* must follow push_const */
|
||||
DEF(push_atom_value, 5, 0, 1, atom)
|
||||
DEF( null, 1, 0, 1, none)
|
||||
DEF( push_this, 1, 0, 1, none) /* only used at the start of a function */
|
||||
DEF( push_false, 1, 0, 1, none)
|
||||
DEF( push_true, 1, 0, 1, none)
|
||||
DEF( object, 1, 0, 1, none)
|
||||
DEF( special_object, 2, 0, 1, u8) /* only used at the start of a function */
|
||||
DEF( rest, 3, 0, 1, u16) /* only used at the start of a function */
|
||||
|
||||
DEF( drop, 1, 1, 0, none) /* a -> */
|
||||
DEF( nip, 1, 2, 1, none) /* a b -> b */
|
||||
@@ -97,53 +93,46 @@ DEF( rot3r, 1, 3, 3, none) /* a b x -> x a b */
|
||||
DEF( rot4l, 1, 4, 4, none) /* x a b c -> a b c x */
|
||||
DEF( rot5l, 1, 5, 5, none) /* x a b c d -> a b c d x */
|
||||
|
||||
DEF(call_constructor, 3, 2, 1, npop) /* func new.target args -> ret. arguments are not counted in n_pop */
|
||||
DEF( call, 3, 1, 1, npop) /* arguments are not counted in n_pop */
|
||||
DEF( tail_call, 3, 1, 0, npop) /* arguments are not counted in n_pop */
|
||||
DEF( call_method, 3, 2, 1, npop) /* arguments are not counted in n_pop */
|
||||
DEF(tail_call_method, 3, 2, 0, npop) /* arguments are not counted in n_pop */
|
||||
DEF( array_from, 3, 0, 1, npop) /* arguments are not counted in n_pop */
|
||||
DEF( apply, 3, 3, 1, u16)
|
||||
DEF( return, 1, 1, 0, none)
|
||||
DEF( return_undef, 1, 0, 0, none)
|
||||
DEF( throw, 1, 1, 0, none)
|
||||
DEF( throw_error, 6, 0, 0, atom_u8)
|
||||
DEF( eval, 5, 1, 1, npop_u16) /* func args... -> ret_val */
|
||||
DEF( apply_eval, 3, 2, 1, u16) /* func array -> ret_eval */
|
||||
DEF( throw_error, 6, 0, 0, key_u8)
|
||||
DEF( regexp, 1, 2, 1, none) /* create a RegExp object from the pattern and a
|
||||
bytecode string */
|
||||
|
||||
DEF( check_var, 5, 0, 1, atom) /* check if a variable exists */
|
||||
DEF( get_var_undef, 5, 0, 1, atom) /* push undefined if the variable does not exist */
|
||||
DEF( get_var, 5, 0, 1, atom) /* throw an exception if the variable does not exist */
|
||||
DEF( put_var, 5, 1, 0, atom) /* must come after get_var */
|
||||
DEF( put_var_init, 5, 1, 0, atom) /* must come after put_var. Used to initialize a global lexical variable */
|
||||
DEF( put_var_strict, 5, 2, 0, atom) /* for strict mode variable write */
|
||||
/* Global variable access - resolved by linker to get/set_global_slot */
|
||||
DEF( check_var, 5, 0, 1, key) /* check if a variable exists - resolved by linker */
|
||||
DEF( get_var_undef, 5, 0, 1, key) /* resolved by linker to get_global_slot */
|
||||
DEF( get_var, 5, 0, 1, key) /* resolved by linker to get_global_slot */
|
||||
DEF( put_var, 5, 1, 0, key) /* resolved by linker to set_global_slot */
|
||||
DEF( put_var_init, 5, 1, 0, key) /* resolved by linker to set_global_slot */
|
||||
DEF( put_var_strict, 5, 2, 0, key) /* resolved by linker to set_global_slot */
|
||||
|
||||
DEF( get_ref_value, 1, 2, 3, none)
|
||||
DEF( put_ref_value, 1, 3, 0, none)
|
||||
|
||||
DEF( define_var, 6, 0, 0, atom_u8)
|
||||
DEF(check_define_var, 6, 0, 0, atom_u8)
|
||||
DEF( define_func, 6, 1, 0, atom_u8)
|
||||
DEF( get_field, 5, 1, 1, atom)
|
||||
DEF( get_field2, 5, 1, 2, atom)
|
||||
DEF( put_field, 5, 2, 0, atom)
|
||||
/* Global variable opcodes - resolved by linker to get/set_global_slot */
|
||||
DEF( define_var, 6, 0, 0, key_u8)
|
||||
DEF(check_define_var, 6, 0, 0, key_u8)
|
||||
DEF( define_func, 6, 1, 0, key_u8)
|
||||
DEF( get_field, 5, 1, 1, key)
|
||||
DEF( get_field2, 5, 1, 2, key)
|
||||
DEF( put_field, 5, 2, 0, key)
|
||||
DEF( get_array_el, 1, 2, 1, none)
|
||||
DEF( get_array_el2, 1, 2, 2, none) /* obj prop -> obj value */
|
||||
DEF( get_array_el3, 1, 2, 3, none) /* obj prop -> obj prop1 value */
|
||||
DEF( put_array_el, 1, 3, 0, none)
|
||||
DEF( define_field, 5, 2, 1, atom)
|
||||
DEF( set_name, 5, 1, 1, atom)
|
||||
DEF( define_field, 5, 2, 1, key)
|
||||
DEF( set_name, 5, 1, 1, key)
|
||||
DEF(set_name_computed, 1, 2, 2, none)
|
||||
DEF( set_proto, 1, 2, 1, none)
|
||||
DEF(define_array_el, 1, 3, 2, none)
|
||||
DEF( append, 1, 3, 2, none) /* append enumerated object, update length */
|
||||
DEF(copy_data_properties, 2, 3, 3, u8)
|
||||
DEF( define_method, 6, 2, 1, atom_u8)
|
||||
DEF( define_method, 6, 2, 1, key_u8)
|
||||
DEF(define_method_computed, 2, 3, 1, u8) /* must come after define_method */
|
||||
DEF( define_class, 6, 2, 2, atom_u8) /* parent ctor -> ctor proto */
|
||||
DEF( define_class_computed, 6, 3, 3, atom_u8) /* field_name parent ctor -> field_name ctor proto (class with computed name) */
|
||||
DEF( define_class, 6, 2, 2, key_u8) /* parent ctor -> ctor proto */
|
||||
DEF( define_class_computed, 6, 3, 3, key_u8) /* field_name parent ctor -> field_name ctor proto (class with computed name) */
|
||||
|
||||
DEF( get_loc, 3, 0, 1, loc)
|
||||
DEF( put_loc, 3, 1, 0, loc) /* must come after get_loc */
|
||||
@@ -151,18 +140,11 @@ DEF( set_loc, 3, 1, 1, loc) /* must come after put_loc */
|
||||
DEF( get_arg, 3, 0, 1, arg)
|
||||
DEF( put_arg, 3, 1, 0, arg) /* must come after get_arg */
|
||||
DEF( set_arg, 3, 1, 1, arg) /* must come after put_arg */
|
||||
DEF( get_var_ref, 3, 0, 1, var_ref)
|
||||
DEF( put_var_ref, 3, 1, 0, var_ref) /* must come after get_var_ref */
|
||||
DEF( set_var_ref, 3, 1, 1, var_ref) /* must come after put_var_ref */
|
||||
DEF(set_loc_uninitialized, 3, 0, 0, loc)
|
||||
DEF( get_loc_check, 3, 0, 1, loc)
|
||||
DEF( put_loc_check, 3, 1, 0, loc) /* must come after get_loc_check */
|
||||
DEF( put_loc_check_init, 3, 1, 0, loc)
|
||||
DEF(get_loc_checkthis, 3, 0, 1, loc)
|
||||
DEF(get_var_ref_check, 3, 0, 1, var_ref)
|
||||
DEF(put_var_ref_check, 3, 1, 0, var_ref) /* must come after get_var_ref_check */
|
||||
DEF(put_var_ref_check_init, 3, 1, 0, var_ref)
|
||||
DEF( close_loc, 3, 0, 0, loc)
|
||||
DEF( if_false, 5, 1, 0, label)
|
||||
DEF( if_true, 5, 1, 0, label) /* must come after if_false */
|
||||
DEF( goto, 5, 0, 0, label) /* must come after if_true */
|
||||
@@ -171,25 +153,8 @@ DEF( gosub, 5, 0, 0, label) /* used to execute the finally block */
|
||||
DEF( ret, 1, 1, 0, none) /* used to return from the finally block */
|
||||
DEF( nip_catch, 1, 2, 1, none) /* catch ... a -> a */
|
||||
|
||||
DEF( to_object, 1, 1, 1, none)
|
||||
//DEF( to_string, 1, 1, 1, none)
|
||||
DEF( to_propkey, 1, 1, 1, none)
|
||||
|
||||
DEF( make_loc_ref, 7, 0, 2, atom_u16)
|
||||
DEF( make_arg_ref, 7, 0, 2, atom_u16)
|
||||
DEF(make_var_ref_ref, 7, 0, 2, atom_u16)
|
||||
DEF( make_var_ref, 5, 0, 2, atom)
|
||||
|
||||
DEF( for_in_start, 1, 1, 1, none)
|
||||
DEF( for_of_start, 1, 1, 3, none)
|
||||
DEF( for_in_next, 1, 1, 3, none)
|
||||
DEF( for_of_next, 2, 3, 5, u8)
|
||||
DEF(iterator_check_object, 1, 1, 1, none)
|
||||
DEF(iterator_get_value_done, 1, 2, 3, none) /* catch_offset obj -> catch_offset value done */
|
||||
DEF( iterator_close, 1, 3, 0, none)
|
||||
DEF( iterator_next, 1, 4, 4, none)
|
||||
DEF( iterator_call, 2, 4, 5, u8)
|
||||
|
||||
/* arithmetic/logic operations */
|
||||
DEF( neg, 1, 1, 1, none)
|
||||
DEF( plus, 1, 1, 1, none)
|
||||
@@ -202,15 +167,18 @@ DEF( inc_loc, 2, 0, 0, loc8)
|
||||
DEF( add_loc, 2, 1, 0, loc8)
|
||||
DEF( not, 1, 1, 1, none)
|
||||
DEF( lnot, 1, 1, 1, none)
|
||||
DEF( typeof, 1, 1, 1, none)
|
||||
DEF( delete, 1, 2, 1, none)
|
||||
DEF( delete_var, 5, 0, 1, atom)
|
||||
DEF( delete_var, 5, 0, 1, key) /* deprecated - global object is immutable */
|
||||
|
||||
DEF( mul, 1, 2, 1, none)
|
||||
DEF( mul_float, 1, 2, 1, none)
|
||||
DEF( div, 1, 2, 1, none)
|
||||
DEF( div_float, 1, 2, 1, none)
|
||||
DEF( mod, 1, 2, 1, none)
|
||||
DEF( add, 1, 2, 1, none)
|
||||
DEF( add_float, 1, 2, 1, none)
|
||||
DEF( sub, 1, 2, 1, none)
|
||||
DEF( sub_float, 1, 2, 1, none)
|
||||
DEF( pow, 1, 2, 1, none)
|
||||
DEF( shl, 1, 2, 1, none)
|
||||
DEF( sar, 1, 2, 1, none)
|
||||
@@ -219,15 +187,25 @@ DEF( lt, 1, 2, 1, none)
|
||||
DEF( lte, 1, 2, 1, none)
|
||||
DEF( gt, 1, 2, 1, none)
|
||||
DEF( gte, 1, 2, 1, none)
|
||||
DEF( instanceof, 1, 2, 1, none)
|
||||
DEF( in, 1, 2, 1, none)
|
||||
DEF( eq, 1, 2, 1, none)
|
||||
DEF( neq, 1, 2, 1, none)
|
||||
DEF( strict_eq, 1, 2, 1, none)
|
||||
DEF( strict_neq, 1, 2, 1, none)
|
||||
DEF( and, 1, 2, 1, none)
|
||||
DEF( xor, 1, 2, 1, none)
|
||||
DEF( or, 1, 2, 1, none)
|
||||
/* template literal concatenation - pops N parts, pushes concatenated string */
|
||||
DEF(template_concat, 3, 0, 1, npop_u16)
|
||||
|
||||
/* Upvalue access (closures via outer_frame chain) */
|
||||
DEF( get_up, 4, 0, 1, u8_u16) /* depth:u8, slot:u16 -> value */
|
||||
DEF( set_up, 4, 1, 0, u8_u16) /* value, depth:u8, slot:u16 -> */
|
||||
|
||||
/* Name resolution with bytecode patching */
|
||||
DEF( get_name, 5, 0, 1, const) /* cpool_idx -> value, patches itself */
|
||||
DEF( get_env_slot, 3, 0, 1, u16) /* slot -> value (patched from get_name) */
|
||||
DEF(get_global_slot, 3, 0, 1, u16) /* slot -> value (patched from get_var) */
|
||||
DEF(set_global_slot, 3, 1, 0, u16) /* value -> slot (patched from put_var) */
|
||||
|
||||
/* must be the last non short and non temporary opcode */
|
||||
DEF( nop, 1, 0, 0, none)
|
||||
|
||||
@@ -240,15 +218,13 @@ def( label, 5, 0, 0, label) /* emitted in phase 1, removed in phase 3 *
|
||||
|
||||
/* the following opcodes must be in the same order as the 'with_x' and
|
||||
get_var_undef, get_var and put_var opcodes */
|
||||
def(scope_get_var_undef, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def( scope_get_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def( scope_put_var, 7, 1, 0, atom_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def(scope_delete_var, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def( scope_make_ref, 11, 0, 2, atom_label_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def( scope_get_ref, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def(scope_put_var_init, 7, 0, 2, atom_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def(scope_get_var_checkthis, 7, 0, 1, atom_u16) /* emitted in phase 1, removed in phase 2, only used to return 'this' in derived class constructors */
|
||||
def(get_field_opt_chain, 5, 1, 1, atom) /* emitted in phase 1, removed in phase 2 */
|
||||
def(scope_get_var_undef, 7, 0, 1, key_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def( scope_get_var, 7, 0, 1, key_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def( scope_put_var, 7, 1, 0, key_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def(scope_delete_var, 7, 0, 1, key_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def(scope_put_var_init, 7, 0, 2, key_u16) /* emitted in phase 1, removed in phase 2 */
|
||||
def(scope_get_var_checkthis, 7, 0, 1, key_u16) /* emitted in phase 1, removed in phase 2, only used to return 'this' in derived class constructors */
|
||||
def(get_field_opt_chain, 5, 1, 1, key) /* emitted in phase 1, removed in phase 2 */
|
||||
def(get_array_el_opt_chain, 1, 2, 1, none) /* emitted in phase 1, removed in phase 2 */
|
||||
def( set_class_name, 5, 1, 1, u32) /* emitted in phase 1, removed in phase 2 */
|
||||
|
||||
@@ -298,20 +274,6 @@ DEF( set_arg0, 1, 1, 1, none_arg)
|
||||
DEF( set_arg1, 1, 1, 1, none_arg)
|
||||
DEF( set_arg2, 1, 1, 1, none_arg)
|
||||
DEF( set_arg3, 1, 1, 1, none_arg)
|
||||
DEF( get_var_ref0, 1, 0, 1, none_var_ref)
|
||||
DEF( get_var_ref1, 1, 0, 1, none_var_ref)
|
||||
DEF( get_var_ref2, 1, 0, 1, none_var_ref)
|
||||
DEF( get_var_ref3, 1, 0, 1, none_var_ref)
|
||||
DEF( put_var_ref0, 1, 1, 0, none_var_ref)
|
||||
DEF( put_var_ref1, 1, 1, 0, none_var_ref)
|
||||
DEF( put_var_ref2, 1, 1, 0, none_var_ref)
|
||||
DEF( put_var_ref3, 1, 1, 0, none_var_ref)
|
||||
DEF( set_var_ref0, 1, 1, 1, none_var_ref)
|
||||
DEF( set_var_ref1, 1, 1, 1, none_var_ref)
|
||||
DEF( set_var_ref2, 1, 1, 1, none_var_ref)
|
||||
DEF( set_var_ref3, 1, 1, 1, none_var_ref)
|
||||
|
||||
DEF( get_length, 1, 1, 1, none)
|
||||
|
||||
DEF( if_false8, 2, 1, 0, label8)
|
||||
DEF( if_true8, 2, 1, 0, label8) /* must come after if_false8 */
|
||||
@@ -324,7 +286,6 @@ DEF( call2, 1, 1, 1, npopx)
|
||||
DEF( call3, 1, 1, 1, npopx)
|
||||
|
||||
DEF( is_null, 1, 1, 1, none)
|
||||
DEF( typeof_is_function, 1, 1, 1, none)
|
||||
#endif
|
||||
|
||||
#undef DEF
|
||||
|
||||
59226
source/quickjs.c
59226
source/quickjs.c
File diff suppressed because it is too large
Load Diff
1690
source/quickjs.h
1690
source/quickjs.h
File diff suppressed because it is too large
Load Diff
@@ -270,7 +270,7 @@ void actor_free(cell_rt *actor)
|
||||
JS_FreeValue(js, actor->message_handle);
|
||||
JS_FreeValue(js, actor->on_exception);
|
||||
JS_FreeValue(js, actor->unneeded);
|
||||
JS_FreeAtom(js, actor->actor_sym);
|
||||
JS_FreeValue(js, actor->actor_sym);
|
||||
|
||||
for (int i = 0; i < hmlen(actor->timers); i++) {
|
||||
JS_FreeValue(js, actor->timers[i].value);
|
||||
@@ -436,7 +436,7 @@ void actor_unneeded(cell_rt *actor, JSValue fn, double seconds)
|
||||
if (actor->disrupt) return;
|
||||
JS_FreeValue(actor->context, actor->unneeded);
|
||||
|
||||
if (!JS_IsFunction(actor->context, fn)) {
|
||||
if (!JS_IsFunction(fn)) {
|
||||
actor->unneeded = JS_NULL;
|
||||
goto END;
|
||||
}
|
||||
@@ -497,7 +497,7 @@ cell_rt *create_actor(void *wota)
|
||||
actor->message_handle = JS_NULL;
|
||||
actor->unneeded = JS_NULL;
|
||||
actor->on_exception = JS_NULL;
|
||||
actor->actor_sym = JS_ATOM_NULL;
|
||||
actor->actor_sym = JS_NULL;
|
||||
|
||||
arrsetcap(actor->letters, 5);
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ void actor_free(cell_rt *actor)
|
||||
JS_FreeValue(js, actor->message_handle);
|
||||
JS_FreeValue(js, actor->on_exception);
|
||||
JS_FreeValue(js, actor->unneeded);
|
||||
JS_FreeAtom(js, actor->actor_sym);
|
||||
JS_FreeValue(js, actor->actor_sym);
|
||||
|
||||
/* Free timer callbacks stored in actor */
|
||||
for (int i = 0; i < hmlen(actor->timers); i++) {
|
||||
@@ -332,7 +332,7 @@ cell_rt *create_actor(void *wota)
|
||||
actor->message_handle = JS_NULL;
|
||||
actor->unneeded = JS_NULL;
|
||||
actor->on_exception = JS_NULL;
|
||||
actor->actor_sym = JS_ATOM_NULL;
|
||||
actor->actor_sym = JS_NULL;
|
||||
|
||||
arrsetcap(actor->letters, 5);
|
||||
|
||||
|
||||
2077
source/suite.c
Normal file
2077
source/suite.c
Normal file
File diff suppressed because it is too large
Load Diff
94
status.md
Normal file
94
status.md
Normal file
@@ -0,0 +1,94 @@
|
||||
QuickJS Mist Memory Format Refactoring
|
||||
Current Status
|
||||
The codebase is partially refactored but doesn't compile due to missing KeyId type definitions.
|
||||
|
||||
Incremental Refactoring Tasks
|
||||
Phase 0: Fix Compilation (Prerequisite)
|
||||
Define missing KeyId type as transitional typedef (will be replaced by JSValue later)
|
||||
Define K_EMPTY, K_TOMB,
|
||||
key_text()
|
||||
,
|
||||
key_is_text()
|
||||
,
|
||||
key_payload()
|
||||
macros/functions
|
||||
Verify build compiles and tests pass
|
||||
Phase 1: New JSValue Encoding in quickjs.h
|
||||
Add new LSB-based tag constants alongside existing tags
|
||||
Add JS_TAG_SHORT_FLOAT for 61-bit truncated double
|
||||
Add JS_TAG_STRING_ASCII for immediate 7-byte ASCII strings
|
||||
Add new value extraction/creation macros
|
||||
Add type check inline functions
|
||||
Keep existing NaN-boxing code active (compile-time switch)
|
||||
Phase 2: Short Float Implementation
|
||||
Implement JS_NewFloat64_ShortFloat() with range checking
|
||||
Implement JS_VALUE_GET_FLOAT64_ShortFloat() for decoding
|
||||
Out-of-range values return JS_NULL
|
||||
Prefer integer encoding when exact
|
||||
Phase 3: Immediate ASCII String
|
||||
Phase 3: Immediate ASCII String
|
||||
Implement JS_TryNewImmediateASCII() for strings up to 7 chars
|
||||
Implement JS_IsImmediateASCII() type check
|
||||
Implement JS_GetImmediateASCIILen() and JS_GetImmediateASCIIChar()
|
||||
Integrate with
|
||||
JS_NewStringLen()
|
||||
to try immediate first
|
||||
Phase 4: Remove JSStringRope
|
||||
Delete JSStringRope structure
|
||||
Remove JS_TAG_STRING_ROPE handling
|
||||
Update string concatenation to create immediate mist_text objects
|
||||
Remove rope-related iterator functions
|
||||
Phase 5: Refactor JSString to UTF-32 (mist_text)
|
||||
Modify struct JSString to store UTF-32 characters only
|
||||
Remove is_wide_char flag and 8.16 unions
|
||||
Update
|
||||
js_alloc_string
|
||||
to allocate UTF-32 buffer
|
||||
Update string creation functions (
|
||||
js_new_string8
|
||||
, etc.)
|
||||
Update all string accessors to use UTF-32
|
||||
Implement immediate-to-UTF32 conversion helper
|
||||
Update string operations (
|
||||
concat
|
||||
,
|
||||
compare
|
||||
) to work on UTF-32
|
||||
Phase 6: Replace KeyId with JSValue in Records
|
||||
Change JSRecordEntry.key from KeyId to JSValue
|
||||
Update
|
||||
rec_hash_key()
|
||||
to hash JSValue keys directly
|
||||
Update
|
||||
rec_find_slot()
|
||||
for JSValue key comparison
|
||||
Update
|
||||
rec_get_own()
|
||||
,
|
||||
rec_get()
|
||||
,
|
||||
rec_set_own()
|
||||
for JSValue keys
|
||||
Remove KeyId typedef and related functions
|
||||
Phase 7: Consolidate JSObject → JSRecord
|
||||
Remove JSShape and JSShapeProperty structures
|
||||
Remove shape hash table from JSRuntime
|
||||
Update all property access to use JSRecord
|
||||
Migrate JSObject users to JSRecord
|
||||
Remove JSObject structure
|
||||
Phase 8: Update GC for New Format
|
||||
Update mark_children for JSRecord with JSValue keys
|
||||
Update free_record for JSValue keys
|
||||
Handle immediate values correctly (no marking needed)
|
||||
Test for cycles and correct collection
|
||||
Phase 9: C Class Storage in Slot 0
|
||||
Implement slot 0 reservation for class_id and opaque pointer
|
||||
Update JS_SetOpaque() / JS_GetOpaque()
|
||||
Migrate existing class storage
|
||||
Verification Checklist
|
||||
Build compiles without errors
|
||||
Existing tests pass
|
||||
Property access works correctly
|
||||
GC correctly handles cycles
|
||||
Short float encoding/decoding verified
|
||||
Immediate ASCII strings work
|
||||
235
test.ce
235
test.ce
@@ -3,13 +3,17 @@ var pkg = use('package')
|
||||
var fd = use('fd')
|
||||
var time = use('time')
|
||||
var json = use('json')
|
||||
var utf8 = use('utf8')
|
||||
var blob = use('blob')
|
||||
var dbg = use('js')
|
||||
|
||||
// run gc with dbg.gc()
|
||||
|
||||
if (!args) args = []
|
||||
|
||||
var target_pkg = null // null = current package
|
||||
var target_test = null // null = all tests, otherwise specific test file
|
||||
var all_pkgs = false
|
||||
var gc_after_each_test = false
|
||||
|
||||
// Actor test support
|
||||
def ACTOR_TEST_TIMEOUT = 30000 // 30 seconds timeout for actor tests
|
||||
@@ -43,7 +47,17 @@ function get_current_package_name() {
|
||||
// cell test package all - run all tests from all packages
|
||||
|
||||
function parse_args() {
|
||||
if (args.length == 0) {
|
||||
var cleaned_args = []
|
||||
for (var i = 0; i < length(args); i++) {
|
||||
if (args[i] == '-g') {
|
||||
gc_after_each_test = true
|
||||
} else {
|
||||
push(cleaned_args, args[i])
|
||||
}
|
||||
}
|
||||
args = cleaned_args
|
||||
|
||||
if (length(args) == 0) {
|
||||
// cell test - run all tests for current package
|
||||
if (!is_valid_package('.')) {
|
||||
log.console('No cell.toml found in current directory')
|
||||
@@ -64,7 +78,7 @@ function parse_args() {
|
||||
}
|
||||
|
||||
if (args[0] == 'package') {
|
||||
if (args.length < 2) {
|
||||
if (length(args) < 2) {
|
||||
log.console('Usage: cell test package <name> [test]')
|
||||
log.console(' cell test package all')
|
||||
return false
|
||||
@@ -84,7 +98,7 @@ function parse_args() {
|
||||
var lock = shop.load_lock()
|
||||
if (lock[name]) {
|
||||
target_pkg = name
|
||||
} else if (name.startsWith('/') && is_valid_package(name)) {
|
||||
} else if (starts_with(name, '/') && is_valid_package(name)) {
|
||||
target_pkg = name
|
||||
} else {
|
||||
// Try to resolve as dependency alias from current package
|
||||
@@ -102,7 +116,7 @@ function parse_args() {
|
||||
}
|
||||
}
|
||||
|
||||
if (args.length >= 3) {
|
||||
if (length(args) >= 3) {
|
||||
// cell test package <name> <test>
|
||||
target_test = args[2]
|
||||
}
|
||||
@@ -115,7 +129,7 @@ function parse_args() {
|
||||
var test_path = args[0]
|
||||
|
||||
// Normalize path - add tests/ prefix if not present and doesn't start with /
|
||||
if (!test_path.startsWith('tests/') && !test_path.startsWith('/')) {
|
||||
if (!starts_with(test_path, 'tests/') && !starts_with(test_path, '/')) {
|
||||
// Check if file exists as-is first
|
||||
if (!fd.is_file(test_path + '.cm') && !fd.is_file(test_path)) {
|
||||
// Try with tests/ prefix
|
||||
@@ -144,14 +158,13 @@ if (!parse_args()) {
|
||||
function ensure_dir(path) {
|
||||
if (fd.is_dir(path)) return true
|
||||
|
||||
var parts = path.split('/')
|
||||
var current = path.startsWith('/') ? '/' : ''
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var parts = array(path, '/')
|
||||
var current = starts_with(path, '/') ? '/' : ''
|
||||
for (var i = 0; i < length(parts); i++) {
|
||||
if (parts[i] == '') continue
|
||||
current += parts[i] + '/'
|
||||
if (!fd.is_dir(current)) {
|
||||
if (!fd.is_dir(current))
|
||||
fd.mkdir(current)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -161,7 +174,7 @@ function get_pkg_dir(package_name) {
|
||||
if (!package_name) {
|
||||
return fd.realpath('.')
|
||||
}
|
||||
if (package_name.startsWith('/')) {
|
||||
if (starts_with(package_name, '/')) {
|
||||
return package_name
|
||||
}
|
||||
return shop.get_package_dir(package_name)
|
||||
@@ -176,23 +189,23 @@ function collect_actor_tests(package_name, specific_test) {
|
||||
|
||||
var files = pkg.list_files(package_name)
|
||||
var actor_tests = []
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
for (var i = 0; i < length(files); i++) {
|
||||
var f = files[i]
|
||||
// Check if file is in tests/ folder and is a .ce actor
|
||||
if (f.startsWith("tests/") && f.endsWith(".ce")) {
|
||||
if (starts_with(f, "tests/") && ends_with(f, ".ce")) {
|
||||
// If specific test requested, filter
|
||||
if (specific_test) {
|
||||
var test_name = f.substring(0, f.length - 3) // remove .ce
|
||||
var test_name = text(f, 0, -3) // remove .ce
|
||||
var match_name = specific_test
|
||||
if (!match_name.startsWith('tests/')) match_name = 'tests/' + match_name
|
||||
if (!match_name.endsWith('.ce')) match_name = match_name
|
||||
if (!starts_with(match_name, 'tests/')) match_name = 'tests/' + match_name
|
||||
if (!ends_with(match_name, '.ce')) match_name = match_name
|
||||
// Match without extension
|
||||
var test_base = test_name
|
||||
var match_base = match_name.endsWith('.ce') ? match_name.substring(0, match_name.length - 3) : match_name
|
||||
var match_base = ends_with(match_name, '.ce') ? text(match_name, 0, -3) : match_name
|
||||
if (test_base != match_base) continue
|
||||
}
|
||||
|
||||
actor_tests.push({
|
||||
push(actor_tests,{
|
||||
package: package_name || "local",
|
||||
file: f,
|
||||
path: prefix + '/' + f
|
||||
@@ -204,7 +217,7 @@ function collect_actor_tests(package_name, specific_test) {
|
||||
|
||||
// Spawn an actor test and track it
|
||||
function spawn_actor_test(test_info) {
|
||||
var test_name = test_info.file.substring(6, test_info.file.length - 3) // remove "tests/" and ".ce"
|
||||
var test_name = text(test_info.file, 6, -3) // remove "tests/" and ".ce"
|
||||
log.console(` [ACTOR] ${test_info.file}`)
|
||||
|
||||
var entry = {
|
||||
@@ -218,14 +231,14 @@ function spawn_actor_test(test_info) {
|
||||
|
||||
try {
|
||||
// Spawn the actor test - it should send back results
|
||||
var actor_path = test_info.path.substring(0, test_info.path.length - 3) // remove .ce
|
||||
var actor_path = text(test_info.path, 0, -3) // remove .ce
|
||||
entry.actor = $start(actor_path)
|
||||
pending_actor_tests.push(entry)
|
||||
push(pending_actor_tests, entry)
|
||||
} catch (e) {
|
||||
entry.status = "failed"
|
||||
entry.error = { message: `Failed to spawn actor: ${e}` }
|
||||
entry.duration_ns = 0
|
||||
actor_test_results.push(entry)
|
||||
push(actor_test_results, entry)
|
||||
log.console(` FAIL ${test_name}: `)
|
||||
log.error(e)
|
||||
}
|
||||
@@ -247,31 +260,31 @@ function run_tests(package_name, specific_test) {
|
||||
|
||||
var files = pkg.list_files(package_name)
|
||||
var test_files = []
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
for (var i = 0; i < length(files); i++) {
|
||||
var f = files[i]
|
||||
// Check if file is in tests/ folder and is a .cm module (not .ce - those are actor tests)
|
||||
if (f.startsWith("tests/") && f.endsWith(".cm")) {
|
||||
if (starts_with(f, "tests/") && ends_with(f, ".cm")) {
|
||||
// If specific test requested, filter
|
||||
if (specific_test) {
|
||||
var test_name = f.substring(0, f.length - 3) // remove .cm
|
||||
var test_name = text(f, 0, -3) // remove .cm
|
||||
var match_name = specific_test
|
||||
if (!match_name.startsWith('tests/')) match_name = 'tests/' + match_name
|
||||
if (!starts_with(match_name, 'tests/')) match_name = 'tests/' + match_name
|
||||
// Match without extension
|
||||
var match_base = match_name.endsWith('.cm') ? match_name.substring(0, match_name.length - 3) : match_name
|
||||
var match_base = ends_with(match_name, '.cm') ? text(match_name, 0, -3) : match_name
|
||||
if (test_name != match_base) continue
|
||||
}
|
||||
test_files.push(f)
|
||||
push(test_files, f)
|
||||
}
|
||||
}
|
||||
|
||||
if (test_files.length > 0) {
|
||||
if (length(test_files) > 0) {
|
||||
if (package_name) log.console(`Running tests for ${package_name}`)
|
||||
else log.console(`Running tests for local package`)
|
||||
}
|
||||
|
||||
for (var i = 0; i < test_files.length; i++) {
|
||||
for (var i = 0; i < length(test_files); i++) {
|
||||
var f = test_files[i]
|
||||
var mod_path = f.substring(0, f.length - 3) // remove .cm
|
||||
var mod_path = text(f, 0, -3) // remove .cm
|
||||
|
||||
var file_result = {
|
||||
name: f,
|
||||
@@ -287,19 +300,19 @@ function run_tests(package_name, specific_test) {
|
||||
test_mod = shop.use(mod_path, use_pkg)
|
||||
|
||||
var tests = []
|
||||
if (typeof test_mod == 'function') {
|
||||
tests.push({name: 'main', fn: test_mod})
|
||||
} else if (typeof test_mod == 'object') {
|
||||
for (var k in test_mod) {
|
||||
if (typeof test_mod[k] == 'function') {
|
||||
tests.push({name: k, fn: test_mod[k]})
|
||||
if (is_function(test_mod)) {
|
||||
push(tests, {name: 'main', fn: test_mod})
|
||||
} else if (is_object(test_mod)) {
|
||||
arrfor(array(test_mod), function(k) {
|
||||
if (is_function(test_mod[k])) {
|
||||
push(tests, {name: k, fn: test_mod[k]})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (tests.length > 0) {
|
||||
if (length(tests) > 0) {
|
||||
log.console(` ${f}`)
|
||||
for (var j = 0; j < tests.length; j++) {
|
||||
for (var j = 0; j < length(tests); j++) {
|
||||
var t = tests[j]
|
||||
var test_entry = {
|
||||
package: pkg_result.package,
|
||||
@@ -312,9 +325,9 @@ function run_tests(package_name, specific_test) {
|
||||
try {
|
||||
var ret = t.fn()
|
||||
|
||||
if (typeof ret == 'string') {
|
||||
throw new Error(ret)
|
||||
} else if (ret && (typeof ret.message == 'string' || ret instanceof Error)) {
|
||||
if (is_text(ret)) {
|
||||
throw Error(ret)
|
||||
} else if (ret && (is_text(ret.message) || is_proto(ret, Error))) {
|
||||
throw ret
|
||||
}
|
||||
|
||||
@@ -325,28 +338,31 @@ function run_tests(package_name, specific_test) {
|
||||
} catch (e) {
|
||||
test_entry.status = "failed"
|
||||
test_entry.error = {
|
||||
message: e.toString(),
|
||||
message: e,
|
||||
stack: e.stack || ""
|
||||
}
|
||||
if (e.name) test_entry.error.name = e.name
|
||||
|
||||
if (typeof e == 'object' && e.message) {
|
||||
if (is_object(e) && e.message) {
|
||||
test_entry.error.message = e.message
|
||||
}
|
||||
|
||||
log.console(` FAIL ${t.name} ${test_entry.error.message}`)
|
||||
if (test_entry.error.stack) {
|
||||
log.console(` ${test_entry.error.stack.split('\n').join('\n ')}`)
|
||||
log.console(` ${text(array(test_entry.error.stack, '\n'), '\n ')}`)
|
||||
}
|
||||
|
||||
pkg_result.failed++
|
||||
file_result.failed++
|
||||
}
|
||||
var end_time = time.number()
|
||||
test_entry.duration_ns = number.round((end_time - start_time) * 1000000000)
|
||||
test_entry.duration_ns = round((end_time - start_time) * 1000000000)
|
||||
|
||||
file_result.tests.push(test_entry)
|
||||
push(file_result.tests, test_entry)
|
||||
pkg_result.total++
|
||||
if (gc_after_each_test) {
|
||||
dbg.gc()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,12 +375,15 @@ function run_tests(package_name, specific_test) {
|
||||
duration_ns: 0,
|
||||
error: { message: `Error loading module: ${e}` }
|
||||
}
|
||||
file_result.tests.push(test_entry)
|
||||
push(file_result.tests, test_entry)
|
||||
pkg_result.failed++
|
||||
file_result.failed++
|
||||
pkg_result.total++
|
||||
if (gc_after_each_test) {
|
||||
dbg.gc()
|
||||
}
|
||||
}
|
||||
pkg_result.files.push(file_result)
|
||||
push(pkg_result.files, file_result)
|
||||
}
|
||||
return pkg_result
|
||||
}
|
||||
@@ -375,25 +394,25 @@ var all_actor_tests = []
|
||||
if (all_pkgs) {
|
||||
// Run local first if we're in a valid package
|
||||
if (is_valid_package('.')) {
|
||||
all_results.push(run_tests(null, null))
|
||||
all_actor_tests = all_actor_tests.concat(collect_actor_tests(null, null))
|
||||
push(all_results, run_tests(null, null))
|
||||
all_actor_tests = array(all_actor_tests, collect_actor_tests(null, null))
|
||||
}
|
||||
|
||||
// Then all packages in lock
|
||||
var packages = shop.list_packages()
|
||||
for (var i = 0; i < packages.length; i++) {
|
||||
all_results.push(run_tests(packages[i], null))
|
||||
all_actor_tests = all_actor_tests.concat(collect_actor_tests(packages[i], null))
|
||||
for (var i = 0; i < length(packages); i++) {
|
||||
push(all_results, run_tests(packages[i], null))
|
||||
all_actor_tests = array(all_actor_tests, collect_actor_tests(packages[i], null))
|
||||
}
|
||||
} else {
|
||||
all_results.push(run_tests(target_pkg, target_test))
|
||||
all_actor_tests = all_actor_tests.concat(collect_actor_tests(target_pkg, target_test))
|
||||
push(all_results, run_tests(target_pkg, target_test))
|
||||
all_actor_tests = array(all_actor_tests, collect_actor_tests(target_pkg, target_test))
|
||||
}
|
||||
|
||||
// Spawn actor tests if any
|
||||
if (all_actor_tests.length > 0) {
|
||||
log.console(`Running ${all_actor_tests.length} actor test(s)...`)
|
||||
for (var i = 0; i < all_actor_tests.length; i++) {
|
||||
if (length(all_actor_tests) > 0) {
|
||||
log.console(`Running ${length(all_actor_tests)} actor test(s)...`)
|
||||
for (var i = 0; i < length(all_actor_tests); i++) {
|
||||
spawn_actor_test(all_actor_tests[i])
|
||||
}
|
||||
}
|
||||
@@ -402,7 +421,7 @@ if (all_actor_tests.length > 0) {
|
||||
function handle_actor_message(msg) {
|
||||
var sender = msg.$sender
|
||||
var found_idx = -1
|
||||
for (var i = 0; i < pending_actor_tests.length; i++) {
|
||||
for (var i = 0; i < length(pending_actor_tests); i++) {
|
||||
if (pending_actor_tests[i].actor == sender) {
|
||||
found_idx = i
|
||||
break
|
||||
@@ -412,26 +431,26 @@ function handle_actor_message(msg) {
|
||||
if (found_idx == -1) return
|
||||
|
||||
var base_entry = pending_actor_tests[found_idx]
|
||||
pending_actor_tests.splice(found_idx, 1)
|
||||
pending_actor_tests = array(array(pending_actor_tests, 0, found_idx), array(pending_actor_tests, found_idx + 1))
|
||||
|
||||
var end_time = time.number()
|
||||
var duration_ns = number.round((end_time - base_entry.start_time) * 1000000000)
|
||||
var duration_ns = round((end_time - base_entry.start_time) * 1000000000)
|
||||
|
||||
var results = []
|
||||
if (isa(msg, array)) {
|
||||
if (is_array(msg)) {
|
||||
results = msg
|
||||
} else if (msg && isa(msg.results, array)) {
|
||||
} else if (msg && is_array(msg.results)) {
|
||||
results = msg.results
|
||||
} else {
|
||||
results = [msg]
|
||||
}
|
||||
|
||||
for (var i = 0; i < results.length; i++) {
|
||||
for (var i = 0; i < length(results); i++) {
|
||||
var res = results[i] || {}
|
||||
var entry = {
|
||||
package: base_entry.package,
|
||||
file: base_entry.file,
|
||||
test: res.test || base_entry.test + (results.length > 1 ? `#${i+1}` : ""),
|
||||
test: res.test || base_entry.test + (length(results) > 1 ? `#${i+1}` : ""),
|
||||
status: "failed",
|
||||
duration_ns: duration_ns
|
||||
}
|
||||
@@ -448,7 +467,11 @@ function handle_actor_message(msg) {
|
||||
log.console(` FAIL ${entry.test}: ${entry.error.message}`)
|
||||
}
|
||||
|
||||
actor_test_results.push(entry)
|
||||
push(actor_test_results, entry)
|
||||
}
|
||||
|
||||
if (gc_after_each_test) {
|
||||
dbg.gc()
|
||||
}
|
||||
|
||||
check_completion()
|
||||
@@ -459,27 +482,27 @@ function check_timeouts() {
|
||||
var now = time.number()
|
||||
var timed_out = []
|
||||
|
||||
for (var i = pending_actor_tests.length - 1; i >= 0; i--) {
|
||||
for (var i = length(pending_actor_tests) - 1; i >= 0; i--) {
|
||||
var entry = pending_actor_tests[i]
|
||||
var elapsed_ms = (now - entry.start_time) * 1000
|
||||
if (elapsed_ms > ACTOR_TEST_TIMEOUT) {
|
||||
timed_out.push(i)
|
||||
push(timed_out, i)
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < timed_out.length; i++) {
|
||||
for (var i = 0; i < length(timed_out); i++) {
|
||||
var idx = timed_out[i]
|
||||
var entry = pending_actor_tests[idx]
|
||||
pending_actor_tests.splice(idx, 1)
|
||||
pending_actor_tests = array(array(pending_actor_tests, 0, idx), array(pending_actor_tests, idx + 1))
|
||||
|
||||
entry.status = "failed"
|
||||
entry.error = { message: "Test timed out" }
|
||||
entry.duration_ns = ACTOR_TEST_TIMEOUT * 1000000
|
||||
actor_test_results.push(entry)
|
||||
push(actor_test_results, entry)
|
||||
log.console(` TIMEOUT ${entry.test}`)
|
||||
}
|
||||
|
||||
if (pending_actor_tests.length > 0) {
|
||||
if (length(pending_actor_tests) > 0) {
|
||||
$delay(check_timeouts, 1000)
|
||||
}
|
||||
check_completion()
|
||||
@@ -489,7 +512,7 @@ function check_timeouts() {
|
||||
var finalized = false
|
||||
function check_completion() {
|
||||
if (finalized) return
|
||||
if (pending_actor_tests.length > 0) return
|
||||
if (length(pending_actor_tests) > 0) return
|
||||
|
||||
finalized = true
|
||||
finalize_results()
|
||||
@@ -497,10 +520,10 @@ function check_completion() {
|
||||
|
||||
function finalize_results() {
|
||||
// Add actor test results to all_results
|
||||
for (var i = 0; i < actor_test_results.length; i++) {
|
||||
for (var i = 0; i < length(actor_test_results); i++) {
|
||||
var r = actor_test_results[i]
|
||||
var pkg_result = null
|
||||
for (var j = 0; j < all_results.length; j++) {
|
||||
for (var j = 0; j < length(all_results); j++) {
|
||||
if (all_results[j].package == r.package) {
|
||||
pkg_result = all_results[j]
|
||||
break
|
||||
@@ -508,11 +531,11 @@ function finalize_results() {
|
||||
}
|
||||
if (!pkg_result) {
|
||||
pkg_result = { package: r.package, files: [], total: 0, passed: 0, failed: 0 }
|
||||
all_results.push(pkg_result)
|
||||
push(all_results, pkg_result)
|
||||
}
|
||||
|
||||
var file_result = null
|
||||
for (var j = 0; j < pkg_result.files.length; j++) {
|
||||
for (var j = 0; j < length(pkg_result.files); j++) {
|
||||
if (pkg_result.files[j].name == r.file) {
|
||||
file_result = pkg_result.files[j]
|
||||
break
|
||||
@@ -520,10 +543,10 @@ function finalize_results() {
|
||||
}
|
||||
if (!file_result) {
|
||||
file_result = { name: r.file, tests: [], passed: 0, failed: 0 }
|
||||
pkg_result.files.push(file_result)
|
||||
push(pkg_result.files, file_result)
|
||||
}
|
||||
|
||||
file_result.tests.push(r)
|
||||
push(file_result.tests, r)
|
||||
pkg_result.total++
|
||||
if (r.status == "passed") {
|
||||
pkg_result.passed++
|
||||
@@ -536,7 +559,7 @@ function finalize_results() {
|
||||
|
||||
// Calculate totals
|
||||
var totals = { total: 0, passed: 0, failed: 0 }
|
||||
for (var i = 0; i < all_results.length; i++) {
|
||||
for (var i = 0; i < length(all_results); i++) {
|
||||
totals.total += all_results[i].total
|
||||
totals.passed += all_results[i].passed
|
||||
totals.failed += all_results[i].failed
|
||||
@@ -550,9 +573,10 @@ function finalize_results() {
|
||||
}
|
||||
|
||||
// If no actor tests, finalize immediately
|
||||
if (all_actor_tests.length == 0) {
|
||||
var totals = { total: 0, passed: 0, failed: 0 }
|
||||
for (var i = 0; i < all_results.length; i++) {
|
||||
var totals
|
||||
if (length(all_actor_tests) == 0) {
|
||||
totals = { total: 0, passed: 0, failed: 0 }
|
||||
for (var i = 0; i < length(all_results); i++) {
|
||||
totals.total += all_results[i].total
|
||||
totals.passed += all_results[i].passed
|
||||
totals.failed += all_results[i].failed
|
||||
@@ -564,10 +588,9 @@ if (all_actor_tests.length == 0) {
|
||||
$delay(check_timeouts, 1000)
|
||||
}
|
||||
|
||||
|
||||
// Generate Reports function
|
||||
function generate_reports(totals) {
|
||||
var timestamp = number.floor(time.number()).toString()
|
||||
var timestamp = text(floor(time.number()))
|
||||
var report_dir = shop.get_reports_dir() + '/test_' + timestamp
|
||||
ensure_dir(report_dir)
|
||||
|
||||
@@ -577,24 +600,24 @@ Total: ${totals.total}, Passed: ${totals.passed}, Failed: ${totals.failed}
|
||||
|
||||
=== SUMMARY ===
|
||||
`
|
||||
for (var i = 0; i < all_results.length; i++) {
|
||||
for (var i = 0; i < length(all_results); i++) {
|
||||
var pkg_res = all_results[i]
|
||||
if (pkg_res.total == 0) continue
|
||||
txt_report += `Package: ${pkg_res.package}\n`
|
||||
for (var j = 0; j < pkg_res.files.length; j++) {
|
||||
for (var j = 0; j < length(pkg_res.files); j++) {
|
||||
var f = pkg_res.files[j]
|
||||
var status = f.failed == 0 ? "PASS" : "FAIL"
|
||||
txt_report += ` [${status}] ${f.name} (${f.passed}/${f.tests.length})\n`
|
||||
txt_report += ` [${status}] ${f.name} (${f.passed}/${length(f.tests)})\n`
|
||||
}
|
||||
}
|
||||
|
||||
txt_report += `\n=== FAILURES ===\n`
|
||||
var has_failures = false
|
||||
for (var i = 0; i < all_results.length; i++) {
|
||||
for (var i = 0; i < length(all_results); i++) {
|
||||
var pkg_res = all_results[i]
|
||||
for (var j = 0; j < pkg_res.files.length; j++) {
|
||||
for (var j = 0; j < length(pkg_res.files); j++) {
|
||||
var f = pkg_res.files[j]
|
||||
for (var k = 0; k < f.tests.length; k++) {
|
||||
for (var k = 0; k < length(f.tests); k++) {
|
||||
var t = f.tests[k]
|
||||
if (t.status == "failed") {
|
||||
has_failures = true
|
||||
@@ -602,7 +625,7 @@ Total: ${totals.total}, Passed: ${totals.passed}, Failed: ${totals.failed}
|
||||
if (t.error) {
|
||||
txt_report += ` Message: ${t.error.message}\n`
|
||||
if (t.error.stack) {
|
||||
txt_report += ` Stack:\n${t.error.stack.split('\n').map(function(l){return ` ${l}`}).join('\n')}\n`
|
||||
txt_report += ` Stack:\n${text(array(array(t.error.stack, '\n'), l => ` ${l}`), '\n')}\n`
|
||||
}
|
||||
}
|
||||
txt_report += `\n`
|
||||
@@ -613,13 +636,13 @@ Total: ${totals.total}, Passed: ${totals.passed}, Failed: ${totals.failed}
|
||||
if (!has_failures) txt_report += `None\n`
|
||||
|
||||
txt_report += `\n=== DETAILED RESULTS ===\n`
|
||||
for (var i = 0; i < all_results.length; i++) {
|
||||
for (var i = 0; i < length(all_results); i++) {
|
||||
var pkg_res = all_results[i]
|
||||
if (pkg_res.total == 0) continue
|
||||
|
||||
for (var j = 0; j < pkg_res.files.length; j++) {
|
||||
for (var j = 0; j < length(pkg_res.files); j++) {
|
||||
var f = pkg_res.files[j]
|
||||
for (var k = 0; k < f.tests.length; k++) {
|
||||
for (var k = 0; k < length(f.tests); k++) {
|
||||
var t = f.tests[k]
|
||||
var dur = `${t.duration_ns || 0}ns`
|
||||
var status = t.status == "passed" ? "PASS" : "FAIL"
|
||||
@@ -628,29 +651,29 @@ Total: ${totals.total}, Passed: ${totals.passed}, Failed: ${totals.failed}
|
||||
}
|
||||
}
|
||||
ensure_dir(report_dir)
|
||||
fd.slurpwrite(`${report_dir}/test.txt`, utf8.encode(txt_report))
|
||||
fd.slurpwrite(`${report_dir}/test.txt`, stone(blob(txt_report)))
|
||||
log.console(`Report written to ${report_dir}/test.txt`)
|
||||
|
||||
// Generate JSON per package
|
||||
for (var i = 0; i < all_results.length; i++) {
|
||||
for (var i = 0; i < length(all_results); i++) {
|
||||
var pkg_res = all_results[i]
|
||||
if (pkg_res.total == 0) continue
|
||||
|
||||
var pkg_tests = []
|
||||
for (var j = 0; j < pkg_res.files.length; j++) {
|
||||
for (var j = 0; j < length(pkg_res.files); j++) {
|
||||
var f = pkg_res.files[j]
|
||||
for (var k = 0; k < f.tests.length; k++) {
|
||||
pkg_tests.push(f.tests[k])
|
||||
for (var k = 0; k < length(f.tests); k++) {
|
||||
push(pkg_tests, f.tests[k])
|
||||
}
|
||||
}
|
||||
|
||||
var json_path = `${report_dir}/${pkg_res.package.replace(/\//g, '_')}.json`
|
||||
fd.slurpwrite(json_path, utf8.encode(json.encode(pkg_tests)))
|
||||
var json_path = `${report_dir}/${replace(pkg_res.package, /\//, '_')}.json`
|
||||
fd.slurpwrite(json_path, stone(blob(json.encode(pkg_tests))))
|
||||
}
|
||||
}
|
||||
|
||||
// If no actor tests, generate reports and stop immediately
|
||||
if (all_actor_tests.length == 0) {
|
||||
if (length(all_actor_tests) == 0) {
|
||||
generate_reports(totals)
|
||||
$stop()
|
||||
} else {
|
||||
|
||||
@@ -4,34 +4,34 @@ var os = use('os');
|
||||
|
||||
function assert(condition, message) {
|
||||
if (!condition) {
|
||||
throw new Error(message || "Assertion failed");
|
||||
throw Error(message || "Assertion failed");
|
||||
}
|
||||
}
|
||||
|
||||
function assertEqual(actual, expected, message) {
|
||||
if (actual != expected) {
|
||||
throw new Error(message || "Expected " + expected + ", got " + actual);
|
||||
throw Error(message || "Expected " + expected + ", got " + actual);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
test_create_empty_blob: function() {
|
||||
var b = new Blob();
|
||||
assertEqual(b.length, 0, "Empty blob should have length 0");
|
||||
var b = Blob();
|
||||
assertEqual(length(b), 0, "Empty blob should have length 0");
|
||||
},
|
||||
|
||||
test_create_blob_with_capacity: function() {
|
||||
var b = new Blob(100);
|
||||
assertEqual(b.length, 0, "New blob with capacity should still have length 0");
|
||||
var b = Blob(100);
|
||||
assertEqual(length(b), 0, "New blob with capacity should still have length 0");
|
||||
},
|
||||
|
||||
test_write_and_read_single_bit: function() {
|
||||
var b = new Blob();
|
||||
var b = Blob();
|
||||
b.write_bit(true);
|
||||
b.write_bit(false);
|
||||
b.write_bit(1);
|
||||
b.write_bit(0);
|
||||
assertEqual(b.length, 4, "Should have 4 bits after writing");
|
||||
assertEqual(length(b), 4, "Should have 4 bits after writing");
|
||||
|
||||
stone(b);
|
||||
assertEqual(b.read_logical(0), true, "First bit should be true");
|
||||
@@ -41,7 +41,7 @@ return {
|
||||
},
|
||||
|
||||
test_out_of_range_read_throws_error: function() {
|
||||
var b = new Blob();
|
||||
var b = Blob();
|
||||
b.write_bit(true);
|
||||
stone(b);
|
||||
|
||||
@@ -63,7 +63,7 @@ return {
|
||||
},
|
||||
|
||||
test_write_and_read_numbers: function() {
|
||||
var b = new Blob();
|
||||
var b = Blob();
|
||||
b.write_number(3.14159);
|
||||
b.write_number(-42);
|
||||
b.write_number(0);
|
||||
@@ -77,7 +77,7 @@ return {
|
||||
},
|
||||
|
||||
test_write_and_read_text: function() {
|
||||
var b = new Blob();
|
||||
var b = Blob();
|
||||
b.write_text("Hello");
|
||||
b.write_text("World");
|
||||
b.write_text("🎉");
|
||||
@@ -87,15 +87,15 @@ return {
|
||||
},
|
||||
|
||||
test_write_and_read_blobs: function() {
|
||||
var b1 = new Blob();
|
||||
var b1 = Blob();
|
||||
b1.write_bit(true);
|
||||
b1.write_bit(false);
|
||||
b1.write_bit(true);
|
||||
|
||||
var b2 = new Blob(10);
|
||||
var b2 = Blob(10);
|
||||
b2.write_blob(b1);
|
||||
b2.write_bit(false);
|
||||
assertEqual(b2.length, 4, "Combined blob should have 4 bits");
|
||||
assertEqual(length(b2), 4, "Combined blob should have 4 bits");
|
||||
|
||||
stone(b2);
|
||||
assertEqual(b2.read_logical(0), true);
|
||||
@@ -105,37 +105,37 @@ return {
|
||||
},
|
||||
|
||||
test_blob_copy_constructor: function() {
|
||||
var b1 = new Blob();
|
||||
var b1 = Blob();
|
||||
b1.write_bit(true);
|
||||
b1.write_bit(false);
|
||||
b1.write_bit(true);
|
||||
b1.write_bit(true);
|
||||
stone(b1);
|
||||
|
||||
var b2 = new Blob(b1);
|
||||
var b2 = Blob(b1);
|
||||
stone(b2);
|
||||
assertEqual(b2.length, 4, "Copied blob should have same length");
|
||||
assertEqual(length(b2), 4, "Copied blob should have same length");
|
||||
assertEqual(b2.read_logical(0), true);
|
||||
assertEqual(b2.read_logical(3), true);
|
||||
},
|
||||
|
||||
test_blob_partial_copy_constructor: function() {
|
||||
var b1 = new Blob();
|
||||
var b1 = Blob();
|
||||
for (var i = 0; i < 10; i++) {
|
||||
b1.write_bit(i % 2 == 0);
|
||||
}
|
||||
stone(b1);
|
||||
|
||||
var b2 = new Blob(b1, 2, 7);
|
||||
var b2 = Blob(b1, 2, 7);
|
||||
stone(b2);
|
||||
assertEqual(b2.length, 5, "Partial copy should have 5 bits");
|
||||
assertEqual(length(b2), 5, "Partial copy should have 5 bits");
|
||||
assertEqual(b2.read_logical(0), true);
|
||||
assertEqual(b2.read_logical(2), true);
|
||||
},
|
||||
|
||||
test_create_blob_with_fill: function() {
|
||||
var b1 = new Blob(8, true);
|
||||
var b2 = new Blob(8, false);
|
||||
var b1 = Blob(8, true);
|
||||
var b2 = Blob(8, false);
|
||||
|
||||
stone(b1);
|
||||
stone(b2);
|
||||
@@ -150,7 +150,7 @@ return {
|
||||
var sequence = [true, false, true, true, false];
|
||||
var index = 0;
|
||||
|
||||
var b = new Blob(5, function() {
|
||||
var b = Blob(5, function() {
|
||||
return sequence[index++] ? 1 : 0;
|
||||
});
|
||||
|
||||
@@ -161,13 +161,13 @@ return {
|
||||
},
|
||||
|
||||
test_write_pad_and_check_padding: function() {
|
||||
var b = new Blob();
|
||||
var b = Blob();
|
||||
b.write_bit(true);
|
||||
b.write_bit(false);
|
||||
b.write_bit(true);
|
||||
b.write_pad(8);
|
||||
|
||||
assertEqual(b.length, 8, "Should be padded to 8 bits");
|
||||
assertEqual(length(b), 8, "Should be padded to 8 bits");
|
||||
stone(b);
|
||||
|
||||
assert(b['pad?'](3, 8), "Should detect valid padding at position 3");
|
||||
@@ -175,7 +175,7 @@ return {
|
||||
},
|
||||
|
||||
test_read_blob_from_stone_blob: function() {
|
||||
var b1 = new Blob();
|
||||
var b1 = Blob();
|
||||
for (var i = 0; i < 16; i++) {
|
||||
b1.write_bit(i % 3 == 0);
|
||||
}
|
||||
@@ -183,14 +183,14 @@ return {
|
||||
|
||||
var b2 = b1.read_blob(4, 12);
|
||||
stone(b2);
|
||||
assertEqual(b2.length, 8, "Read blob should have 8 bits");
|
||||
assertEqual(length(b2), 8, "Read blob should have 8 bits");
|
||||
|
||||
assertEqual(b2.read_logical(2), true);
|
||||
assertEqual(b2.read_logical(5), true);
|
||||
},
|
||||
|
||||
test_stone_blob_is_immutable: function() {
|
||||
var b = new Blob();
|
||||
var b = Blob();
|
||||
b.write_bit(true);
|
||||
stone(b);
|
||||
|
||||
@@ -204,7 +204,7 @@ return {
|
||||
},
|
||||
|
||||
test_multiple_stone_calls_are_safe: function() {
|
||||
var b = new Blob();
|
||||
var b = Blob();
|
||||
b.write_bit(true);
|
||||
assert(!stone.p(b), "Blob should not be a stone before stone() call");
|
||||
stone(b);
|
||||
@@ -218,7 +218,7 @@ return {
|
||||
test_invalid_constructor_arguments: function() {
|
||||
var threw = false;
|
||||
try {
|
||||
var b = new Blob("invalid");
|
||||
var b = Blob("invalid");
|
||||
} catch (e) {
|
||||
threw = true;
|
||||
}
|
||||
@@ -226,7 +226,7 @@ return {
|
||||
},
|
||||
|
||||
test_write_bit_validation: function() {
|
||||
var b = new Blob();
|
||||
var b = Blob();
|
||||
b.write_bit(0);
|
||||
b.write_bit(1);
|
||||
|
||||
@@ -240,7 +240,7 @@ return {
|
||||
},
|
||||
|
||||
test_complex_data_round_trip: function() {
|
||||
var b = new Blob();
|
||||
var b = Blob();
|
||||
|
||||
b.write_text("Test");
|
||||
b.write_number(123.456);
|
||||
@@ -248,31 +248,31 @@ return {
|
||||
b.write_bit(false);
|
||||
b.write_number(-999.999);
|
||||
|
||||
var originalLength = b.length;
|
||||
var originalLength = length(b);
|
||||
stone(b);
|
||||
|
||||
var b2 = new Blob(b);
|
||||
var b2 = Blob(b);
|
||||
stone(b2);
|
||||
assertEqual(b2.length, originalLength, "Copy should have same length");
|
||||
assertEqual(length(b2), originalLength, "Copy should have same length");
|
||||
assertEqual(b2.read_text(0), "Test", "First text should match");
|
||||
},
|
||||
|
||||
test_zero_capacity_blob: function() {
|
||||
var b = new Blob(0);
|
||||
assertEqual(b.length, 0, "Zero capacity blob should have length 0");
|
||||
var b = Blob(0);
|
||||
assertEqual(length(b), 0, "Zero capacity blob should have length 0");
|
||||
b.write_bit(true);
|
||||
assertEqual(b.length, 1, "Should expand when writing");
|
||||
assertEqual(length(b), 1, "Should expand when writing");
|
||||
},
|
||||
|
||||
test_large_blob_handling: function() {
|
||||
var b = new Blob();
|
||||
var b = Blob();
|
||||
var testSize = 1000;
|
||||
|
||||
for (var i = 0; i < testSize; i++) {
|
||||
b.write_bit(i % 7 == 0);
|
||||
}
|
||||
|
||||
assertEqual(b.length, testSize, "Should have " + testSize + " bits");
|
||||
assertEqual(length(b), testSize, "Should have " + testSize + " bits");
|
||||
stone(b);
|
||||
|
||||
assertEqual(b.read_logical(0), true, "Bit 0 should be true");
|
||||
@@ -282,7 +282,7 @@ return {
|
||||
},
|
||||
|
||||
test_non_stone_blob_read_methods_should_throw: function() {
|
||||
var b = new Blob();
|
||||
var b = Blob();
|
||||
b.write_bit(true);
|
||||
b.write_number(42);
|
||||
b.write_text("test");
|
||||
@@ -329,14 +329,14 @@ return {
|
||||
},
|
||||
|
||||
test_empty_text_write_and_read: function() {
|
||||
var b = new Blob();
|
||||
var b = Blob();
|
||||
b.write_text("");
|
||||
stone(b);
|
||||
assertEqual(b.read_text(0), "", "Empty string should round-trip");
|
||||
},
|
||||
|
||||
test_invalid_read_positions: function() {
|
||||
var b = new Blob();
|
||||
var b = Blob();
|
||||
b.write_number(42);
|
||||
stone(b);
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ return {
|
||||
fd.write(f, bigdata)
|
||||
fd.close(f)
|
||||
|
||||
var data = new blob
|
||||
var data = blob()
|
||||
var st = time.number()
|
||||
var f2 = fd.open(tmp, 'r')
|
||||
var chunksize = 1024 // reduced for test
|
||||
@@ -20,11 +20,7 @@ return {
|
||||
while(true) {
|
||||
var chunk = fd.read(f2, chunksize);
|
||||
data.write_blob(chunk);
|
||||
// chunk.length is in bits, chunksize is bytes?
|
||||
// fd.read usually takes bytes. Blob.length is bits.
|
||||
// If chunk is blob, length is bits.
|
||||
// fd.read returns blob.
|
||||
if (chunk.length < chunksize * 8) break;
|
||||
if (length(chunk) < chunksize * 8) break;
|
||||
}
|
||||
fd.close(f2)
|
||||
log.console(`read took ${time.number()-st}`)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user