Compare commits
23 Commits
d0bf757d91
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a1b41d5ecf | ||
|
|
eb19b18594 | ||
|
|
e203700d37 | ||
|
|
c56444556d | ||
|
|
080e675d18 | ||
|
|
957b964d9d | ||
|
|
fa9d2609b1 | ||
|
|
e38c2f07bf | ||
|
|
ecc1777b24 | ||
|
|
1cfd5b8133 | ||
|
|
9c1141f408 | ||
|
|
696cca530b | ||
|
|
c92a4087a6 | ||
|
|
01637c49b0 | ||
|
|
f9e660ebaa | ||
|
|
4f8fada57d | ||
|
|
adcaa92bea | ||
|
|
fc36707b39 | ||
|
|
7cb8ce7945 | ||
|
|
bb7997a751 | ||
|
|
327b990442 | ||
|
|
51c0a0b306 | ||
|
|
8ac82016dd |
4
audit.ce
4
audit.ce
@@ -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"
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
30
bench.ce
30
bench.ce
@@ -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
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ function build_index(txt) {
|
||||
if (!index[w]) {
|
||||
index[w] = []
|
||||
}
|
||||
push(index[w], i)
|
||||
index[w][] = i
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
@@ -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++) {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
17499
boot/mcode.cm.mcode
17499
boot/mcode.cm.mcode
File diff suppressed because one or more lines are too long
@@ -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
|
||||
|
||||
20117
boot/streamline.cm.mcode
20117
boot/streamline.cm.mcode
File diff suppressed because one or more lines are too long
159
build.cm
159
build.cm
@@ -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
174
cellfs.cm
@@ -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
18
cfg.ce
@@ -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, ", ")
|
||||
|
||||
12
clean.ce
12
clean.ce
@@ -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
24
diff.ce
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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, ", ")
|
||||
|
||||
@@ -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, ", ")
|
||||
|
||||
@@ -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
2
fd.cm
@@ -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
26
fold.cm
@@ -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
12
fuzz.ce
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
12
graph.ce
12
graph.ce
@@ -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
556
http.cm
@@ -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
|
||||
}
|
||||
|
||||
@@ -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'))) {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
512
internal/enet.c
512
internal/enet.c
@@ -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));
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
111
internal/shop.cm
111
internal/shop.cm
@@ -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
|
||||
|
||||
6
link.cm
6
link.cm
@@ -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
14
list.ce
@@ -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
496
log.ce
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
2
mcode.ce
2
mcode.ce
@@ -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, ", ")
|
||||
|
||||
55
mcode.cm
55
mcode.cm
@@ -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)
|
||||
|
||||
@@ -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',
|
||||
|
||||
90
net/socket.c
90
net/socket.c
@@ -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
238
net/tls.c
Normal 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
|
||||
6
pack.ce
6
pack.ce
@@ -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
|
||||
|
||||
12
package.cm
12
package.cm
@@ -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
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
98
parse.cm
98
parse.cm
@@ -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 {
|
||||
|
||||
14
qbe_emit.cm
14
qbe_emit.cm
@@ -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 {
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
10
resolve.ce
10
resolve.ce
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
6
slots.ce
6
slots.ce
@@ -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, ", ")
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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, ", ")
|
||||
|
||||
@@ -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
40
test.ce
@@ -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
251
tests/cellfs_test.ce
Normal 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()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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 = [
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
|
||||
@@ -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 },
|
||||
|
||||
60
tokenize.cm
60
tokenize.cm
@@ -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
16
toml.cm
@@ -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
|
||||
|
||||
85
update.ce
85
update.ce
@@ -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()
|
||||
|
||||
3
util.cm
3
util.cm
@@ -1,5 +1,6 @@
|
||||
var shop = use('internal/shop')
|
||||
|
||||
return {
|
||||
file_reload: shop.file_reload
|
||||
file_reload: shop.file_reload,
|
||||
reload: shop.module_reload
|
||||
}
|
||||
10
verify.ce
10
verify.ce
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
28
verify_ir.cm
28
verify_ir.cm
@@ -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
|
||||
}
|
||||
|
||||
|
||||
4
xref.ce
4
xref.ce
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user