23 Commits

Author SHA1 Message Date
John Alanbrook
a1b41d5ecf rm push/pop 2026-02-26 08:13:18 -06:00
John Alanbrook
eb19b18594 slow messags 2026-02-26 00:56:43 -06:00
John Alanbrook
e203700d37 Merge branch 'fix_log_err' 2026-02-25 23:30:36 -06:00
John Alanbrook
c56444556d fix log in engine err 2026-02-25 23:30:32 -06:00
John Alanbrook
080e675d18 better update output 2026-02-25 23:29:37 -06:00
John Alanbrook
957b964d9d better disrupt message on fd 2026-02-25 20:38:34 -06:00
John Alanbrook
fa9d2609b1 fix fd bug 2026-02-25 20:25:36 -06:00
John Alanbrook
e38c2f07bf Merge branch 'async_fd' 2026-02-25 17:43:12 -06:00
John Alanbrook
ecc1777b24 async cellfs 2026-02-25 17:43:01 -06:00
John Alanbrook
1cfd5b8133 add hot reload to util 2026-02-25 17:28:11 -06:00
John Alanbrook
9c1141f408 Merge branch 'async_http' 2026-02-25 16:39:16 -06:00
John Alanbrook
696cca530b internal pronto 2026-02-25 16:39:12 -06:00
John Alanbrook
c92a4087a6 Merge branch 'async_fd' 2026-02-25 16:24:20 -06:00
John Alanbrook
01637c49b0 fix sendmessage 2026-02-25 16:24:08 -06:00
John Alanbrook
f9e660ebaa better log 2026-02-25 16:07:39 -06:00
John Alanbrook
4f8fada57d Merge branch 'audit_build' 2026-02-25 16:06:17 -06:00
John Alanbrook
adcaa92bea better logging for compiling 2026-02-25 16:05:37 -06:00
John Alanbrook
fc36707b39 Merge branch 'async_fd' 2026-02-25 16:03:55 -06:00
John Alanbrook
7cb8ce7945 async fd 2026-02-25 16:03:52 -06:00
John Alanbrook
bb7997a751 fix sendmessage 2026-02-25 15:26:20 -06:00
John Alanbrook
327b990442 Merge branch 'master' into async_http 2026-02-25 14:48:46 -06:00
John Alanbrook
51c0a0b306 tls and http 2026-02-25 14:48:37 -06:00
John Alanbrook
8ac82016dd api for sending wota messages direct 2026-02-25 12:45:02 -06:00
80 changed files with 22136 additions and 20179 deletions

View File

@@ -114,13 +114,13 @@ var run = function() {
total_scripts = total_scripts + result.total
arrfor(result.errors, function(e) {
push(all_failures, p + ": " + e)
all_failures[] = p + ": " + e
})
// Check use() resolution
resolution = shop.audit_use_resolution(p)
arrfor(resolution.unresolved, function(u) {
push(all_unresolved, p + '/' + u.script + ": use('" + u.module + "') cannot be resolved")
all_unresolved[] = p + '/' + u.script + ": use('" + u.module + "') cannot be resolved"
})
})

View File

@@ -28,7 +28,7 @@ function strip_mode_flags() {
} else if (a == '--compare') {
bench_mode = "compare"
} else {
push(filtered, a)
filtered[] = a
}
})
_args = filtered
@@ -197,7 +197,7 @@ function collect_benches(package_name, specific_bench) {
match_base = ends_with(match_name, '.cm') ? text(match_name, 0, -3) : match_name
if (bench_name != match_base) return
}
push(bench_files, f)
bench_files[] = f
}
})
return bench_files
@@ -355,7 +355,7 @@ function run_single_bench(bench_fn, bench_name) {
if (teardown_fn) teardown_fn(state)
ns_per_op = is_batch ? duration / batch_size : duration
push(timings_per_op, ns_per_op)
timings_per_op[] = ns_per_op
} else {
start = os.now()
if (is_batch) {
@@ -366,7 +366,7 @@ function run_single_bench(bench_fn, bench_name) {
duration = os.now() - start
ns_per_op = is_batch ? duration / batch_size : duration
push(timings_per_op, ns_per_op)
timings_per_op[] = ns_per_op
}
}
@@ -442,11 +442,11 @@ function load_bench_module(f, package_name, mode) {
function collect_bench_fns(bench_mod) {
var benches = []
if (is_function(bench_mod)) {
push(benches, {name: 'main', fn: bench_mod})
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]})
benches[] = {name: k, fn: bench_mod[k]}
})
}
return benches
@@ -524,7 +524,7 @@ function run_benchmarks(package_name, specific_bench) {
result = run_single_bench(b.fn, b.name)
result.package = pkg_result.package
result.mode = bench_mode == "compare" ? "bytecode" : bench_mode
push(file_result.benchmarks, result)
file_result.benchmarks[] = result
pkg_result.total++
log.console(` ${result.name}`)
@@ -538,7 +538,7 @@ function run_benchmarks(package_name, specific_bench) {
nat_result = run_single_bench(native_benches[nat_b].fn, b.name)
nat_result.package = pkg_result.package
nat_result.mode = "native"
push(file_result.benchmarks, nat_result)
file_result.benchmarks[] = nat_result
pkg_result.total++
print_bench_result(nat_result, "native ")
@@ -570,7 +570,7 @@ function run_benchmarks(package_name, specific_bench) {
name: b.name,
error: "benchmark disrupted"
}
push(file_result.benchmarks, error_result)
file_result.benchmarks[] = error_result
pkg_result.total++
}
})
@@ -586,12 +586,12 @@ function run_benchmarks(package_name, specific_bench) {
name: "load_module",
error: "error loading module"
}
push(file_result.benchmarks, error_result)
file_result.benchmarks[] = error_result
pkg_result.total++
}
if (length(file_result.benchmarks) > 0) {
push(pkg_result.files, file_result)
pkg_result.files[] = file_result
}
})
@@ -604,15 +604,15 @@ var packages = null
if (all_pkgs) {
if (testlib.is_valid_package('.')) {
push(all_results, run_benchmarks(null, null))
all_results[] = run_benchmarks(null, null)
}
packages = shop.list_packages()
arrfor(packages, function(p) {
push(all_results, run_benchmarks(p, null))
all_results[] = run_benchmarks(p, null)
})
} else {
push(all_results, run_benchmarks(target_pkg, target_bench))
all_results[] = run_benchmarks(target_pkg, target_bench)
}
// Calculate totals
@@ -688,7 +688,7 @@ Total benchmarks: ${total_benches}
var pkg_benches = []
arrfor(pkg_res.files, function(f) {
arrfor(f.benchmarks, function(benchmark) {
push(pkg_benches, benchmark)
pkg_benches[] = benchmark
})
})

View File

@@ -27,13 +27,13 @@ function send(mailbox, msg) {
function receive(mailbox) {
if (length(mailbox.queue) == 0) return null
mailbox.delivered++
return pop(mailbox.queue)
return mailbox.queue[]
}
function drain(mailbox) {
var count = 0
while (length(mailbox.queue) > 0) {
pop(mailbox.queue)
mailbox.queue[]
count++
}
return count

View File

@@ -13,13 +13,13 @@ function generate_records(n) {
var dept_vals = ["eng", "sales", "ops", "hr", "marketing"]
for (i = 0; i < n; i++) {
x = ((x * 1103515245 + 12345) & 0x7FFFFFFF) | 0
push(records, {
records[] = {
id: i + 1,
name: `user_${i}`,
score: (x % 1000) / 10,
status: status_vals[i % 4],
department: dept_vals[i % 5]
})
}
}
return records
}
@@ -30,7 +30,7 @@ function filter_records(records, field, value) {
var i = 0
for (i = 0; i < length(records); i++) {
if (records[i][field] == value) {
push(result, records[i])
result[] = records[i]
}
}
return result
@@ -45,7 +45,7 @@ function group_by(records, field) {
key = records[i][field]
if (!key) key = "unknown"
if (!groups[key]) groups[key] = []
push(groups[key], records[i])
groups[key][] = records[i]
}
return groups
}
@@ -70,13 +70,13 @@ function aggregate(groups) {
if (grp[j].score < mn) mn = grp[j].score
if (grp[j].score > mx) mx = grp[j].score
}
push(result, {
result[] = {
group: keys[i],
count: length(grp),
average: total / length(grp),
low: mn,
high: mx
})
}
}
return result
}

View File

@@ -57,7 +57,7 @@ function build_chain(n) {
var constraints = []
var i = 0
for (i = 0; i < n; i++) {
push(vars, make_variable(`v${i}`, 0))
vars[] = make_variable(`v${i}`, 0)
}
// Set first variable
@@ -69,8 +69,8 @@ function build_chain(n) {
self.variables[1].value = self.variables[0].value + 1
self.output = self.variables[1]
})
push(constraints, c)
push(vars[i].constraints, c)
constraints[] = c
vars[i].constraints[] = c
}
return {vars: vars, constraints: constraints}
@@ -83,8 +83,8 @@ function build_projection(n) {
var constraints = []
var i = 0
for (i = 0; i < n; i++) {
push(src, make_variable(`src${i}`, i * 10))
push(dst, make_variable(`dst${i}`, 0))
src[] = make_variable(`src${i}`, i * 10)
dst[] = make_variable(`dst${i}`, 0)
}
var scale_c = null
@@ -93,8 +93,8 @@ function build_projection(n) {
self.variables[1].value = self.variables[0].value * 2 + 1
self.output = self.variables[1]
})
push(constraints, scale_c)
push(dst[i].constraints, scale_c)
constraints[] = scale_c
dst[i].constraints[] = scale_c
}
return {src: src, dst: dst, constraints: constraints}

View File

@@ -12,7 +12,7 @@ function make_words(count) {
var words = []
var i = 0
for (i = 0; i < count; i++) {
push(words, base_words[i % length(base_words)])
words[] = base_words[i % length(base_words)]
}
return words
}
@@ -39,7 +39,7 @@ function top_n(freq, n) {
var pairs = []
var i = 0
for (i = 0; i < length(keys); i++) {
push(pairs, {word: keys[i], count: freq[keys[i]]})
pairs[] = {word: keys[i], count: freq[keys[i]]}
}
var sorted = sort(pairs, "count")
// Return last N (highest counts)
@@ -47,7 +47,7 @@ function top_n(freq, n) {
var start = length(sorted) - n
if (start < 0) start = 0
for (i = start; i < length(sorted); i++) {
push(result, sorted[i])
result[] = sorted[i]
}
return result
}
@@ -62,7 +62,7 @@ function group_by_length(words) {
w = words[i]
k = text(length(w))
if (!groups[k]) groups[k] = []
push(groups[k], w)
groups[k][] = w
}
return groups
}

View File

@@ -27,13 +27,13 @@ function make_array_data(size) {
var arr = []
var i = 0
for (i = 0; i < size; i++) {
push(arr, {
arr[] = {
id: i,
name: `item_${i}`,
active: i % 2 == 0,
score: i * 1.5,
tags: [`tag_${i % 5}`, `tag_${(i + 1) % 5}`]
})
}
}
return arr
}

View File

@@ -16,7 +16,7 @@ function make_obj_yx(x, y) {
function make_packed_array(n) {
var a = []
var i = 0
for (i = 0; i < n; i++) push(a, i)
for (i = 0; i < n; i++) a[] = i
return a
}
@@ -144,7 +144,7 @@ return {
var a = null
for (j = 0; j < n; j++) {
a = []
for (i = 0; i < 256; i++) push(a, i)
for (i = 0; i < 256; i++) a[] = i
x = (x + length(a)) | 0
}
return blackhole(sink, x)

View File

@@ -272,7 +272,7 @@ return {
for (i = 0; i < n; i++) {
push(a, i)
if (length(a) > 64) {
v = pop(a)
v = a[]
x = (x + v) | 0
}
}

View File

@@ -16,21 +16,21 @@ function tokenize(src) {
ch = chars[i]
if (ch == " " || ch == "\n" || ch == "\t") {
if (length(buf) > 0) {
push(tokens, buf)
tokens[] = buf
buf = ""
}
} else if (ch == "(" || ch == ")" || ch == "+" || ch == "-"
|| ch == "*" || ch == "=" || ch == ";" || ch == ",") {
if (length(buf) > 0) {
push(tokens, buf)
tokens[] = buf
buf = ""
}
push(tokens, ch)
tokens[] = ch
} else {
buf = buf + ch
}
}
if (length(buf) > 0) push(tokens, buf)
if (length(buf) > 0) tokens[] = buf
return tokens
}
@@ -49,21 +49,21 @@ function parse_tokens(tokens) {
i++ // skip =
i++
if (i < length(tokens)) node.value = tokens[i]
push(ast, node)
ast[] = node
} else if (tok == "return") {
node = {type: "return", value: null}
i++
if (i < length(tokens)) node.value = tokens[i]
push(ast, node)
ast[] = node
} else if (tok == "function") {
node = {type: "func", name: null, body: []}
i++
if (i < length(tokens)) node.name = tokens[i]
// Skip to matching )
while (i < length(tokens) && tokens[i] != ")") i++
push(ast, node)
ast[] = node
} else {
push(ast, {type: "expr", value: tok})
ast[] = {type: "expr", value: tok}
}
}
return ast
@@ -121,7 +121,7 @@ function simulate_build(n_modules, deps_per_module) {
// Generate all module sources
for (i = 0; i < n_modules; i++) {
src = generate_module(i, deps_per_module)
push(modules, src)
modules[] = src
}
// "Load" each module: tokenize → parse → evaluate
@@ -173,7 +173,7 @@ function topo_sort(n_modules, deps_per_module) {
for (j = 0; j < deps_per_module; j++) {
if (j < i) {
dep = "mod_" + text(j)
push(adj[dep], name)
adj[dep][] = name
in_degree[name] = in_degree[name] + 1
}
}
@@ -183,7 +183,7 @@ function topo_sort(n_modules, deps_per_module) {
var queue = []
var keys = array(in_degree)
for (i = 0; i < length(keys); i++) {
if (in_degree[keys[i]] == 0) push(queue, keys[i])
if (in_degree[keys[i]] == 0) queue[] = keys[i]
}
var order = []
@@ -193,12 +193,12 @@ function topo_sort(n_modules, deps_per_module) {
while (qi < length(queue)) {
current = queue[qi]
qi++
push(order, current)
order[] = current
neighbors = adj[current]
if (neighbors) {
for (i = 0; i < length(neighbors); i++) {
in_degree[neighbors[i]] = in_degree[neighbors[i]] - 1
if (in_degree[neighbors[i]] == 0) push(queue, neighbors[i])
if (in_degree[neighbors[i]] == 0) queue[] = neighbors[i]
}
}
}

View File

@@ -7,7 +7,7 @@ function make_random_array(n, seed) {
var i = 0
for (i = 0; i < n; i++) {
x = ((x * 1103515245 + 12345) & 0x7FFFFFFF) | 0
push(a, x % 10000)
a[] = x % 10000
}
return a
}
@@ -15,7 +15,7 @@ function make_random_array(n, seed) {
function make_descending(n) {
var a = []
var i = 0
for (i = n - 1; i >= 0; i--) push(a, i)
for (i = n - 1; i >= 0; i--) a[] = i
return a
}
@@ -58,19 +58,19 @@ function merge(a, b) {
var j = 0
while (i < length(a) && j < length(b)) {
if (a[i] <= b[j]) {
push(result, a[i])
result[] = a[i]
i++
} else {
push(result, b[j])
result[] = b[j]
j++
}
}
while (i < length(a)) {
push(result, a[i])
result[] = a[i]
i++
}
while (j < length(b)) {
push(result, b[j])
result[] = b[j]
j++
}
return result
@@ -97,7 +97,7 @@ function sort_records(n) {
var i = 0
for (i = 0; i < n; i++) {
x = ((x * 1103515245 + 12345) & 0x7FFFFFFF) | 0
push(records, {id: i, score: x % 10000, name: `item_${i}`})
records[] = {id: i, score: x % 10000, name: `item_${i}`}
}
return sort(records, "score")
}

View File

@@ -23,7 +23,7 @@ function build_index(txt) {
if (!index[w]) {
index[w] = []
}
push(index[w], i)
index[w][] = i
}
return index
}

View File

@@ -48,7 +48,7 @@ function tree_map(node, fn) {
function tree_flatten(node, result) {
if (!node) return null
tree_flatten(node.left, result)
push(result, node.val)
result[] = node.val
tree_flatten(node.right, result)
return null
}
@@ -126,7 +126,7 @@ return {
// Build a balanced BST of 1024 elements
var data = []
var i = 0
for (i = 0; i < 1024; i++) push(data, i)
for (i = 0; i < 1024; i++) data[] = i
var bst = build_balanced(data, 0, 1023)
var found = 0
for (i = 0; i < n; i++) {

View File

@@ -95,12 +95,12 @@ function benchArrayOps() {
var arr = [];
var j = 0
for (j = 0; j < iterations.medium; j++) {
push(arr, j);
arr[] = j;
}
});
var arr = [];
for (i = 0; i < 10000; i++) push(arr, i);
for (i = 0; i < 10000; i++) arr[] = i;
var accessTime = measureTime(function() {
var sum = 0;
@@ -188,7 +188,7 @@ function benchStringOps() {
});
for (i = 0; i < 1000; i++) {
push(strings, "string" + i);
strings[] = "string" + i;
}
var joinTime = measureTime(function() {
@@ -261,13 +261,13 @@ function benchClosures() {
var funcs = [];
var j = 0
for (j = 0; j < iterations.medium; j++) {
push(funcs, makeAdder(j));
funcs[] = makeAdder(j);
}
});
var adders = [];
for (i = 0; i < 1000; i++) {
push(adders, makeAdder(i));
adders[] = makeAdder(i);
}
var closureCallTime = measureTime(function() {

View File

@@ -15,7 +15,7 @@ var nll = null
var oll = null
for (i = 0; i < 10000; i++) {
accstr += i;
push(newarr, text(i))
newarr[] = text(i)
}
var jsonDecodeTimes = [];
var jsonEncodeTimes = [];
@@ -26,19 +26,19 @@ var notaSizes = [];
for (i = 0; i < 100; i++) {
start = os.now();
jll = json.decode(ll);
push(jsonDecodeTimes, (os.now() - start) * 1000);
jsonDecodeTimes[] = (os.now() - start) * 1000;
start = os.now();
jsonStr = JSON.stringify(jll);
push(jsonEncodeTimes, (os.now() - start) * 1000);
jsonEncodeTimes[] = (os.now() - start) * 1000;
start = os.now();
nll = nota.encode(jll);
push(notaEncodeTimes, (os.now() - start) * 1000);
notaEncodeTimes[] = (os.now() - start) * 1000;
start = os.now();
oll = nota.decode(nll);
push(notaDecodeTimes, (os.now() - start) * 1000);
notaDecodeTimes[] = (os.now() - start) * 1000;
}
function getStats(arr) {

View File

@@ -99,7 +99,7 @@ function runBenchmarkForLibrary(lib, bench) {
for (j = 0; j < length(bench.data); j++) {
e = lib.encode(bench.data[j]);
if (i == 0) {
push(encodedList, e);
encodedList[] = e;
totalSize += lib.getSize(e);
}
}

View File

@@ -5251,88 +5251,54 @@
["add", 29, 29, 7, 260, 17],
["jump", "while_start_360", 260, 17],
"while_end_361",
["access", 7, "bootstrap: native cache seeded\n", 262, 12],
[
"access",
11,
{
"name": "os",
"kind": "name",
"make": "intrinsic"
},
262,
3
],
["is_proxy", 12, 11, 262, 3],
["jump_false", 12, "record_path_368", 262, 3],
["null", 12, 262, 3],
["access", 14, "print", 262, 3],
["array", 31, 0, 262, 3],
["stone_text", 7],
["push", 31, 7, 262, 3],
["frame", 32, 11, 2, 262, 3],
["setarg", 32, 0, 12, 262, 3],
["stone_text", 14],
["setarg", 32, 1, 14, 262, 3],
["setarg", 32, 2, 31, 262, 3],
["invoke", 32, 12, 262, 3],
["jump", "call_done_369", 262, 3],
"record_path_368",
["load_field", 14, 11, "print", 262, 3],
["frame", 31, 14, 1, 262, 3],
["setarg", 31, 0, 11, 262, 3],
["stone_text", 7],
["setarg", 31, 1, 7, 262, 3],
["invoke", 31, 12, 262, 3],
"call_done_369",
["jump", "if_end_350", 262, 3],
["jump", "if_end_350", 260, 17],
"if_else_349",
["record", 7, 2],
["access", 11, "tokenize", 266, 12],
["store_field", 7, 11, "name", 266, 12],
["access", 11, "tokenize.cm", 266, 30],
["store_field", 7, 11, "path", 266, 30],
["access", 11, "tokenize", 265, 12],
["store_field", 7, 11, "name", 265, 12],
["access", 11, "tokenize.cm", 265, 30],
["store_field", 7, 11, "path", 265, 30],
["record", 11, 2],
["access", 12, "parse", 267, 12],
["store_field", 11, 12, "name", 267, 12],
["access", 12, "parse.cm", 267, 27],
["store_field", 11, 12, "path", 267, 27],
["access", 12, "parse", 266, 12],
["store_field", 11, 12, "name", 266, 12],
["access", 12, "parse.cm", 266, 27],
["store_field", 11, 12, "path", 266, 27],
["record", 12, 2],
["access", 14, "fold", 268, 12],
["store_field", 12, 14, "name", 268, 12],
["access", 14, "fold.cm", 268, 26],
["store_field", 12, 14, "path", 268, 26],
["access", 14, "fold", 267, 12],
["store_field", 12, 14, "name", 267, 12],
["access", 14, "fold.cm", 267, 26],
["store_field", 12, 14, "path", 267, 26],
["record", 14, 2],
["access", 31, "mcode", 269, 12],
["store_field", 14, 31, "name", 269, 12],
["access", 31, "mcode.cm", 269, 27],
["store_field", 14, 31, "path", 269, 27],
["access", 31, "mcode", 268, 12],
["store_field", 14, 31, "name", 268, 12],
["access", 31, "mcode.cm", 268, 27],
["store_field", 14, 31, "path", 268, 27],
["record", 31, 2],
["access", 32, "streamline", 270, 12],
["store_field", 31, 32, "name", 270, 12],
["access", 32, "streamline.cm", 270, 32],
["store_field", 31, 32, "path", 270, 32],
["access", 32, "streamline", 269, 12],
["store_field", 31, 32, "name", 269, 12],
["access", 32, "streamline.cm", 269, 32],
["store_field", 31, 32, "path", 269, 32],
["record", 32, 2],
["access", 33, "engine", 271, 12],
["store_field", 32, 33, "name", 271, 12],
["access", 33, "internal/engine.cm", 271, 28],
["store_field", 32, 33, "path", 271, 28],
["array", 33, 6, 271, 28],
["push", 33, 7, 271, 28],
["push", 33, 11, 271, 28],
["push", 33, 12, 271, 28],
["push", 33, 14, 271, 28],
["push", 33, 31, 271, 28],
["push", 33, 32, 271, 28],
["move", 28, 33, 271, 28],
["access", 29, 0, 273, 9],
"while_start_370",
["length", 7, 28, 274, 23],
["lt", 11, 29, 7, 274, 23],
["jump_false", 11, "while_end_371", 274, 23],
["load_dynamic", 7, 28, 29, 275, 20],
["move", 30, 7, 275, 20],
["load_field", 11, 7, "name", 276, 23],
["access", 33, "engine", 270, 12],
["store_field", 32, 33, "name", 270, 12],
["access", 33, "internal/engine.cm", 270, 28],
["store_field", 32, 33, "path", 270, 28],
["array", 33, 6, 270, 28],
["push", 33, 7, 270, 28],
["push", 33, 11, 270, 28],
["push", 33, 12, 270, 28],
["push", 33, 14, 270, 28],
["push", 33, 31, 270, 28],
["push", 33, 32, 270, 28],
["move", 28, 33, 270, 28],
["access", 29, 0, 272, 9],
"while_start_368",
["length", 7, 28, 273, 23],
["lt", 11, 29, 7, 273, 23],
["jump_false", 11, "while_end_369", 273, 23],
["load_dynamic", 7, 28, 29, 274, 20],
["move", 30, 7, 274, 20],
["load_field", 11, 7, "name", 275, 23],
[
"access",
7,
@@ -5341,24 +5307,24 @@
"kind": "name",
"make": "intrinsic"
},
276,
275,
33
],
["access", 12, "/", 276, 45],
["is_text", 14, 7, 276, 45],
["jump_false", 14, "add_cn_373", 276, 45],
["access", 12, "/", 275, 45],
["is_text", 14, 7, 275, 45],
["jump_false", 14, "add_cn_371", 275, 45],
"_nop_tc_7",
"_nop_tc_8",
["concat", 31, 7, 12, 276, 45],
["jump", "add_done_372", 276, 45],
"add_cn_373",
["is_num", 14, 7, 276, 45],
["jump_false", 14, "add_err_374", 276, 45],
["concat", 31, 7, 12, 275, 45],
["jump", "add_done_370", 275, 45],
"add_cn_371",
["is_num", 14, 7, 275, 45],
["jump_false", 14, "add_err_372", 275, 45],
"_nop_tc_9",
"_nop_dj_3",
"_nop_ucfg_6",
"_nop_ucfg_7",
"add_err_374",
"add_err_372",
[
"access",
7,
@@ -5367,38 +5333,38 @@
"kind": "name",
"make": "intrinsic"
},
276,
275,
45
],
["access", 12, "error", 276, 45],
["access", 14, "cannot apply '+': operands must both be text or both be numbers", 276, 45],
["array", 32, 0, 276, 45],
["access", 12, "error", 275, 45],
["access", 14, "cannot apply '+': operands must both be text or both be numbers", 275, 45],
["array", 32, 0, 275, 45],
["stone_text", 14],
["push", 32, 14, 276, 45],
["frame", 14, 7, 2, 276, 45],
["null", 7, 276, 45],
["setarg", 14, 0, 7, 276, 45],
["push", 32, 14, 275, 45],
["frame", 14, 7, 2, 275, 45],
["null", 7, 275, 45],
["setarg", 14, 0, 7, 275, 45],
["stone_text", 12],
["setarg", 14, 1, 12, 276, 45],
["setarg", 14, 2, 32, 276, 45],
["invoke", 14, 7, 276, 45],
["disrupt", 276, 45],
"add_done_372",
["load_field", 7, 30, "path", 276, 51],
["setarg", 14, 1, 12, 275, 45],
["setarg", 14, 2, 32, 275, 45],
["invoke", 14, 7, 275, 45],
["disrupt", 275, 45],
"add_done_370",
["load_field", 7, 30, "path", 275, 51],
"_nop_tc_4",
"_nop_tc_5",
["is_text", 12, 7, 276, 51],
["jump_false", 12, "add_cn_376", 276, 51],
["concat", 12, 31, 7, 276, 51],
["jump", "add_done_375", 276, 51],
"add_cn_376",
["is_text", 12, 7, 275, 51],
["jump_false", 12, "add_cn_374", 275, 51],
["concat", 12, 31, 7, 275, 51],
["jump", "add_done_373", 275, 51],
"add_cn_374",
"_nop_tc_6",
["jump", "add_err_377", 276, 51],
["jump", "add_err_375", 275, 51],
"_nop_ucfg_5",
"_nop_ucfg_6",
"_nop_ucfg_7",
"_nop_ucfg_8",
"add_err_377",
"add_err_375",
[
"access",
7,
@@ -5407,71 +5373,37 @@
"kind": "name",
"make": "intrinsic"
},
276,
275,
51
],
["access", 14, "error", 276, 51],
["access", 31, "cannot apply '+': operands must both be text or both be numbers", 276, 51],
["array", 32, 0, 276, 51],
["access", 14, "error", 275, 51],
["access", 31, "cannot apply '+': operands must both be text or both be numbers", 275, 51],
["array", 32, 0, 275, 51],
["stone_text", 31],
["push", 32, 31, 276, 51],
["frame", 31, 7, 2, 276, 51],
["null", 7, 276, 51],
["setarg", 31, 0, 7, 276, 51],
["push", 32, 31, 275, 51],
["frame", 31, 7, 2, 275, 51],
["null", 7, 275, 51],
["setarg", 31, 0, 7, 275, 51],
["stone_text", 14],
["setarg", 31, 1, 14, 276, 51],
["setarg", 31, 2, 32, 276, 51],
["invoke", 31, 7, 276, 51],
["disrupt", 276, 51],
"add_done_375",
["frame", 7, 10, 2, 276, 5],
["setarg", 7, 1, 11, 276, 5],
["setarg", 31, 1, 14, 275, 51],
["setarg", 31, 2, 32, 275, 51],
["invoke", 31, 7, 275, 51],
["disrupt", 275, 51],
"add_done_373",
["frame", 7, 10, 2, 275, 5],
["setarg", 7, 1, 11, 275, 5],
["stone_text", 12],
["setarg", 7, 2, 12, 276, 5],
["invoke", 7, 11, 276, 5],
["access", 7, 1, 277, 17],
["add", 29, 29, 7, 277, 17],
["jump", "while_start_370", 277, 17],
"while_end_371",
["access", 7, "bootstrap: cache seeded\n", 279, 12],
[
"access",
10,
{
"name": "os",
"kind": "name",
"make": "intrinsic"
},
279,
3
],
["is_proxy", 11, 10, 279, 3],
["jump_false", 11, "record_path_378", 279, 3],
["null", 11, 279, 3],
["access", 12, "print", 279, 3],
["array", 14, 0, 279, 3],
["stone_text", 7],
["push", 14, 7, 279, 3],
["frame", 28, 10, 2, 279, 3],
["setarg", 28, 0, 11, 279, 3],
["stone_text", 12],
["setarg", 28, 1, 12, 279, 3],
["setarg", 28, 2, 14, 279, 3],
["invoke", 28, 11, 279, 3],
["jump", "call_done_379", 279, 3],
"record_path_378",
["load_field", 12, 10, "print", 279, 3],
["frame", 14, 12, 1, 279, 3],
["setarg", 14, 0, 10, 279, 3],
["stone_text", 7],
["setarg", 14, 1, 7, 279, 3],
["invoke", 14, 11, 279, 3],
"call_done_379",
["setarg", 7, 2, 12, 275, 5],
["invoke", 7, 11, 275, 5],
["access", 7, 1, 276, 17],
["add", 29, 29, 7, 276, 17],
["jump", "while_start_368", 276, 17],
"while_end_369",
"if_end_350",
["null", 7, 279, 3],
["return", 7, 279, 3]
["null", 7, 276, 17],
["return", 7, 276, 17]
],
"_write_types": [null, null, null, "bool", null, null, null, null, "function", "function", "function", null, "function", null, null, null, null, null, "function", null, null, null, "function", "function", null, null, "int", "function", "function", "function", "function", "function", "function", "function", "function", "function", "function", "function", "function", "function", "function", "function", "function", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "function", null, null, "text", null, null, "text", null, null, null, null, null, null, null, null, null, "null", "text", "array", null, null, null, "text", null, "text", null, null, null, "null", "text", "array", null, null, null, "text", null, "text", "text", "bool", null, null, "text", "text", "array", null, null, "null", null, null, "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "array", "int", "bool", null, null, null, "text", "text", "bool", null, null, "text", "text", "array", null, null, "null", null, null, "bool", "bool", null, "text", "text", "array", null, null, "null", null, null, "int", "text", null, null, null, "null", "text", "array", null, null, null, "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "array", "int", "bool", null, null, null, "text", "text", "bool", null, null, "text", "text", "array", null, null, "null", null, null, "bool", "bool", null, "text", "text", "array", null, null, "null", null, null, "int", "text", null, null, null, "null", "text", "array", null, null, null, "null"],
"_write_types": [null, null, null, "bool", null, null, null, null, "function", "function", "function", null, "function", null, null, null, null, null, "function", null, null, null, "function", "function", null, null, "int", "function", "function", "function", "function", "function", "function", "function", "function", "function", "function", "function", "function", "function", "function", "function", "function", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "text", null, null, "function", null, null, "text", null, null, "text", null, null, null, null, null, null, null, null, null, "null", "text", "array", null, null, null, "text", null, "text", null, null, null, "null", "text", "array", null, null, null, "text", null, "text", "text", "bool", null, null, "text", "text", "array", null, null, "null", null, null, "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "array", "int", "bool", null, null, null, "text", "text", "bool", null, null, "text", "text", "array", null, null, "null", null, null, "bool", "bool", null, "text", "text", "array", null, null, "null", null, null, "int", "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "record", "text", "text", "array", "int", "bool", null, null, null, "text", "text", "bool", null, null, "text", "text", "array", null, null, "null", null, null, "bool", "bool", null, "text", "text", "array", null, null, "null", null, null, "int", "null"],
"nr_args": 0,
"closure_written": {
"7": true,

File diff suppressed because one or more lines are too long

View File

@@ -3348,7 +3348,7 @@
{
"_closure_slot_types": {},
"disruption_pc": 0,
"nr_slots": 7,
"nr_slots": 8,
"nr_close_slots": 0,
"instructions": [
["get", 2, 14, 2, 1167, 15],
@@ -3356,23 +3356,35 @@
["invoke", 3, 2, 1167, 15],
["move", 3, 2, 1167, 15],
["access", 2, 8, 1, 13],
"_nop_tc_1",
"_nop_tc_2",
["is_num", 4, 1, 1, 13],
["jump_false", 4, "num_err_160", 1, 13],
["multiply", 4, 1, 2, 1, 13],
["jump", "num_done_161", 1, 13],
"num_err_160",
"_nop_ucfg_1",
"_nop_ucfg_2",
"_nop_ucfg_3",
"_nop_ucfg_4",
"_nop_ucfg_5",
"_nop_ucfg_6",
"_nop_ucfg_7",
"_nop_ucfg_8",
"_nop_ucfg_9",
"_nop_ucfg_10",
"_nop_ucfg_11",
"_nop_ucfg_12",
[
"access",
2,
{
"name": "log",
"kind": "name",
"make": "intrinsic"
},
1,
13
],
["access", 5, "error", 1, 13],
["access", 6, "operands must be numbers", 1, 13],
["array", 7, 0, 1, 13],
["stone_text", 6],
["push", 7, 6, 1, 13],
["frame", 6, 2, 2, 1, 13],
["null", 2, 1, 13],
["setarg", 6, 0, 2, 1, 13],
["stone_text", 5],
["setarg", 6, 1, 5, 1, 13],
["setarg", 6, 2, 7, 1, 13],
["invoke", 6, 2, 1, 13],
["disrupt", 1, 13],
"num_done_161",
[
"access",
@@ -3459,7 +3471,7 @@
"_nop_ur_1",
"_nop_ur_2"
],
"_write_types": [null, null, null, null, null, null, "int", "num", null, null, null, null, null, null, null, null, null, null, null, "array", null, "text", null, null, null, null, null, "array", null, "text", null, null, null, null, null, "array", null, "text", null, null, null],
"_write_types": [null, null, null, null, null, null, "int", "num", "bool", null, "text", "text", "array", null, null, "null", null, null, null, "array", null, "text", null, null, null, null, null, "array", null, "text", null, null, null, null, null, "array", null, "text", null, null, null],
"name": "<anonymous>",
"filename": ".cell/packages/core/qbe_emit.cm",
"nr_args": 1
@@ -3467,7 +3479,7 @@
{
"_closure_slot_types": {},
"disruption_pc": 0,
"nr_slots": 9,
"nr_slots": 10,
"nr_close_slots": 0,
"instructions": [
["get", 3, 14, 2, 1174, 15],
@@ -3541,23 +3553,35 @@
"if_else_162",
"if_end_163",
["access", 5, 8, 1, 13],
"_nop_tc_1",
"_nop_tc_2",
["is_num", 6, 1, 1, 13],
["jump_false", 6, "num_err_164", 1, 13],
["multiply", 6, 1, 5, 1, 13],
["jump", "num_done_165", 1, 13],
"num_err_164",
"_nop_ucfg_1",
"_nop_ucfg_2",
"_nop_ucfg_3",
"_nop_ucfg_4",
"_nop_ucfg_5",
"_nop_ucfg_6",
"_nop_ucfg_7",
"_nop_ucfg_8",
"_nop_ucfg_9",
"_nop_ucfg_10",
"_nop_ucfg_11",
"_nop_ucfg_12",
[
"access",
5,
{
"name": "log",
"kind": "name",
"make": "intrinsic"
},
1,
13
],
["access", 7, "error", 1, 13],
["access", 8, "operands must be numbers", 1, 13],
["array", 9, 0, 1, 13],
["stone_text", 8],
["push", 9, 8, 1, 13],
["frame", 8, 5, 2, 1, 13],
["null", 5, 1, 13],
["setarg", 8, 0, 5, 1, 13],
["stone_text", 7],
["setarg", 8, 1, 7, 1, 13],
["setarg", 8, 2, 9, 1, 13],
["invoke", 8, 5, 1, 13],
["disrupt", 1, 13],
"num_done_165",
[
"access",
@@ -3624,7 +3648,7 @@
["null", 3, 1181, 7],
["return", 3, 1181, 7]
],
"_write_types": [null, null, null, null, null, null, null, null, "text", "bool", null, null, null, "array", null, "text", null, "array", null, "text", null, null, null, null, null, "int", "num", null, null, null, null, null, null, null, null, null, null, null, "array", null, "text", null, null, null, null, null, "array", null, "text", null, null, null, null, null, "null"],
"_write_types": [null, null, null, null, null, null, null, null, "text", "bool", null, null, null, "array", null, "text", null, "array", null, "text", null, null, null, null, null, "int", "num", "bool", null, "text", "text", "array", null, null, "null", null, null, null, "array", null, "text", null, null, null, null, null, "array", null, "text", null, null, null, null, null, "null"],
"name": "<anonymous>",
"filename": ".cell/packages/core/qbe_emit.cm",
"nr_args": 2

File diff suppressed because one or more lines are too long

159
build.cm
View File

@@ -65,7 +65,7 @@ function replace_sigils(str, pkg_dir) {
function replace_sigils_array(flags, pkg_dir) {
var result = []
arrfor(flags, function(flag) {
push(result, replace_sigils(flag, pkg_dir))
result[] = replace_sigils(flag, pkg_dir)
})
return result
}
@@ -179,7 +179,7 @@ function bmfst_save(cmd_str, src_path, deps, obj_path) {
arrfor(deps, function(dep_path) {
var st = memo_stat(dep_path)
if (st)
push(entries, {p: dep_path, m: st.m, s: st.s})
entries[] = {p: dep_path, m: st.m, s: st.s}
})
var mf = {o: obj_path, d: entries}
var mf_path = bmfst_path(cmd_str, src_path)
@@ -191,16 +191,16 @@ function bmfst_save(cmd_str, src_path, deps, obj_path) {
function bmfst_dl_key(setup, link_info) {
var parts = [setup.cmd_str, setup.src_path]
push(parts, 'target:' + text(link_info.target))
push(parts, 'cc:' + text(link_info.cc))
parts[] = 'target:' + text(link_info.target)
parts[] = 'cc:' + text(link_info.cc)
arrfor(link_info.extra_objects, function(obj) {
if (obj != null) push(parts, 'extra:' + text(obj))
if (obj != null) parts[] = 'extra:' + text(obj)
})
arrfor(link_info.ldflags, function(flag) {
push(parts, 'ldflag:' + text(flag))
parts[] = 'ldflag:' + text(flag)
})
arrfor(link_info.target_ldflags, function(flag) {
push(parts, 'target_ldflag:' + text(flag))
parts[] = 'target_ldflag:' + text(flag)
})
return text(parts, '\n')
}
@@ -227,7 +227,7 @@ function bmfst_dl_save(setup, link_info, deps, dylib_path) {
arrfor(deps, function(dep_path) {
var st = memo_stat(dep_path)
if (st)
push(entries, {p: dep_path, m: st.m, s: st.s})
entries[] = {p: dep_path, m: st.m, s: st.s}
})
var mf = {dylib: dylib_path, d: entries}
var mf_path = cache_path(bmfst_dl_key(setup, link_info), SALT_BMFST_DL)
@@ -259,7 +259,7 @@ function get_c_deps(cc, flags, src_path) {
var dep_file = '/tmp/cell_deps_' + content_hash(src_path) + '.d'
var dep_cmd = [cc, '-MM', '-MG', '-MF', '"' + dep_file + '"']
dep_cmd = array(dep_cmd, flags)
push(dep_cmd, '"' + src_path + '"')
dep_cmd[] = '"' + src_path + '"'
var ret = os.system(text(dep_cmd, ' ') + ' 2>/dev/null')
if (ret != 0) return [src_path]
if (!fd.is_file(dep_file)) return [src_path]
@@ -274,9 +274,9 @@ function hash_all_deps(cmd_str, deps) {
arrfor(deps, function(dep_path) {
var content = memo_read(dep_path)
if (content != null)
push(parts, dep_path + '\n' + content)
parts[] = dep_path + '\n' + content
else
push(parts, dep_path + '\n<missing>')
parts[] = dep_path + '\n<missing>'
})
return text(parts, '\n')
}
@@ -310,16 +310,16 @@ function compile_setup(pkg, file, target, opts) {
common_flags = array(common_flags, ['-Os', '-DNDEBUG'])
}
push(common_flags, '-DCELL_USE_NAME=' + sym_name)
push(common_flags, '-I"' + pkg_dir + '"')
common_flags[] = '-DCELL_USE_NAME=' + sym_name
common_flags[] = '-I"' + pkg_dir + '"'
if (fd.is_dir(pkg_dir + '/include')) {
push(common_flags, '-I"' + pkg_dir + '/include"')
common_flags[] = '-I"' + pkg_dir + '/include"'
}
if (pkg != 'core') {
core_dir = shop.get_package_dir('core')
push(common_flags, '-I"' + core_dir + '/source"')
common_flags[] = '-I"' + core_dir + '/source"'
}
arrfor(cflags, function(flag) {
@@ -331,16 +331,16 @@ function compile_setup(pkg, file, target, opts) {
f = '-I"' + pkg_dir + '/' + ipath + '"'
}
}
push(common_flags, f)
common_flags[] = f
})
arrfor(target_cflags, function(flag) {
push(common_flags, flag)
common_flags[] = flag
})
var cmd_parts = [cc, '-c', '-fPIC']
cmd_parts = array(cmd_parts, common_flags)
push(cmd_parts, '"' + src_path + '"')
cmd_parts[] = '"' + src_path + '"'
return {
cmd_str: text(cmd_parts, ' '),
@@ -464,7 +464,7 @@ Build.compile_file = function(pkg, file, target, opts) {
// Compile
log.shop('compiling ' + file)
log.console('Compiling ' + file)
log.build('Compiling ' + file)
err_path = '/tmp/cell_build_err_' + content_hash(setup.src_path) + '.log'
full_cmd = setup.cmd_str + ' -o "' + obj_path + '" 2>"' + err_path + '"'
ret = os.system(full_cmd)
@@ -513,7 +513,7 @@ Build.build_package = function(pkg, target, exclude_main, buildtype) {
arrfor(c_files, function(file) {
var obj = Build.compile_file(pkg, file, _target, {buildtype: _buildtype, cflags: cached_cflags})
push(objects, obj)
objects[] = obj
})
return objects
@@ -527,16 +527,16 @@ Build.build_package = function(pkg, target, exclude_main, buildtype) {
// link_opts: {extra_objects, ldflags, target_ldflags, target, cc}
function compute_dylib_content(full_content, link_opts) {
var parts = [full_content]
push(parts, 'target:' + text(link_opts.target))
push(parts, 'cc:' + text(link_opts.cc))
parts[] = 'target:' + text(link_opts.target)
parts[] = 'cc:' + text(link_opts.cc)
arrfor(link_opts.extra_objects, function(obj) {
if (obj != null) push(parts, 'extra:' + text(obj))
if (obj != null) parts[] = 'extra:' + text(obj)
})
arrfor(link_opts.ldflags, function(flag) {
push(parts, 'ldflag:' + text(flag))
parts[] = 'ldflag:' + text(flag)
})
arrfor(link_opts.target_ldflags, function(flag) {
push(parts, 'target_ldflag:' + text(flag))
parts[] = 'target_ldflag:' + text(flag)
})
return text(parts, '\n')
}
@@ -570,7 +570,7 @@ Build.build_module_dylib = function(pkg, file, target, opts) {
f = '-L"' + setup.pkg_dir + '/' + lpath + '"'
}
}
push(resolved_ldflags, f)
resolved_ldflags[] = f
})
var build_dir = get_build_dir()
@@ -603,6 +603,8 @@ Build.build_module_dylib = function(pkg, file, target, opts) {
var post_probe = null
var fallback_probe = null
var _fail_msg2 = null
var link_err_path = null
var link_err_text = null
if (probe && probe.fail) {
_fail_msg2 = probe.fail_path ? text(fd.slurp(probe.fail_path)) : null
@@ -681,26 +683,32 @@ Build.build_module_dylib = function(pkg, file, target, opts) {
'-Wl,-rpath,' + local_dir
])
} else if (tc.system == 'windows') {
push(cmd_parts, '-Wl,--allow-shlib-undefined')
cmd_parts[] = '-Wl,--allow-shlib-undefined'
}
push(cmd_parts, '-L"' + local_dir + '"')
push(cmd_parts, '"' + text(obj) + '"')
cmd_parts[] = '-L"' + local_dir + '"'
cmd_parts[] = '"' + text(obj) + '"'
arrfor(_extra, function(extra_obj) {
if (extra_obj != null) push(cmd_parts, '"' + text(extra_obj) + '"')
if (extra_obj != null) cmd_parts[] = '"' + text(extra_obj) + '"'
})
cmd_parts = array(cmd_parts, resolved_ldflags)
cmd_parts = array(cmd_parts, target_ldflags)
push(cmd_parts, '-o')
push(cmd_parts, '"' + dylib_path + '"')
cmd_parts[] = '-o'
cmd_parts[] = '"' + dylib_path + '"'
cmd_str = text(cmd_parts, ' ')
if (_opts.verbose) log.build('[verbose] link: ' + cmd_str)
log.shop('linking ' + file)
log.console('Linking module ' + file + ' -> ' + fd.basename(dylib_path))
ret = os.system(cmd_str)
log.build('Linking module ' + file + ' -> ' + fd.basename(dylib_path))
link_err_path = '/tmp/cell_link_err_' + content_hash(file) + '.log'
ret = os.system(cmd_str + ' 2>"' + link_err_path + '"')
if (ret != 0) {
log.error('Linking failed: ' + file)
if (fd.is_file(link_err_path))
link_err_text = text(fd.slurp(link_err_path))
if (link_err_text)
log.error('Linking failed: ' + file + '\n' + link_err_text)
else
log.error('Linking failed: ' + file)
return null
}
@@ -729,7 +737,7 @@ Build.compile_c_module = function(pkg, file, target, opts) {
if (pkg != 'core') {
arrfor(sources, function(src_file) {
var obj = Build.compile_file(pkg, src_file, _target, {buildtype: _buildtype, cflags: cached_cflags})
if (obj != null) push(support_objects, obj)
if (obj != null) support_objects[] = obj
})
}
@@ -745,6 +753,9 @@ Build.build_dynamic = function(pkg, target, buildtype, opts) {
var _opts = opts || {}
var c_files = pkg_tools.get_c_files(pkg, _target, true)
var results = []
var total = length(c_files)
var done = 0
var failed = 0
// Pre-fetch cflags once to avoid repeated TOML reads
var pkg_dir = shop.get_package_dir(pkg)
@@ -756,7 +767,7 @@ Build.build_dynamic = function(pkg, target, buildtype, opts) {
if (pkg != 'core') {
arrfor(sources, function(src_file) {
var obj = Build.compile_file(pkg, src_file, _target, {buildtype: _buildtype, cflags: cached_cflags, verbose: _opts.verbose, force: _opts.force})
if (obj != null) push(support_objects, obj)
if (obj != null) support_objects[] = obj
})
}
@@ -764,10 +775,16 @@ Build.build_dynamic = function(pkg, target, buildtype, opts) {
var sym_name = shop.c_symbol_for_file(pkg, file)
var dylib = Build.build_module_dylib(pkg, file, _target, {buildtype: _buildtype, extra_objects: support_objects, cflags: cached_cflags, verbose: _opts.verbose, force: _opts.force})
if (dylib) {
push(results, {file: file, symbol: sym_name, dylib: dylib})
results[] = {file: file, symbol: sym_name, dylib: dylib}
} else {
failed = failed + 1
}
done = done + 1
})
if (total > 0)
log.build(` Building C modules (${text(done)} ok${failed > 0 ? `, ${text(failed)} failed` : ''})`)
// Write manifest so runtime can find dylibs without the build module
var mpath = manifest_path(pkg)
fd.slurpwrite(mpath, stone(blob(json.encode(results))))
@@ -797,7 +814,7 @@ Build.build_static = function(packages, target, output, buildtype) {
var objects = Build.build_package(pkg, _target, !is_core, _buildtype)
arrfor(objects, function(obj) {
push(all_objects, obj)
all_objects[] = obj
})
// Collect LDFLAGS (with sigil replacement)
@@ -817,7 +834,7 @@ Build.build_static = function(packages, target, output, buildtype) {
f = '-L"' + pkg_dir + '/' + lpath + '"'
}
}
push(all_ldflags, f)
all_ldflags[] = f
})
}
})
@@ -839,18 +856,18 @@ Build.build_static = function(packages, target, output, buildtype) {
var cmd_parts = [cc]
arrfor(all_objects, function(obj) {
push(cmd_parts, '"' + obj + '"')
cmd_parts[] = '"' + obj + '"'
})
arrfor(all_ldflags, function(flag) {
push(cmd_parts, flag)
cmd_parts[] = flag
})
arrfor(target_ldflags, function(flag) {
push(cmd_parts, flag)
cmd_parts[] = flag
})
push(cmd_parts, '-o', '"' + out_path + '"')
cmd_parts[] = '-o', '"' + out_path + '"'
var cmd_str = text(cmd_parts, ' ')
@@ -904,7 +921,7 @@ function qbe_insert_dead_labels(il_text) {
line = lines[i]
trimmed = trim(line)
if (need_label && !starts_with(trimmed, '@') && !starts_with(trimmed, '}') && length(trimmed) > 0) {
push(result, "@_dead_" + text(dead_id))
result[] = "@_dead_" + text(dead_id)
dead_id = dead_id + 1
need_label = false
}
@@ -914,7 +931,7 @@ function qbe_insert_dead_labels(il_text) {
if (starts_with(trimmed, 'ret ') || starts_with(trimmed, 'jmp ')) {
need_label = true
}
push(result, line)
result[] = line
i = i + 1
}
return text(result, "\n")
@@ -1105,16 +1122,16 @@ Build.compile_cm_to_mach = function(src_path) {
// output: path to write the generated .c file
Build.generate_module_table = function(modules, output) {
var lines = []
push(lines, '/* Generated module table — do not edit */')
push(lines, '#include <stddef.h>')
push(lines, '#include <string.h>')
push(lines, '')
push(lines, 'struct cell_embedded_entry {')
push(lines, ' const char *name;')
push(lines, ' const unsigned char *data;')
push(lines, ' size_t size;')
push(lines, '};')
push(lines, '')
lines[] = '/* Generated module table — do not edit */'
lines[] = '#include <stddef.h>'
lines[] = '#include <string.h>'
lines[] = ''
lines[] = 'struct cell_embedded_entry {'
lines[] = ' const char *name;'
lines[] = ' const unsigned char *data;'
lines[] = ' size_t size;'
lines[] = '};'
lines[] = ''
var entries = []
arrfor(modules, function(mod) {
@@ -1123,27 +1140,27 @@ Build.generate_module_table = function(modules, output) {
var bytes = array(mach)
var hex = []
arrfor(bytes, function(b) {
push(hex, '0x' + text(b, 'h2'))
hex[] = '0x' + text(b, 'h2')
})
push(lines, 'static const unsigned char mod_' + safe + '_data[] = {')
push(lines, ' ' + text(hex, ', '))
push(lines, '};')
push(lines, '')
push(entries, safe)
lines[] = 'static const unsigned char mod_' + safe + '_data[] = {'
lines[] = ' ' + text(hex, ', ')
lines[] = '};'
lines[] = ''
entries[] = safe
log.console('Embedded: ' + mod.name + ' (' + text(length(bytes)) + ' bytes)')
})
// Lookup function
push(lines, 'const struct cell_embedded_entry *cell_embedded_module_lookup(const char *name) {')
lines[] = 'const struct cell_embedded_entry *cell_embedded_module_lookup(const char *name) {'
arrfor(modules, function(mod, i) {
var safe = entries[i]
push(lines, ' if (strcmp(name, "' + mod.name + '") == 0) {')
push(lines, ' static const struct cell_embedded_entry e = {"' + mod.name + '", mod_' + safe + '_data, sizeof(mod_' + safe + '_data)};')
push(lines, ' return &e;')
push(lines, ' }')
lines[] = ' if (strcmp(name, "' + mod.name + '") == 0) {'
lines[] = ' static const struct cell_embedded_entry e = {"' + mod.name + '", mod_' + safe + '_data, sizeof(mod_' + safe + '_data)};'
lines[] = ' return &e;'
lines[] = ' }'
})
push(lines, ' return (void *)0;')
push(lines, '}')
lines[] = ' return (void *)0;'
lines[] = '}'
var c_text = text(lines, '\n')
fd.slurpwrite(output, stone(blob(c_text)))
@@ -1171,14 +1188,14 @@ Build.build_all_dynamic = function(target, buildtype, opts) {
// Build core first
if (find(packages, function(p) { return p == 'core' }) != null) {
core_mods = Build.build_dynamic('core', _target, _buildtype, _opts)
push(results, {package: 'core', modules: core_mods})
results[] = {package: 'core', modules: core_mods}
}
// Build other packages
arrfor(packages, function(pkg) {
if (pkg == 'core') return
var pkg_mods = Build.build_dynamic(pkg, _target, _buildtype, _opts)
push(results, {package: pkg, modules: pkg_mods})
results[] = {package: pkg, modules: pkg_mods}
})
// Print build report

174
cellfs.cm
View File

@@ -4,10 +4,11 @@ var fd = use('fd')
var miniz = use('miniz')
var qop = use('internal/qop')
var wildstar = use('internal/wildstar')
var blib = use('blob')
var mounts = []
var writepath = "."
var write_mount = null
function normalize_path(path) {
if (!path) return ""
@@ -30,7 +31,7 @@ function mount_exists(mount, path) {
result = mount.handle.stat(path) != null
} disruption {}
_check()
} else {
} else if (mount.type == 'fs') {
full_path = fd.join_paths(mount.source, path)
_check = function() {
st = fd.stat(full_path)
@@ -119,12 +120,12 @@ function resolve(path, must_exist) {
}
function mount(source, name) {
var st = fd.stat(source)
var st = null
var blob = null
var qop_archive = null
var zip = null
var _try_qop = null
var http = null
var mount_info = {
source: source,
@@ -134,6 +135,29 @@ function mount(source, name) {
zip_blob: null
}
if (starts_with(source, 'http://') || starts_with(source, 'https://')) {
http = use('http')
mount_info.type = 'http'
mount_info.handle = {
base_url: source,
get: function(path, callback) {
var url = source + '/' + path
$clock(function(_t) {
var resp = http.request('GET', url, null, null)
if (resp && resp.status == 200) {
callback(resp.body)
} else {
callback(null, "HTTP " + text(resp ? resp.status : 0) + ": " + url)
}
})
}
}
mounts[] = mount_info
return
}
st = fd.stat(source)
if (st.isDirectory) {
mount_info.type = 'fs'
} else if (st.isFile) {
@@ -146,24 +170,24 @@ function mount(source, name) {
_try_qop()
if (qop_archive) {
mount_info.type = 'qop'
mount_info.handle = qop_archive
mount_info.zip_blob = blob
mount_info.type = 'qop'
mount_info.handle = qop_archive
mount_info.zip_blob = blob
} else {
zip = miniz.read(blob)
if (!is_object(zip) || !is_function(zip.count)) {
log.error("Invalid archive file (not zip or qop): " + source); disrupt
}
zip = miniz.read(blob)
if (!is_object(zip) || !is_function(zip.count)) {
log.error("Invalid archive file (not zip or qop): " + source); disrupt
}
mount_info.type = 'zip'
mount_info.handle = zip
mount_info.zip_blob = blob
mount_info.type = 'zip'
mount_info.handle = zip
mount_info.zip_blob = blob
}
} else {
log.error("Unsupported mount source type: " + source); disrupt
}
push(mounts, mount_info)
mounts[] = mount_info
}
function unmount(name_or_source) {
@@ -191,11 +215,13 @@ function slurp(path) {
}
function slurpwrite(path, data) {
var full_path = writepath + "/" + path
var f = fd.open(full_path, 'w')
fd.write(f, data)
fd.close(f)
var full_path = null
if (write_mount) {
full_path = fd.join_paths(write_mount.source, path)
} else {
full_path = fd.join_paths(".", path)
}
fd.slurpwrite(full_path, data)
}
function exists(path) {
@@ -276,12 +302,25 @@ function rm(path) {
}
function mkdir(path) {
var full = fd.join_paths(writepath, path)
var full = null
if (write_mount) {
full = fd.join_paths(write_mount.source, path)
} else {
full = fd.join_paths(".", path)
}
fd.mkdir(full)
}
function set_writepath(path) {
writepath = path
function set_writepath(mount_name) {
var found = null
if (mount_name == null) { write_mount = null; return }
arrfor(mounts, function(m) {
if (m.name == mount_name) { found = m; return true }
}, false, true)
if (!found || found.type != 'fs') {
log.error("writepath: must be an fs mount"); disrupt
}
write_mount = found
}
function basedir() {
@@ -317,7 +356,7 @@ function enumerate(_path, recurse) {
arrfor(list, function(item) {
var item_rel = rel_prefix ? rel_prefix + "/" + item : item
var child_st = null
push(results, item_rel)
results[] = item_rel
if (recurse) {
child_st = fd.stat(fd.join_paths(curr_full, item))
@@ -357,7 +396,7 @@ function enumerate(_path, recurse) {
if (!seen[rel]) {
seen[rel] = true
push(results, rel)
results[] = rel
}
}
})
@@ -416,7 +455,7 @@ function globfs(globs, _dir) {
}
} else {
if (!check_neg(item_rel) && check_pos(item_rel)) {
push(results, item_rel)
results[] = item_rel
}
}
})
@@ -440,7 +479,7 @@ function globfs(globs, _dir) {
if (length(rel) == 0) return
if (!check_neg(rel) && check_pos(rel)) {
push(results, rel)
results[] = rel
}
}
})
@@ -449,6 +488,82 @@ function globfs(globs, _dir) {
return results
}
// Requestor factory: returns a requestor for reading a file at path
function get(path) {
return function get_requestor(callback, value) {
var res = resolve(path, false)
var full = null
var f = null
var acc = null
var cancelled = false
var data = null
var _close = null
if (!res) { callback(null, "not found: " + path); return }
if (res.mount.type == 'zip') {
callback(res.mount.handle.slurp(res.path))
return
}
if (res.mount.type == 'qop') {
data = res.mount.handle.read(res.path)
if (data) {
callback(data)
} else {
callback(null, "not found in qop: " + path)
}
return
}
if (res.mount.type == 'http') {
res.mount.handle.get(res.path, callback)
return
}
full = fd.join_paths(res.mount.source, res.path)
f = fd.open(full, 'r')
acc = blob()
function next(_t) {
var chunk = null
if (cancelled) return
chunk = fd.read(f, 65536)
if (length(chunk) == 0) {
fd.close(f)
stone(acc)
callback(acc)
return
}
acc.write_blob(chunk)
$clock(next)
}
next()
return function cancel() {
cancelled = true
_close = function() { fd.close(f) } disruption {}
_close()
}
}
}
// Requestor factory: returns a requestor for writing data to path
function put(path, data) {
return function put_requestor(callback, value) {
var _data = data != null ? data : value
var full = null
var _do = null
if (!write_mount) { callback(null, "no write mount set"); return }
full = fd.join_paths(write_mount.source, path)
_do = function() {
fd.slurpwrite(full, _data)
callback(true)
} disruption {
callback(null, "write failed: " + path)
}
_do()
}
}
cellfs.mount = mount
cellfs.mount_package = mount_package
cellfs.unmount = unmount
@@ -467,7 +582,8 @@ cellfs.writepath = set_writepath
cellfs.basedir = basedir
cellfs.prefdir = prefdir
cellfs.realdir = realdir
cellfs.mount('.')
cellfs.get = get
cellfs.put = put
cellfs.resolve = resolve
return cellfs

18
cfg.ce
View File

@@ -168,7 +168,7 @@ var run = function() {
if (is_array(instr)) {
if (block_start_pcs[text(pc)]) {
if (current_block != null) {
push(blocks, current_block)
blocks[] = current_block
}
current_block = {
id: length(blocks),
@@ -184,7 +184,7 @@ var run = function() {
}
if (current_block != null) {
push(current_block.instrs, {pc: pc, instr: instr})
current_block.instrs[] = {pc: pc, instr: instr}
current_block.end_pc = pc
n = length(instr)
line_num = instr[n - 2]
@@ -200,7 +200,7 @@ var run = function() {
ii = ii + 1
}
if (current_block != null) {
push(blocks, current_block)
blocks[] = current_block
}
// Build block index
@@ -235,19 +235,19 @@ var run = function() {
if (target_bi <= bi) {
edge_type = "loop back-edge"
}
push(blk.edges, {target: target_bi, kind: edge_type})
blk.edges[] = {target: target_bi, kind: edge_type}
}
if (is_conditional_jump(last_op)) {
if (bi + 1 < length(blocks)) {
push(blk.edges, {target: bi + 1, kind: "fallthrough"})
blk.edges[] = {target: bi + 1, kind: "fallthrough"}
}
}
} else if (is_terminator(last_op)) {
push(blk.edges, {target: -1, kind: "EXIT (" + last_op + ")"})
blk.edges[] = {target: -1, kind: "EXIT (" + last_op + ")"}
} else {
if (bi + 1 < length(blocks)) {
push(blk.edges, {target: bi + 1, kind: "fallthrough"})
blk.edges[] = {target: bi + 1, kind: "fallthrough"}
}
}
}
@@ -308,7 +308,7 @@ var run = function() {
parts = []
j = 1
while (j < n - 2) {
push(parts, fmt_val(instr[j]))
parts[] = fmt_val(instr[j])
j = j + 1
}
operands = text(parts, ", ")
@@ -381,7 +381,7 @@ var run = function() {
parts = []
j = 1
while (j < n - 2) {
push(parts, fmt_val(instr[j]))
parts[] = fmt_val(instr[j])
j = j + 1
}
operands = text(parts, ", ")

View File

@@ -93,13 +93,13 @@ if (is_shop_scope) {
packages_to_clean = shop.list_packages()
} else {
// Single package
push(packages_to_clean, scope)
packages_to_clean[] = scope
if (deep) {
_gather = function() {
deps = pkg.gather_dependencies(scope)
arrfor(deps, function(dep) {
push(packages_to_clean, dep)
packages_to_clean[] = dep
})
} disruption {
// Skip if can't read dependencies
@@ -116,11 +116,11 @@ var packages_dir = replace(shop.get_package_dir(''), /\/$/, '') // Get base pack
if (clean_build) {
// Nuke entire build cache (content-addressed, per-package clean impractical)
if (fd.is_dir(build_dir)) {
push(dirs_to_delete, build_dir)
dirs_to_delete[] = build_dir
}
// Clean orphaned lib/ directory if it exists (legacy)
if (fd.is_dir(lib_dir)) {
push(dirs_to_delete, lib_dir)
dirs_to_delete[] = lib_dir
}
}
@@ -128,7 +128,7 @@ if (clean_fetch) {
if (is_shop_scope) {
// Clean entire packages directory (dangerous!)
if (fd.is_dir(packages_dir)) {
push(dirs_to_delete, packages_dir)
dirs_to_delete[] = packages_dir
}
} else {
// Clean specific package directories
@@ -137,7 +137,7 @@ if (clean_fetch) {
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)
dirs_to_delete[] = pkg_dir
}
})
}

24
diff.ce
View File

@@ -55,7 +55,7 @@ function collect_tests(specific_test) {
match_base = ends_with(match_name, '.cm') ? text(match_name, 0, -3) : match_name
if (test_name != match_base) continue
}
push(test_files, f)
test_files[] = f
}
}
return test_files
@@ -100,7 +100,7 @@ function diff_test_file(file_path) {
src = text(fd.slurp(src_path))
ast = analyze(src, src_path)
} disruption {
push(results.errors, `failed to parse ${file_path}`)
results.errors[] = `failed to parse ${file_path}`
return results
}
_read()
@@ -124,14 +124,14 @@ function diff_test_file(file_path) {
// Compare module-level behavior
if (opt_error != noopt_error) {
push(results.errors, `module load mismatch: opt=${opt_error != null ? opt_error : "ok"} noopt=${noopt_error != null ? noopt_error : "ok"}`)
results.errors[] = `module load mismatch: opt=${opt_error != null ? opt_error : "ok"} noopt=${noopt_error != null ? noopt_error : "ok"}`
results.failed = results.failed + 1
return results
}
if (opt_error != null) {
// Both disrupted during load — that's consistent
results.passed = results.passed + 1
push(results.tests, {name: "<module>", status: "passed"})
results.tests[] = {name: "<module>", status: "passed"}
return results
}
@@ -161,15 +161,15 @@ function diff_test_file(file_path) {
_run_one_noopt()
if (opt_err != noopt_err) {
push(results.tests, {name: k, status: "failed"})
push(results.errors, `${k}: disruption mismatch opt=${opt_err != null ? opt_err : "ok"} noopt=${noopt_err != null ? noopt_err : "ok"}`)
results.tests[] = {name: k, status: "failed"}
results.errors[] = `${k}: disruption mismatch opt=${opt_err != null ? opt_err : "ok"} noopt=${noopt_err != null ? noopt_err : "ok"}`
results.failed = results.failed + 1
} else if (!values_equal(opt_result, noopt_result)) {
push(results.tests, {name: k, status: "failed"})
push(results.errors, `${k}: result mismatch opt=${describe(opt_result)} noopt=${describe(noopt_result)}`)
results.tests[] = {name: k, status: "failed"}
results.errors[] = `${k}: result mismatch opt=${describe(opt_result)} noopt=${describe(noopt_result)}`
results.failed = results.failed + 1
} else {
push(results.tests, {name: k, status: "passed"})
results.tests[] = {name: k, status: "passed"}
results.passed = results.passed + 1
}
}
@@ -178,11 +178,11 @@ function diff_test_file(file_path) {
} else {
// Compare direct return values
if (!values_equal(mod_opt, mod_noopt)) {
push(results.tests, {name: "<return>", status: "failed"})
push(results.errors, `return value mismatch: opt=${describe(mod_opt)} noopt=${describe(mod_noopt)}`)
results.tests[] = {name: "<return>", status: "failed"}
results.errors[] = `return value mismatch: opt=${describe(mod_opt)} noopt=${describe(mod_noopt)}`
results.failed = results.failed + 1
} else {
push(results.tests, {name: "<return>", status: "passed"})
results.tests[] = {name: "<return>", status: "passed"}
results.passed = results.passed + 1
}
}

View File

@@ -93,7 +93,7 @@ var run = function() {
var operands = null
var line_str = null
while (j < n - 2) {
push(parts, fmt_val(instr[j]))
parts[] = fmt_val(instr[j])
j = j + 1
}
operands = text(parts, ", ")

View File

@@ -206,7 +206,7 @@ var run = function() {
parts = []
j = 1
while (j < n - 2) {
push(parts, fmt_val(instr[j]))
parts[] = fmt_val(instr[j])
j = j + 1
}
operands = text(parts, ", ")

View File

@@ -9,13 +9,16 @@ Logging in ƿit is channel-based. Any `log.X(value)` call writes to channel `"X"
## Channels
Three channels are conventional:
These channels are conventional:
| Channel | Usage |
|---------|-------|
| `log.console(msg)` | General output |
| `log.error(msg)` | Errors and warnings |
| `log.error(msg)` | Errors |
| `log.warn(msg)` | Compiler diagnostics and warnings |
| `log.system(msg)` | Internal system messages |
| `log.build(msg)` | Per-file compile/link output |
| `log.shop(msg)` | Package management internals |
Any name works. `log.debug(msg)` creates channel `"debug"`, `log.perf(msg)` creates `"perf"`, and so on.
@@ -29,16 +32,18 @@ Non-text values are JSON-encoded automatically.
## Default Behavior
With no configuration, a default sink routes `console`, `error`, and `system` to the terminal in pretty format. The `error` channel includes a stack trace by default:
With no configuration, a default sink routes `console` and `error` to the terminal in clean format. The `error` channel includes a stack trace by default:
```
[a3f12] [console] server started on port 8080
[a3f12] [error] connection refused
server started on port 8080
error: connection refused
at handle_request (server.ce:42:3)
at main (main.ce:5:1)
```
The format is `[actor_id] [channel] message`. Error stack traces are always on unless you explicitly configure a sink without them.
Clean format prints messages without actor ID or channel prefix. Error messages are prefixed with `error:`. Other formats (`pretty`, `bare`) include actor IDs and are available for debugging. Stack traces are always on for errors unless you explicitly configure a sink without them.
Channels like `warn`, `build`, and `shop` are not routed to the terminal by default. Enable them with `pit log enable <channel>`.
## Configuration
@@ -67,7 +72,7 @@ exclude = ["console"]
| Field | Values | Description |
|-------|--------|-------------|
| `type` | `"console"`, `"file"` | Where output goes |
| `format` | `"pretty"`, `"bare"`, `"json"` | How output is formatted |
| `format` | `"clean"`, `"pretty"`, `"bare"`, `"json"` | How output is formatted |
| `channels` | array of names, or `["*"]` | Which channels this sink receives. Quote `'*'` on the CLI to prevent shell glob expansion. |
| `exclude` | array of names | Channels to skip (useful with `"*"`) |
| `stack` | array of channel names | Channels that capture a stack trace |
@@ -75,6 +80,13 @@ exclude = ["console"]
### Formats
**clean** — CLI-friendly. No actor ID or channel prefix. Error channel messages are prefixed with `error:`. This is the default format.
```
server started on port 8080
error: connection refused
```
**pretty** — human-readable, one line per message. Includes actor ID, channel, source location, and message.
```
@@ -158,7 +170,10 @@ The `pit log` command manages sinks and reads log files. See [CLI — pit log](/
```bash
pit log list # show sinks
pit log add terminal console --format=bare --channels=console
pit log channels # list channels with enabled/disabled status
pit log enable warn # enable a channel on the terminal sink
pit log disable warn # disable a channel
pit log add terminal console --format=clean --channels=console
pit log add dump file .cell/logs/dump.jsonl '--channels=*' --exclude=console
pit log add debug console --channels=error,debug --stack=error,debug
pit log remove terminal
@@ -166,6 +181,16 @@ pit log read dump --lines=20 --channel=error
pit log tail dump
```
### Channel toggling
The `enable` and `disable` commands modify the terminal sink's channel list without touching other sink configuration. This is the easiest way to opt in to extra output:
```bash
pit log enable warn # see compiler warnings
pit log enable build # see per-file compile/link output
pit log disable warn # hide warnings again
```
## Examples
### Development setup

2
fd.cm
View File

@@ -83,7 +83,7 @@ fd.globfs = function(globs, dir) {
}
} else {
if (!check_neg(item_rel) && check_pos(item_rel)) {
push(results, item_rel)
results[] = item_rel
}
}
});

26
fold.cm
View File

@@ -709,26 +709,26 @@ var fold = function(ast) {
if (sv != null && sv.nr_uses == 0) {
if (is_pure(stmt.right)) stmt.dead = true
if (stmt.right != null && stmt.right.kind == "(" && stmt.right.expression != null && stmt.right.expression.name == "use") {
push(ast._diagnostics, {
ast._diagnostics[] = {
severity: "warning",
line: stmt.left.from_row + 1,
col: stmt.left.from_column + 1,
message: `unused import '${name}'`
})
}
} else if (stmt.kind == "def") {
push(ast._diagnostics, {
ast._diagnostics[] = {
severity: "warning",
line: stmt.left.from_row + 1,
col: stmt.left.from_column + 1,
message: `unused constant '${name}'`
})
}
} else {
push(ast._diagnostics, {
ast._diagnostics[] = {
severity: "warning",
line: stmt.left.from_row + 1,
col: stmt.left.from_column + 1,
message: `unused variable '${name}'`
})
}
}
}
}
@@ -742,15 +742,15 @@ var fold = function(ast) {
sv = scope_var(fn_nr, stmt.name)
if (sv != null && sv.nr_uses == 0) {
stmt.dead = true
push(ast._diagnostics, {
ast._diagnostics[] = {
severity: "warning",
line: stmt.from_row + 1,
col: stmt.from_column + 1,
message: `unused function '${stmt.name}'`
})
}
}
}
if (stmt.dead != true) push(out, stmt)
if (stmt.dead != true) out[] = stmt
i = i + 1
}
return out
@@ -1039,7 +1039,7 @@ var fold = function(ast) {
i = 0
while (i < length(ast.intrinsics)) {
if (used_intrinsics[ast.intrinsics[i]] == true) {
push(new_intrinsics, ast.intrinsics[i])
new_intrinsics[] = ast.intrinsics[i]
}
i = i + 1
}
@@ -1071,16 +1071,16 @@ var fold = function(ast) {
fn_sv = scope_var(0, fn.name)
if (fn_sv != null && fn_sv.nr_uses == 0) {
fn.dead = true
push(ast._diagnostics, {
ast._diagnostics[] = {
severity: "warning",
line: fn.from_row + 1,
col: fn.from_column + 1,
message: `unused function '${fn.name}'`
})
}
}
}
if (fn.dead != true) {
push(live_fns, fn)
live_fns[] = fn
}
fi = fi + 1
}

12
fuzz.ce
View File

@@ -89,7 +89,7 @@ function run_fuzz(seed_val) {
var _parse = function() {
ast = analyze(src, name + ".cm")
} disruption {
push(errors, "parse error")
errors[] = "parse error"
}
_parse()
if (length(errors) > 0) return {seed: seed_val, errors: errors, src: src}
@@ -112,7 +112,7 @@ function run_fuzz(seed_val) {
// Check module-level behavior
if (opt_err != noopt_err) {
push(errors, `module load: opt=${opt_err != null ? opt_err : "ok"} noopt=${noopt_err != null ? noopt_err : "ok"}`)
errors[] = `module load: opt=${opt_err != null ? opt_err : "ok"} noopt=${noopt_err != null ? noopt_err : "ok"}`
return {seed: seed_val, errors: errors, src: src}
}
if (opt_err != null) {
@@ -137,10 +137,10 @@ function run_fuzz(seed_val) {
_run()
if (is_text(ret)) {
push(errors, `self-check ${key}: ${ret}`)
errors[] = `self-check ${key}: ${ret}`
}
if (run_err != null) {
push(errors, `self-check ${key}: unexpected disruption`)
errors[] = `self-check ${key}: unexpected disruption`
}
}
k = k + 1
@@ -174,9 +174,9 @@ function run_fuzz(seed_val) {
_run_noopt()
if (opt_fn_err != noopt_fn_err) {
push(errors, `diff ${key2}: opt=${opt_fn_err != null ? opt_fn_err : "ok"} noopt=${noopt_fn_err != null ? noopt_fn_err : "ok"}`)
errors[] = `diff ${key2}: opt=${opt_fn_err != null ? opt_fn_err : "ok"} noopt=${noopt_fn_err != null ? noopt_fn_err : "ok"}`
} else if (!values_equal(opt_result, noopt_result)) {
push(errors, `diff ${key2}: opt=${describe(opt_result)} noopt=${describe(noopt_result)}`)
errors[] = `diff ${key2}: opt=${describe(opt_result)} noopt=${describe(noopt_result)}`
}
}
k2 = k2 + 1

View File

@@ -241,7 +241,7 @@ function gen_array_test() {
var v = 0
while (i < n) {
v = rand_int(-100, 100)
push(vals, v)
vals[] = v
sum = sum + v
i = i + 1
}
@@ -249,7 +249,7 @@ function gen_array_test() {
var val_strs = []
i = 0
while (i < n) {
push(val_strs, text(vals[i]))
val_strs[] = text(vals[i])
i = i + 1
}

View File

@@ -98,7 +98,7 @@ function gather_graph(locator, visited) {
arrfor(array(deps), function(alias) {
var dep_locator = deps[alias]
add_node(dep_locator)
push(edges, { from: locator, to: dep_locator, alias: alias })
edges[] = { from: locator, to: dep_locator, alias: alias }
gather_graph(dep_locator, visited)
})
}
@@ -117,7 +117,7 @@ if (show_world) {
packages = shop.list_packages()
arrfor(packages, function(p) {
if (p != 'core') {
push(roots, p)
roots[] = p
}
})
} else {
@@ -128,7 +128,7 @@ if (show_world) {
target_locator = shop.resolve_locator(target_locator)
push(roots, target_locator)
roots[] = target_locator
}
arrfor(roots, function(root) {
@@ -164,7 +164,7 @@ if (format == 'tree') {
children = []
arrfor(edges, function(e) {
if (e.from == locator) {
push(children, e)
children[] = e
}
})
@@ -180,7 +180,7 @@ if (format == 'tree') {
children = []
arrfor(edges, function(e) {
if (e.from == roots[i]) {
push(children, e)
children[] = e
}
})
@@ -230,7 +230,7 @@ if (format == 'tree') {
}
arrfor(array(nodes), function(id) {
push(output.nodes, nodes[id])
output.nodes[] = nodes[id]
})
output.edges = edges

556
http.cm
View File

@@ -1,14 +1,20 @@
var socket = use('socket')
var c_http = use('net/http')
var tls = use('net/tls')
var Blob = use('blob')
def CRLF = "\r\n"
def status_texts = {
"200": "OK", "201": "Created", "204": "No Content",
"301": "Moved Permanently", "302": "Found", "307": "Temporary Redirect",
"400": "Bad Request", "401": "Unauthorized", "403": "Forbidden",
"404": "Not Found", "405": "Method Not Allowed", "500": "Internal Server Error"
}
// ============================================================
// Server (unchanged)
// ============================================================
function serve(port) {
var fd = socket.socket("AF_INET", "SOCK_STREAM")
socket.setsockopt(fd, "SOL_SOCKET", "SO_REUSEADDR", true)
@@ -152,6 +158,10 @@ function sse_close(conn) {
socket.close(conn)
}
// ============================================================
// Blocking client request (kept for compatibility)
// ============================================================
function request(method, url, headers, body) {
var parts = array(url, "/")
var host_port = parts[2]
@@ -221,13 +231,553 @@ function request(method, url, headers, body) {
}
}
// ============================================================
// Requestor-based async fetch
// ============================================================
// parse_url requestor — sync, extract {scheme, host, port, path} from URL
var parse_url = function(callback, value) {
var url = null
var method = "GET"
var req_headers = null
var req_body = null
log.console("value type=" + text(is_text(value)) + " val=" + text(value))
if (is_text(value)) {
url = value
log.console("url after assign=" + text(is_text(url)) + " url=" + text(url))
} else {
url = value.url
if (value.method != null) method = value.method
if (value.headers != null) req_headers = value.headers
if (value.body != null) req_body = value.body
}
// strip scheme
var scheme = "http"
var rest = url
var scheme_end = search(url, "://")
log.console("A: url_type=" + text(is_text(url)) + " scheme_end=" + text(scheme_end))
if (scheme_end != null) {
scheme = lower(text(url, 0, scheme_end))
rest = text(url, scheme_end + 3, length(url))
log.console("B: scheme=" + scheme + " rest=" + rest + " rest_type=" + text(is_text(rest)))
}
// split host from path
var slash = search(rest, "/")
var host_port = rest
var path = "/"
log.console("C: slash=" + text(slash))
if (slash != null) {
host_port = text(rest, 0, slash)
path = text(rest, slash, length(rest))
}
// split host:port
var hp = array(host_port, ":")
var host = hp[0]
var port = null
if (length(hp) > 1) {
port = number(hp[1])
} else {
port = scheme == "https" ? 443 : 80
}
callback({
scheme: scheme, host: host, port: port, path: path,
host_port: host_port, method: method,
req_headers: req_headers, req_body: req_body
})
return null
}
// resolve_dns requestor — blocking getaddrinfo, swappable later
var resolve_dns = function(callback, state) {
var ok = true
var addrs = null
var _resolve = function() {
addrs = socket.getaddrinfo(state.host, text(state.port))
} disruption {
ok = false
}
_resolve()
if (!ok || addrs == null || length(addrs) == 0) {
callback(null, "dns resolution failed for " + state.host)
return null
}
callback(record(state, {address: addrs[0].address}))
return null
}
// open_connection requestor — non-blocking connect + optional TLS
var open_connection = function(callback, state) {
var fd = socket.socket("AF_INET", "SOCK_STREAM")
var cancelled = false
var cancel = function() {
var _close = null
if (!cancelled) {
cancelled = true
_close = function() {
socket.unwatch(fd)
socket.close(fd)
} disruption {}
_close()
}
}
socket.setnonblock(fd)
var finish_connect = function(the_fd) {
var ctx = null
if (state.scheme == "https") {
ctx = tls.wrap(the_fd, state.host)
}
callback(record(state, {fd: the_fd, tls: ctx}))
}
// non-blocking connect — EINPROGRESS is expected
var connect_err = false
var _connect = function() {
socket.connect(fd, {address: state.address, port: state.port})
} disruption {
connect_err = true
}
_connect()
// if connect succeeded immediately (localhost, etc)
var _finish_immediate = null
if (!connect_err && !cancelled) {
_finish_immediate = function() {
finish_connect(fd)
} disruption {
cancel()
callback(null, "connection setup failed")
}
_finish_immediate()
return cancel
}
// wait for connect to complete
socket.on_writable(fd, function() {
if (cancelled) return
var err = socket.getsockopt(fd, "SOL_SOCKET", "SO_ERROR")
if (err != 0) {
cancel()
callback(null, "connect failed (errno " + text(err) + ")")
return
}
var _finish = function() {
finish_connect(fd)
} disruption {
cancel()
callback(null, "connection setup failed")
}
_finish()
})
return cancel
}
// send_request requestor — format + send HTTP/1.1 request
var send_request = function(callback, state) {
var cancelled = false
var cancel = function() {
cancelled = true
}
var _send = function() {
var body_str = ""
var keys = null
var i = 0
if (state.req_body != null) {
if (is_text(state.req_body)) body_str = state.req_body
else body_str = text(state.req_body)
}
var req = state.method + " " + state.path + " HTTP/1.1" + CRLF
req = req + "Host: " + state.host_port + CRLF
req = req + "Connection: close" + CRLF
req = req + "User-Agent: cell/1.0" + CRLF
req = req + "Accept: */*" + CRLF
if (state.req_headers != null) {
keys = array(state.req_headers)
i = 0
while (i < length(keys)) {
req = req + keys[i] + ": " + state.req_headers[keys[i]] + CRLF
i = i + 1
}
}
if (length(body_str) > 0) {
req = req + "Content-Length: " + text(length(body_str)) + CRLF
}
req = req + CRLF + body_str
if (state.tls != null) {
tls.send(state.tls, req)
} else {
socket.send(state.fd, req)
}
} disruption {
if (!cancelled) callback(null, "send request failed")
return cancel
}
_send()
if (!cancelled) callback(state)
return cancel
}
// parse response headers from raw text
function parse_headers(raw) {
var hdr_end = search(raw, CRLF + CRLF)
if (hdr_end == null) return null
var header_text = text(raw, 0, hdr_end)
var lines = array(header_text, CRLF)
var status_parts = array(lines[0], " ")
var status_code = number(status_parts[1])
var headers = {}
var i = 1
var colon = null
while (i < length(lines)) {
colon = search(lines[i], ": ")
if (colon != null) {
headers[lower(text(lines[i], 0, colon))] = text(lines[i], colon + 2)
}
i = i + 1
}
return {
status: status_code, headers: headers,
body_start: hdr_end + 4
}
}
// decode chunked transfer encoding (text version, for async responses)
function decode_chunked(body_text) {
var result = ""
var pos = 0
var chunk_end = null
var chunk_size = null
while (pos < length(body_text)) {
chunk_end = search(text(body_text, pos), CRLF)
if (chunk_end == null) return result
chunk_size = number(text(body_text, pos, pos + chunk_end), 16)
if (chunk_size == null || chunk_size == 0) return result
pos = pos + chunk_end + 2
result = result + text(body_text, pos, pos + chunk_size)
pos = pos + chunk_size + 2
}
return result
}
// decode chunked transfer encoding (blob version, preserves binary data)
function decode_chunked_blob(buf, body_start_bytes) {
var result = Blob()
var pos = body_start_bytes
var total_bytes = length(buf) / 8
var header_end = null
var header_blob = null
var header_text = null
var crlf_pos = null
var chunk_size = null
var chunk_data = null
while (pos < total_bytes) {
header_end = pos + 20
if (header_end > total_bytes) header_end = total_bytes
header_blob = buf.read_blob(pos * 8, header_end * 8)
stone(header_blob)
header_text = text(header_blob)
crlf_pos = search(header_text, CRLF)
if (crlf_pos == null) break
chunk_size = number(text(header_text, 0, crlf_pos), 16)
if (chunk_size == null || chunk_size == 0) break
pos = pos + crlf_pos + 2
chunk_data = buf.read_blob(pos * 8, (pos + chunk_size) * 8)
stone(chunk_data)
result.write_blob(chunk_data)
pos = pos + chunk_size + 2
}
stone(result)
return result
}
// receive_response requestor — async incremental receive
var receive_response = function(callback, state) {
var cancelled = false
var buffer = ""
var parsed = null
var content_length = null
var is_chunked = false
var body_complete = false
var cancel = function() {
var _cleanup = null
if (!cancelled) {
cancelled = true
_cleanup = function() {
if (state.tls != null) {
tls.close(state.tls)
} else {
socket.unwatch(state.fd)
socket.close(state.fd)
}
} disruption {}
_cleanup()
}
}
var finish = function() {
if (cancelled) return
var body_text = text(buffer, parsed.body_start)
if (is_chunked) {
body_text = decode_chunked(body_text)
}
// clean up connection
var _cleanup = function() {
if (state.tls != null) {
tls.close(state.tls)
} else {
socket.close(state.fd)
}
} disruption {}
_cleanup()
callback({
status: parsed.status,
headers: parsed.headers,
body: body_text
})
}
var check_complete = function() {
var te = null
var cl = null
var body_text = null
// still waiting for headers
if (parsed == null) {
parsed = parse_headers(buffer)
if (parsed == null) return false
te = parsed.headers["transfer-encoding"]
if (te != null && search(lower(te), "chunked") != null) {
is_chunked = true
}
cl = parsed.headers["content-length"]
if (cl != null) content_length = number(cl)
}
body_text = text(buffer, parsed.body_start)
if (is_chunked) {
// chunked: look for the terminating 0-length chunk
if (search(body_text, CRLF + "0" + CRLF) != null) return true
if (starts_with(body_text, "0" + CRLF)) return true
return false
}
if (content_length != null) {
return length(body_text) >= content_length
}
// connection: close — we read until EOF (handled by recv returning 0 bytes)
return false
}
var on_data = function() {
if (cancelled) return
var chunk = null
var got_data = false
var eof = false
var _recv = function() {
if (state.tls != null) {
chunk = tls.recv(state.tls, 16384)
} else {
chunk = socket.recv(state.fd, 16384)
}
} disruption {
// recv error — treat as EOF
eof = true
}
_recv()
var chunk_text = null
if (!eof && chunk != null) {
stone(chunk)
chunk_text = text(chunk)
if (length(chunk_text) > 0) {
buffer = buffer + chunk_text
got_data = true
} else {
eof = true
}
}
if (got_data && check_complete()) {
finish()
return
}
if (eof) {
// connection closed — if we have headers, deliver what we have
if (parsed != null || parse_headers(buffer) != null) {
if (parsed == null) parsed = parse_headers(buffer)
finish()
} else {
cancel()
callback(null, "connection closed before headers received")
}
return
}
// re-register for more data (one-shot watches)
if (!cancelled) {
if (state.tls != null) {
tls.on_readable(state.tls, on_data)
} else {
socket.on_readable(state.fd, on_data)
}
}
}
// start reading
if (state.tls != null) {
tls.on_readable(state.tls, on_data)
} else {
socket.on_readable(state.fd, on_data)
}
return cancel
}
// ============================================================
// fetch — synchronous HTTP(S) GET, returns response body (stoned blob)
// ============================================================
var fetch = function(url) {
var scheme = "http"
var rest = url
var scheme_end = search(url, "://")
var slash = null
var host_port = null
var path = "/"
var hp = null
var host = null
var port = null
var fd = null
var ctx = null
var buf = Blob()
var raw_text = null
var hdr_end = null
var header_text = null
var body_start_bits = null
var body = null
var addrs = null
var address = null
var ok = true
var status_line = null
var status_code = null
if (scheme_end != null) {
scheme = lower(text(url, 0, scheme_end))
rest = text(url, scheme_end + 3, length(url))
}
slash = search(rest, "/")
host_port = rest
if (slash != null) {
host_port = text(rest, 0, slash)
path = text(rest, slash, length(rest))
}
hp = array(host_port, ":")
host = hp[0]
port = length(hp) > 1 ? number(hp[1]) : (scheme == "https" ? 443 : 80)
addrs = socket.getaddrinfo(host, text(port))
if (addrs == null || length(addrs) == 0) return null
address = addrs[0].address
fd = socket.socket("AF_INET", "SOCK_STREAM")
var _do = function() {
var req = null
var chunk = null
socket.connect(fd, {address: address, port: port})
if (scheme == "https") ctx = tls.wrap(fd, host)
req = "GET " + path + " HTTP/1.1" + CRLF
req = req + "Host: " + host_port + CRLF
req = req + "Connection: close" + CRLF
req = req + "User-Agent: cell/1.0" + CRLF
req = req + "Accept: */*" + CRLF + CRLF
if (ctx != null) tls.send(ctx, req)
else socket.send(fd, req)
while (true) {
if (ctx != null) chunk = tls.recv(ctx, 16384)
else chunk = socket.recv(fd, 16384)
if (chunk == null) break
stone(chunk)
if (length(chunk) == 0) break
buf.write_blob(chunk)
}
} disruption {
ok = false
}
_do()
var _cleanup = function() {
if (ctx != null) tls.close(ctx)
else socket.close(fd)
} disruption {}
_cleanup()
if (!ok) return null
stone(buf)
raw_text = text(buf)
hdr_end = search(raw_text, CRLF + CRLF)
if (hdr_end == null) return null
header_text = text(raw_text, 0, hdr_end)
status_line = text(header_text, 0, search(header_text, CRLF) || length(header_text))
status_code = number(text(status_line, 9, 12))
if (status_code == null || status_code < 200 || status_code >= 300) {
log.error("fetch: " + status_line)
disrupt
}
if (search(lower(header_text), "transfer-encoding: chunked") != null) {
body = decode_chunked_blob(buf, hdr_end + 4)
return body
}
// Headers are ASCII so char offset = byte offset
body_start_bits = (hdr_end + 4) * 8
body = buf.read_blob(body_start_bits, length(buf))
stone(body)
return body
}
// ============================================================
// fetch_requestor — async requestor pipeline for fetch
// ============================================================
var fetch_requestor = sequence([
parse_url,
resolve_dns,
open_connection,
send_request,
receive_response
])
function close(fd) {
socket.close(fd)
}
return {
// server
serve: serve, accept: accept, on_request: on_request,
respond: respond, request: request,
respond: respond, close: close,
sse_open: sse_open, sse_event: sse_event, sse_close: sse_close,
close: close, fetch: c_http.fetch
// client
fetch: fetch,
fetch_requestor: fetch_requestor,
request: request
}

View File

@@ -109,7 +109,7 @@ function trace_imports(file_path, depth) {
all_packages[imp_pkg] = true
push(all_imports, {
all_imports[] = {
from: file_path,
from_pkg: file_pkg,
module_path: mod_path,
@@ -117,7 +117,7 @@ function trace_imports(file_path, depth) {
package: imp_pkg,
type: imp_type,
depth: depth
})
}
// Recurse into resolved scripts
if (resolved && (ends_with(resolved, '.cm') || ends_with(resolved, '.ce'))) {

View File

@@ -259,7 +259,6 @@ if (_native) {
compile_native_cached(_te.name, core_path + '/' + _te.path)
_ti = _ti + 1
}
os.print("bootstrap: native cache seeded\n")
} else {
// Bytecode path: seed cache with everything engine needs
_targets = [
@@ -276,5 +275,4 @@ if (_native) {
compile_and_cache(_te.name, core_path + '/' + _te.path)
_ti = _ti + 1
}
os.print("bootstrap: cache seeded\n")
}

View File

@@ -14,31 +14,28 @@ static void js_enet_host_finalizer(JSRuntime *rt, JSValue val)
if (host) enet_host_destroy(host);
}
static void js_enet_peer_finalizer(JSRuntime *rt, JSValue val)
static JSClassDef enet_host_def = {
"ENetHost",
.finalizer = js_enet_host_finalizer,
};
static JSClassDef enet_peer_def = {
"ENetPeer",
};
/* Helper: create a JS peer wrapper for an ENetPeer pointer.
Fresh wrapper each time — no caching in peer->data. */
static JSValue peer_wrap(JSContext *ctx, ENetPeer *peer)
{
ENetPeer *peer = JS_GetOpaque(val, enet_peer_class_id);
if (peer && peer->data) {
free(peer->data);
}
JSValue obj = JS_NewObjectClass(ctx, enet_peer_class_id);
if (JS_IsException(obj)) return obj;
JS_SetOpaque(obj, peer);
return obj;
}
// Initialize the ENet library. Must be called before using any ENet functionality.
static JSValue js_enet_initialize(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
if (enet_initialize() != 0) return JS_RaiseDisrupt(ctx, "Error initializing ENet");
return JS_NULL;
}
/* ── Host functions ─────────────────────────────────────────── */
// Deinitialize the ENet library, cleaning up all resources.
static JSValue js_enet_deinitialize(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
enet_deinitialize();
return JS_NULL;
}
// Create an ENet host for either a client-like unbound host or a server bound to a specific
// address and port.
static JSValue js_enet_host_create(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
static JSValue js_enet_create_host(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetHost *host;
ENetAddress address;
@@ -74,7 +71,7 @@ static JSValue js_enet_host_create(JSContext *ctx, JSValueConst this_val, int ar
int err = enet_address_set_host_ip(&address, addr_str);
if (err != 0) {
JS_FreeCString(ctx, addr_str);
return JS_RaiseDisrupt(ctx, "Failed to set host IP from '%s'. Error: %d", addr_str, err);
return JS_RaiseDisrupt(ctx, "Failed to set host IP. Error: %d", err);
}
}
address.port = (enet_uint16)port32;
@@ -103,97 +100,76 @@ wrap:
return obj;
}
// Helper function to get a JSValue for an ENetPeer.
static JSValue peer_get_value(JSContext *ctx, ENetPeer *peer)
/* service(host, callback [, timeout]) */
static JSValue js_enet_service(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
if (!peer->data) {
peer->data = malloc(sizeof(JSValue));
*(JSValue*)peer->data = JS_NewObjectClass(ctx, enet_peer_class_id);
JS_SetOpaque(*(JSValue*)peer->data, peer);
}
return *(JSValue*)peer->data;
}
if (argc < 2) return JS_RaiseDisrupt(ctx, "service: expected (host, callback)");
// Poll for and process any available network events from this host,
// calling the provided callback for each event.
static JSValue js_enet_host_service(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
if (!host) return JS_EXCEPTION;
ENetHost *host = JS_GetOpaque(argv[0], enet_host_id);
if (!host) return JS_RaiseDisrupt(ctx, "service: invalid host");
if (argc < 1 || !JS_IsFunction(argv[0]))
return JS_RaiseDisrupt(ctx, "Expected a callback function as first argument");
if (!JS_IsFunction(argv[1]))
return JS_RaiseDisrupt(ctx, "service: expected callback function");
enet_uint32 timeout_ms = 0;
if (argc >= 2 && !JS_IsNull(argv[1])) {
if (argc >= 3 && !JS_IsNull(argv[2])) {
double secs = 0;
JS_ToFloat64(ctx, &secs, argv[1]);
JS_ToFloat64(ctx, &secs, argv[2]);
if (secs > 0) timeout_ms = (enet_uint32)(secs * 1000.0);
}
JS_FRAME(ctx);
JSGCRef event_ref = { .val = JS_NULL, .prev = NULL };
JS_PushGCRef(ctx, &event_ref);
JS_ROOT(event_obj, JS_NULL);
ENetEvent event;
while (enet_host_service(host, &event, timeout_ms) > 0) {
event_ref.val = JS_NewObject(ctx);
event_obj.val = JS_NewObject(ctx);
JSValue peer_val = peer_get_value(ctx, event.peer);
JS_SetPropertyStr(ctx, event_ref.val, "peer", peer_val);
JSValue peer_val = peer_wrap(ctx, event.peer);
JS_SetPropertyStr(ctx, event_obj.val, "peer", peer_val);
switch (event.type) {
case ENET_EVENT_TYPE_CONNECT: {
JSValue type_str = JS_NewString(ctx, "connect");
JS_SetPropertyStr(ctx, event_ref.val, "type", type_str);
case ENET_EVENT_TYPE_CONNECT:
JS_SetPropertyStr(ctx, event_obj.val, "type", JS_NewString(ctx, "connect"));
break;
}
case ENET_EVENT_TYPE_RECEIVE: {
JSValue type_str = JS_NewString(ctx, "receive");
JS_SetPropertyStr(ctx, event_ref.val, "type", type_str);
JS_SetPropertyStr(ctx, event_ref.val, "channelID", JS_NewInt32(ctx, event.channelID));
case ENET_EVENT_TYPE_RECEIVE:
JS_SetPropertyStr(ctx, event_obj.val, "type", JS_NewString(ctx, "receive"));
JS_SetPropertyStr(ctx, event_obj.val, "channelID", JS_NewInt32(ctx, event.channelID));
if (event.packet->dataLength > 0) {
JSValue data_val = js_new_blob_stoned_copy(ctx, event.packet->data, event.packet->dataLength);
JS_SetPropertyStr(ctx, event_ref.val, "data", data_val);
JS_SetPropertyStr(ctx, event_obj.val, "data", data_val);
}
enet_packet_destroy(event.packet);
break;
}
case ENET_EVENT_TYPE_DISCONNECT: {
JSValue type_str = JS_NewString(ctx, "disconnect");
JS_SetPropertyStr(ctx, event_ref.val, "type", type_str);
case ENET_EVENT_TYPE_DISCONNECT:
JS_SetPropertyStr(ctx, event_obj.val, "type", JS_NewString(ctx, "disconnect"));
break;
}
case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT: {
JSValue type_str = JS_NewString(ctx, "disconnect_timeout");
JS_SetPropertyStr(ctx, event_ref.val, "type", type_str);
case ENET_EVENT_TYPE_DISCONNECT_TIMEOUT:
JS_SetPropertyStr(ctx, event_obj.val, "type", JS_NewString(ctx, "disconnect_timeout"));
break;
}
case ENET_EVENT_TYPE_NONE: {
JSValue type_str = JS_NewString(ctx, "none");
JS_SetPropertyStr(ctx, event_ref.val, "type", type_str);
case ENET_EVENT_TYPE_NONE:
JS_SetPropertyStr(ctx, event_obj.val, "type", JS_NewString(ctx, "none"));
break;
}
}
JS_Call(ctx, argv[0], JS_NULL, 1, &event_ref.val);
JS_Call(ctx, argv[1], JS_NULL, 1, &event_obj.val);
}
JS_RETURN_NULL();
}
// Initiate a connection from this host to a remote server.
static JSValue js_enet_host_connect(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
/* connect(host, address, port) → peer */
static JSValue js_enet_connect(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
if (!host) return JS_EXCEPTION;
if (argc < 3) return JS_RaiseDisrupt(ctx, "connect: expected (host, address, port)");
if (argc < 2) return JS_RaiseDisrupt(ctx, "Expected 2 arguments: hostname, port");
ENetHost *host = JS_GetOpaque(argv[0], enet_host_id);
if (!host) return JS_RaiseDisrupt(ctx, "connect: invalid host");
const char *hostname = JS_ToCString(ctx, argv[0]);
const char *hostname = JS_ToCString(ctx, argv[1]);
if (!hostname) return JS_EXCEPTION;
int port;
JS_ToInt32(ctx, &port, argv[1]);
JS_ToInt32(ctx, &port, argv[2]);
ENetAddress address;
enet_address_set_host(&address, hostname);
@@ -201,43 +177,43 @@ static JSValue js_enet_host_connect(JSContext *ctx, JSValueConst this_val, int a
address.port = port;
ENetPeer *peer = enet_host_connect(host, &address, 2, 0);
if (!peer) return JS_RaiseDisrupt(ctx, "No available peers for initiating an ENet connection");
if (!peer) return JS_RaiseDisrupt(ctx, "No available peers for connection");
return peer_get_value(ctx, peer);
return peer_wrap(ctx, peer);
}
// Flush all pending outgoing packets for this host immediately.
static JSValue js_enet_host_flush(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
/* flush(host) */
static JSValue js_enet_flush(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
if (!host) return JS_EXCEPTION;
if (argc < 1) return JS_RaiseDisrupt(ctx, "flush: expected (host)");
ENetHost *host = JS_GetOpaque(argv[0], enet_host_id);
if (!host) return JS_RaiseDisrupt(ctx, "flush: invalid host");
enet_host_flush(host);
return JS_NULL;
}
// Broadcast a string or blob to all connected peers on channel 0.
static JSValue js_enet_host_broadcast(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
/* broadcast(host, data) */
static JSValue js_enet_broadcast(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetHost *host = JS_GetOpaque(this_val, enet_host_id);
if (!host) return JS_EXCEPTION;
if (argc < 1) return JS_RaiseDisrupt(ctx, "Expected a string or blob to broadcast");
if (argc < 2) return JS_RaiseDisrupt(ctx, "broadcast: expected (host, data)");
ENetHost *host = JS_GetOpaque(argv[0], enet_host_id);
if (!host) return JS_RaiseDisrupt(ctx, "broadcast: invalid host");
const char *data_str = NULL;
size_t data_len = 0;
uint8_t *buf = NULL;
if (JS_IsText(argv[0])) {
data_str = JS_ToCStringLen(ctx, &data_len, argv[0]);
if (JS_IsText(argv[1])) {
data_str = JS_ToCStringLen(ctx, &data_len, argv[1]);
if (!data_str) return JS_EXCEPTION;
} else if (js_is_blob(ctx, argv[0])) {
buf = js_get_blob_data(ctx, &data_len, argv[0]);
} else if (js_is_blob(ctx, argv[1])) {
buf = js_get_blob_data(ctx, &data_len, argv[1]);
if (!buf) return JS_EXCEPTION;
} else {
return JS_RaiseDisrupt(ctx, "broadcast() only accepts a string or blob");
return JS_RaiseDisrupt(ctx, "broadcast: data must be string or blob");
}
ENetPacket *packet = enet_packet_create(data_str ? (const void*)data_str : (const void*)buf, data_len, ENET_PACKET_FLAG_RELIABLE);
ENetPacket *packet = enet_packet_create(data_str ? (const void *)data_str : (const void *)buf, data_len, ENET_PACKET_FLAG_RELIABLE);
if (data_str) JS_FreeCString(ctx, data_str);
if (!packet) return JS_RaiseDisrupt(ctx, "Failed to create ENet packet");
@@ -245,55 +221,51 @@ static JSValue js_enet_host_broadcast(JSContext *ctx, JSValueConst this_val, int
return JS_NULL;
}
// Host property getters
static JSValue js_enet_host_get_port(JSContext *js, JSValueConst self)
/* host_port(host) → number */
static JSValue js_enet_host_port(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetHost *host = JS_GetOpaque(self, enet_host_id);
if (!host) return JS_EXCEPTION;
return JS_NewInt32(js, host->address.port);
if (argc < 1) return JS_RaiseDisrupt(ctx, "host_port: expected (host)");
ENetHost *host = JS_GetOpaque(argv[0], enet_host_id);
if (!host) return JS_RaiseDisrupt(ctx, "host_port: invalid host");
return JS_NewInt32(ctx, host->address.port);
}
static JSValue js_enet_host_get_address(JSContext *js, JSValueConst self)
/* host_address(host) → string */
static JSValue js_enet_host_address(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetHost *me = JS_GetOpaque(self, enet_host_id);
if (!me) return JS_EXCEPTION;
if (argc < 1) return JS_RaiseDisrupt(ctx, "host_address: expected (host)");
ENetHost *host = JS_GetOpaque(argv[0], enet_host_id);
if (!host) return JS_RaiseDisrupt(ctx, "host_address: invalid host");
char ip_str[128];
if (enet_address_get_host_ip(&me->address, ip_str, sizeof(ip_str)) != 0)
if (enet_address_get_host_ip(&host->address, ip_str, sizeof(ip_str)) != 0)
return JS_NULL;
return JS_NewString(js, ip_str);
return JS_NewString(ctx, ip_str);
}
// Peer-level operations
static JSValue js_enet_peer_disconnect(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) return JS_EXCEPTION;
enet_peer_disconnect(peer, 0);
return JS_NULL;
}
/* ── Peer functions ─────────────────────────────────────────── */
static JSValue js_enet_peer_send(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
/* send(peer, data) */
static JSValue js_enet_send(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) return JS_EXCEPTION;
if (argc < 1) return JS_RaiseDisrupt(ctx, "Expected a string or blob to send");
if (argc < 2) return JS_RaiseDisrupt(ctx, "send: expected (peer, data)");
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
if (!peer) return JS_RaiseDisrupt(ctx, "send: invalid peer");
const char *data_str = NULL;
size_t data_len = 0;
uint8_t *buf = NULL;
if (JS_IsText(argv[0])) {
data_str = JS_ToCStringLen(ctx, &data_len, argv[0]);
if (JS_IsText(argv[1])) {
data_str = JS_ToCStringLen(ctx, &data_len, argv[1]);
if (!data_str) return JS_EXCEPTION;
} else if (js_is_blob(ctx, argv[0])) {
buf = js_get_blob_data(ctx, &data_len, argv[0]);
} else if (js_is_blob(ctx, argv[1])) {
buf = js_get_blob_data(ctx, &data_len, argv[1]);
if (!buf) return JS_EXCEPTION;
} else {
return JS_RaiseDisrupt(ctx, "send() only accepts a string or blob");
return JS_RaiseDisrupt(ctx, "send: data must be string or blob");
}
ENetPacket *packet = enet_packet_create(data_str ? (const void*)data_str : (const void*)buf, data_len, ENET_PACKET_FLAG_RELIABLE);
ENetPacket *packet = enet_packet_create(data_str ? (const void *)data_str : (const void *)buf, data_len, ENET_PACKET_FLAG_RELIABLE);
if (data_str) JS_FreeCString(ctx, data_str);
if (!packet) return JS_RaiseDisrupt(ctx, "Failed to create ENet packet");
@@ -301,225 +273,185 @@ static JSValue js_enet_peer_send(JSContext *ctx, JSValueConst this_val, int argc
return JS_NULL;
}
static JSValue js_enet_peer_disconnect_now(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
/* disconnect(peer) */
static JSValue js_enet_disconnect(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) return JS_EXCEPTION;
if (argc < 1) return JS_RaiseDisrupt(ctx, "disconnect: expected (peer)");
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
if (!peer) return JS_RaiseDisrupt(ctx, "disconnect: invalid peer");
enet_peer_disconnect(peer, 0);
return JS_NULL;
}
/* disconnect_now(peer) */
static JSValue js_enet_disconnect_now(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
if (argc < 1) return JS_RaiseDisrupt(ctx, "disconnect_now: expected (peer)");
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
if (!peer) return JS_RaiseDisrupt(ctx, "disconnect_now: invalid peer");
enet_peer_disconnect_now(peer, 0);
return JS_NULL;
}
static JSValue js_enet_peer_disconnect_later(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
/* disconnect_later(peer) */
static JSValue js_enet_disconnect_later(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) return JS_EXCEPTION;
if (argc < 1) return JS_RaiseDisrupt(ctx, "disconnect_later: expected (peer)");
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
if (!peer) return JS_RaiseDisrupt(ctx, "disconnect_later: invalid peer");
enet_peer_disconnect_later(peer, 0);
return JS_NULL;
}
static JSValue js_enet_peer_reset(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
/* reset(peer) */
static JSValue js_enet_reset(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) return JS_EXCEPTION;
if (argc < 1) return JS_RaiseDisrupt(ctx, "reset: expected (peer)");
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
if (!peer) return JS_RaiseDisrupt(ctx, "reset: invalid peer");
enet_peer_reset(peer);
return JS_NULL;
}
static JSValue js_enet_peer_ping(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
/* ping(peer) */
static JSValue js_enet_ping(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) return JS_EXCEPTION;
if (argc < 1) return JS_RaiseDisrupt(ctx, "ping: expected (peer)");
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
if (!peer) return JS_RaiseDisrupt(ctx, "ping: invalid peer");
enet_peer_ping(peer);
return JS_NULL;
}
static JSValue js_enet_peer_throttle_configure(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
/* throttle_configure(peer, interval, acceleration, deceleration) */
static JSValue js_enet_throttle_configure(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) return JS_EXCEPTION;
if (argc < 4) return JS_RaiseDisrupt(ctx, "throttle_configure: expected (peer, interval, accel, decel)");
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
if (!peer) return JS_RaiseDisrupt(ctx, "throttle_configure: invalid peer");
int interval, acceleration, deceleration;
if (argc < 3 || JS_ToInt32(ctx, &interval, argv[0]) || JS_ToInt32(ctx, &acceleration, argv[1]) || JS_ToInt32(ctx, &deceleration, argv[2]))
return JS_RaiseDisrupt(ctx, "Expected 3 int arguments: interval, acceleration, deceleration");
if (JS_ToInt32(ctx, &interval, argv[1]) || JS_ToInt32(ctx, &acceleration, argv[2]) || JS_ToInt32(ctx, &deceleration, argv[3]))
return JS_RaiseDisrupt(ctx, "throttle_configure: expected integer arguments");
enet_peer_throttle_configure(peer, interval, acceleration, deceleration);
return JS_NULL;
}
/* peer_timeout(peer, limit, min, max) */
static JSValue js_enet_peer_timeout(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) return JS_EXCEPTION;
if (argc < 4) return JS_RaiseDisrupt(ctx, "peer_timeout: expected (peer, limit, min, max)");
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
if (!peer) return JS_RaiseDisrupt(ctx, "peer_timeout: invalid peer");
int timeout_limit, timeout_min, timeout_max;
if (argc < 3 || JS_ToInt32(ctx, &timeout_limit, argv[0]) || JS_ToInt32(ctx, &timeout_min, argv[1]) || JS_ToInt32(ctx, &timeout_max, argv[2]))
return JS_RaiseDisrupt(ctx, "Expected 3 integer arguments: timeout_limit, timeout_min, timeout_max");
if (JS_ToInt32(ctx, &timeout_limit, argv[1]) || JS_ToInt32(ctx, &timeout_min, argv[2]) || JS_ToInt32(ctx, &timeout_max, argv[3]))
return JS_RaiseDisrupt(ctx, "peer_timeout: expected integer arguments");
enet_peer_timeout(peer, timeout_limit, timeout_min, timeout_max);
return JS_NULL;
}
// Class definitions
static JSClassDef enet_host = {
"ENetHost",
.finalizer = js_enet_host_finalizer,
};
/* ── Peer property getters ──────────────────────────────────── */
static JSClassDef enet_peer_class = {
"ENetPeer",
.finalizer = js_enet_peer_finalizer,
};
static JSValue js_enet_resolve_hostname(JSContext *js, JSValue self, int argc, JSValue *argv)
{
const char *hostname = JS_ToCString(js, argv[0]);
JS_FreeCString(js, hostname);
return JS_NULL;
#define PEER_GETTER(name, field, convert) \
static JSValue js_enet_##name(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { \
if (argc < 1) return JS_RaiseDisrupt(ctx, #name ": expected (peer)"); \
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id); \
if (!peer) return JS_RaiseDisrupt(ctx, #name ": invalid peer"); \
return convert(ctx, peer->field); \
}
static const JSCFunctionListEntry js_enet_funcs[] = {
JS_CFUNC_DEF("initialize", 0, js_enet_initialize),
JS_CFUNC_DEF("deinitialize", 0, js_enet_deinitialize),
JS_CFUNC_DEF("create_host", 1, js_enet_host_create),
JS_CFUNC_DEF("resolve_hostname", 1, js_enet_resolve_hostname),
};
static inline JSValue _int32(JSContext *ctx, int v) { return JS_NewInt32(ctx, v); }
static inline JSValue _uint32(JSContext *ctx, unsigned int v) { return JS_NewUint32(ctx, v); }
static const JSCFunctionListEntry js_enet_host_funcs[] = {
JS_CFUNC_DEF("service", 2, js_enet_host_service),
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_CFUNC0_DEF("port", js_enet_host_get_port),
JS_CFUNC0_DEF("address", js_enet_host_get_address),
};
PEER_GETTER(peer_rtt, roundTripTime, _int32)
PEER_GETTER(peer_rtt_variance, roundTripTimeVariance, _int32)
PEER_GETTER(peer_last_send_time, lastSendTime, _int32)
PEER_GETTER(peer_last_receive_time, lastReceiveTime, _int32)
PEER_GETTER(peer_mtu, mtu, _int32)
PEER_GETTER(peer_outgoing_data_total, outgoingDataTotal, _int32)
PEER_GETTER(peer_incoming_data_total, incomingDataTotal, _int32)
PEER_GETTER(peer_packet_loss, packetLoss, _int32)
PEER_GETTER(peer_state, state, _int32)
PEER_GETTER(peer_reliable_data_in_transit, reliableDataInTransit, _int32)
// Peer property getters (zero-arg methods)
static JSValue js_enet_peer_get_rtt(JSContext *ctx, JSValueConst this_val)
static JSValue js_enet_peer_incoming_bandwidth(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) return JS_EXCEPTION;
return JS_NewInt32(ctx, peer->roundTripTime);
}
static JSValue js_enet_peer_get_incoming_bandwidth(JSContext *ctx, JSValueConst this_val)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) return JS_EXCEPTION;
if (argc < 1) return JS_RaiseDisrupt(ctx, "peer_incoming_bandwidth: expected (peer)");
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
if (!peer) return JS_RaiseDisrupt(ctx, "peer_incoming_bandwidth: invalid peer");
if (peer->incomingBandwidth == 0) return JS_NewFloat64(ctx, INFINITY);
return JS_NewInt32(ctx, peer->incomingBandwidth);
}
static JSValue js_enet_peer_get_outgoing_bandwidth(JSContext *ctx, JSValueConst this_val)
static JSValue js_enet_peer_outgoing_bandwidth(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) return JS_EXCEPTION;
if (argc < 1) return JS_RaiseDisrupt(ctx, "peer_outgoing_bandwidth: expected (peer)");
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
if (!peer) return JS_RaiseDisrupt(ctx, "peer_outgoing_bandwidth: invalid peer");
if (peer->outgoingBandwidth == 0) return JS_NewFloat64(ctx, INFINITY);
return JS_NewInt32(ctx, peer->outgoingBandwidth);
}
static JSValue js_enet_peer_get_last_send_time(JSContext *ctx, JSValueConst this_val)
static JSValue js_enet_peer_port(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) return JS_EXCEPTION;
return JS_NewInt32(ctx, peer->lastSendTime);
if (argc < 1) return JS_RaiseDisrupt(ctx, "peer_port: expected (peer)");
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
if (!peer) return JS_RaiseDisrupt(ctx, "peer_port: invalid peer");
return JS_NewUint32(ctx, peer->address.port);
}
static JSValue js_enet_peer_get_last_receive_time(JSContext *ctx, JSValueConst this_val)
static JSValue js_enet_peer_address(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) return JS_EXCEPTION;
return JS_NewInt32(ctx, peer->lastReceiveTime);
}
static JSValue js_enet_peer_get_mtu(JSContext *ctx, JSValueConst this_val)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) return JS_NewFloat64(ctx, INFINITY);
return JS_NewInt32(ctx, peer->mtu);
}
static JSValue js_enet_peer_get_outgoing_data_total(JSContext *ctx, JSValueConst this_val)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) return JS_NewFloat64(ctx, INFINITY);
return JS_NewInt32(ctx, peer->outgoingDataTotal);
}
static JSValue js_enet_peer_get_incoming_data_total(JSContext *ctx, JSValueConst this_val)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) return JS_NewFloat64(ctx, INFINITY);
return JS_NewInt32(ctx, peer->incomingDataTotal);
}
static JSValue js_enet_peer_get_rtt_variance(JSContext *ctx, JSValueConst this_val)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) return JS_NewFloat64(ctx, INFINITY);
return JS_NewInt32(ctx, peer->roundTripTimeVariance);
}
static JSValue js_enet_peer_get_packet_loss(JSContext *ctx, JSValueConst this_val)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) return JS_NewFloat64(ctx, INFINITY);
return JS_NewInt32(ctx, peer->packetLoss);
}
static JSValue js_enet_peer_get_state(JSContext *ctx, JSValueConst this_val)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) return JS_NewInt32(ctx, -1);
return JS_NewInt32(ctx, peer->state);
}
static JSValue js_enet_peer_get_reliable_data_in_transit(JSContext *ctx, JSValueConst this_val)
{
ENetPeer *peer = JS_GetOpaque(this_val, enet_peer_class_id);
if (!peer) return JS_NewFloat64(ctx, INFINITY);
return JS_NewInt32(ctx, peer->reliableDataInTransit);
}
static JSValue js_enet_peer_get_port(JSContext *js, JSValueConst self)
{
ENetPeer *peer = JS_GetOpaque(self, enet_peer_class_id);
if (!peer) return JS_EXCEPTION;
return JS_NewUint32(js, peer->address.port);
}
static JSValue js_enet_peer_get_address(JSContext *js, JSValueConst self)
{
ENetPeer *peer = JS_GetOpaque(self, enet_peer_class_id);
if (!peer) return JS_EXCEPTION;
if (argc < 1) return JS_RaiseDisrupt(ctx, "peer_address: expected (peer)");
ENetPeer *peer = JS_GetOpaque(argv[0], enet_peer_class_id);
if (!peer) return JS_RaiseDisrupt(ctx, "peer_address: invalid peer");
char ip_str[128];
if (enet_address_get_host_ip(&peer->address, ip_str, sizeof(ip_str)) != 0)
return JS_NULL;
return JS_NewString(js, ip_str);
return JS_NewString(ctx, ip_str);
}
static const JSCFunctionListEntry js_enet_peer_funcs[] = {
JS_CFUNC_DEF("send", 1, js_enet_peer_send),
JS_CFUNC_DEF("disconnect", 0, js_enet_peer_disconnect),
JS_CFUNC_DEF("disconnect_now", 0, js_enet_peer_disconnect_now),
JS_CFUNC_DEF("disconnect_later", 0, js_enet_peer_disconnect_later),
JS_CFUNC_DEF("reset", 0, js_enet_peer_reset),
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_CFUNC0_DEF("rtt", js_enet_peer_get_rtt),
JS_CFUNC0_DEF("incoming_bandwidth", js_enet_peer_get_incoming_bandwidth),
JS_CFUNC0_DEF("outgoing_bandwidth", js_enet_peer_get_outgoing_bandwidth),
JS_CFUNC0_DEF("last_send_time", js_enet_peer_get_last_send_time),
JS_CFUNC0_DEF("last_receive_time", js_enet_peer_get_last_receive_time),
JS_CFUNC0_DEF("mtu", js_enet_peer_get_mtu),
JS_CFUNC0_DEF("outgoing_data_total", js_enet_peer_get_outgoing_data_total),
JS_CFUNC0_DEF("incoming_data_total", js_enet_peer_get_incoming_data_total),
JS_CFUNC0_DEF("rtt_variance", js_enet_peer_get_rtt_variance),
JS_CFUNC0_DEF("packet_loss", js_enet_peer_get_packet_loss),
JS_CFUNC0_DEF("state", js_enet_peer_get_state),
JS_CFUNC0_DEF("reliable_data_in_transit", js_enet_peer_get_reliable_data_in_transit),
JS_CFUNC0_DEF("port", js_enet_peer_get_port),
JS_CFUNC0_DEF("address", js_enet_peer_get_address),
static JSValue js_enet_resolve_hostname(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv)
{
const char *hostname = JS_ToCString(ctx, argv[0]);
JS_FreeCString(ctx, hostname);
return JS_NULL;
}
/* ── Module export ──────────────────────────────────────────── */
static const JSCFunctionListEntry js_enet_funcs[] = {
/* host */
JS_CFUNC_DEF("create_host", 1, js_enet_create_host),
JS_CFUNC_DEF("service", 2, js_enet_service),
JS_CFUNC_DEF("connect", 3, js_enet_connect),
JS_CFUNC_DEF("flush", 1, js_enet_flush),
JS_CFUNC_DEF("broadcast", 2, js_enet_broadcast),
JS_CFUNC_DEF("host_port", 1, js_enet_host_port),
JS_CFUNC_DEF("host_address", 1, js_enet_host_address),
/* peer */
JS_CFUNC_DEF("send", 2, js_enet_send),
JS_CFUNC_DEF("disconnect", 1, js_enet_disconnect),
JS_CFUNC_DEF("disconnect_now", 1, js_enet_disconnect_now),
JS_CFUNC_DEF("disconnect_later", 1, js_enet_disconnect_later),
JS_CFUNC_DEF("reset", 1, js_enet_reset),
JS_CFUNC_DEF("ping", 1, js_enet_ping),
JS_CFUNC_DEF("throttle_configure", 4, js_enet_throttle_configure),
JS_CFUNC_DEF("peer_timeout", 4, js_enet_peer_timeout),
JS_CFUNC_DEF("peer_address", 1, js_enet_peer_address),
JS_CFUNC_DEF("peer_port", 1, js_enet_peer_port),
JS_CFUNC_DEF("peer_rtt", 1, js_enet_peer_rtt),
JS_CFUNC_DEF("peer_rtt_variance", 1, js_enet_peer_rtt_variance),
JS_CFUNC_DEF("peer_incoming_bandwidth", 1, js_enet_peer_incoming_bandwidth),
JS_CFUNC_DEF("peer_outgoing_bandwidth", 1, js_enet_peer_outgoing_bandwidth),
JS_CFUNC_DEF("peer_last_send_time", 1, js_enet_peer_last_send_time),
JS_CFUNC_DEF("peer_last_receive_time", 1, js_enet_peer_last_receive_time),
JS_CFUNC_DEF("peer_mtu", 1, js_enet_peer_mtu),
JS_CFUNC_DEF("peer_outgoing_data_total", 1, js_enet_peer_outgoing_data_total),
JS_CFUNC_DEF("peer_incoming_data_total", 1, js_enet_peer_incoming_data_total),
JS_CFUNC_DEF("peer_packet_loss", 1, js_enet_peer_packet_loss),
JS_CFUNC_DEF("peer_state", 1, js_enet_peer_state),
JS_CFUNC_DEF("peer_reliable_data_in_transit", 1, js_enet_peer_reliable_data_in_transit),
JS_CFUNC_DEF("resolve_hostname", 1, js_enet_resolve_hostname),
};
JSValue js_core_internal_enet_use(JSContext *ctx)
@@ -529,16 +461,10 @@ JSValue js_core_internal_enet_use(JSContext *ctx)
JS_FRAME(ctx);
JS_NewClassID(&enet_host_id);
JS_NewClass(ctx, enet_host_id, &enet_host);
JS_ROOT(host_proto, JS_NewObject(ctx));
JS_SetPropertyFunctionList(ctx, host_proto.val, js_enet_host_funcs, countof(js_enet_host_funcs));
JS_SetClassProto(ctx, enet_host_id, host_proto.val);
JS_NewClass(ctx, enet_host_id, &enet_host_def);
JS_NewClassID(&enet_peer_class_id);
JS_NewClass(ctx, enet_peer_class_id, &enet_peer_class);
JS_ROOT(peer_proto, JS_NewObject(ctx));
JS_SetPropertyFunctionList(ctx, peer_proto.val, js_enet_peer_funcs, countof(js_enet_peer_funcs));
JS_SetClassProto(ctx, enet_peer_class_id, peer_proto.val);
JS_NewClass(ctx, enet_peer_class_id, &enet_peer_def);
JS_ROOT(export_obj, JS_NewObject(ctx));
JS_SetPropertyFunctionList(ctx, export_obj.val, js_enet_funcs, countof(js_enet_funcs));

View File

@@ -5,6 +5,10 @@ var native_mode = false
var _no_warn = (init != null && init.no_warn) ? true : false
var SYSYM = '__SYSTEM__'
var log = function(name, args) {
}
var _cell = {}
var need_stop = false
@@ -285,7 +289,7 @@ function analyze(src, filename) {
_i = 0
while (_i < length(folded._diagnostics)) {
e = folded._diagnostics[_i]
os.print(`${filename}:${text(e.line)}:${text(e.col)}: ${e.severity}: ${e.message}\n`)
log.warn(`${filename}:${text(e.line)}:${text(e.col)}: ${e.severity}: ${e.message}`)
_i = _i + 1
}
if (_wm) {
@@ -443,8 +447,12 @@ function run_ast_fn(name, ast, env, pkg) {
_has_errors = false
while (_di < length(optimized._diagnostics)) {
_diag = optimized._diagnostics[_di]
os.print(`${_diag.file}:${text(_diag.line)}:${text(_diag.col)}: ${_diag.severity}: ${_diag.message}\n`)
if (_diag.severity == "error") _has_errors = true
if (_diag.severity == "error") {
log.error(`${_diag.file}:${text(_diag.line)}:${text(_diag.col)}: ${_diag.severity}: ${_diag.message}`)
_has_errors = true
} else {
log.warn(`${_diag.file}:${text(_diag.line)}:${text(_diag.col)}: ${_diag.severity}: ${_diag.message}`)
}
_di = _di + 1
}
if (_has_errors) disrupt
@@ -506,8 +514,12 @@ function compile_user_blob(name, ast, pkg) {
_has_errors = false
while (_di < length(optimized._diagnostics)) {
_diag = optimized._diagnostics[_di]
os.print(`${_diag.file}:${text(_diag.line)}:${text(_diag.col)}: ${_diag.severity}: ${_diag.message}\n`)
if (_diag.severity == "error") _has_errors = true
if (_diag.severity == "error") {
log.error(`${_diag.file}:${text(_diag.line)}:${text(_diag.col)}: ${_diag.severity}: ${_diag.message}`)
_has_errors = true
} else {
log.warn(`${_diag.file}:${text(_diag.line)}:${text(_diag.col)}: ${_diag.severity}: ${_diag.message}`)
}
_di = _di + 1
}
if (_has_errors) disrupt
@@ -531,7 +543,7 @@ if (_init != null && _init.native_mode)
if (args != null && (_init == null || !_init.program)) {
_program = args[0]
while (_j < length(args)) {
push(_user_args, args[_j])
_user_args[] = args[_j]
_j = _j + 1
}
if (_init == null) {
@@ -812,11 +824,29 @@ function create_actor(desc) {
var $_ = {}
// Forward-declare actor system variables so closures in $_ can capture them.
// Initialized here; values used at runtime when fully set up.
var time = null
var enet = null
var HEADER = {}
var underlings = {}
var overling = null
var root = null
var receive_fn = null
var greeters = {}
var message_queue = []
var couplings = {}
var peers = {}
var id_address = {}
var peer_queue = {}
var portal = null
var portal_fn = null
var replies = {}
use_cache['core/json'] = json
// Create runtime_env early (empty) -- filled after pronto loads.
// Shop accesses it lazily (in inject_env, called at module-use time, not load time)
// so it sees the filled version.
// Runtime env: passed to package modules via shop's inject_env.
// Requestor functions are added immediately below; actor/log/send added later.
var runtime_env = {}
// Populate core_extras with everything shop (and other core modules) need
@@ -840,6 +870,232 @@ core_extras.ensure_build_dir = ensure_build_dir
core_extras.compile_to_blob = compile_to_blob
core_extras.native_mode = native_mode
// Load pronto early so requestor functions (sequence, parallel, etc.) are
// available to core modules loaded below (http, shop, etc.)
var pronto = use_core('internal/pronto')
var fallback = pronto.fallback
var parallel = pronto.parallel
var race = pronto.race
var sequence = pronto.sequence
core_extras.fallback = fallback
core_extras.parallel = parallel
core_extras.race = race
core_extras.sequence = sequence
// Set actor identity before shop loads so $self is available
if (!_cell.args.id) _cell.id = guid()
else _cell.id = _cell.args.id
$_.self = create_actor({id: _cell.id})
overling = _cell.args.overling_id ? create_actor({id: _cell.args.overling_id}) : null
$_.overling = overling
root = _cell.args.root_id ? create_actor({id: _cell.args.root_id}) : null
if (root == null) root = $_.self
// Define all actor intrinsic functions ($clock, $delay, etc.) before shop loads.
// Closures here capture module-level variables by reference; those variables
// are fully initialized before these functions are ever called at runtime.
$_.clock = function(fn) {
actor_mod.clock(_ => {
fn(time.number())
send_messages()
})
}
$_.delay = function delay(fn, seconds) {
var _seconds = seconds == null ? 0 : seconds
function delay_turn() {
fn()
send_messages()
}
var id = actor_mod.delay(delay_turn, _seconds)
log.connection(`$delay: registered timer id=${text(id)} seconds=${text(_seconds)}`)
return function() { actor_mod.removetimer(id) }
}
$_.stop = function stop(actor) {
if (!actor) {
need_stop = true
return
}
if (!is_actor(actor)) {
log.error('Can only call stop on an actor.')
disrupt
}
if (is_null(underlings[actor[ACTORDATA].id])) {
log.error('Can only call stop on an underling or self.')
disrupt
}
sys_msg(actor, {kind:"stop"})
}
$_.start = function start(cb, program) {
if (!program) return
var id = guid()
var oid = $_.self[ACTORDATA].id
var startup = {
id,
overling_id: oid,
root_id: root ? root[ACTORDATA].id : null,
program,
native_mode: native_mode,
no_warn: _no_warn,
}
greeters[id] = cb
message_queue[] = { startup }
}
$_.receiver = function receiver(fn) {
receive_fn = fn
}
$_.unneeded = function unneeded(fn, seconds) {
actor_mod.unneeded(fn, seconds)
}
$_.couple = function couple(actor) {
if (actor == $_.self) return
couplings[actor[ACTORDATA].id] = true
sys_msg(actor, {kind:'couple', from_id: _cell.id})
log.system(`coupled to ${actor[ACTORDATA].id}`)
}
$_.contact = function(callback, record) {
log.connection(`contact: creating actor for ${record.address}:${text(record.port)}`)
var a = create_actor(record)
log.connection(`contact: actor created, sending contact`)
send(a, record, function(reply) {
var server = null
if (reply && reply.actor_id) {
server = create_actor({id: reply.actor_id, address: record.address, port: record.port})
log.connection(`contact: connected, server id=${reply.actor_id}`)
callback(server)
} else {
log.connection(`contact: connection failed or no reply`)
callback(null)
}
})
}
$_.portal = function(fn, port) {
if (portal) {
log.error(`Already started a portal listening on ${enet.host_port(portal)}`)
disrupt
}
if (!port) {
log.error("Requires a valid port.")
disrupt
}
log.connection(`portal: starting on port ${text(port)}`)
portal = enet.create_host({address: "any", port})
log.connection(`portal: created host=${portal}`)
portal_fn = fn
enet_check()
}
$_.connection = function(callback, actor, config) {
var peer = peers[actor[ACTORDATA].id]
if (peer) {
callback(peer_connection(peer))
return
}
if (actor_mod.mailbox_exist(actor[ACTORDATA].id)) {
callback({type:"local"})
return
}
callback()
}
$_.time_limit = function(requestor, seconds) {
if (!pronto.is_requestor(requestor)) {
log.error('time_limit: first argument must be a requestor')
disrupt
}
if (!is_number(seconds) || seconds <= 0) {
log.error('time_limit: seconds must be a positive number')
disrupt
}
return function time_limit_requestor(callback, value) {
pronto.check_callback(callback, 'time_limit')
var finished = false
var requestor_cancel = null
var timer_cancel = null
function cancel(reason) {
if (finished) return
finished = true
if (timer_cancel) {
timer_cancel()
timer_cancel = null
}
if (requestor_cancel) {
requestor_cancel(reason)
requestor_cancel = null
}
}
function safe_cancel_requestor(reason) {
if (requestor_cancel) {
requestor_cancel(reason)
requestor_cancel = null
}
}
timer_cancel = $_.delay(function() {
if (finished) return
def reason = {
factory: time_limit_requestor,
excuse: 'Timeout.',
evidence: seconds,
message: 'Timeout. ' + text(seconds)
}
safe_cancel_requestor(reason)
finished = true
callback(null, reason)
}, seconds)
function do_request() {
requestor_cancel = requestor(function(val, reason) {
if (finished) return
finished = true
if (timer_cancel) {
timer_cancel()
timer_cancel = null
}
callback(val, reason)
}, value)
} disruption {
cancel('requestor failed')
callback(null, 'requestor failed')
}
do_request()
return function(reason) {
safe_cancel_requestor(reason)
}
}
}
// Make actor intrinsics available to core modules loaded via use_core
core_extras['$self'] = $_.self
core_extras['$overling'] = $_.overling
core_extras['$clock'] = $_.clock
core_extras['$delay'] = $_.delay
core_extras['$start'] = $_.start
core_extras['$stop'] = $_.stop
core_extras['$receiver'] = $_.receiver
core_extras['$contact'] = $_.contact
core_extras['$portal'] = $_.portal
core_extras['$time_limit'] = $_.time_limit
core_extras['$couple'] = $_.couple
core_extras['$unneeded'] = $_.unneeded
core_extras['$connection'] = $_.connection
core_extras['$fd'] = fd
// NOW load shop -- it receives all of the above via env
var shop = use_core('internal/shop')
use_core('build')
@@ -859,7 +1115,7 @@ _summary_resolver = function(path, ctx) {
return summary_fn()
}
var time = use_core('time')
time = use_core('time')
var toml = use_core('toml')
// --- Logging system (full version) ---
@@ -892,7 +1148,11 @@ function build_sink_routing() {
if (!is_array(sink.exclude)) sink.exclude = []
if (is_text(sink.stack)) sink.stack = [sink.stack]
if (!is_array(sink.stack)) sink.stack = []
if (sink.type == "file" && sink.path) ensure_log_dir(sink.path)
if (sink.type == "file" && sink.path) {
ensure_log_dir(sink.path)
if (sink.mode == "overwrite")
fd.slurpwrite(sink.path, stone(_make_blob("")))
}
arrfor(sink.stack, function(ch) {
stack_channels[ch] = true
})
@@ -920,15 +1180,20 @@ function load_log_config() {
sink: {
terminal: {
type: "console",
format: "pretty",
channels: ["*"],
exclude: ["system", "shop", "build"],
format: "clean",
channels: ["console", "error"],
stack: ["error"]
}
}
}
}
build_sink_routing()
var names = array(log_config.sink)
arrfor(names, function(name) {
var sink = log_config.sink[name]
if (sink.type == "file")
os.print("[log] " + name + " -> " + sink.path + "\n")
})
}
function pretty_format(rec) {
@@ -964,6 +1229,25 @@ function bare_format(rec) {
return out
}
function clean_format(rec) {
var ev = is_text(rec.event) ? rec.event : json.encode(rec.event, false)
var out = null
var i = 0
var fr = null
if (rec.channel == "error") {
out = `error: ${ev}\n`
} else {
out = `${ev}\n`
}
if (rec.stack && length(rec.stack) > 0) {
for (i = 0; i < length(rec.stack); i = i + 1) {
fr = rec.stack[i]
out = out + ` at ${fr.fn} (${fr.file}:${text(fr.line)}:${text(fr.col)})\n`
}
}
return out
}
function sink_excluded(sink, channel) {
var excluded = false
if (!sink.exclude || length(sink.exclude) == 0) return false
@@ -975,17 +1259,25 @@ function sink_excluded(sink, channel) {
function dispatch_to_sink(sink, rec) {
var line = null
var st = null
if (sink_excluded(sink, rec.channel)) return
if (sink.type == "console") {
if (sink.format == "json")
os.print(json.encode(rec, false) + "\n")
else if (sink.format == "bare")
os.print(bare_format(rec))
else if (sink.format == "clean")
os.print(clean_format(rec))
else
os.print(pretty_format(rec))
} else if (sink.type == "file") {
line = json.encode(rec, false) + "\n"
fd.slurpappend(sink.path, stone(blob(line)))
if (sink.max_size) {
st = fd.stat(sink.path)
if (st && st.size > sink.max_size)
fd.slurpwrite(sink.path, stone(_make_blob("")))
}
fd.slurpappend(sink.path, stone(_make_blob(line)))
}
}
@@ -1030,12 +1322,6 @@ actor_mod.set_log(log)
// (before the full log was ready) captured the bootstrap function reference.
_log_full = log
var pronto = use_core('pronto')
var fallback = pronto.fallback
var parallel = pronto.parallel
var race = pronto.race
var sequence = pronto.sequence
runtime_env.actor = actor
runtime_env.log = log
runtime_env.send = send
@@ -1048,73 +1334,6 @@ runtime_env.sequence = sequence
// Make runtime functions available to modules loaded via use_core
arrfor(array(runtime_env), function(k) { core_extras[k] = runtime_env[k] })
$_.time_limit = function(requestor, seconds)
{
if (!pronto.is_requestor(requestor)) {
log.error('time_limit: first argument must be a requestor')
disrupt
}
if (!is_number(seconds) || seconds <= 0) {
log.error('time_limit: seconds must be a positive number')
disrupt
}
return function time_limit_requestor(callback, value) {
pronto.check_callback(callback, 'time_limit')
var finished = false
var requestor_cancel = null
var timer_cancel = null
function cancel(reason) {
if (finished) return
finished = true
if (timer_cancel) {
timer_cancel()
timer_cancel = null
}
if (requestor_cancel) {
requestor_cancel(reason)
requestor_cancel = null
}
}
function safe_cancel_requestor(reason) {
if (requestor_cancel) {
requestor_cancel(reason)
requestor_cancel = null
}
}
timer_cancel = $_.delay(function() {
if (finished) return
def reason = make_reason(factory, 'Timeout.', seconds)
safe_cancel_requestor(reason)
finished = true
callback(null, reason)
}, seconds)
function do_request() {
requestor_cancel = requestor(function(val, reason) {
if (finished) return
finished = true
if (timer_cancel) {
timer_cancel()
timer_cancel = null
}
callback(val, reason)
}, value)
} disruption {
cancel('requestor failed')
callback(null, 'requestor failed')
}
do_request()
return function(reason) {
safe_cancel_requestor(reason)
}
}
}
var config = {
ar_timer: 60,
actor_memory:0,
@@ -1161,84 +1380,36 @@ function guid(bits)
return text(guid,'h')
}
var HEADER = {}
// takes a function input value that will eventually be called with the current time in number form.
$_.clock = function(fn) {
actor_mod.clock(_ => {
fn(time.number())
send_messages()
})
}
var underlings = {} // this is more like "all actors that are notified when we die"
var overling = null
var root = null
var receive_fn = null
var greeters = {} // Router functions for when messages are received for a specific actor
var enet = use_core('internal/enet')
var peers = {}
var id_address = {}
var peer_queue = {}
var portal = null
var portal_fn = null
var enet = use_core('enet')
enet = use_core('internal/enet')
function peer_connection(peer) {
return {
latency: peer.rtt(),
latency: enet.peer_rtt(peer),
bandwidth: {
incoming: peer.incoming_bandwidth(),
outgoing: peer.outgoing_bandwidth()
incoming: enet.peer_incoming_bandwidth(peer),
outgoing: enet.peer_outgoing_bandwidth(peer)
},
activity: {
last_sent: peer.last_send_time(),
last_received: peer.last_receive_time()
last_sent: enet.peer_last_send_time(peer),
last_received: enet.peer_last_receive_time(peer)
},
mtu: peer.mtu(),
mtu: enet.peer_mtu(peer),
data: {
incoming_total: peer.incoming_data_total(),
outgoing_total: peer.outgoing_data_total(),
reliable_in_transit: peer.reliable_data_in_transit()
incoming_total: enet.peer_incoming_data_total(peer),
outgoing_total: enet.peer_outgoing_data_total(peer),
reliable_in_transit: enet.peer_reliable_data_in_transit(peer)
},
latency_variance: peer.rtt_variance(),
packet_loss: peer.packet_loss(),
state: peer.state()
latency_variance: enet.peer_rtt_variance(peer),
packet_loss: enet.peer_packet_loss(peer),
state: enet.peer_state(peer)
}
}
// takes a callback function, an actor object, and a configuration record for getting information about the status of a connection to the actor. The configuration record is used to request the sort of information that needs to be communicated. This can include latency, bandwidth, activity, congestion, cost, partitions. The callback is given a record containing the requested information.
$_.connection = function(callback, actor, config) {
var peer = peers[actor[ACTORDATA].id]
if (peer) {
callback(peer_connection(peer))
return
}
if (actor_mod.mailbox_exist(actor[ACTORDATA].id)) {
callback({type:"local"})
return
}
callback()
}
// takes a function input value that will eventually be called with the current time in number form.
$_.portal = function(fn, port) {
if (portal) {
log.error(`Already started a portal listening on ${portal.port()}`)
disrupt
}
if (!port) {
log.error("Requires a valid port.")
disrupt
}
log.system(`starting a portal on port ${port}`)
portal = enet.create_host({address: "any", port})
portal_fn = fn
enet_check()
// Strip ::ffff: prefix from IPv6-mapped IPv4 addresses
function normalize_addr(addr) {
if (starts_with(addr, "::ffff:"))
return text(addr, 7)
return addr
}
function handle_host(e) {
@@ -1246,29 +1417,38 @@ function handle_host(e) {
var data = null
var addr = null
var port = null
var pkey = null
log.connection(`handle_host: event type=${e.type}`)
if (e.type == "connect") {
addr = e.peer.address()
port = e.peer.port()
log.system(`connected a new peer: ${addr}:${port}`)
peers[`${addr}:${port}`] = e.peer
queue = peer_queue[e.peer]
addr = normalize_addr(enet.peer_address(e.peer))
port = enet.peer_port(e.peer)
pkey = addr + ":" + text(port)
log.connection(`handle_host: peer connected ${pkey}`)
peers[pkey] = e.peer
queue = peer_queue[pkey]
if (queue) {
arrfor(queue, (msg, index) => e.peer.send(nota.encode(msg)))
log.system(`sent queue out of queue`)
delete peer_queue[e.peer]
log.connection(`handle_host: flushing ${text(length(queue))} queued messages to ${pkey}`)
arrfor(queue, (msg, index) => enet.send(e.peer, nota.encode(msg)))
delete peer_queue[pkey]
} else {
log.connection(`handle_host: no queued messages for ${pkey}`)
}
} else if (e.type == "disconnect") {
delete peer_queue[e.peer]
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())
addr = normalize_addr(enet.peer_address(e.peer))
port = enet.peer_port(e.peer)
pkey = addr + ":" + text(port)
log.connection(`handle_host: peer disconnected ${pkey}`)
delete peer_queue[pkey]
delete peers[pkey]
} else if (e.type == "receive") {
data = nota.decode(e.data)
if (data.replycc && !data.replycc.address) {
data.replycc[ACTORDATA].address = e.peer.address()
data.replycc[ACTORDATA].port = e.peer.port()
log.connection(`handle_host: received data type=${data.type}`)
if (data.replycc_id && !data.replycc) {
data.replycc = create_actor({id: data.replycc_id, address: normalize_addr(enet.peer_address(e.peer)), port: enet.peer_port(e.peer)})
} else if (data.replycc && !data.replycc.address) {
data.replycc[ACTORDATA].address = normalize_addr(enet.peer_address(e.peer))
data.replycc[ACTORDATA].port = enet.peer_port(e.peer)
}
if (data.data) populate_actor_addresses(data.data, e)
handle_message(data)
@@ -1279,8 +1459,8 @@ function handle_host(e) {
function populate_actor_addresses(obj, e) {
if (!is_object(obj)) return
if (obj[ACTORDATA] && !obj[ACTORDATA].address) {
obj[ACTORDATA].address = e.peer.address()
obj[ACTORDATA].port = e.peer.port()
obj[ACTORDATA].address = normalize_addr(enet.peer_address(e.peer))
obj[ACTORDATA].port = enet.peer_port(e.peer)
}
arrfor(array(obj), function(key, index) {
if (key in obj)
@@ -1288,81 +1468,9 @@ function populate_actor_addresses(obj, e) {
})
}
// takes a callback function, an actor object, and a configuration record for getting information about the status of a connection to the actor. The configuration record is used to request the sort of information that needs to be communicated. This can include latency, bandwidth, activity, congestion, cost, partitions. The callback is given a record containing the requested information.
$_.contact = function(callback, record) {
send(create_actor(record), record, callback)
}
// registers a function that will receive all messages...
$_.receiver = function receiver(fn) {
receive_fn = fn
}
// Holds all messages queued during the current turn.
var message_queue = []
$_.start = function start(cb, program) {
if (!program) return
var id = guid()
var oid = $_.self[ACTORDATA].id
var startup = {
id,
overling_id: oid,
root_id: root ? root[ACTORDATA].id : null,
program,
native_mode: native_mode,
no_warn: _no_warn,
}
greeters[id] = cb
push(message_queue, { startup })
}
// stops an underling or self.
$_.stop = function stop(actor) {
if (!actor) {
need_stop = true
return
}
if (!is_actor(actor)) {
log.error('Can only call stop on an actor.')
disrupt
}
if (is_null(underlings[actor[ACTORDATA].id])) {
log.error('Can only call stop on an underling or self.')
disrupt
}
sys_msg(actor, {kind:"stop"})
}
// schedules the removal of an actor after a specified amount of time.
$_.unneeded = function unneeded(fn, seconds) {
actor_mod.unneeded(fn, seconds)
}
// schedules the invocation of a function after a specified amount of time.
$_.delay = function delay(fn, seconds) {
var _seconds = seconds == null ? 0 : seconds
function delay_turn() {
fn()
send_messages()
}
var id = actor_mod.delay(delay_turn, _seconds)
return function() { actor_mod.removetimer(id) }
}
// causes this actor to stop when another actor stops.
var couplings = {}
$_.couple = function couple(actor) {
if (actor == $_.self) return // can't couple to self
couplings[actor[ACTORDATA].id] = true
sys_msg(actor, {kind:'couple', from_id: _cell.id})
log.system(`coupled to ${actor[ACTORDATA].id}`)
}
function actor_prep(actor, send) {
push(message_queue, {actor,send});
message_queue[] = {actor,send};
}
// Send a message immediately without queuing
@@ -1373,6 +1481,7 @@ function actor_send_immediate(actor, send) {
function actor_send(actor, message) {
var wota_blob = null
var peer = null
var pkey = null
if (actor[HEADER] && !actor[HEADER].replycc) // attempting to respond to a message but sender is not expecting; silently drop
return
@@ -1389,12 +1498,14 @@ function actor_send(actor, message) {
// message to self
if (actor[ACTORDATA].id == _cell.id) {
log.connection(`actor_send: message to self, type=${message.type}`)
if (receive_fn) receive_fn(message.data)
return
}
// message to actor in same flock
if (actor[ACTORDATA].id && actor_mod.mailbox_exist(actor[ACTORDATA].id)) {
log.connection(`actor_send: local mailbox for ${text(actor[ACTORDATA].id, 0, 8)}`)
wota_blob = wota.encode(message)
actor_mod.mailbox_push(actor[ACTORDATA].id, wota_blob)
return
@@ -1406,23 +1517,27 @@ function actor_send(actor, message) {
else
message.type = "contact"
peer = peers[actor[ACTORDATA].address + ":" + actor[ACTORDATA].port]
pkey = actor[ACTORDATA].address + ":" + text(actor[ACTORDATA].port)
log.connection(`actor_send: remote ${pkey} msg.type=${message.type}`)
peer = peers[pkey]
if (!peer) {
if (!portal) {
log.system(`creating a contactor ...`)
log.connection(`actor_send: no portal, creating contactor`)
portal = enet.create_host({address:"any"})
log.system(`allowing contact to port ${portal.port()}`)
log.connection(`actor_send: contactor on port ${text(enet.host_port(portal))}`)
enet_check()
}
log.system(`no peer! connecting to ${actor[ACTORDATA].address}:${actor[ACTORDATA].port}`)
peer = portal.connect(actor[ACTORDATA].address, actor[ACTORDATA].port)
peer_queue.set(peer, [message])
log.connection(`actor_send: no peer for ${pkey}, connecting...`)
peer = enet.connect(portal, actor[ACTORDATA].address, actor[ACTORDATA].port)
log.connection(`actor_send: connect initiated, peer=${peer}, queuing message`)
peer_queue[pkey] = [message]
} else {
peer.send(nota.encode(message))
log.connection(`actor_send: have peer for ${pkey}, sending directly`)
enet.send(peer, nota.encode(message))
}
return
}
log.system(`Unable to send message to actor ${actor[ACTORDATA].id}`)
log.connection(`actor_send: no route for actor id=${actor[ACTORDATA].id} (no address, not local)`)
}
function send_messages() {
@@ -1435,6 +1550,8 @@ function send_messages() {
var _qi = 0
var _qm = null
if (length(message_queue) > 0)
log.connection(`send_messages: processing ${text(length(message_queue))} queued messages`)
while (_qi < length(message_queue)) {
_qm = message_queue[_qi]
if (_qm.startup) {
@@ -1448,8 +1565,6 @@ function send_messages() {
message_queue = []
}
var replies = {}
function send(actor, message, reply) {
var send_msg = null
var target = null
@@ -1498,11 +1613,6 @@ function send(actor, message, reply) {
stone(send)
if (!_cell.args.id) _cell.id = guid()
else _cell.id = _cell.args.id
$_.self = create_actor({id: _cell.id})
// Actor's timeslice for processing a single message
function turn(msg)
{
@@ -1520,12 +1630,6 @@ if (config.actor_memory)
if (config.stack_max)
js.max_stacksize(config.system.stack_max);
overling = _cell.args.overling_id ? create_actor({id: _cell.args.overling_id}) : null
$_.overling = overling
root = _cell.args.root_id ? create_actor({id: _cell.args.root_id}) : null
if (root == null) root = $_.self
if (overling) {
$_.couple(overling)
report_to_overling({type:'greet', actor_id: _cell.id})
@@ -1606,13 +1710,40 @@ function handle_sysym(msg)
function handle_message(msg) {
var letter = null
var fn = null
var conn = null
var pkey = null
var peer = null
var reply_msg = null
log.connection(`handle_message: type=${msg.type}`)
if (msg[SYSYM]) {
handle_sysym(msg[SYSYM])
return
}
if (msg.type == "user") {
if (msg.type == "contact") {
// Remote $contact arrived — create a connection actor for the caller.
// msg.replycc was constructed by handle_host with id + address + port.
conn = msg.replycc
log.connection(`handle_message: contact from ${conn ? conn[ACTORDATA].id : "unknown"}`)
// Reply directly over enet so the client's $contact callback fires
if (conn && msg.reply) {
pkey = conn[ACTORDATA].address + ":" + text(conn[ACTORDATA].port)
peer = peers[pkey]
if (peer) {
reply_msg = {type: "user", data: {type: "connected", actor_id: _cell.id}, return: msg.reply}
log.connection(`handle_message: sending contact reply to ${pkey}`)
enet.send(peer, nota.encode(reply_msg))
}
}
if (portal_fn) {
log.connection(`handle_message: calling portal_fn`)
portal_fn(conn)
}
} else if (msg.type == "user") {
letter = msg.data // what the sender really sent
if (msg.replycc_id) {
msg.replycc = create_actor({id: msg.replycc_id})
@@ -1622,11 +1753,13 @@ function handle_message(msg) {
if (msg.return) {
fn = replies[msg.return]
log.connection(`handle_message: reply callback ${msg.return} fn=${fn ? "yes" : "no"}`)
if (fn) fn(letter)
delete replies[msg.return]
return
}
log.connection(`handle_message: dispatching to receive_fn=${receive_fn ? "yes" : "no"}`)
if (receive_fn) receive_fn(letter)
} else if (msg.type == "stopped") {
handle_actor_disconnect(msg.id)
@@ -1635,8 +1768,12 @@ function handle_message(msg) {
function enet_check()
{
if (portal) portal.service(handle_host)
if (portal) {
log.connection(`enet_check: servicing portal`)
enet.service(portal, handle_host)
} else {
log.connection(`enet_check: no portal`)
}
$_.delay(enet_check, ENETSERVICE);
}

View File

@@ -117,10 +117,10 @@ JSC_CCALL(fd_read,
JSC_SCALL(fd_slurp,
struct stat st;
if (stat(str, &st) != 0)
return JS_RaiseDisrupt(js, "stat failed: %s", strerror(errno));
return JS_RaiseDisrupt(js, "stat failed for %s: %s", str, strerror(errno));
if (!S_ISREG(st.st_mode))
return JS_RaiseDisrupt(js, "path is not a regular file");
return JS_RaiseDisrupt(js, "path %s is not a regular file", str);
size_t size = st.st_size;
if (size == 0)
@@ -636,7 +636,8 @@ static void visit_directory(JSContext *js, JSValue *results, int *result_count,
} else {
strcpy(item_rel, ffd.cFileName);
}
JS_SetPropertyNumber(js, *results, (*result_count)++, JS_NewString(js, item_rel));
JSValue name_str = JS_NewString(js, item_rel);
JS_SetPropertyNumber(js, *results, (*result_count)++, name_str);
if (recurse) {
struct stat st;
@@ -661,7 +662,8 @@ static void visit_directory(JSContext *js, JSValue *results, int *result_count,
} else {
strcpy(item_rel, dir->d_name);
}
JS_SetPropertyNumber(js, *results, (*result_count)++, JS_NewString(js, item_rel));
JSValue name_str = JS_NewString(js, item_rel);
JS_SetPropertyNumber(js, *results, (*result_count)++, name_str);
if (recurse) {
struct stat st;
@@ -761,6 +763,22 @@ JSC_CCALL(fd_readlink,
#endif
)
JSC_CCALL(fd_on_readable,
int fd = js2fd(js, argv[0]);
if (fd < 0) return JS_EXCEPTION;
if (!JS_IsFunction(argv[1]))
return JS_RaiseDisrupt(js, "on_readable: callback must be a function");
actor_watch_readable(js, fd, argv[1]);
return JS_NULL;
)
JSC_CCALL(fd_unwatch,
int fd = js2fd(js, argv[0]);
if (fd < 0) return JS_EXCEPTION;
actor_unwatch(js, fd);
return JS_NULL;
)
static const JSCFunctionListEntry js_fd_funcs[] = {
MIST_FUNC_DEF(fd, open, 2),
MIST_FUNC_DEF(fd, write, 2),
@@ -787,6 +805,8 @@ static const JSCFunctionListEntry js_fd_funcs[] = {
MIST_FUNC_DEF(fd, symlink, 2),
MIST_FUNC_DEF(fd, realpath, 1),
MIST_FUNC_DEF(fd, readlink, 1),
MIST_FUNC_DEF(fd, on_readable, 2),
MIST_FUNC_DEF(fd, unwatch, 1),
};
JSValue js_core_internal_fd_use(JSContext *js) {

View File

@@ -703,6 +703,27 @@ static JSValue js_os_stack(JSContext *js, JSValue self, int argc, JSValue *argv)
JS_RETURN(arr.val);
}
static JSValue js_os_unstone(JSContext *js, JSValue self, int argc, JSValue *argv) {
if (argc < 1) return JS_NULL;
JSValue obj = argv[0];
if (mist_is_blob(obj)) {
JSBlob *bd = (JSBlob *)chase(obj);
bd->mist_hdr = objhdr_set_s(bd->mist_hdr, false);
return obj;
}
if (JS_IsArray(obj)) {
JSArray *arr = JS_VALUE_GET_ARRAY(obj);
arr->mist_hdr = objhdr_set_s(arr->mist_hdr, false);
return obj;
}
if (mist_is_gc_object(obj)) {
JSRecord *rec = JS_VALUE_GET_RECORD(obj);
rec->mist_hdr = objhdr_set_s(rec->mist_hdr, false);
return obj;
}
return JS_NULL;
}
static const JSCFunctionListEntry js_os_funcs[] = {
MIST_FUNC_DEF(os, platform, 0),
MIST_FUNC_DEF(os, arch, 0),
@@ -731,6 +752,7 @@ static const JSCFunctionListEntry js_os_funcs[] = {
MIST_FUNC_DEF(os, getenv, 1),
MIST_FUNC_DEF(os, qbe, 1),
MIST_FUNC_DEF(os, stack, 1),
MIST_FUNC_DEF(os, unstone, 1),
};
JSValue js_core_internal_os_use(JSContext *js) {

View File

@@ -455,6 +455,7 @@ Shop.extract_commit_hash = function(pkg, response) {
var open_dls = {}
var package_dylibs = {} // pkg -> [{file, symbol, dylib}, ...]
var reload_hashes = {} // cache_key -> content hash for reload change detection
function open_dylib_cached(path) {
var handle = open_dls[path]
@@ -1020,19 +1021,41 @@ function ensure_package_dylibs(pkg) {
var build_mod = use_cache['core/build']
var target = null
var c_files = null
var _all_ok = true
var _ri = 0
if (build_mod) {
target = detect_host_target()
if (!target) return null
c_files = pkg_tools.get_c_files(_pkg, target, true)
if (!c_files || length(c_files) == 0) {
package_dylibs[_pkg] = []
return []
// Fast path: if manifest exists and all dylibs are present, skip build_dynamic
results = read_dylib_manifest(_pkg)
if (results != null) {
_all_ok = true
_ri = 0
while (_ri < length(results)) {
if (results[_ri].dylib && !fd.is_file(results[_ri].dylib)) {
_all_ok = false
break
}
_ri = _ri + 1
}
if (_all_ok) {
log.shop('manifest ok for ' + _pkg + ' (' + text(length(results)) + ' modules)')
} else {
results = null
}
}
if (results == null) {
target = detect_host_target()
if (!target) return null
log.shop('ensuring C modules for ' + _pkg)
results = build_mod.build_dynamic(_pkg, target, 'release', {})
c_files = pkg_tools.get_c_files(_pkg, target, true)
if (!c_files || length(c_files) == 0) {
package_dylibs[_pkg] = []
return []
}
log.shop('ensuring C modules for ' + _pkg)
results = build_mod.build_dynamic(_pkg, target, 'release', {})
}
} else {
// No build module at runtime — read manifest from cell build
results = read_dylib_manifest(_pkg)
@@ -1476,18 +1499,18 @@ Shop.use = function use(path, _pkg_ctx) {
if (use_cache[info.cache_key])
return use_cache[info.cache_key]
push(use_stack, _use_entry)
use_stack[] = _use_entry
var _use_result = null
var _use_ok = false
var _load = function() {
_use_result = execute_module(info)
_use_ok = true
} disruption {
pop(use_stack)
use_stack[]
disrupt
}
_load()
pop(use_stack)
use_stack[]
use_cache[info.cache_key] = _use_result
return _use_result
}
@@ -1621,12 +1644,16 @@ function download_zip(pkg, commit_hash) {
return _download()
}
// Get zip from cache, returns null if not cached
// Get zip from cache, returns null if not cached or empty
function get_cached_zip(pkg, commit_hash) {
var cache_path = get_cache_path(pkg, commit_hash)
if (fd.is_file(cache_path))
return fd.slurp(cache_path)
var data = null
if (fd.is_file(cache_path)) {
data = fd.slurp(cache_path)
stone(data)
if (length(data) > 0) return data
fd.remove(cache_path)
}
return null
}
@@ -1882,6 +1909,7 @@ Shop.sync_with_deps = function(pkg, opts) {
if (visited[current]) continue
visited[current] = true
log.build(' Fetching ' + current + '...')
Shop.sync(current, opts)
_read_deps = function() {
@@ -1895,7 +1923,7 @@ Shop.sync_with_deps = function(pkg, opts) {
arrfor(array(deps), function(alias) {
dep_locator = deps[alias]
if (!visited[dep_locator])
push(queue, dep_locator)
queue[] = dep_locator
})
}
}
@@ -1981,19 +2009,27 @@ Shop.file_reload = function(file)
}
Shop.module_reload = function(path, package) {
if (!Shop.is_loaded(path,package)) return
if (!Shop.is_loaded(path, package)) return false
// Clear the module info cache for this path
var lookup_key = package ? package + ':' + path : ':' + path
module_info_cache[lookup_key] = null
var info = resolve_module_info(path, package)
if (!info) return false
// Invalidate package dylib cache so next resolve triggers rebuild
if (package) {
package_dylibs[package] = null
// Check if source actually changed
var mod_path = null
var source = null
var new_hash = null
if (info.mod_resolve) mod_path = info.mod_resolve.path
if (mod_path && fd.is_file(mod_path)) {
source = fd.slurp(mod_path)
new_hash = content_hash(stone(blob(text(source))))
if (reload_hashes[info.cache_key] == new_hash) return false
reload_hashes[info.cache_key] = new_hash
}
var info = resolve_module_info(path, package)
if (!info) return
// Clear caches
module_info_cache[lookup_key] = null
if (package) package_dylibs[package] = null
var cache_key = info.cache_key
var old = use_cache[cache_key]
@@ -2002,13 +2038,18 @@ Shop.module_reload = function(path, package) {
var newmod = get_module(path, package)
use_cache[cache_key] = newmod
// Smart update: unstone -> merge -> re-stone to preserve references
if (old && is_object(old) && is_object(newmod)) {
os.unstone(old)
arrfor(array(newmod), function(k) { old[k] = newmod[k] })
arrfor(array(old), function(k) {
if (!(k in newmod)) old[k] = null
})
stone(old)
use_cache[cache_key] = old
}
return true
}
function get_package_scripts(package)
@@ -2021,7 +2062,7 @@ function get_package_scripts(package)
for (i = 0; i < length(files); i++) {
file = files[i]
if (ends_with(file, '.cm') || ends_with(file, '.ce')) {
push(scripts, file)
scripts[] = file
}
}
@@ -2043,7 +2084,7 @@ function extract_use_calls(source) {
if (end == null) end = search(text(source, start), '"')
if (end != null) {
arg = text(source, start, start + end)
push(uses, arg)
uses[] = arg
}
idx = search(text(source, idx + 4), "use(")
if (idx != null) idx = idx + (source.length - (source.length - idx))
@@ -2066,12 +2107,18 @@ Shop.build_package_scripts = function(package)
resolve_mod_fn(pkg_dir + '/' + script, package)
ok = ok + 1
} disruption {
push(errors, script)
log.console(" compile error: " + package + '/' + script)
errors[] = script
log.build(" compile error: " + package + '/' + script)
}
_try()
})
if (length(errors) > 0) {
log.build(' Compiling scripts (' + text(ok) + ' ok, ' + text(length(errors)) + ' errors)')
} else if (ok > 0) {
log.build(' Compiling scripts (' + text(ok) + ' ok)')
}
return {ok: ok, errors: errors, total: length(scripts)}
}
@@ -2113,14 +2160,14 @@ Shop.audit_use_resolution = function(package) {
end = search(rest, quote)
if (end == null) continue
arg = text(rest, 0, end)
if (length(arg) > 0) push(uses, arg)
if (length(arg) > 0) uses[] = arg
rest = text(rest, end + 1)
}
arrfor(uses, function(mod) {
var _resolve = function() {
info = resolve_module_info(mod, package)
if (!info) push(unresolved, {script: script, module: mod})
if (!info) unresolved[] = {script: script, module: mod}
} disruption {}
_resolve()
})
@@ -2371,7 +2418,7 @@ Shop.audit_packages = function() {
if (package == 'core') return
if (fd.is_dir(package)) return
if (fetch_remote_hash(package)) return
push(bad, package)
bad[] = package
})
return bad

View File

@@ -208,11 +208,11 @@ Link.sync_all = function(shop) {
// Validate target exists
var link_target = resolve_link_target(target)
if (!fd.is_dir(link_target)) {
push(errors, canonical + ': target ' + link_target + ' does not exist')
errors[] = canonical + ': target ' + link_target + ' does not exist'
return
}
if (!fd.is_file(link_target + '/cell.toml')) {
push(errors, canonical + ': target ' + link_target + ' is not a valid package')
errors[] = canonical + ': target ' + link_target + ' is not a valid package'
return
}
@@ -246,7 +246,7 @@ Link.sync_all = function(shop) {
count = count + 1
} disruption {
push(errors, canonical + ': sync failed')
errors[] = canonical + ': sync failed'
}
_sync()
})

14
list.ce
View File

@@ -89,16 +89,16 @@ var run = function() {
// Add status indicators
status = []
if (link_target) {
push(status, "linked -> " + link_target)
status[] = "linked -> " + link_target
}
if (lock_entry && lock_entry.commit) {
push(status, "@" + text(lock_entry.commit, 0, 8))
status[] = "@" + text(lock_entry.commit, 0, 8)
}
if (lock_entry && lock_entry.type == 'local') {
push(status, "local")
status[] = "local"
}
if (!lock_entry) {
push(status, "not installed")
status[] = "not installed"
}
if (length(status) > 0) {
@@ -136,11 +136,11 @@ if (mode == 'local') {
var link_target = links[p]
if (link_target) {
push(linked_pkgs, p)
linked_pkgs[] = p
} else if (lock_entry && lock_entry.type == 'local') {
push(local_pkgs, p)
local_pkgs[] = p
} else {
push(remote_pkgs, p)
remote_pkgs[] = p
}
})

496
log.ce
View File

@@ -1,15 +1,17 @@
// cell log - Manage and read log sinks
// cell log - Manage log sink configuration
//
// Usage:
// cell log list List configured sinks
// cell log list Show sinks and channel routing
// cell log channels List channels with status
// cell log enable <channel> Enable a channel on terminal
// cell log disable <channel> Disable a channel on terminal
// cell log add <name> console [opts] Add a console sink
// cell log add <name> file <path> [opts] Add a file sink
// cell log remove <name> Remove a sink
// cell log read <sink> [opts] Read from a file sink
// cell log tail <sink> [--lines=N] Follow a file sink
//
// The --stack option controls which channels capture a stack trace.
// Default: --stack=error (errors always show a stack trace).
// cell log route <channel> <sink> Route a channel to a sink
// cell log unroute <channel> <sink> Remove a channel from a sink
// cell log stack <channel> Enable stack traces on a channel
// cell log unstack <channel> Disable stack traces on a channel
var toml = use('toml')
var fd = use('fd')
@@ -18,9 +20,8 @@ var json = use('json')
var log_path = shop_path + '/log.toml'
function load_config() {
if (fd.is_file(log_path)) {
if (fd.is_file(log_path))
return toml.decode(text(fd.slurp(log_path)))
}
return null
}
@@ -45,23 +46,24 @@ function print_help() {
log.console("Usage: cell log <command> [options]")
log.console("")
log.console("Commands:")
log.console(" list List configured sinks")
log.console(" list Show sinks and channel routing")
log.console(" channels List channels with status")
log.console(" enable <channel> Enable a channel on terminal")
log.console(" disable <channel> Disable a channel on terminal")
log.console(" add <name> console [opts] Add a console sink")
log.console(" add <name> file <path> [opts] Add a file sink")
log.console(" remove <name> Remove a sink")
log.console(" read <sink> [opts] Read from a file sink")
log.console(" tail <sink> [--lines=N] Follow a file sink")
log.console(" route <channel> <sink> Route a channel to a sink")
log.console(" unroute <channel> <sink> Remove a channel from a sink")
log.console(" stack <channel> Enable stack traces on a channel")
log.console(" unstack <channel> Disable stack traces on a channel")
log.console("")
log.console("Options for add:")
log.console(" --format=pretty|bare|json Output format (default: pretty for console, json for file)")
log.console(" --channels=ch1,ch2 Channels to subscribe (default: console,error,system)")
log.console(" --exclude=ch1,ch2 Channels to exclude (for wildcard sinks)")
log.console(" --stack=ch1,ch2 Channels that capture a stack trace (default: error)")
log.console("")
log.console("Options for read:")
log.console(" --lines=N Show last N lines (default: all)")
log.console(" --channel=X Filter by channel")
log.console(" --since=timestamp Only show entries after timestamp")
log.console(" --channels=ch1,ch2 Channels to subscribe (default: *)")
log.console(" --exclude=ch1,ch2 Channels to exclude")
log.console(" --mode=append|overwrite File write mode (default: append)")
log.console(" --max_size=N Max file size in bytes before truncation")
}
function parse_opt(arg, prefix) {
@@ -71,36 +73,85 @@ function parse_opt(arg, prefix) {
return null
}
function format_entry(entry) {
var aid = text(entry.actor_id, 0, 5)
var src = ""
var ev = null
if (entry.source && entry.source.file)
src = entry.source.file + ":" + text(entry.source.line)
ev = is_text(entry.event) ? entry.event : json.encode(entry.event)
return "[" + aid + "] [" + entry.channel + "] " + src + " " + ev
// Collect all stack channels across all sinks
function collect_stack_channels(config) {
var stack_chs = {}
var names = array(config.sink)
arrfor(names, function(n) {
var s = config.sink[n]
if (is_array(s.stack)) {
arrfor(s.stack, function(ch) { stack_chs[ch] = true })
}
})
return stack_chs
}
// Find which sinks a stack channel is declared on (for modification)
function find_stack_sink(config, channel) {
var names = array(config.sink)
var found = null
arrfor(names, function(n) {
if (found) return
var s = config.sink[n]
if (is_array(s.stack)) {
arrfor(s.stack, function(ch) {
if (ch == channel) found = n
})
}
})
return found
}
function do_list() {
var config = load_config()
var names = null
var channel_routing = {}
var stack_chs = null
names = (config && config.sink) ? array(config.sink) : []
if (length(names) == 0) {
log.console("No log sinks configured.")
log.console("Default: console pretty for console/error/system (stack traces on error)")
return
}
// Show sinks
log.console("Sinks:")
arrfor(names, function(n) {
var s = config.sink[n]
var ch = is_array(s.channels) ? text(s.channels, ', ') : '(none)'
var ex = is_array(s.exclude) ? " exclude=" + text(s.exclude, ',') : ""
var stk = is_array(s.stack) ? " stack=" + text(s.stack, ',') : ""
var fmt = s.format || (s.type == 'file' ? 'json' : 'pretty')
var mode = s.mode ? " mode=" + s.mode : ""
var maxsz = s.max_size ? " max_size=" + text(s.max_size) : ""
var ex = is_array(s.exclude) ? " exclude=" + text(s.exclude, ',') : ""
if (s.type == 'file')
log.console(" " + n + ": " + s.type + " -> " + s.path + " [" + ch + "] format=" + fmt + ex + stk)
log.console(" " + n + ": file -> " + s.path + " format=" + fmt + mode + maxsz)
else
log.console(" " + n + ": " + s.type + " [" + ch + "] format=" + fmt + ex + stk)
log.console(" " + n + ": console format=" + fmt + ex)
})
// Build channel -> sinks map
arrfor(names, function(n) {
var s = config.sink[n]
var chs = is_array(s.channels) ? s.channels : []
arrfor(chs, function(ch) {
if (!channel_routing[ch]) channel_routing[ch] = []
channel_routing[ch][] = n
})
})
// Show routing
log.console("")
log.console("Routing:")
var channels = array(channel_routing)
arrfor(channels, function(ch) {
log.console(" " + ch + " -> " + text(channel_routing[ch], ', '))
})
// Show stack traces
stack_chs = collect_stack_channels(config)
var stack_list = array(stack_chs)
if (length(stack_list) > 0) {
log.console("")
log.console("Stack traces on: " + text(stack_list, ', '))
}
}
function do_add() {
@@ -108,14 +159,15 @@ function do_add() {
var sink_type = null
var path = null
var format = null
var channels = ["console", "error", "system"]
var channels = ["*"]
var exclude = null
var stack_chs = ["error"]
var mode = null
var max_size = null
var config = null
var val = null
var i = 0
if (length(args) < 3) {
log.error("Usage: cell log add <name> console|file [path] [options]")
log.console("Usage: cell log add <name> console|file [path] [options]")
return
}
name = args[1]
@@ -123,7 +175,7 @@ function do_add() {
if (sink_type == 'file') {
if (length(args) < 4) {
log.error("Usage: cell log add <name> file <path> [options]")
log.console("Usage: cell log add <name> file <path> [options]")
return
}
path = args[3]
@@ -133,7 +185,7 @@ function do_add() {
format = "pretty"
i = 3
} else {
log.error("Unknown sink type: " + sink_type + " (use 'console' or 'file')")
log.console("Unknown sink type: " + sink_type + " (use 'console' or 'file')")
return
}
@@ -144,17 +196,21 @@ function do_add() {
if (val) { channels = array(val, ','); continue }
val = parse_opt(args[i], 'exclude')
if (val) { exclude = array(val, ','); continue }
val = parse_opt(args[i], 'stack')
if (val) { stack_chs = array(val, ','); continue }
val = parse_opt(args[i], 'mode')
if (val) { mode = val; continue }
val = parse_opt(args[i], 'max_size')
if (val) { max_size = number(val); continue }
}
config = load_config()
if (!config) config = {}
if (!config.sink) config.sink = {}
config.sink[name] = {type: sink_type, format: format, channels: channels, stack: stack_chs}
config.sink[name] = {type: sink_type, format: format, channels: channels}
if (path) config.sink[name].path = path
if (exclude) config.sink[name].exclude = exclude
if (mode) config.sink[name].mode = mode
if (max_size) config.sink[name].max_size = max_size
save_config(config)
log.console("Added sink: " + name)
@@ -164,13 +220,13 @@ function do_remove() {
var name = null
var config = null
if (length(args) < 2) {
log.error("Usage: cell log remove <name>")
log.console("Usage: cell log remove <name>")
return
}
name = args[1]
config = load_config()
if (!config || !config.sink || !config.sink[name]) {
log.error("Sink not found: " + name)
log.console("Sink not found: " + name)
return
}
delete config.sink[name]
@@ -178,154 +234,244 @@ function do_remove() {
log.console("Removed sink: " + name)
}
function do_read() {
var name = null
var max_lines = 0
var filter_channel = null
var since = 0
function do_route() {
var channel = null
var sink_name = null
var config = null
var sink = null
var content = null
var lines = null
var entries = []
var entry = null
var val = null
var i = 0
if (length(args) < 2) {
log.error("Usage: cell log read <sink_name> [options]")
var already = false
if (length(args) < 3) {
log.console("Usage: cell log route <channel> <sink>")
return
}
name = args[1]
for (i = 2; i < length(args); i++) {
val = parse_opt(args[i], 'lines')
if (val) { max_lines = number(val); continue }
val = parse_opt(args[i], 'channel')
if (val) { filter_channel = val; continue }
val = parse_opt(args[i], 'since')
if (val) { since = number(val); continue }
}
channel = args[1]
sink_name = args[2]
config = load_config()
if (!config || !config.sink || !config.sink[name]) {
log.error("Sink not found: " + name)
if (!config || !config.sink || !config.sink[sink_name]) {
log.console("Sink not found: " + sink_name)
return
}
sink = config.sink[name]
if (sink.type != 'file') {
log.error("Can only read from file sinks")
return
}
if (!fd.is_file(sink.path)) {
log.console("Log file does not exist yet: " + sink.path)
return
}
content = text(fd.slurp(sink.path))
lines = array(content, '\n')
arrfor(lines, function(line) {
var parse_fn = null
if (length(line) == 0) return
parse_fn = function() {
entry = json.decode(line)
} disruption {
entry = null
}
parse_fn()
if (!entry) return
if (filter_channel && entry.channel != filter_channel) return
if (since > 0 && entry.timestamp < since) return
entries[] = entry
})
if (max_lines > 0 && length(entries) > max_lines)
entries = array(entries, length(entries) - max_lines, length(entries))
arrfor(entries, function(e) {
log.console(format_entry(e))
sink = config.sink[sink_name]
if (!is_array(sink.channels)) sink.channels = []
arrfor(sink.channels, function(ch) {
if (ch == channel) already = true
})
if (already) {
log.console(channel + " already routed to " + sink_name)
return
}
sink.channels[] = channel
save_config(config)
log.console(channel + " -> " + sink_name)
}
function do_tail() {
var name = null
var tail_lines = 10
function do_unroute() {
var channel = null
var sink_name = null
var config = null
var sink = null
var last_size = 0
var val = null
var i = 0
if (length(args) < 2) {
log.error("Usage: cell log tail <sink_name> [--lines=N]")
var found = false
if (length(args) < 3) {
log.console("Usage: cell log unroute <channel> <sink>")
return
}
name = args[1]
for (i = 2; i < length(args); i++) {
val = parse_opt(args[i], 'lines')
if (val) { tail_lines = number(val); continue }
}
channel = args[1]
sink_name = args[2]
config = load_config()
if (!config || !config.sink || !config.sink[name]) {
log.error("Sink not found: " + name)
if (!config || !config.sink || !config.sink[sink_name]) {
log.console("Sink not found: " + sink_name)
return
}
sink = config.sink[name]
if (sink.type != 'file') {
log.error("Can only tail file sinks")
sink = config.sink[sink_name]
if (!is_array(sink.channels)) sink.channels = []
sink.channels = filter(sink.channels, function(ch) { return ch != channel })
save_config(config)
log.console(channel + " removed from " + sink_name)
}
function do_stack() {
var channel = null
var config = null
var names = null
var added = false
if (length(args) < 2) {
log.console("Usage: cell log stack <channel>")
return
}
if (!fd.is_file(sink.path))
log.console("Waiting for log file: " + sink.path)
function poll() {
var st = null
var poll_content = null
var poll_lines = null
var start = 0
var poll_entry = null
var old_line_count = 0
var idx = 0
var parse_fn = null
if (!fd.is_file(sink.path)) {
$delay(poll, 1)
return
channel = args[1]
config = load_config()
if (!config || !config.sink) {
log.console("No sinks configured")
return
}
// Add to first sink that already has a stack array, or first sink overall
names = array(config.sink)
arrfor(names, function(n) {
var s = config.sink[n]
var already = false
if (added) return
if (is_array(s.stack)) {
arrfor(s.stack, function(ch) { if (ch == channel) already = true })
if (!already) s.stack[] = channel
added = true
}
st = fd.stat(sink.path)
if (st.size == last_size) {
$delay(poll, 1)
return
})
if (!added && length(names) > 0) {
config.sink[names[0]].stack = [channel]
added = true
}
if (added) {
save_config(config)
log.console("Stack traces enabled on: " + channel)
}
}
function do_unstack() {
var channel = null
var config = null
var names = null
if (length(args) < 2) {
log.console("Usage: cell log unstack <channel>")
return
}
channel = args[1]
config = load_config()
if (!config || !config.sink) {
log.console("No sinks configured")
return
}
names = array(config.sink)
arrfor(names, function(n) {
var s = config.sink[n]
if (is_array(s.stack))
s.stack = filter(s.stack, function(ch) { return ch != channel })
})
save_config(config)
log.console("Stack traces disabled on: " + channel)
}
var known_channels = ["console", "error", "warn", "system", "build", "shop", "compile", "test"]
function find_terminal_sink(config) {
var names = null
var found = null
if (!config || !config.sink) return null
names = array(config.sink)
if (config.sink.terminal) return config.sink.terminal
arrfor(names, function(n) {
if (!found && config.sink[n].type == "console")
found = config.sink[n]
})
return found
}
function do_enable() {
var channel = null
var config = null
var sink = null
var i = 0
var already = false
var new_exclude = []
if (length(args) < 2) {
log.error("Usage: cell log enable <channel>")
return
}
channel = args[1]
config = load_config()
if (!config) config = {sink: {}}
if (!config.sink) config.sink = {}
sink = find_terminal_sink(config)
if (!sink) {
config.sink.terminal = {type: "console", format: "clean", channels: ["console", "error", channel], stack: ["error"]}
save_config(config)
log.console("Enabled channel: " + channel)
return
}
if (is_array(sink.channels) && length(sink.channels) == 1 && sink.channels[0] == "*") {
if (is_array(sink.exclude)) {
new_exclude = []
arrfor(sink.exclude, function(ex) {
if (ex != channel) new_exclude[] = ex
})
sink.exclude = new_exclude
}
} else {
if (!is_array(sink.channels)) sink.channels = ["console", "error"]
arrfor(sink.channels, function(ch) {
if (ch == channel) already = true
})
if (!already) sink.channels[] = channel
}
save_config(config)
log.console("Enabled channel: " + channel)
}
poll_content = text(fd.slurp(sink.path))
poll_lines = array(poll_content, '\n')
if (last_size == 0 && length(poll_lines) > tail_lines) {
start = length(poll_lines) - tail_lines
} else if (last_size > 0) {
old_line_count = length(array(text(poll_content, 0, last_size), '\n'))
start = old_line_count
function do_disable() {
var channel = null
var config = null
var sink = null
var i = 0
var new_channels = []
var already_excluded = false
if (length(args) < 2) {
log.error("Usage: cell log disable <channel>")
return
}
channel = args[1]
config = load_config()
if (!config || !config.sink) {
log.error("No log configuration found")
return
}
sink = find_terminal_sink(config)
if (!sink) {
log.error("No terminal sink found")
return
}
if (is_array(sink.channels) && length(sink.channels) == 1 && sink.channels[0] == "*") {
if (!is_array(sink.exclude)) sink.exclude = []
already_excluded = false
arrfor(sink.exclude, function(ex) {
if (ex == channel) already_excluded = true
})
if (!already_excluded) sink.exclude[] = channel
} else {
if (is_array(sink.channels)) {
arrfor(sink.channels, function(ch) {
if (ch != channel) new_channels[] = ch
})
sink.channels = new_channels
}
}
save_config(config)
log.console("Disabled channel: " + channel)
}
last_size = st.size
for (idx = start; idx < length(poll_lines); idx++) {
if (length(poll_lines[idx]) == 0) continue
parse_fn = function() {
poll_entry = json.decode(poll_lines[idx])
} disruption {
poll_entry = null
function do_channels() {
var config = load_config()
var sink = null
var is_wildcard = false
var active = {}
if (config) sink = find_terminal_sink(config)
if (sink) {
if (is_array(sink.channels) && length(sink.channels) == 1 && sink.channels[0] == "*") {
is_wildcard = true
arrfor(known_channels, function(ch) { active[ch] = true })
if (is_array(sink.exclude)) {
arrfor(sink.exclude, function(ex) { active[ex] = false })
}
parse_fn()
if (!poll_entry) continue
os.print(format_entry(poll_entry) + "\n")
} else if (is_array(sink.channels)) {
arrfor(sink.channels, function(ch) { active[ch] = true })
}
$delay(poll, 1)
} else {
active.console = true
active.error = true
}
poll()
log.console("Channels:")
arrfor(known_channels, function(ch) {
var status = active[ch] ? "enabled" : "disabled"
log.console(" " + ch + ": " + status)
})
}
// Main dispatch
@@ -335,16 +481,26 @@ if (length(args) == 0) {
print_help()
} else if (args[0] == 'list') {
do_list()
} else if (args[0] == 'channels') {
do_channels()
} else if (args[0] == 'enable') {
do_enable()
} else if (args[0] == 'disable') {
do_disable()
} else if (args[0] == 'add') {
do_add()
} else if (args[0] == 'remove') {
do_remove()
} else if (args[0] == 'read') {
do_read()
} else if (args[0] == 'tail') {
do_tail()
} else if (args[0] == 'route') {
do_route()
} else if (args[0] == 'unroute') {
do_unroute()
} else if (args[0] == 'stack') {
do_stack()
} else if (args[0] == 'unstack') {
do_unstack()
} else {
log.error("Unknown command: " + args[0])
log.console("Unknown command: " + args[0])
print_help()
}

View File

@@ -85,7 +85,7 @@ var dump_function = function(func, name) {
parts = []
j = 1
while (j < n - 2) {
push(parts, fmt_val(instr[j]))
parts[] = fmt_val(instr[j])
j = j + 1
}
operands = text(parts, ", ")

View File

@@ -166,7 +166,7 @@ var mcode = function(ast) {
// Variable tracking
var add_var = function(name, slot, is_const) {
push(s_vars, {name: name, slot: slot, is_const: is_const, is_closure: false})
s_vars[] = {name: name, slot: slot, is_const: is_const, is_closure: false}
}
var find_var = function(name) {
@@ -228,13 +228,13 @@ var mcode = function(ast) {
// Instruction emission
var add_instr = function(instr) {
push(instr, s_cur_line)
push(instr, s_cur_col)
push(s_instructions, instr)
instr[] = s_cur_line
instr[] = s_cur_col
s_instructions[] = instr
}
var emit_label = function(label) {
push(s_instructions, label)
s_instructions[] = label
}
var emit_0 = function(op) {
@@ -743,7 +743,7 @@ var mcode = function(ast) {
slot = alloc_slot()
lit = {kind: "name", name: name, make: "intrinsic"}
add_instr(["access", slot, lit])
push(s_intrinsic_cache, {name: name, slot: slot})
s_intrinsic_cache[] = {name: name, slot: slot}
_i = _i + 1
}
}
@@ -1974,7 +1974,7 @@ var mcode = function(ast) {
expr_slots = []
_i = 0
while (_i < nexpr) {
push(expr_slots, gen_expr(list[_i], -1))
expr_slots[] = gen_expr(list[_i], -1)
_i = _i + 1
}
// Create array from expression results
@@ -2083,6 +2083,23 @@ var mcode = function(ast) {
if (kind == "[") {
obj = expr.left
idx = expr.right
if (idx == null) {
// arr[] pop expression
obj_slot = gen_expr(obj, -1)
guard_t = alloc_slot()
guard_err = gen_label("pop_err")
guard_done = gen_label("pop_done")
emit_2("is_array", guard_t, obj_slot)
emit_jump_cond("jump_false", guard_t, guard_err)
slot = target >= 0 ? target : alloc_slot()
emit_2("pop", slot, obj_slot)
emit_jump(guard_done)
emit_label(guard_err)
emit_log_error("cannot pop: target must be an array")
emit_0("disrupt")
emit_label(guard_done)
return slot
}
obj_slot = gen_expr(obj, -1)
idx_slot = gen_expr(idx, -1)
slot = alloc_slot()
@@ -2264,7 +2281,7 @@ var mcode = function(ast) {
_i = 0
nargs = args_list != null ? length(args_list) : 0
while (_i < nargs) {
push(arg_slots, gen_expr(args_list[_i], -1))
arg_slots[] = gen_expr(args_list[_i], -1)
_i = _i + 1
}
dest = alloc_slot()
@@ -2432,7 +2449,7 @@ var mcode = function(ast) {
elem_slots = []
_i = 0
while (_i < count) {
push(elem_slots, gen_expr(list[_i], -1))
elem_slots[] = gen_expr(list[_i], -1)
_i = _i + 1
}
dest = alloc_slot()
@@ -2449,7 +2466,7 @@ var mcode = function(ast) {
if (kind == "record") {
list = expr.list
dest = alloc_slot()
push(s_instructions, ["record", dest, length(list)])
s_instructions[] = ["record", dest, length(list)]
_i = 0
while (_i < length(list)) {
pair = list[_i]
@@ -2479,7 +2496,7 @@ var mcode = function(ast) {
func = gen_function(expr)
func_id = s_func_counter
s_func_counter = s_func_counter + 1
push(s_functions, func)
s_functions[] = func
dest = alloc_slot()
emit_2("function", dest, func_id)
return dest
@@ -2797,7 +2814,7 @@ var mcode = function(ast) {
_i = 0
nargs = args_list != null ? length(args_list) : 0
while (_i < nargs) {
push(arg_slots, gen_expr(args_list[_i], -1))
arg_slots[] = gen_expr(args_list[_i], -1)
_i = _i + 1
}
callee_kind = callee.kind
@@ -2852,7 +2869,7 @@ var mcode = function(ast) {
case_kind = case_node.kind
if (case_kind == "default") {
default_label = gen_label("switch_default")
push(case_labels, default_label)
case_labels[] = default_label
} else {
case_label = gen_label("switch_case")
case_expr = case_node.expression
@@ -2862,7 +2879,7 @@ var mcode = function(ast) {
_bp_rn = case_expr
emit_binop("eq", cmp_slot, switch_val, case_val)
emit_jump_cond("jump_true", cmp_slot, case_label)
push(case_labels, case_label)
case_labels[] = case_label
}
_i = _i + 1
}
@@ -2894,7 +2911,7 @@ var mcode = function(ast) {
func = gen_function(stmt)
func_id = s_func_counter
s_func_counter = s_func_counter + 1
push(s_functions, func)
s_functions[] = func
local_slot = find_var(name)
dest = alloc_slot()
emit_2("function", dest, func_id)
@@ -2948,7 +2965,7 @@ var mcode = function(ast) {
var saved_func = 0
var captured_this = 0
push(parent_states, saved)
parent_states[] = saved
s_instructions = []
s_vars = []
@@ -3039,7 +3056,7 @@ var mcode = function(ast) {
compiled = gen_function(fn)
func_id = s_func_counter
s_func_counter = s_func_counter + 1
push(s_functions, compiled)
s_functions[] = compiled
local_slot = find_var(fname)
dest = alloc_slot()
emit_2("function", dest, func_id)
@@ -3112,7 +3129,7 @@ var mcode = function(ast) {
saved_func = s_func_counter
// Pop parent state
pop(parent_states)
parent_states[]
restore_state(saved)
s_label_counter = saved_label
s_func_counter = saved_func
@@ -3179,7 +3196,7 @@ var mcode = function(ast) {
compiled = gen_function(fn)
func_id = s_func_counter
s_func_counter = s_func_counter + 1
push(s_functions, compiled)
s_functions[] = compiled
local_slot = find_var(name)
dest = alloc_slot()
emit_2("function", dest, func_id)

View File

@@ -34,6 +34,7 @@ if host_machine.system() == 'darwin'
fworks = [
'CoreFoundation',
'CFNetwork',
'Security',
]
foreach fkit : fworks
deps += dependency('appleframeworks', modules: fkit)
@@ -82,6 +83,7 @@ scripts = [
'internal/os.c',
'internal/fd.c',
'net/http.c',
'net/tls.c',
'net/socket.c',
'internal/enet.c',
'archive/miniz.c',

View File

@@ -24,6 +24,9 @@
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#ifndef _WIN32
#include <fcntl.h>
#endif
// Helper to convert JS value to file descriptor
static int js2fd(JSContext *ctx, JSValueConst val)
@@ -582,6 +585,87 @@ JSC_CCALL(socket_unwatch,
return JS_NULL;
)
JSC_CCALL(socket_on_writable,
int sockfd = js2fd(js, argv[0]);
if (sockfd < 0) return JS_EXCEPTION;
if (!JS_IsFunction(argv[1]))
return JS_RaiseDisrupt(js, "on_writable: callback must be a function");
actor_watch_writable(js, sockfd, argv[1]);
return JS_NULL;
)
JSC_CCALL(socket_setnonblock,
int sockfd = js2fd(js, argv[0]);
if (sockfd < 0) return JS_EXCEPTION;
#ifdef _WIN32
u_long mode = 1;
if (ioctlsocket(sockfd, FIONBIO, &mode) != 0)
return JS_RaiseDisrupt(js, "setnonblock failed");
#else
int flags = fcntl(sockfd, F_GETFL, 0);
if (flags < 0 || fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) < 0)
return JS_RaiseDisrupt(js, "setnonblock failed: %s", strerror(errno));
#endif
return JS_NULL;
)
JSC_CCALL(socket_getsockopt,
int sockfd = js2fd(js, argv[0]);
if (sockfd < 0) return JS_EXCEPTION;
int level = SOL_SOCKET;
int optname = 0;
// Parse level
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;
else if (strcmp(level_str, "IPPROTO_IP") == 0) level = IPPROTO_IP;
else if (strcmp(level_str, "IPPROTO_IPV6") == 0) level = IPPROTO_IPV6;
JS_FreeCString(js, level_str);
} else {
level = js2number(js, argv[1]);
}
// Parse option name
if (JS_IsText(argv[2])) {
const char *opt_str = JS_ToCString(js, argv[2]);
if (strcmp(opt_str, "SO_ERROR") == 0) optname = SO_ERROR;
else if (strcmp(opt_str, "SO_REUSEADDR") == 0) optname = SO_REUSEADDR;
else if (strcmp(opt_str, "SO_KEEPALIVE") == 0) optname = SO_KEEPALIVE;
else if (strcmp(opt_str, "SO_BROADCAST") == 0) optname = SO_BROADCAST;
JS_FreeCString(js, opt_str);
} else {
optname = js2number(js, argv[2]);
}
int optval = 0;
socklen_t optlen = sizeof(optval);
if (getsockopt(sockfd, level, optname, &optval, &optlen) < 0)
return JS_RaiseDisrupt(js, "getsockopt failed: %s", strerror(errno));
return JS_NewInt32(js, optval);
)
JSC_CCALL(socket_send_self,
if (argc < 1 || !JS_IsText(argv[0]))
return JS_RaiseDisrupt(js, "send_self: expects a text argument");
const char *msg = JS_ToCString(js, argv[0]);
WotaBuffer wb;
wota_buffer_init(&wb, 16);
wota_write_record(&wb, 1);
wota_write_text(&wb, "text");
wota_write_text(&wb, msg);
JS_FreeCString(js, msg);
const char *err = JS_SendMessage(js, &wb);
if (err) {
wota_buffer_free(&wb);
return JS_RaiseDisrupt(js, "send_self failed: %s", err);
}
return JS_NULL;
)
static const JSCFunctionListEntry js_socket_funcs[] = {
MIST_FUNC_DEF(socket, getaddrinfo, 3),
MIST_FUNC_DEF(socket, socket, 3),
@@ -600,7 +684,11 @@ static const JSCFunctionListEntry js_socket_funcs[] = {
MIST_FUNC_DEF(socket, setsockopt, 4),
MIST_FUNC_DEF(socket, close, 1),
MIST_FUNC_DEF(socket, on_readable, 2),
MIST_FUNC_DEF(socket, on_writable, 2),
MIST_FUNC_DEF(socket, unwatch, 1),
MIST_FUNC_DEF(socket, setnonblock, 1),
MIST_FUNC_DEF(socket, getsockopt, 3),
MIST_FUNC_DEF(socket, send_self, 1),
};
JSValue js_core_socket_use(JSContext *js) {
@@ -625,6 +713,8 @@ JSValue js_core_socket_use(JSContext *js) {
JS_SetPropertyStr(js, mod.val, "SOL_SOCKET", JS_NewInt32(js, SOL_SOCKET));
JS_SetPropertyStr(js, mod.val, "SO_REUSEADDR", JS_NewInt32(js, SO_REUSEADDR));
JS_SetPropertyStr(js, mod.val, "SO_ERROR", JS_NewInt32(js, SO_ERROR));
JS_SetPropertyStr(js, mod.val, "SO_KEEPALIVE", JS_NewInt32(js, SO_KEEPALIVE));
JS_RETURN(mod.val);
}

238
net/tls.c Normal file
View File

@@ -0,0 +1,238 @@
#include "cell.h"
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#if defined(__APPLE__)
/* SecureTransport — deprecated but functional, no external deps */
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#include <Security/Security.h>
#include <Security/SecureTransport.h>
#include <sys/socket.h>
#include <unistd.h>
#include <poll.h>
typedef struct {
SSLContextRef ssl;
int fd;
} tls_ctx;
static void tls_ctx_free(JSRuntime *rt, tls_ctx *ctx) {
if (!ctx) return;
if (ctx->ssl) {
SSLClose(ctx->ssl);
CFRelease(ctx->ssl);
}
if (ctx->fd >= 0)
close(ctx->fd);
free(ctx);
}
QJSCLASS(tls_ctx,)
static OSStatus tls_read_cb(SSLConnectionRef conn, void *data, size_t *len) {
int fd = *(const int *)conn;
size_t requested = *len;
size_t total = 0;
while (total < requested) {
ssize_t n = read(fd, (char *)data + total, requested - total);
if (n > 0) {
total += n;
} else if (n == 0) {
*len = total;
return errSSLClosedGraceful;
} else {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
*len = total;
return (total > 0) ? noErr : errSSLWouldBlock;
}
*len = total;
return errSSLClosedAbort;
}
}
*len = total;
return noErr;
}
static OSStatus tls_write_cb(SSLConnectionRef conn, const void *data, size_t *len) {
int fd = *(const int *)conn;
size_t requested = *len;
size_t total = 0;
while (total < requested) {
ssize_t n = write(fd, (const char *)data + total, requested - total);
if (n > 0) {
total += n;
} else if (n == 0) {
*len = total;
return errSSLClosedGraceful;
} else {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
*len = total;
return (total > 0) ? noErr : errSSLWouldBlock;
}
*len = total;
return errSSLClosedAbort;
}
}
*len = total;
return noErr;
}
/* tls.wrap(fd, hostname) -> ctx */
JSC_CCALL(tls_wrap,
int fd = -1;
if (JS_ToInt32(js, &fd, argv[0]) < 0)
return JS_RaiseDisrupt(js, "tls.wrap: fd must be a number");
const char *hostname = JS_ToCString(js, argv[1]);
if (!hostname)
return JS_RaiseDisrupt(js, "tls.wrap: hostname must be a string");
tls_ctx *ctx = calloc(1, sizeof(tls_ctx));
ctx->fd = fd;
ctx->ssl = SSLCreateContext(NULL, kSSLClientSide, kSSLStreamType);
if (!ctx->ssl) {
free(ctx);
JS_FreeCString(js, hostname);
return JS_RaiseDisrupt(js, "tls.wrap: SSLCreateContext failed");
}
SSLSetIOFuncs(ctx->ssl, tls_read_cb, tls_write_cb);
SSLSetConnection(ctx->ssl, &ctx->fd);
SSLSetPeerDomainName(ctx->ssl, hostname, strlen(hostname));
JS_FreeCString(js, hostname);
/* Retry handshake on non-blocking sockets (errSSLWouldBlock) */
OSStatus status;
for (int attempts = 0; attempts < 200; attempts++) {
status = SSLHandshake(ctx->ssl);
if (status == noErr) break;
if (status != errSSLWouldBlock) break;
struct pollfd pfd = { .fd = ctx->fd, .events = POLLIN | POLLOUT };
poll(&pfd, 1, 50);
}
if (status != noErr) {
CFRelease(ctx->ssl);
ctx->ssl = NULL;
ctx->fd = -1; /* don't close caller's fd */
free(ctx);
return JS_RaiseDisrupt(js, "tls.wrap: handshake failed (status %d)", (int)status);
}
return tls_ctx2js(js, ctx);
)
/* tls.send(ctx, data) -> bytes_sent */
JSC_CCALL(tls_send,
tls_ctx *ctx = js2tls_ctx(js, argv[0]);
if (!ctx || !ctx->ssl)
return JS_RaiseDisrupt(js, "tls.send: invalid context");
size_t len;
size_t written = 0;
OSStatus status;
if (JS_IsText(argv[1])) {
const char *data = JS_ToCStringLen(js, &len, argv[1]);
status = SSLWrite(ctx->ssl, data, len, &written);
JS_FreeCString(js, data);
} else {
unsigned char *data = js_get_blob_data(js, &len, argv[1]);
if (!data)
return JS_RaiseDisrupt(js, "tls.send: invalid data");
status = SSLWrite(ctx->ssl, data, len, &written);
}
if (status != noErr && status != errSSLWouldBlock)
return JS_RaiseDisrupt(js, "tls.send: write failed (status %d)", (int)status);
return JS_NewInt64(js, (int64_t)written);
)
/* tls.recv(ctx, len) -> blob */
JSC_CCALL(tls_recv,
tls_ctx *ctx = js2tls_ctx(js, argv[0]);
if (!ctx || !ctx->ssl)
return JS_RaiseDisrupt(js, "tls.recv: invalid context");
size_t len = 4096;
if (argc > 1) len = js2number(js, argv[1]);
void *out;
ret = js_new_blob_alloc(js, len, &out);
if (JS_IsException(ret)) return ret;
size_t received = 0;
OSStatus status = SSLRead(ctx->ssl, out, len, &received);
if (status != noErr && status != errSSLWouldBlock &&
status != errSSLClosedGraceful) {
return JS_RaiseDisrupt(js, "tls.recv: read failed (status %d)", (int)status);
}
js_blob_stone(ret, received);
return ret;
)
/* tls.close(ctx) -> null */
JSC_CCALL(tls_close,
tls_ctx *ctx = js2tls_ctx(js, argv[0]);
if (!ctx) return JS_NULL;
if (ctx->ssl) {
SSLClose(ctx->ssl);
CFRelease(ctx->ssl);
ctx->ssl = NULL;
}
if (ctx->fd >= 0) {
close(ctx->fd);
ctx->fd = -1;
}
return JS_NULL;
)
/* tls.fd(ctx) -> number — get underlying fd for on_readable */
JSC_CCALL(tls_fd,
tls_ctx *ctx = js2tls_ctx(js, argv[0]);
if (!ctx)
return JS_RaiseDisrupt(js, "tls.fd: invalid context");
return JS_NewInt32(js, ctx->fd);
)
/* tls.on_readable(ctx, callback) -> null */
JSC_CCALL(tls_on_readable,
tls_ctx *ctx = js2tls_ctx(js, argv[0]);
if (!ctx)
return JS_RaiseDisrupt(js, "tls.on_readable: invalid context");
if (!JS_IsFunction(argv[1]))
return JS_RaiseDisrupt(js, "tls.on_readable: callback must be a function");
actor_watch_readable(js, ctx->fd, argv[1]);
return JS_NULL;
)
static const JSCFunctionListEntry js_tls_funcs[] = {
MIST_FUNC_DEF(tls, wrap, 2),
MIST_FUNC_DEF(tls, send, 2),
MIST_FUNC_DEF(tls, recv, 2),
MIST_FUNC_DEF(tls, close, 1),
MIST_FUNC_DEF(tls, fd, 1),
MIST_FUNC_DEF(tls, on_readable, 2),
};
JSValue js_core_net_tls_use(JSContext *js) {
JS_FRAME(js);
QJSCLASSPREP_NO_FUNCS(tls_ctx);
JS_ROOT(mod, JS_NewObject(js));
JS_SetPropertyFunctionList(js, mod.val, js_tls_funcs, countof(js_tls_funcs));
JS_RETURN(mod.val);
}
#pragma clang diagnostic pop
#else
/* Stub for non-Apple platforms — TLS not yet implemented */
JSValue js_core_net_tls_use(JSContext *js) {
return JS_RaiseDisrupt(js, "TLS not available on this platform");
}
#endif

View File

@@ -88,9 +88,9 @@ var packages = ['core']
var deps = pkg_tools.gather_dependencies(target_package)
for (i = 0; i < length(deps); i++) {
push(packages, deps[i])
packages[] = deps[i]
}
push(packages, target_package)
packages[] = target_package
// Remove duplicates
var unique_packages = []
@@ -98,7 +98,7 @@ var seen = {}
for (i = 0; i < length(packages); i++) {
if (!seen[packages[i]]) {
seen[packages[i]] = true
push(unique_packages, packages[i])
unique_packages[] = packages[i]
}
}
packages = unique_packages

View File

@@ -198,7 +198,7 @@ package.find_packages = function(dir) {
var list = fd.readdir(dir)
if (!list) return found
if (fd.is_file(dir + '/cell.toml'))
push(found, dir)
found[] = dir
arrfor(list, function(item) {
if (item == '.' || item == '..' || item == '.cell' || item == '.git') return
var full = dir + '/' + item
@@ -207,7 +207,7 @@ package.find_packages = function(dir) {
if (st && st.isDirectory) {
sub = package.find_packages(full)
arrfor(sub, function(p) {
push(found, p)
found[] = p
})
}
})
@@ -227,14 +227,14 @@ package.list_modules = function(name) {
var stem = null
for (i = 0; i < length(files); i++) {
if (ends_with(files[i], '.cm')) {
push(modules, text(files[i], 0, -3))
modules[] = text(files[i], 0, -3)
}
}
var c_files = package.get_c_files(name, null, true)
for (i = 0; i < length(c_files); i++) {
stem = ends_with(c_files[i], '.cpp') ? text(c_files[i], 0, -4) : text(c_files[i], 0, -2)
if (find(modules, function(m) { return m == stem }) == null)
push(modules, stem)
modules[] = stem
}
return modules
}
@@ -245,7 +245,7 @@ package.list_programs = function(name) {
var i = 0
for (i = 0; i < length(files); i++) {
if (ends_with(files[i], '.ce')) {
push(programs, text(files[i], 0, -3))
programs[] = text(files[i], 0, -3)
}
}
return programs
@@ -360,7 +360,7 @@ package.get_c_files = function(name, target, exclude_main) {
basename = fd.basename(selected)
if (basename == 'main.c' || starts_with(basename, 'main_')) return
}
push(result, selected)
result[] = selected
}
})

View File

@@ -90,12 +90,12 @@ var parse = function(tokens, src, filename, tokenizer) {
var parse_error = function(token, msg) {
if (error_count >= 5) return null
error_count = error_count + 1
push(errors, {
errors[] = {
message: msg,
line: token.from_row + 1,
column: token.from_column + 1,
offset: token.at
})
}
}
var _keywords = {
@@ -230,8 +230,8 @@ var parse = function(tokens, src, filename, tokenizer) {
if (tv[tvi] == "\\" && tvi + 1 < tvlen) {
esc_ch = tv[tvi + 1]
esc_val = template_escape_map[esc_ch]
if (esc_val != null) { push(fmt_parts, esc_val) }
else { push(fmt_parts, esc_ch) }
if (esc_val != null) { fmt_parts[] = esc_val }
else { fmt_parts[] = esc_ch }
tvi = tvi + 2
} else if (tv[tvi] == "$" && tvi + 1 < tvlen && tv[tvi + 1] == "{") {
tvi = tvi + 2
@@ -239,27 +239,27 @@ var parse = function(tokens, src, filename, tokenizer) {
expr_parts = []
while (tvi < tvlen && depth > 0) {
tc = tv[tvi]
if (tc == "{") { depth = depth + 1; push(expr_parts, tc); tvi = tvi + 1 }
if (tc == "{") { depth = depth + 1; expr_parts[] = tc; tvi = tvi + 1 }
else if (tc == "}") {
depth = depth - 1
if (depth > 0) { push(expr_parts, tc) }
if (depth > 0) { expr_parts[] = tc }
tvi = tvi + 1
}
else if (tc == "'" || tc == "\"" || tc == "`") {
tq = tc
push(expr_parts, tc)
expr_parts[] = tc
tvi = tvi + 1
while (tvi < tvlen && tv[tvi] != tq) {
if (tv[tvi] == "\\" && tvi + 1 < tvlen) {
push(expr_parts, tv[tvi])
expr_parts[] = tv[tvi]
tvi = tvi + 1
}
push(expr_parts, tv[tvi])
expr_parts[] = tv[tvi]
tvi = tvi + 1
}
if (tvi < tvlen) { push(expr_parts, tv[tvi]); tvi = tvi + 1 }
if (tvi < tvlen) { expr_parts[] = tv[tvi]; tvi = tvi + 1 }
} else {
push(expr_parts, tc)
expr_parts[] = tc
tvi = tvi + 1
}
}
@@ -274,14 +274,14 @@ var parse = function(tokens, src, filename, tokenizer) {
} else {
sub_expr = sub_stmt
}
push(tpl_list, sub_expr)
tpl_list[] = sub_expr
}
push(fmt_parts, "{")
push(fmt_parts, text(idx))
push(fmt_parts, "}")
fmt_parts[] = "{"
fmt_parts[] = text(idx)
fmt_parts[] = "}"
idx = idx + 1
} else {
push(fmt_parts, tv[tvi])
fmt_parts[] = tv[tvi]
tvi = tvi + 1
}
}
@@ -332,7 +332,7 @@ var parse = function(tokens, src, filename, tokenizer) {
advance()
while (tok.kind != "]" && tok.kind != "eof") {
elem = parse_assign_expr()
if (elem != null) push(list, elem)
if (elem != null) list[] = elem
if (tok.kind == ",") advance()
else break
}
@@ -395,7 +395,7 @@ var parse = function(tokens, src, filename, tokenizer) {
advance()
param.expression = parse_assign_expr()
}
push(params, param)
params[] = param
} else {
parse_error(tok, "expected parameter name")
break
@@ -436,7 +436,7 @@ var parse = function(tokens, src, filename, tokenizer) {
} else {
parse_error(tok, "expected ':' after property name")
}
push(list, pair)
list[] = pair
if (tok.kind == ",") advance()
else if (tok.kind == "{") {
if (right && right.kind == "(") {
@@ -473,17 +473,17 @@ var parse = function(tokens, src, filename, tokenizer) {
flags_parts = []
while (rpos < _src_len && src[rpos] != "/") {
if (src[rpos] == "\\" && rpos + 1 < _src_len) {
push(pattern_parts, src[rpos])
push(pattern_parts, src[rpos + 1])
pattern_parts[] = src[rpos]
pattern_parts[] = src[rpos + 1]
rpos = rpos + 2
} else {
push(pattern_parts, src[rpos])
pattern_parts[] = src[rpos]
rpos = rpos + 1
}
}
if (rpos < _src_len) rpos = rpos + 1
while (rpos < _src_len && is_letter(src[rpos])) {
push(flags_parts, src[rpos])
flags_parts[] = src[rpos]
rpos = rpos + 1
}
node.pattern = text(pattern_parts)
@@ -557,7 +557,7 @@ var parse = function(tokens, src, filename, tokenizer) {
new_node.list = args_list
while (tok.kind != ")" && tok.kind != "eof") {
arg = parse_assign_expr()
if (arg != null) push(args_list, arg)
if (arg != null) args_list[] = arg
if (tok.kind == ",") advance()
else break
}
@@ -830,7 +830,7 @@ var parse = function(tokens, src, filename, tokenizer) {
before = cursor
stmt = parse_statement()
if (stmt != null) {
push(stmts, stmt)
stmts[] = stmt
} else if (cursor == before) {
sync_to_statement()
}
@@ -872,14 +872,14 @@ var parse = function(tokens, src, filename, tokenizer) {
param.name = tok.value
pname = tok.value
if (find(prev_names, pname) != null) parse_error(tok, "duplicate parameter name '" + pname + "'")
push(prev_names, pname)
prev_names[] = pname
advance()
ast_node_end(param)
if (tok.kind == "=" || tok.kind == "|") {
advance()
param.expression = parse_assign_expr()
}
push(params, param)
params[] = param
} else {
parse_error(tok, "expected parameter name")
break
@@ -959,7 +959,7 @@ var parse = function(tokens, src, filename, tokenizer) {
param.name = tok.value
advance()
ast_node_end(param)
push(params, param)
params[] = param
} else if (tok.kind == "(") {
advance()
prev_names = []
@@ -969,14 +969,14 @@ var parse = function(tokens, src, filename, tokenizer) {
param.name = tok.value
pname = tok.value
if (find(prev_names, pname) != null) parse_error(tok, "duplicate parameter name '" + pname + "'")
push(prev_names, pname)
prev_names[] = pname
advance()
ast_node_end(param)
if (tok.kind == "=" || tok.kind == "|") {
advance()
param.expression = parse_assign_expr()
}
push(params, param)
params[] = param
} else {
parse_error(tok, "expected parameter name")
break
@@ -1010,7 +1010,7 @@ var parse = function(tokens, src, filename, tokenizer) {
expr = parse_assign_expr()
ret.expression = expr
ast_node_end(ret)
push(stmts, ret)
stmts[] = ret
node.statements = stmts
}
@@ -1110,7 +1110,7 @@ var parse = function(tokens, src, filename, tokenizer) {
parse_error(start, "'var' declarations must be initialized; use 'var " + var_name + " = null' if no value is needed")
}
ast_node_end(node)
push(decls, node)
decls[] = node
decl_count = decl_count + 1
if (tok.kind == ",") advance()
else break
@@ -1142,7 +1142,7 @@ var parse = function(tokens, src, filename, tokenizer) {
_control_depth = _control_depth + 1
_expecting_body = true
body = parse_statement()
if (body != null) push(then_stmts, body)
if (body != null) then_stmts[] = body
else_ifs = []
node.list = else_ifs
if (tok.kind == "else") {
@@ -1151,7 +1151,7 @@ var parse = function(tokens, src, filename, tokenizer) {
_control_depth = saved_cd
_control_type = saved_ct
elif = parse_statement()
if (elif != null) push(else_ifs, elif)
if (elif != null) else_ifs[] = elif
ast_node_end(node)
return node
} else {
@@ -1159,7 +1159,7 @@ var parse = function(tokens, src, filename, tokenizer) {
node.else = else_stmts
_expecting_body = true
body = parse_statement()
if (body != null) push(else_stmts, body)
if (body != null) else_stmts[] = body
}
}
_control_depth = saved_cd
@@ -1185,7 +1185,7 @@ var parse = function(tokens, src, filename, tokenizer) {
_control_depth = _control_depth + 1
_expecting_body = true
body = parse_statement()
if (body != null) push(stmts, body)
if (body != null) stmts[] = body
_control_depth = saved_cd
_control_type = saved_ct
ast_node_end(node)
@@ -1203,7 +1203,7 @@ var parse = function(tokens, src, filename, tokenizer) {
_control_depth = _control_depth + 1
_expecting_body = true
body = parse_statement()
if (body != null) push(stmts, body)
if (body != null) stmts[] = body
_control_depth = saved_cd
_control_type = saved_ct
if (tok.kind == "while") advance()
@@ -1256,7 +1256,7 @@ var parse = function(tokens, src, filename, tokenizer) {
_control_depth = _control_depth + 1
_expecting_body = true
body = parse_statement()
if (body != null) push(stmts, body)
if (body != null) stmts[] = body
_control_depth = saved_cd
_control_type = saved_ct
ast_node_end(node)
@@ -1402,9 +1402,9 @@ var parse = function(tokens, src, filename, tokenizer) {
stmt = parse_statement()
if (stmt != null) {
if (stmt.kind == "function") {
push(functions, stmt)
functions[] = stmt
} else {
push(statements, stmt)
statements[] = stmt
}
} else if (cursor == before) {
sync_to_statement()
@@ -1426,7 +1426,7 @@ var parse = function(tokens, src, filename, tokenizer) {
var err = {message: msg}
if (node.from_row != null) err.line = node.from_row + 1
if (node.from_column != null) err.column = node.from_column + 1
push(sem_errors, err)
sem_errors[] = err
}
var make_scope = function(parent, fn_nr, opts) {
@@ -1452,7 +1452,7 @@ var parse = function(tokens, src, filename, tokenizer) {
}
if (make_opts.reached == false) entry.reached = false
if (make_opts.decl_line != null) entry.decl_line = make_opts.decl_line
push(scope.vars, entry)
scope.vars[] = entry
}
var sem_lookup_var = function(scope, name) {
@@ -1503,7 +1503,7 @@ var parse = function(tokens, src, filename, tokenizer) {
}
var sem_add_intrinsic = function(name) {
if (find(intrinsics, name) == null) push(intrinsics, name)
if (find(intrinsics, name) == null) intrinsics[] = name
}
var functino_names = {
@@ -1828,7 +1828,7 @@ var parse = function(tokens, src, filename, tokenizer) {
}
}
sr = sem_build_scope_record(fn_scope)
push(scopes_array, sr.rec)
scopes_array[] = sr.rec
expr.nr_slots = sr.nr_slots
expr.nr_close_slots = sr.nr_close
return null
@@ -1858,9 +1858,9 @@ var parse = function(tokens, src, filename, tokenizer) {
r.v.nr_uses = r.v.nr_uses + 1
if (r.level > 0) r.v.closure = 1
if (r.v.reached == false && r.v.decl_line != null && expr.from_row != null && expr.from_row + 1 < r.v.decl_line) {
push(hoisted_fn_refs, {name: name, line: expr.from_row + 1,
hoisted_fn_refs[] = {name: name, line: expr.from_row + 1,
col: expr.from_column != null ? expr.from_column + 1 : null,
decl_line: r.v.decl_line})
decl_line: r.v.decl_line}
}
} else {
expr.level = -1
@@ -2110,7 +2110,7 @@ var parse = function(tokens, src, filename, tokenizer) {
}
}
sr = sem_build_scope_record(fn_scope)
push(scopes_array, sr.rec)
scopes_array[] = sr.rec
stmt.nr_slots = sr.nr_slots
stmt.nr_close_slots = sr.nr_close
return null
@@ -2153,7 +2153,7 @@ var parse = function(tokens, src, filename, tokenizer) {
new_scopes = [sr.rec]
i = 0
while (i < length(scopes_array)) {
push(new_scopes, scopes_array[i])
new_scopes[] = scopes_array[i]
i = i + 1
}
scopes_array = new_scopes
@@ -2183,7 +2183,7 @@ var parse = function(tokens, src, filename, tokenizer) {
if (ast.errors != null) {
_mi = 0
while (_mi < length(errors)) {
push(ast.errors, errors[_mi])
ast.errors[] = errors[_mi]
_mi = _mi + 1
}
} else {

View File

@@ -948,7 +948,7 @@ var qbe_emit = function(ir, qbe, export_name) {
// ============================================================
var emit = function(s) {
push(out, s)
out[] = s
}
var fresh = function() {
@@ -982,9 +982,9 @@ var qbe_emit = function(ir, qbe, export_name) {
escaped = replace(escaped, "\r", "\\r")
escaped = replace(escaped, "\t", "\\t")
var line = "data " + label + ' = ' + '{ b "' + escaped + '", b 0 }'
push(data_out, line)
data_out[] = line
var entry = { label: label, idx: length(str_entries) }
push(str_entries, entry)
str_entries[] = entry
str_table[val] = entry
return entry
}
@@ -2909,16 +2909,16 @@ var qbe_emit = function(ir, qbe, export_name) {
// Export nr_slots for main function so the module loader can use right-sized frames
var main_name = export_name ? sanitize(export_name) : "cell_main"
push(data_out, "export data $" + main_name + "_nr_slots = { w " + text(ir.main.nr_slots) + " }")
push(data_out, "export data $cell_lit_count = { w " + text(length(str_entries)) + " }")
data_out[] = "export data $" + main_name + "_nr_slots = { w " + text(ir.main.nr_slots) + " }"
data_out[] = "export data $cell_lit_count = { w " + text(length(str_entries)) + " }"
if (length(str_entries) > 0) {
lit_data = []
si = 0
while (si < length(str_entries)) {
push(lit_data, `l ${str_entries[si].label}`)
lit_data[] = `l ${str_entries[si].label}`
si = si + 1
}
push(data_out, "export data $cell_lit_table = { " + text(lit_data, ", ") + " }")
data_out[] = "export data $cell_lit_table = { " + text(lit_data, ", ") + " }"
}
return {

View File

@@ -149,7 +149,7 @@ if (!is_array(args) || length(args) < 1) {
}
for (; i < length(args) - 1; i++) {
push(sources, args[i])
sources[] = args[i]
}
archive = args[length(args) - 1]

View File

@@ -78,7 +78,7 @@ var run = function() {
arrfor(all_packages, function(p) {
if (p == 'core') return
if (!needed[p] && find(packages_to_remove, p) == null) {
push(packages_to_remove, p)
packages_to_remove[] = p
}
})
}

View File

@@ -165,11 +165,11 @@ for (i = 0; i < length(sorted); i++) {
// Format output
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 (has_c_files) push(status_parts, "has C modules")
if (is_linked) status_parts[] = "linked"
if (is_local) status_parts[] = "local"
if (!is_in_lock) status_parts[] = "not in lock"
if (!is_fetched) status_parts[] = "not fetched"
if (has_c_files) status_parts[] = "has C modules"
commit_str = ""
if (lock_entry && lock_entry.commit) {

View File

@@ -21,7 +21,7 @@ var packages = shop.list_packages()
arrfor(packages, function(package_name) {
// Check if package name matches
if (search(package_name, query) != null) {
push(found_packages, package_name)
found_packages[] = package_name
}
// Search modules and actors within the package
@@ -29,14 +29,14 @@ arrfor(packages, function(package_name) {
var modules = pkg.list_modules(package_name)
arrfor(modules, function(mod) {
if (search(mod, query) != null) {
push(found_modules, package_name + ':' + mod)
found_modules[] = package_name + ':' + mod
}
})
var actors = pkg.list_programs(package_name)
arrfor(actors, function(actor) {
if (search(actor, query) != null) {
push(found_actors, package_name + ':' + actor)
found_actors[] = package_name + ':' + actor
}
})
} disruption {

View File

@@ -179,7 +179,7 @@ var run = function() {
first_def[slot_num] = pc
first_def_op[slot_num] = op
}
push(events, {kind: "DEF", slot: operand_val, pc: pc, instr: instr})
events[] = {kind: "DEF", slot: operand_val, pc: pc, instr: instr}
}
di = di + 1
}
@@ -191,7 +191,7 @@ var run = function() {
slot_num = text(operand_val)
if (!uses[slot_num]) uses[slot_num] = 0
uses[slot_num] = uses[slot_num] + 1
push(events, {kind: "USE", slot: operand_val, pc: pc, instr: instr})
events[] = {kind: "USE", slot: operand_val, pc: pc, instr: instr}
}
ui = ui + 1
}
@@ -219,7 +219,7 @@ var run = function() {
parts = []
j = 1
while (j < n - 2) {
push(parts, fmt_val(evt.instr[j]))
parts[] = fmt_val(evt.instr[j])
j = j + 1
}
operands = text(parts, ", ")

View File

@@ -313,6 +313,26 @@ void actor_disrupt(JSContext *ctx)
JSValue js_core_internal_os_use(JSContext *js);
JSValue js_core_json_use(JSContext *js);
/* Engine-env log proxy: routes log("channel", [msg]) through JS_Log.
Before set_log is called, JS_Log falls back to stderr.
After set_log, JS_Log forwards to the engine's JS log function.
This exists so mcode-generated type-check error paths (which always
access 'log' as an intrinsic) work inside engine.cm itself. */
static JSValue js_engine_log(JSContext *js, JSValue self,
int argc, JSValue *argv) {
if (argc < 2) return JS_NULL;
const char *channel = JS_ToCString(js, argv[0]);
if (!channel) return JS_NULL;
JSValue msg_val = JS_GetPropertyNumber(js, argv[1], 0);
const char *msg = JS_ToCString(js, msg_val);
if (msg) {
JS_Log(js, channel, "%s", msg);
JS_FreeCString(js, msg);
}
JS_FreeCString(js, channel);
return JS_NULL;
}
void script_startup(JSContext *js)
{
if (!g_runtime) {
@@ -414,6 +434,8 @@ void script_startup(JSContext *js)
}
tmp = shop_path ? JS_NewString(js, shop_path) : JS_NULL;
JS_SetPropertyStr(js, env_ref.val, "shop_path", tmp);
tmp = JS_NewCFunction(js, js_engine_log, "log", 2);
JS_SetPropertyStr(js, env_ref.val, "log", tmp);
// Stone the environment
JSValue hidden_env = JS_Stone(js, env_ref.val);
@@ -761,6 +783,8 @@ int cell_init(int argc, char **argv)
}
JS_SetPropertyStr(ctx, env_ref.val, "args", args_ref.val);
JS_DeleteGCRef(ctx, &args_ref);
tmp = JS_NewCFunction(ctx, js_engine_log, "log", 2);
JS_SetPropertyStr(ctx, env_ref.val, "log", tmp);
JSValue hidden_env = JS_Stone(ctx, env_ref.val);
g_crash_ctx = ctx;

View File

@@ -4,6 +4,7 @@
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "wota.h"
#ifdef __cplusplus
extern "C" {
@@ -1150,6 +1151,18 @@ JSValue CELL_USE_NAME(JSContext *js) { \
#define CELL_PROGRAM_INIT(c) \
JSValue CELL_USE_NAME(JSContext *js) { do { c ; } while(0); }
/* ============================================================
WOTA Message Sending — C modules can send messages to actors.
============================================================ */
/* Check whether an actor with the given ID exists. */
int JS_ActorExists(const char *actor_id);
/* Send a WOTA-encoded message to the actor that owns ctx.
Takes ownership of wb's data on success (caller must not free).
On failure returns an error string; caller must free wb. */
const char *JS_SendMessage(JSContext *ctx, WotaBuffer *wb);
#undef js_unlikely
#undef inline

View File

@@ -876,7 +876,7 @@ typedef struct {
#define ACTOR_SLOW 5
#define ACTOR_REFRESHED 6
#define ACTOR_FAST_TIMER_NS (10ULL * 1000000)
#define ACTOR_FAST_TIMER_NS (1000ULL * 1000000)
#define ACTOR_SLOW_TIMER_NS (60000ULL * 1000000)
#define ACTOR_SLOW_STRIKES_MAX 3
#define ACTOR_MEMORY_LIMIT (1024ULL * 1024 * 1024)

View File

@@ -133,7 +133,8 @@ static void js_log_callback(JSContext *ctx, const char *channel, const char *msg
JS_FRAME(ctx);
JS_ROOT(stack, JS_GetStack(ctx));
JS_ROOT(args_array, JS_NewArray(ctx));
JS_SetPropertyNumber(ctx, args_array.val, 0, JS_NewString(ctx, msg));
JSValue msg_str = JS_NewString(ctx, msg);
JS_SetPropertyNumber(ctx, args_array.val, 0, msg_str);
JS_SetPropertyNumber(ctx, args_array.val, 1, stack.val);
JSValue argv[2];
argv[0] = JS_NewString(ctx, channel);

View File

@@ -1915,12 +1915,13 @@ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) {
if (ctx->trace_hook && (ctx->trace_type & JS_HOOK_GC))
ctx->trace_hook(ctx, JS_HOOK_GC, NULL, ctx->trace_data);
/* Check memory limit — kill actor if heap exceeds cap */
/* Check memory limit — kill actor if heap exceeds cap.
Use JS_Log "memory" channel which always fprintf's (safe during GC). */
if (ctx->heap_memory_limit > 0 && ctx->current_block_size > ctx->heap_memory_limit) {
#ifdef ACTOR_TRACE
fprintf(stderr, "[ACTOR_TRACE] heap %zu > limit %zu, OOM\n",
ctx->current_block_size, ctx->heap_memory_limit);
#endif
JS_Log(ctx, "memory", "%s: heap %zuKB exceeds limit %zuMB, killing",
ctx->name ? ctx->name : ctx->id,
ctx->current_block_size / 1024,
ctx->heap_memory_limit / (1024 * 1024));
return -1;
}
@@ -3277,24 +3278,24 @@ JS_RaiseDisrupt (JSContext *ctx, const char *fmt, ...) {
va_start (ap, fmt);
vsnprintf (buf, sizeof (buf), fmt, ap);
va_end (ap);
JS_Log (ctx, "error", "%s", buf);
if (ctx->log_callback)
JS_Log (ctx, "error", "%s", buf);
ctx->current_exception = JS_TRUE;
return JS_EXCEPTION;
}
/* Log to "memory" channel + disrupt. Skips JS callback (can't allocate). */
/* Log to "memory" channel + disrupt. Skips JS callback (can't allocate).
Uses fprintf directly — safe even during OOM. */
JSValue JS_RaiseOOM (JSContext *ctx) {
size_t used = (size_t)((uint8_t *)ctx->heap_free - (uint8_t *)ctx->heap_base);
size_t block = ctx->current_block_size;
size_t limit = ctx->heap_memory_limit;
const char *name = ctx->name ? ctx->name : ctx->id;
const char *label = ctx->actor_label;
if (limit > 0) {
fprintf(stderr, "out of memory: heap %zuKB / %zuKB block, limit %zuMB",
used / 1024, block / 1024, limit / (1024 * 1024));
} else {
fprintf(stderr, "out of memory: heap %zuKB / %zuKB block, no limit",
used / 1024, block / 1024);
}
fprintf(stderr, "[memory] %s: out of memory — heap %zuKB / %zuKB block",
name ? name : "?", used / 1024, block / 1024);
if (limit > 0)
fprintf(stderr, ", limit %zuMB", limit / (1024 * 1024));
if (label)
fprintf(stderr, " [%s]", label);
fprintf(stderr, "\n");
@@ -12008,7 +12009,7 @@ void JS_CrashPrintStack(JSContext *ctx) {
if (!ctx) return;
if (JS_IsNull(ctx->reg_current_frame)) return;
static const char hdr[] = "\n--- JS Stack at crash ---\n";
static const char hdr[] = "\n--- JS Stack ---\n";
write(STDERR_FILENO, hdr, sizeof(hdr) - 1);
JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(ctx->reg_current_frame);

View File

@@ -293,9 +293,15 @@ void *timer_thread_func(void *arg) {
/* Only fire if turn_gen still matches (stale timers are ignored) */
uint32_t cur = atomic_load_explicit(&t.actor->turn_gen, memory_order_relaxed);
if (cur == t.turn_gen) {
/* Can't call JS_Log from timer thread — use fprintf */
const char *name = t.actor->name ? t.actor->name : t.actor->id;
if (t.type == TIMER_PAUSE) {
fprintf(stderr, "[slow] %s: pausing (turn exceeded %llums)\n",
name, (unsigned long long)(ACTOR_FAST_TIMER_NS / 1000000));
JS_SetPauseFlag(t.actor, 1);
} else {
fprintf(stderr, "[slow] %s: kill timer fired (turn exceeded %llus)\n",
name, (unsigned long long)(ACTOR_SLOW_TIMER_NS / 1000000000));
t.actor->disrupt = 1;
JS_SetPauseFlag(t.actor, 2);
}
@@ -577,17 +583,11 @@ int actor_exists(const char *id)
void set_actor_state(JSContext *actor)
{
if (actor->disrupt) {
#ifdef SCHEDULER_DEBUG
fprintf(stderr, "set_actor_state: %s disrupted, freeing\n", actor->name ? actor->name : actor->id);
#endif
actor_free(actor);
return;
}
pthread_mutex_lock(actor->msg_mutex);
#ifdef SCHEDULER_DEBUG
fprintf(stderr, "set_actor_state: %s state=%d letters=%ld\n", actor->name ? actor->name : actor->id, actor->state, (long)arrlen(actor->letters));
#endif
switch(actor->state) {
case ACTOR_RUNNING:
case ACTOR_READY:
@@ -601,9 +601,6 @@ void set_actor_state(JSContext *actor)
actor->is_quiescent = 0;
atomic_fetch_sub(&engine.quiescent_count, 1);
}
#ifdef SCHEDULER_DEBUG
fprintf(stderr, "set_actor_state: %s IDLE->READY, enqueueing (main=%d)\n", actor->name ? actor->name : actor->id, actor->main_thread_only);
#endif
actor->state = ACTOR_READY;
actor->ar = 0;
enqueue_actor_priority(actor);
@@ -846,6 +843,8 @@ void actor_turn(JSContext *actor)
if (JS_IsSuspended(result)) {
/* Still suspended after kill timer — shouldn't happen, kill it */
fprintf(stderr, "[slow] %s: still suspended after resume, killing\n",
actor->name ? actor->name : actor->id);
actor->disrupt = 1;
goto ENDTURN;
}
@@ -854,16 +853,12 @@ void actor_turn(JSContext *actor)
actor->disrupt = 1;
}
actor->slow_strikes++;
#ifdef ACTOR_TRACE
fprintf(stderr, "[ACTOR_TRACE] %s: slow strike %d/%d\n",
actor->name ? actor->name : actor->id,
actor->slow_strikes, ACTOR_SLOW_STRIKES_MAX);
#endif
JS_Log(actor, "slow", "%s: slow strike %d/%d",
actor->name ? actor->name : actor->id,
actor->slow_strikes, ACTOR_SLOW_STRIKES_MAX);
if (actor->slow_strikes >= ACTOR_SLOW_STRIKES_MAX) {
#ifdef ACTOR_TRACE
fprintf(stderr, "[ACTOR_TRACE] %s: %d slow strikes, killing\n",
actor->name ? actor->name : actor->id, actor->slow_strikes);
#endif
JS_Log(actor, "slow", "%s: killed after %d consecutive slow turns",
actor->name ? actor->name : actor->id, actor->slow_strikes);
actor->disrupt = 1;
}
goto ENDTURN;
@@ -876,10 +871,6 @@ void actor_turn(JSContext *actor)
pthread_mutex_unlock(actor->msg_mutex);
goto ENDTURN;
}
#ifdef SCHEDULER_DEBUG
fprintf(stderr, "actor_turn: %s has %d letters, type=%d\n",
actor->name ? actor->name : actor->id, pending, actor->letters[0].type);
#endif
letter l = actor->letters[0];
arrdel(actor->letters, 0);
pthread_mutex_unlock(actor->msg_mutex);
@@ -937,29 +928,22 @@ ENDTURN:
actor->actor_trace_hook(actor, CELL_HOOK_EXIT);
if (actor->disrupt) {
#ifdef SCHEDULER_DEBUG
fprintf(stderr, "actor_turn ENDTURN: %s disrupted, freeing\n",
actor->name ? actor->name : actor->id);
#endif
pthread_mutex_unlock(actor->mutex);
actor_free(actor);
return;
}
#ifdef SCHEDULER_DEBUG
fprintf(stderr, "actor_turn ENDTURN: %s has %ld letters, calling set_actor_state\n",
actor->name ? actor->name : actor->id, (long)arrlen(actor->letters));
#endif
set_actor_state(actor);
pthread_mutex_unlock(actor->mutex);
return;
ENDTURN_SLOW:
g_crash_ctx = NULL;
#ifdef ACTOR_TRACE
fprintf(stderr, "[ACTOR_TRACE] %s: suspended mid-turn -> SLOW\n",
actor->name ? actor->name : actor->id);
#endif
/* VM suspended mid-turn — can't call JS_Log, use fprintf.
Print stack trace while frames are still intact. */
fprintf(stderr, "[slow] %s: suspended mid-turn, entering slow queue (strike %d/%d)\n",
actor->name ? actor->name : actor->id,
actor->slow_strikes + 1, ACTOR_SLOW_STRIKES_MAX);
if (actor->actor_trace_hook)
actor->actor_trace_hook(actor, CELL_HOOK_EXIT);
enqueue_actor_priority(actor);
@@ -1054,3 +1038,57 @@ JSValue actor_remove_timer(JSContext *actor, uint32_t timer_id)
// Note: We don't remove from heap, it will misfire safely
return cb;
}
int JS_ActorExists(const char *actor_id)
{
return actor_exists(actor_id);
}
const char *JS_SendMessage(JSContext *ctx, WotaBuffer *wb)
{
if (!wb || !wb->data || wb->size == 0)
return "Empty WOTA buffer";
/* Wrap the caller's payload in the engine protocol envelope:
{type: "user", data: <payload>}
The header takes ~6 words; pre-allocate enough for header + payload. */
WotaBuffer envelope;
wota_buffer_init(&envelope, wb->size + 8);
wota_write_record(&envelope, 2);
wota_write_text(&envelope, "type");
wota_write_text(&envelope, "user");
wota_write_text(&envelope, "data");
/* Append the caller's pre-encoded WOTA payload words directly. */
size_t need = envelope.size + wb->size;
if (need > envelope.capacity) {
size_t new_cap = envelope.capacity ? envelope.capacity * 2 : 8;
while (new_cap < need) new_cap *= 2;
envelope.data = realloc(envelope.data, new_cap * sizeof(uint64_t));
envelope.capacity = new_cap;
}
memcpy(envelope.data + envelope.size, wb->data,
wb->size * sizeof(uint64_t));
envelope.size += wb->size;
size_t byte_len = envelope.size * sizeof(uint64_t);
blob *msg = blob_new(byte_len * 8);
if (!msg) {
wota_buffer_free(&envelope);
return "Could not allocate blob";
}
blob_write_bytes(msg, envelope.data, byte_len);
blob_make_stone(msg);
wota_buffer_free(&envelope);
const char *err = send_message(ctx->id, msg);
if (!err) {
/* Success — send_message took ownership of the blob.
Free the WotaBuffer internals since we consumed them. */
wota_buffer_free(wb);
}
/* On failure, send_message already destroyed the blob.
Caller still owns wb and must free it. */
return err;
}

View File

@@ -243,7 +243,7 @@ var type_annotation = function(slot_types, instr) {
if (is_number(v)) {
t = slot_types[text(v)]
if (t != null && t != T_UNKNOWN) {
push(parts, `s${text(v)}:${t}`)
parts[] = `s${text(v)}:${t}`
}
}
j = j + 1
@@ -290,7 +290,7 @@ var dump_function_typed = function(func, name) {
operand_parts = []
j = 1
while (j < n - 2) {
push(operand_parts, fmt_val(instr[j]))
operand_parts[] = fmt_val(instr[j])
j = j + 1
}
operands = text(operand_parts, ", ")

View File

@@ -278,7 +278,11 @@ var streamline = function(ir, log) {
store_index: [1, T_ARRAY, 2, T_INT], store_field: [1, T_RECORD],
push: [1, T_ARRAY],
load_index: [2, T_ARRAY, 3, T_INT], load_field: [2, T_RECORD],
pop: [2, T_ARRAY]
pop: [2, T_ARRAY],
is_text: [2, T_UNKNOWN], is_int: [2, T_UNKNOWN], is_num: [2, T_UNKNOWN],
is_bool: [2, T_UNKNOWN], is_null: [2, T_UNKNOWN],
is_array: [2, T_UNKNOWN], is_func: [2, T_UNKNOWN],
is_record: [2, T_UNKNOWN], is_blob: [2, T_UNKNOWN]
}
var infer_param_types = function(func) {

40
test.ce
View File

@@ -62,7 +62,7 @@ function parse_args() {
} else if (_args[i] == '--diff') {
diff_mode = true
} else {
push(cleaned_args, _args[i])
cleaned_args[] = _args[i]
}
}
_args = cleaned_args
@@ -247,11 +247,11 @@ function collect_actor_tests(package_name, specific_test) {
if (test_base != match_base) continue
}
push(actor_tests, {
actor_tests[] = {
package: package_name || "local",
file: f,
path: prefix + '/' + f
})
}
}
}
return actor_tests
@@ -296,16 +296,16 @@ function spawn_actor_test(test_info) {
entry.error = { message: event.reason || "Actor disrupted" }
log.console(` FAIL ${test_name}: ${entry.error.message}`)
}
push(actor_test_results, entry)
actor_test_results[] = entry
if (gc_after_each_test) dbg.gc()
check_completion()
}, actor_path)
push(pending_actor_tests, entry)
pending_actor_tests[] = entry
} disruption {
entry.status = "failed"
entry.error = { message: "Failed to spawn actor" }
entry.duration_ns = 0
push(actor_test_results, entry)
actor_test_results[] = entry
log.console(` FAIL ${test_name}: Failed to spawn`)
}
_spawn()
@@ -347,7 +347,7 @@ function run_tests(package_name, specific_test) {
match_base = ends_with(match_name, '.cm') ? text(match_name, 0, -3) : match_name
if (test_name != match_base) continue
}
push(test_files, f)
test_files[] = f
}
}
@@ -408,14 +408,14 @@ function run_tests(package_name, specific_test) {
var first_null_key = null
var first_other_key = null
if (is_function(test_mod)) {
push(tests, {name: 'main', fn: test_mod})
tests[] = {name: 'main', fn: test_mod}
} else if (is_object(test_mod)) {
all_keys = array(test_mod)
log.console(` Found ${length(all_keys)} test entries`)
arrfor(all_keys, function(k) {
if (is_function(test_mod[k])) {
fn_count = fn_count + 1
push(tests, {name: k, fn: test_mod[k]})
tests[] = {name: k, fn: test_mod[k]}
} else if (is_null(test_mod[k])) {
null_count = null_count + 1
if (!first_null_key) first_null_key = k
@@ -494,7 +494,7 @@ function run_tests(package_name, specific_test) {
file_result.failed = file_result.failed + 1
}
push(file_result.tests, test_entry)
file_result.tests[] = test_entry
pkg_result.total = pkg_result.total + 1
if (gc_after_each_test) {
dbg.gc()
@@ -512,7 +512,7 @@ function run_tests(package_name, specific_test) {
file_result.failed = file_result.failed + 1
pkg_result.total = pkg_result.total + 1
}
push(pkg_result.files, file_result)
pkg_result.files[] = file_result
}
return pkg_result
}
@@ -525,18 +525,18 @@ var i = 0
if (all_pkgs) {
// Run local first if we're in a valid package
if (is_valid_package('.')) {
push(all_results, run_tests(null, null))
all_results[] = run_tests(null, null)
all_actor_tests = array(all_actor_tests, collect_actor_tests(null, null))
}
// Then all packages in lock
packages = shop.list_packages()
for (i = 0; i < length(packages); i++) {
push(all_results, run_tests(packages[i], null))
all_results[] = run_tests(packages[i], null)
all_actor_tests = array(all_actor_tests, collect_actor_tests(packages[i], null))
}
} else {
push(all_results, run_tests(target_pkg, target_test))
all_results[] = run_tests(target_pkg, target_test)
all_actor_tests = array(all_actor_tests, collect_actor_tests(target_pkg, target_test))
}
@@ -561,7 +561,7 @@ function check_timeouts() {
entry = pending_actor_tests[i]
elapsed_ms = (now - entry.start_time) * 1000
if (elapsed_ms > ACTOR_TEST_TIMEOUT) {
push(timed_out, i)
timed_out[] = i
}
}
@@ -573,7 +573,7 @@ function check_timeouts() {
entry.status = "failed"
entry.error = { message: "Test timed out" }
entry.duration_ns = ACTOR_TEST_TIMEOUT * 1000000
push(actor_test_results, entry)
actor_test_results[] = entry
log.console(` TIMEOUT ${entry.test}`)
}
@@ -612,7 +612,7 @@ function finalize_results() {
}
if (!pkg_result) {
pkg_result = { package: r.package, files: [], total: 0, passed: 0, failed: 0 }
push(all_results, pkg_result)
all_results[] = pkg_result
}
file_result = null
@@ -624,10 +624,10 @@ function finalize_results() {
}
if (!file_result) {
file_result = { name: r.file, tests: [], passed: 0, failed: 0 }
push(pkg_result.files, file_result)
pkg_result.files[] = file_result
}
push(file_result.tests, r)
file_result.tests[] = r
pkg_result.total = pkg_result.total + 1
if (r.status == "passed") {
pkg_result.passed = pkg_result.passed + 1
@@ -760,7 +760,7 @@ Total: ${totals.total}, Passed: ${totals.passed}, Failed: ${totals.failed}
for (j = 0; j < length(pkg_res.files); j++) {
f = pkg_res.files[j]
for (k = 0; k < length(f.tests); k++) {
push(pkg_tests, f.tests[k])
pkg_tests[] = f.tests[k]
}
}

251
tests/cellfs_test.ce Normal file
View File

@@ -0,0 +1,251 @@
// Test: cellfs mounting, sync access, and async requestors
//
// Known limitation:
// - is_directory() uses raw path instead of res.path for fs mounts,
// so @-prefixed paths fail (e.g. '@mount/dir'). Works via stat().
// - cellfs.slurp() has no http code path; use cellfs.get() for http mounts.
var cellfs = use('cellfs')
var fd = use('fd')
var pkg_dir = '.cell/packages/gitea.pockle.world/john/prosperon'
// Mount the prosperon package directory as 'prosperon'
cellfs.mount(pkg_dir, 'prosperon')
// --- exists ---
var found = cellfs.exists('@prosperon/color.cm')
if (!found) {
log.error("exists('@prosperon/color.cm') returned false")
disrupt
}
log.console("exists: ok")
// exists returns false for missing files
var missing = cellfs.exists('@prosperon/no_such_file.cm')
if (missing) {
log.error("exists returned true for missing file")
disrupt
}
log.console("exists (missing): ok")
// --- slurp ---
var data = cellfs.slurp('@prosperon/color.cm')
if (!is_blob(data)) {
log.error("slurp did not return a blob")
disrupt
}
if (length(data) == 0) {
log.error("slurp returned empty blob")
disrupt
}
log.console(`slurp: ok (${length(data)} bits)`)
// --- enumerate ---
var files = cellfs.enumerate('@prosperon', false)
if (!is_array(files)) {
log.error("enumerate did not return an array")
disrupt
}
if (length(files) == 0) {
log.error("enumerate returned empty array")
disrupt
}
// color.cm should be in the listing
var found_color = false
arrfor(files, function(f) {
if (f == 'color.cm') {
found_color = true
return true
}
}, false, true)
if (!found_color) {
log.error("enumerate did not include color.cm")
disrupt
}
log.console(`enumerate: ok (${length(files)} entries, found color.cm)`)
// enumerate recursive
var rfiles = cellfs.enumerate('@prosperon', true)
if (length(rfiles) <= length(files)) {
log.error("recursive enumerate should return more entries")
disrupt
}
log.console(`enumerate recursive: ok (${length(rfiles)} entries)`)
// --- stat ---
var st = cellfs.stat('@prosperon/color.cm')
if (!is_object(st)) {
log.error("stat did not return an object")
disrupt
}
if (st.filesize == null || st.filesize == 0) {
log.error("stat filesize missing or zero")
disrupt
}
log.console(`stat: ok (size=${st.filesize}, mtime=${st.modtime})`)
// stat on a directory
var dir_st = cellfs.stat('@prosperon/docs')
if (!dir_st.isDirectory) {
log.error("stat('@prosperon/docs').isDirectory returned false")
disrupt
}
log.console("stat (directory): ok")
// --- searchpath ---
var sp = cellfs.searchpath()
if (!is_array(sp)) {
log.error("searchpath did not return an array")
disrupt
}
if (length(sp) == 0) {
log.error("searchpath returned empty array")
disrupt
}
log.console(`searchpath: ok (${length(sp)} mounts)`)
// --- resolve ---
var res = cellfs.resolve('@prosperon/color.cm')
if (!is_object(res)) {
log.error("resolve did not return an object")
disrupt
}
if (res.mount.name != 'prosperon') {
log.error("resolve returned wrong mount name")
disrupt
}
if (res.path != 'color.cm') {
log.error("resolve returned wrong path")
disrupt
}
log.console("resolve: ok")
// --- realdir ---
var rd = cellfs.realdir('@prosperon/color.cm')
if (!is_text(rd)) {
log.error("realdir did not return text")
disrupt
}
if (!ends_with(rd, 'color.cm')) {
log.error("realdir does not end with color.cm")
disrupt
}
log.console(`realdir: ok (${rd})`)
// --- unmount and re-mount ---
cellfs.unmount('prosperon')
var after_unmount = cellfs.searchpath()
var unmount_ok = true
arrfor(after_unmount, function(m) {
if (m.name == 'prosperon') {
unmount_ok = false
return true
}
}, false, true)
if (!unmount_ok) {
log.error("unmount failed, mount still present")
disrupt
}
log.console("unmount: ok")
// re-mount for further tests
cellfs.mount(pkg_dir, 'prosperon')
// --- match (wildstar) ---
var m1 = cellfs.match('color.cm', '*.cm')
if (!m1) {
log.error("match('color.cm', '*.cm') returned false")
disrupt
}
var m2 = cellfs.match('color.cm', '*.ce')
if (m2) {
log.error("match('color.cm', '*.ce') returned true")
disrupt
}
log.console("match: ok")
// --- globfs ---
var cm_files = cellfs.globfs(['*.cm'], '@prosperon')
if (!is_array(cm_files)) {
log.error("globfs did not return an array")
disrupt
}
if (length(cm_files) == 0) {
log.error("globfs returned empty array")
disrupt
}
// all results should end in .cm
var all_cm = true
arrfor(cm_files, function(f) {
if (!ends_with(f, '.cm')) {
all_cm = false
return true
}
}, false, true)
if (!all_cm) {
log.error("globfs returned non-.cm files")
disrupt
}
log.console(`globfs: ok (${length(cm_files)} .cm files)`)
log.console("--- sync tests passed ---")
// --- Requestor tests ---
// get requestor for a local fs mount
var get_color = cellfs.get('@prosperon/color.cm')
get_color(function(result, reason) {
if (reason != null) {
log.error(`get color.cm failed: ${reason}`)
disrupt
}
if (!is_blob(result)) {
log.error("get did not return a blob")
disrupt
}
if (length(result) == 0) {
log.error("get returned empty blob")
disrupt
}
log.console(`get (fs): ok (${length(result)} bits)`)
// parallel requestor test - fetch multiple files at once
var get_core = cellfs.get('@prosperon/core.cm')
var get_ease = cellfs.get('@prosperon/ease.cm')
parallel([get_color, get_core, get_ease])(function(results, reason) {
if (reason != null) {
log.error(`parallel get failed: ${reason}`)
disrupt
}
if (length(results) != 3) {
log.error(`parallel expected 3 results, got ${length(results)}`)
disrupt
}
log.console(`parallel get: ok (${length(results)} files fetched)`)
// HTTP mount test — network may not be available in test env
cellfs.mount('http://example.com', 'web')
var web_res = cellfs.resolve('@web/')
if (web_res.mount.type != 'http') {
log.error("http mount type is not 'http'")
disrupt
}
log.console("http mount: ok (type=http)")
var get_web = cellfs.get('@web/')
get_web(function(body, reason) {
if (reason != null) {
log.console(`get (http): skipped (${reason})`)
} else {
log.console(`get (http): ok (${length(body)} bits)`)
}
log.console("--- requestor tests passed ---")
log.console("all cellfs tests passed")
$stop()
})
})
})

View File

@@ -123,7 +123,7 @@ var testarr = []
var i = 0
var t = null
for (i = 0; i < 500; i++) {
push(testarr, 1)
testarr[] = 1
}
var testCases = [

View File

@@ -1,5 +1,5 @@
// Test pronto functions
// Tests for fallback, parallel, race, sequence, time_limit, requestorize, objectify
// Tests for fallback, parallel, race, sequence
var test_count = 0
@@ -89,49 +89,17 @@ return {
}, 1000)
},
test_time_limit: function() {
log.console("Testing time_limit...")
var slow_req = make_requestor("slow_req", 0.5, true) // takes 0.5s
var timed_req = time_limit(slow_req, 0.2) // 0.2s limit
test_immediate_requestors: function() {
log.console("Testing immediate requestors...")
var req1 = make_requestor("imm1", 0, true)
var req2 = make_requestor("imm2", 0, true)
timed_req(function(result, reason) {
sequence([req1, req2])(function(result, reason) {
if (result != null) {
log.console(`Time limit succeeded: ${result}`)
log.console(`Immediate sequence result: ${result}`)
} else {
log.console(`Time limit failed: ${reason}`)
log.console(`Immediate sequence failed: ${reason}`)
}
}, 100)
},
test_requestorize: function() {
log.console("Testing requestorize...")
var add_one = function(x) { return x + 1 }
var req = requestorize(add_one)
req(function(result, reason) {
if (result != null) {
log.console(`Requestorize result: ${result}`)
} else {
log.console(`Requestorize failed: ${reason}`)
}
}, 42)
},
test_objectify: function() {
log.console("Testing objectify...")
var req_a = make_requestor("obj_req_a", 0.1, true)
var req_b = make_requestor("obj_req_b", 0.1, true)
var req_c = make_requestor("obj_req_c", 0.1, true)
var parallel_obj = objectify(parallel)
var req = parallel_obj({a: req_a, b: req_b, c: req_c})
req(function(result, reason) {
if (result != null) {
log.console(`Objectify result: ${result}`)
} else {
log.console(`Objectify failed: ${reason}`)
}
}, 1000)
}, 0)
}
}

View File

@@ -2918,7 +2918,7 @@ return {
test_arrfor_with_index: function() {
var arr = ["a", "b", "c"]
var indices = []
arrfor(arr, (x, i) => { push(indices, i) })
arrfor(arr, (x, i) => { indices[] = i })
if (indices[0] != 0 || indices[2] != 2) return "arrfor with index failed"
},
@@ -2931,7 +2931,7 @@ return {
test_arrfor_mutation: function() {
var arr = [1, 2, 3]
var results = []
arrfor(arr, x => { push(results, x * 2) })
arrfor(arr, x => { results[] = x * 2 })
if (results[0] != 2 || results[1] != 4 || results[2] != 6) return "arrfor mutation failed"
},

View File

@@ -65,7 +65,7 @@ var testarr = []
var i = 0
var t = null
var name = null
for (i = 0; i < 500; i++) { push(testarr, 1) }
for (i = 0; i < 500; i++) { testarr[] = 1 }
var testCases = [
{ name: 'zero', input: 0 },

View File

@@ -103,26 +103,26 @@ var tokenize = function(src, filename) {
run_start = pos
while (pos < len && pk() != quote) {
if (pk() == "\\") {
if (pos > run_start) push(parts, text(src, run_start, pos))
if (pos > run_start) parts[] = text(src, run_start, pos)
adv()
esc = adv()
esc_val = escape_map[esc]
if (esc_val != null) { push(parts, esc_val) }
else if (esc == "u") { push(parts, read_unicode_escape()) }
else { push(parts, esc) }
if (esc_val != null) { parts[] = esc_val }
else if (esc == "u") { parts[] = read_unicode_escape() }
else { parts[] = esc }
run_start = pos
} else {
adv()
}
}
if (pos > run_start) push(parts, text(src, run_start, pos))
if (pos > run_start) parts[] = text(src, run_start, pos)
if (pos < len) adv() // skip closing quote
push(tokens, {
tokens[] = {
kind: "text", at: start,
from_row: start_row, from_column: start_col,
to_row: row, to_column: col,
value: text(parts)
})
}
}
var read_template = function() {
@@ -139,12 +139,12 @@ var tokenize = function(src, filename) {
run_start = pos
while (pos < len && pk() != "`") {
if (pk() == "\\" && pos + 1 < len) {
if (pos > run_start) push(parts, text(src, run_start, pos))
push(parts, text(src, pos, pos + 2))
if (pos > run_start) parts[] = text(src, run_start, pos)
parts[] = text(src, pos, pos + 2)
adv(); adv()
run_start = pos
} else if (pk() == "$" && pos + 1 < len && pk_at(1) == "{") {
if (pos > run_start) push(parts, text(src, run_start, pos))
if (pos > run_start) parts[] = text(src, run_start, pos)
interp_start = pos
adv(); adv() // $ {
depth = 1
@@ -164,20 +164,20 @@ var tokenize = function(src, filename) {
if (pos < len) adv()
} else { adv() }
}
push(parts, text(src, interp_start, pos))
parts[] = text(src, interp_start, pos)
run_start = pos
} else {
adv()
}
}
if (pos > run_start) push(parts, text(src, run_start, pos))
if (pos > run_start) parts[] = text(src, run_start, pos)
if (pos < len) adv() // skip closing backtick
push(tokens, {
tokens[] = {
kind: "text", at: start,
from_row: start_row, from_column: start_col,
to_row: row, to_column: col,
value: text(parts)
})
}
}
var read_number = function() {
@@ -207,12 +207,12 @@ var tokenize = function(src, filename) {
}
}
raw = substr(start, pos)
push(tokens, {
tokens[] = {
kind: "number", at: start,
from_row: start_row, from_column: start_col,
to_row: row, to_column: col,
value: raw, number: number(raw)
})
}
}
var read_name = function() {
@@ -225,18 +225,18 @@ var tokenize = function(src, filename) {
name = substr(start, pos)
kw = keywords[name]
if (kw != null) {
push(tokens, {
tokens[] = {
kind: kw, at: start,
from_row: start_row, from_column: start_col,
to_row: row, to_column: col
})
}
} else {
push(tokens, {
tokens[] = {
kind: "name", at: start,
from_row: start_row, from_column: start_col,
to_row: row, to_column: col,
value: name
})
}
}
}
@@ -258,12 +258,12 @@ var tokenize = function(src, filename) {
}
}
raw = substr(start, pos)
push(tokens, {
tokens[] = {
kind: "comment", at: start,
from_row: start_row, from_column: start_col,
to_row: row, to_column: col,
value: raw
})
}
}
var emit_op = function(kind, count) {
@@ -272,11 +272,11 @@ var tokenize = function(src, filename) {
var start_col = col
var i = 0
while (i < count) { adv(); i = i + 1 }
push(tokens, {
tokens[] = {
kind: kind, at: start,
from_row: start_row, from_column: start_col,
to_row: row, to_column: col
})
}
}
var emit_ident = function(count) {
@@ -285,12 +285,12 @@ var tokenize = function(src, filename) {
var start_col = col
var i = 0
while (i < count) { adv(); i = i + 1 }
push(tokens, {
tokens[] = {
kind: "name", at: start,
from_row: start_row, from_column: start_col,
to_row: row, to_column: col,
value: text(src, start, pos)
})
}
}
var tokenize_one = function() {
@@ -304,21 +304,21 @@ var tokenize = function(src, filename) {
if (c == "\n") {
start = pos; start_row = row; start_col = col
adv()
push(tokens, { kind: "newline", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: "\n" })
tokens[] = { kind: "newline", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: "\n" }
return true
}
if (c == "\r") {
start = pos; start_row = row; start_col = col
adv()
if (pos < len && pk() == "\n") adv()
push(tokens, { kind: "newline", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: "\n" })
tokens[] = { kind: "newline", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: "\n" }
return true
}
if (c == " " || c == "\t") {
start = pos; start_row = row; start_col = col
while (pos < len && (pk() == " " || pk() == "\t")) adv()
raw = substr(start, pos)
push(tokens, { kind: "space", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: raw })
tokens[] = { kind: "space", at: start, from_row: start_row, from_column: start_col, to_row: row, to_column: col, value: raw }
return true
}
if (c == "'" || c == "\"") { read_string(c); return true }
@@ -446,7 +446,7 @@ var tokenize = function(src, filename) {
}
// EOF token
push(tokens, { kind: "eof", at: pos, from_row: row, from_column: col, to_row: row, to_column: col })
tokens[] = { kind: "eof", at: pos, from_row: row, from_column: col, to_row: row, to_column: col }
return {filename: filename, tokens: tokens}
}

16
toml.cm
View File

@@ -127,7 +127,7 @@ function parse_key_path(str) {
} else if (c == '.' && !in_quote) {
piece = trim(current)
if (piece == null) piece = trim(current)
push(parts, parse_key(piece))
parts[] = parse_key(piece)
current = ''
continue
}
@@ -136,7 +136,7 @@ function parse_key_path(str) {
var tail = trim(current)
if (tail == null) tail = trim(current)
if (length(tail) > 0) push(parts, parse_key(tail))
if (length(tail) > 0) parts[] = parse_key(tail)
return parts
}
@@ -165,7 +165,7 @@ function parse_array(str) {
} else if (ch == ',' && !in_quotes) {
piece = trim(current)
if (piece == null) piece = trim(current)
push(items, parse_value(piece))
items[] = parse_value(piece)
current = ''
} else {
current = current + ch
@@ -174,7 +174,7 @@ function parse_array(str) {
var last = trim(current)
if (last == null) last = trim(current)
if (last) push(items, parse_value(last))
if (last) items[] = parse_value(last)
return items
}
@@ -204,7 +204,7 @@ function encode_toml(obj) {
if (is_number(value)) return text(value)
if (is_array(value)) {
items = []
for (i = 0; i < length(value); i++) push(items, encode_value(value[i]))
for (i = 0; i < length(value); i++) items[] = encode_value(value[i])
return '[' + text(items, ', ') + ']'
}
return text(value)
@@ -230,7 +230,7 @@ function encode_toml(obj) {
for (i = 0; i < length(keys); i++) {
key = keys[i]
value = obj[key]
if (!is_object(value)) push(result, quote_key(key) + ' = ' + encode_value(value))
if (!is_object(value)) result[] = quote_key(key) + ' = ' + encode_value(value)
}
// Second pass: encode nested objects
@@ -252,14 +252,14 @@ function encode_toml(obj) {
if (is_object(value)) {
quoted = quote_key(key)
section_path = path ? path + '.' + quoted : quoted
push(result, '[' + section_path + ']')
result[] = '[' + section_path + ']'
// Direct properties
section_keys = array(value)
for (j = 0; j < length(section_keys); j++) {
sk = section_keys[j]
sv = value[sk]
if (!is_object(sv)) push(result, quote_key(sk) + ' = ' + encode_value(sv))
if (!is_object(sv)) result[] = quote_key(sk) + ' = ' + encode_value(sv)
}
// Nested sections

View File

@@ -20,7 +20,6 @@ var target_triple = null
var follow_links = false
var git_pull = false
var i = 0
var updated = 0
var packages = null
var run = function() {
@@ -54,7 +53,14 @@ var run = function() {
if (target_pkg)
target_pkg = shop.resolve_locator(target_pkg)
function update_one(pkg) {
if (target_pkg) {
update_single(target_pkg)
} else {
update_all()
}
}
function check_one(pkg) {
var effective_pkg = pkg
var link_target = null
var lock = shop.load_lock()
@@ -62,59 +68,84 @@ function update_one(pkg) {
var old_commit = old_entry ? old_entry.commit : null
var info = shop.resolve_package_info(pkg)
var new_entry = null
var old_str = null
if (follow_links) {
link_target = link.get_target(pkg)
if (link_target) {
effective_pkg = link_target
log.console(" Following link: " + pkg + " -> " + effective_pkg)
}
}
// For local packages with --git, pull first
if (git_pull && info == 'local' && fd.is_dir(effective_pkg + '/.git')) {
log.console(" " + effective_pkg + " (git pull)")
log.build(" " + effective_pkg + " (git pull)")
os.system('git -C "' + effective_pkg + '" pull')
}
// Check for update (sets lock entry if changed)
new_entry = shop.update(effective_pkg)
if (new_entry && new_entry.commit) {
old_str = old_commit ? text(old_commit, 0, 8) : "(new)"
log.console(" " + effective_pkg + " " + old_str + " -> " + text(new_entry.commit, 0, 8))
return {
pkg: pkg,
effective: effective_pkg,
old_commit: old_commit,
new_entry: new_entry,
changed: new_entry != null && new_entry.commit != null
}
// Sync: fetch, extract, build
shop.sync(effective_pkg, {target: target_triple})
return new_entry
}
if (target_pkg) {
if (update_one(target_pkg)) {
log.console("Updated " + target_pkg + ".")
function sync_one(effective_pkg) {
shop.sync(effective_pkg, {target: target_triple})
}
function update_single(pkg) {
var result = check_one(pkg)
var old_str = null
if (result.changed) {
old_str = result.old_commit ? text(result.old_commit, 0, 8) : "(new)"
log.console("==> Upgrading " + result.effective)
log.console(" " + old_str + " -> " + text(result.new_entry.commit, 0, 8))
log.console("==> Syncing " + result.effective)
sync_one(result.effective)
log.console("Updated " + result.effective + ".")
} else {
log.console(target_pkg + " is up to date.")
log.console(pkg + " is up to date.")
}
} else {
}
function update_all() {
var results = []
var changed = []
var result = null
packages = shop.list_packages()
log.console("Checking for updates (" + text(length(packages)) + " packages)...")
log.console("Checking for updates...")
arrfor(packages, function(pkg) {
if (pkg == 'core') return
if (update_one(pkg))
updated = updated + 1
result = check_one(pkg)
results[] = result
if (result.changed)
changed[] = result
})
if (updated > 0) {
log.console("Updated " + text(updated) + " package(s).")
} else {
if (length(changed) == 0) {
log.console("All packages are up to date.")
return
}
log.console("==> Upgrading " + text(length(changed)) + " outdated package" + (length(changed) != 1 ? "s" : "") + ":")
arrfor(changed, function(r) {
var old_str = r.old_commit ? text(r.old_commit, 0, 8) : "(new)"
log.console(" " + r.effective + " " + old_str + " -> " + text(r.new_entry.commit, 0, 8))
})
arrfor(changed, function(r) {
log.console("==> Syncing " + r.effective)
sync_one(r.effective)
})
log.console("Updated " + text(length(changed)) + " package(s).")
}
}
run()
$stop()

View File

@@ -1,5 +1,6 @@
var shop = use('internal/shop')
return {
file_reload: shop.file_reload
file_reload: shop.file_reload,
reload: shop.module_reload
}

View File

@@ -67,11 +67,11 @@ var warnings = []
var checked = 0
function add_error(msg) {
push(errors, msg)
errors[] = msg
}
function add_warning(msg) {
push(warnings, msg)
warnings[] = msg
}
// Verify a single package
@@ -211,12 +211,12 @@ if (scope == 'shop') {
if (deep) {
// Gather all dependencies
all_deps = pkg.gather_dependencies(locator)
push(packages_to_verify, locator)
packages_to_verify[] = locator
arrfor(all_deps, function(dep) {
push(packages_to_verify, dep)
packages_to_verify[] = dep
})
} else {
push(packages_to_verify, locator)
packages_to_verify[] = locator
}
}

View File

@@ -172,7 +172,7 @@ var check_slot_bounds = function(func) {
if (pos < length(instr) - 2) {
val = instr[pos]
if (is_number(val) && (val < 0 || val >= nr_slots)) {
push(errors, `slot_bounds: instr ${text(i)} op=${op} slot[${text(positions[j])}]=${text(val)} out of range 0..${text(nr_slots - 1)}`)
errors[] = `slot_bounds: instr ${text(i)} op=${op} slot[${text(positions[j])}]=${text(val)} out of range 0..${text(nr_slots - 1)}`
}
}
j = j + 1
@@ -217,7 +217,7 @@ var check_jump_targets = function(func) {
if (label_pos != null) {
target = instr[label_pos + 1]
if (is_text(target) && labels[target] != true) {
push(errors, `jump_targets: instr ${text(i)} op=${op} target label "${target}" not found`)
errors[] = `jump_targets: instr ${text(i)} op=${op} target label "${target}" not found`
}
}
}
@@ -301,46 +301,46 @@ var check_type_consistency = function(func) {
s2 = text(instr[2])
t2 = slot_types[s2]
if (t2 != null && t2 != T_INT && t2 != "unknown") {
push(errors, `type_consistency: instr ${text(i)} op=${op} src1 slot ${s2} has type ${t2}, expected int`)
errors[] = `type_consistency: instr ${text(i)} op=${op} src1 slot ${s2} has type ${t2}, expected int`
}
if (length(instr) >= 6) {
s3 = text(instr[3])
t3 = slot_types[s3]
if (t3 != null && t3 != T_INT && t3 != "unknown") {
push(errors, `type_consistency: instr ${text(i)} op=${op} src2 slot ${s3} has type ${t3}, expected int`)
errors[] = `type_consistency: instr ${text(i)} op=${op} src2 slot ${s3} has type ${t3}, expected int`
}
}
} else if (float_ops[op] == true && length(instr) >= 5) {
s2 = text(instr[2])
t2 = slot_types[s2]
if (t2 != null && t2 != T_FLOAT && t2 != "unknown") {
push(errors, `type_consistency: instr ${text(i)} op=${op} src1 slot ${s2} has type ${t2}, expected float`)
errors[] = `type_consistency: instr ${text(i)} op=${op} src1 slot ${s2} has type ${t2}, expected float`
}
if (length(instr) >= 6) {
s3 = text(instr[3])
t3 = slot_types[s3]
if (t3 != null && t3 != T_FLOAT && t3 != "unknown") {
push(errors, `type_consistency: instr ${text(i)} op=${op} src2 slot ${s3} has type ${t3}, expected float`)
errors[] = `type_consistency: instr ${text(i)} op=${op} src2 slot ${s3} has type ${t3}, expected float`
}
}
} else if (text_ops[op] == true && length(instr) >= 5) {
s2 = text(instr[2])
t2 = slot_types[s2]
if (t2 != null && t2 != T_TEXT && t2 != "unknown") {
push(errors, `type_consistency: instr ${text(i)} op=${op} src1 slot ${s2} has type ${t2}, expected text`)
errors[] = `type_consistency: instr ${text(i)} op=${op} src1 slot ${s2} has type ${t2}, expected text`
}
if (length(instr) >= 6) {
s3 = text(instr[3])
t3 = slot_types[s3]
if (t3 != null && t3 != T_TEXT && t3 != "unknown") {
push(errors, `type_consistency: instr ${text(i)} op=${op} src2 slot ${s3} has type ${t3}, expected text`)
errors[] = `type_consistency: instr ${text(i)} op=${op} src2 slot ${s3} has type ${t3}, expected text`
}
}
} else if (bool_ops[op] == true && length(instr) >= 5) {
s2 = text(instr[2])
t2 = slot_types[s2]
if (t2 != null && t2 != T_BOOL && t2 != "unknown") {
push(errors, `type_consistency: instr ${text(i)} op=${op} src1 slot ${s2} has type ${t2}, expected bool`)
errors[] = `type_consistency: instr ${text(i)} op=${op} src1 slot ${s2} has type ${t2}, expected bool`
}
}
@@ -394,7 +394,7 @@ var check_nop_consistency = function(func) {
if (label_pos != null) {
target = instr[label_pos + 1]
if (is_text(target) && nops[target] == true) {
push(errors, `nop_consistency: instr ${text(i)} op=${op} jumps to nop marker "${target}"`)
errors[] = `nop_consistency: instr ${text(i)} op=${op} jumps to nop marker "${target}"`
}
}
}
@@ -415,28 +415,28 @@ var verify_all = function(func, pass_name) {
check_errors = check_slot_bounds(func)
i = 0
while (i < length(check_errors)) {
push(all_errors, `${prefix}${fn_name}: ${check_errors[i]}`)
all_errors[] = `${prefix}${fn_name}: ${check_errors[i]}`
i = i + 1
}
check_errors = check_jump_targets(func)
i = 0
while (i < length(check_errors)) {
push(all_errors, `${prefix}${fn_name}: ${check_errors[i]}`)
all_errors[] = `${prefix}${fn_name}: ${check_errors[i]}`
i = i + 1
}
check_errors = check_type_consistency(func)
i = 0
while (i < length(check_errors)) {
push(all_errors, `${prefix}${fn_name}: ${check_errors[i]}`)
all_errors[] = `${prefix}${fn_name}: ${check_errors[i]}`
i = i + 1
}
check_errors = check_nop_consistency(func)
i = 0
while (i < length(check_errors)) {
push(all_errors, `${prefix}${fn_name}: ${check_errors[i]}`)
all_errors[] = `${prefix}${fn_name}: ${check_errors[i]}`
i = i + 1
}

View File

@@ -93,11 +93,11 @@ var run = function() {
if (!creates[text(parent_idx)]) {
creates[text(parent_idx)] = []
}
push(creates[text(parent_idx)], {child: child_idx, line: instr_line})
creates[text(parent_idx)][] = {child: child_idx, line: instr_line}
if (!created_by[text(child_idx)]) {
created_by[text(child_idx)] = []
}
push(created_by[text(child_idx)], {parent: parent_idx, line: instr_line})
created_by[text(child_idx)][] = {parent: parent_idx, line: instr_line}
}
j = j + 1
}