Files
rose-ash/shared/static/scripts/sx-wasm.js
giles 0caa965de0
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 23m17s
OCaml CEK machine compiled to WebAssembly for browser execution
- wasm_of_ocaml compiles OCaml SX engine to WASM (722/722 spec tests)
- js_of_ocaml fallback also working (722/722 spec tests)
- Thin JS platform layer (sx-platform.js) with ~80 DOM/browser natives
- Lambda callback bridge: SX lambdas callable from JS via handle table
- Side-channel pattern bypasses js_of_ocaml return-value property stripping
- Web adapters (signals, deps, router, adapter-html) load as SX source
- Render mode dispatch: HTML tags + fragments route to OCaml renderer
- Island/component accessors handle both Component and Island types
- Dict-based signal support (signals.sx creates dicts, not native Signal)
- Scope stack implementation (collect!/collected/emit!/emitted/context)
- Bundle script embeds web adapters + WASM loader + platform layer
- SX_USE_WASM env var toggles WASM engine in dev/production
- Bootstrap extended: --web flag transpiles web adapters, :effects stripping

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-16 07:13:49 +00:00

2585 lines
201 KiB
JavaScript

(function(Object){
typeof globalThis !== "object"
&&
(this
? get()
: (Object.defineProperty
(Object.prototype, "_T_", {configurable: true, get: get}),
_T_));
function get(){
var global = this || self;
global.globalThis = global;
delete Object.prototype._T_;
}
}
(Object));
(js=>
async args=>{
"use strict";
const
{link, src, generated, disable_effects} = args,
isNode = globalThis.process?.versions?.node,
math =
{cos: Math.cos,
sin: Math.sin,
tan: Math.tan,
acos: Math.acos,
asin: Math.asin,
atan: Math.atan,
cosh: Math.cosh,
sinh: Math.sinh,
tanh: Math.tanh,
acosh: Math.acosh,
asinh: Math.asinh,
atanh: Math.atanh,
cbrt: Math.cbrt,
exp: Math.exp,
expm1: Math.expm1,
log: Math.log,
log1p: Math.log1p,
log2: Math.log2,
log10: Math.log10,
atan2: Math.atan2,
hypot: Math.hypot,
pow: Math.pow,
fmod: (x, y)=>x % y},
typed_arrays =
[Float32Array,
Float64Array,
Int8Array,
Uint8Array,
Int16Array,
Uint16Array,
Int32Array,
Int32Array,
Int32Array,
Int32Array,
Float32Array,
Float64Array,
Uint8Array,
Uint16Array,
Uint8ClampedArray],
fs = isNode && require("node:fs"),
fs_cst = fs?.constants,
access_flags =
fs ? [fs_cst.R_OK, fs_cst.W_OK, fs_cst.X_OK, fs_cst.F_OK] : [],
open_flags =
fs
? [fs_cst.O_RDONLY,
fs_cst.O_WRONLY,
fs_cst.O_RDWR,
fs_cst.O_APPEND,
fs_cst.O_CREAT,
fs_cst.O_TRUNC,
fs_cst.O_EXCL,
fs_cst.O_NONBLOCK,
fs_cst.O_NOCTTY,
fs_cst.O_DSYNC,
fs_cst.O_SYNC]
: [];
var
out_channels =
{map: new WeakMap(),
set: new Set(),
finalization:
new FinalizationRegistry(ref=>out_channels.set.delete(ref))};
function register_channel(ch){
const ref = new WeakRef(ch);
out_channels.map.set(ch, ref);
out_channels.set.add(ref);
out_channels.finalization.register(ch, ref, ch);
}
function unregister_channel(ch){
const ref = out_channels.map.get(ch);
if(ref){
out_channels.map.delete(ch);
out_channels.set.delete(ref);
out_channels.finalization.unregister(ch);
}
}
function channel_list(){
return [...out_channels.set].map(ref=>ref.deref()).filter(ch=>ch);
}
var start_fiber;
function make_suspending(f){
return WebAssembly?.Suspending ? new WebAssembly.Suspending(f) : f;
}
function make_promising(f){
return ! disable_effects && WebAssembly?.promising && f
? WebAssembly.promising(f)
: f;
}
const
decoder = new TextDecoder("utf-8", {ignoreBOM: 1}),
encoder = new TextEncoder();
function hash_int(h, d){
d = Math.imul(d, 0xcc9e2d51 | 0);
d = d << 15 | d >>> 17;
d = Math.imul(d, 0x1b873593);
h ^= d;
h = h << 13 | h >>> 19;
return (h + (h << 2) | 0) + (0xe6546b64 | 0) | 0;
}
function hash_string(h, s){
for(var i = 0; i < s.length; i++) h = hash_int(h, s.charCodeAt(i));
return h ^ s.length;
}
function getenv(n){
if(isNode && globalThis.process.env[n] !== undefined)
return globalThis.process.env[n];
return globalThis.jsoo_env?.[n];
}
let record_backtrace_flag = 0;
for(const l of getenv("OCAMLRUNPARAM")?.split(",") || []){
if(l === "b") record_backtrace_flag = 1;
if(l.startsWith("b=")) record_backtrace_flag = + l.slice(2) ? 1 : 0;
}
function alloc_stat(s, large){
var kind;
if(s.isFile())
kind = 0;
else if(s.isDirectory())
kind = 1;
else if(s.isCharacterDevice())
kind = 2;
else if(s.isBlockDevice())
kind = 3;
else if(s.isSymbolicLink())
kind = 4;
else if(s.isFIFO()) kind = 5; else if(s.isSocket()) kind = 6;
return caml_alloc_stat
(large,
s.dev,
s.ino | 0,
kind,
s.mode,
s.nlink,
s.uid,
s.gid,
s.rdev,
BigInt(s.size),
s.atimeMs / 1000,
s.mtimeMs / 1000,
s.ctimeMs / 1000);
}
const
on_windows = isNode && globalThis.process.platform === "win32",
call = Function.prototype.call,
DV = DataView.prototype,
bindings =
{jstag:
WebAssembly.JSTag
|| new WebAssembly.Tag({parameters: ["externref"], results: []}),
identity: x=>x,
from_bool: x=>! ! x,
get: (x, y)=>x[y],
set: (x, y, z)=>x[y] = z,
delete: (x, y)=>delete x[y],
instanceof: (x, y)=>x instanceof y,
typeof: x=>typeof x,
equals: (x, y)=>x == y,
strict_equals: (x, y)=>x === y,
fun_call: (f, o, args)=>f.apply(o, args),
meth_call: (o, f, args)=>o[f].apply(o, args),
new_array: n=>new Array(n),
new_obj: ()=>({}),
new: (c, args)=>new c(...args),
global_this: globalThis,
iter_props:
(o, f)=>{for(var nm in o) if(Object.hasOwn(o, nm)) f(nm);},
array_length: a=>a.length,
array_get: (a, i)=>a[i],
array_set: (a, i, v)=>a[i] = v,
read_string: l=>decoder.decode(new Uint8Array(buffer, 0, l)),
read_string_stream:
(l, stream)=>
decoder.decode(new Uint8Array(buffer, 0, l), {stream: stream}),
append_string: (s1, s2)=>s1 + s2,
write_string:
s=>{
var start = 0, len = s.length;
for(;;){
const
{read, written} = encoder.encodeInto(s.slice(start), out_buffer);
len -= read;
if(! len) return written;
caml_extract_bytes(written);
start += read;
}},
ta_create: (k, sz)=>new typed_arrays[k](sz),
ta_normalize:
a=>
a instanceof Uint32Array
? new Int32Array(a.buffer, a.byteOffset, a.length)
: a,
ta_kind: a=>typed_arrays.findIndex(c=>a instanceof c),
ta_length: a=>a.length,
ta_get_i32: (a, i)=>a[i],
ta_fill: (a, v)=>a.fill(v),
ta_blit: (s, d)=>d.set(s),
ta_subarray: (a, i, j)=>a.subarray(i, j),
ta_set: (a, b, i)=>a.set(b, i),
ta_new: len=>new Uint8Array(len),
ta_copy: (ta, t, s, e)=>ta.copyWithin(t, s, e),
ta_bytes:
a=>
new
Uint8Array
(a.buffer, a.byteOffset, a.length * a.BYTES_PER_ELEMENT),
ta_blit_from_bytes:
(s, p1, a, p2, l)=>{
for(let i = 0; i < l; i++) a[p2 + i] = bytes_get(s, p1 + i);},
ta_blit_to_bytes:
(a, p1, s, p2, l)=>{
for(let i = 0; i < l; i++) bytes_set(s, p2 + i, a[p1 + i]);},
dv_make: a=>new DataView(a.buffer, a.byteOffset, a.byteLength),
dv_get_f64: call.bind(DV.getFloat64),
dv_get_f32: call.bind(DV.getFloat32),
dv_get_i64: call.bind(DV.getBigInt64),
dv_get_i32: call.bind(DV.getInt32),
dv_get_i16: call.bind(DV.getInt16),
dv_get_ui16: call.bind(DV.getUint16),
dv_get_i8: call.bind(DV.getInt8),
dv_get_ui8: call.bind(DV.getUint8),
dv_set_f64: call.bind(DV.setFloat64),
dv_set_f32: call.bind(DV.setFloat32),
dv_set_i64: call.bind(DV.setBigInt64),
dv_set_i32: call.bind(DV.setInt32),
dv_set_i16: call.bind(DV.setInt16),
dv_set_i8: call.bind(DV.setInt8),
littleEndian: new Uint8Array(new Uint32Array([1]).buffer)[0],
wrap_callback:
f=>
function(...args){
if(args.length === 0) args = [undefined];
return caml_callback(f, args.length, args, 1);
},
wrap_callback_args:
f=>function(...args){return caml_callback(f, 1, [args], 0);},
wrap_callback_strict:
(arity, f)=>
function(...args){
args.length = arity;
return caml_callback(f, arity, args, 0);
},
wrap_callback_unsafe:
f=>function(...args){return caml_callback(f, args.length, args, 2);},
wrap_meth_callback:
f=>
function(...args){
args.unshift(this);
return caml_callback(f, args.length, args, 1);
},
wrap_meth_callback_args:
f=>function(...args){return caml_callback(f, 2, [this, args], 0);},
wrap_meth_callback_strict:
(arity, f)=>
function(...args){
args.length = arity;
args.unshift(this);
return caml_callback(f, args.length, args, 0);
},
wrap_meth_callback_unsafe:
f=>
function(...args){
args.unshift(this);
return caml_callback(f, args.length, args, 2);
},
wrap_fun_arguments: f=>function(...args){return f(args);},
format_float:
(prec, conversion, pad, x)=>{
function toFixed(x, dp){
if(Math.abs(x) < 1.0)
return x.toFixed(dp);
else{
var e = Number.parseInt(x.toString().split("+")[1]);
if(e > 20){
e -= 20;
x /= Math.pow(10, e);
x += new Array(e + 1).join("0");
if(dp > 0) x = x + "." + new Array(dp + 1).join("0");
return x;
}
else
return x.toFixed(dp);
}
}
switch(conversion){
case 0:
var s = x.toExponential(prec), i = s.length;
if(s.charAt(i - 3) === "e")
s = s.slice(0, i - 1) + "0" + s.slice(i - 1);
break;
case 1:
s = toFixed(x, prec); break;
case 2:
prec = prec ? prec : 1;
s = x.toExponential(prec - 1);
var j = s.indexOf("e"), exp = + s.slice(j + 1);
if(exp < - 4 || x >= 1e21 || x.toFixed(0).length > prec){
var i = j - 1;
while(s.charAt(i) === "0") i--;
if(s.charAt(i) === ".") i--;
s = s.slice(0, i + 1) + s.slice(j);
i = s.length;
if(s.charAt(i - 3) === "e")
s = s.slice(0, i - 1) + "0" + s.slice(i - 1);
break;
}
else{
var p = prec;
if(exp < 0){
p -= exp + 1;
s = x.toFixed(p);
}
else
while(s = x.toFixed(p), s.length > prec + 1) p--;
if(p){
var i = s.length - 1;
while(s.charAt(i) === "0") i--;
if(s.charAt(i) === ".") i--;
s = s.slice(0, i + 1);
}
}
break;
}
return pad ? " " + s : s;},
gettimeofday: ()=>new Date().getTime() / 1000,
times:
()=>{
if(globalThis.process?.cpuUsage){
var t = globalThis.process.cpuUsage();
return caml_alloc_times(t.user / 1e6, t.system / 1e6);
}
else{
var t = performance.now() / 1000;
return caml_alloc_times(t, 0);
}},
gmtime:
t=>{
var
d = new Date(t * 1000),
d_num = d.getTime(),
januaryfirst =
new Date(Date.UTC(d.getUTCFullYear(), 0, 1)).getTime(),
doy = Math.floor((d_num - januaryfirst) / 86400000);
return caml_alloc_tm
(d.getUTCSeconds(),
d.getUTCMinutes(),
d.getUTCHours(),
d.getUTCDate(),
d.getUTCMonth(),
d.getUTCFullYear() - 1900,
d.getUTCDay(),
doy,
false);},
localtime:
t=>{
var
d = new Date(t * 1000),
d_num = d.getTime(),
januaryfirst = new Date(d.getFullYear(), 0, 1).getTime(),
doy = Math.floor((d_num - januaryfirst) / 86400000),
jan = new Date(d.getFullYear(), 0, 1),
jul = new Date(d.getFullYear(), 6, 1),
stdTimezoneOffset =
Math.max(jan.getTimezoneOffset(), jul.getTimezoneOffset());
return caml_alloc_tm
(d.getSeconds(),
d.getMinutes(),
d.getHours(),
d.getDate(),
d.getMonth(),
d.getFullYear() - 1900,
d.getDay(),
doy,
d.getTimezoneOffset() < stdTimezoneOffset);},
mktime:
(year, month, day, h, m, s)=>
new Date(year, month, day, h, m, s).getTime(),
random_seed: ()=>crypto.getRandomValues(new Int32Array(12)),
access:
(p, flags)=>
fs.accessSync
(p,
access_flags.reduce((f, v, i)=>flags & 1 << i ? f | v : f, 0)),
open:
(p, flags, perm)=>
fs.openSync
(p,
open_flags.reduce((f, v, i)=>flags & 1 << i ? f | v : f, 0),
perm),
close: fd=>fs.closeSync(fd),
write:
(fd, b, o, l, p)=>
fs
? fs.writeSync(fd, b, o, l, p === null ? p : Number(p))
: (console
[fd === 2 ? "error" : "log"]
(typeof b === "string"
? b
: decoder.decode(b.slice(o, o + l))),
l),
read: (fd, b, o, l, p)=>fs.readSync(fd, b, o, l, p),
fsync: fd=>fs.fsyncSync(fd),
file_size: fd=>fs.fstatSync(fd, {bigint: true}).size,
register_channel: register_channel,
unregister_channel: unregister_channel,
channel_list: channel_list,
exit: n=>isNode && globalThis.process.exit(n),
argv: ()=>isNode ? globalThis.process.argv.slice(1) : ["a.out"],
on_windows: + on_windows,
getenv: getenv,
backtrace_status: ()=>record_backtrace_flag,
record_backtrace: b=>record_backtrace_flag = b,
system:
c=>{
var
res =
require("node:child_process").spawnSync
(c, {shell: true, stdio: "inherit"});
if(res.error) throw res.error;
return res.signal ? 255 : res.status;},
isatty: fd=>+ require("node:tty").isatty(fd),
time: ()=>performance.now(),
getcwd: ()=>isNode ? globalThis.process.cwd() : "/static",
chdir: x=>globalThis.process.chdir(x),
mkdir: (p, m)=>fs.mkdirSync(p, m),
rmdir: p=>fs.rmdirSync(p),
link: (d, s)=>fs.linkSync(d, s),
symlink:
(t, p, kind)=>fs.symlinkSync(t, p, [null, "file", "dir"][kind]),
readlink: p=>fs.readlinkSync(p),
unlink: p=>fs.unlinkSync(p),
read_dir: p=>fs.readdirSync(p),
opendir: p=>fs.opendirSync(p),
readdir:
d=>{var n = d.readSync()?.name; return n === undefined ? null : n;},
closedir: d=>d.closeSync(),
stat: (p, l)=>alloc_stat(fs.statSync(p), l),
lstat: (p, l)=>alloc_stat(fs.lstatSync(p), l),
fstat: (fd, l)=>alloc_stat(fs.fstatSync(fd), l),
chmod: (p, perms)=>fs.chmodSync(p, perms),
fchmod: (p, perms)=>fs.fchmodSync(p, perms),
file_exists: p=>+ fs.existsSync(p),
is_directory: p=>+ fs.lstatSync(p).isDirectory(),
is_file: p=>+ fs.lstatSync(p).isFile(),
utimes: (p, a, m)=>fs.utimesSync(p, a, m),
truncate: (p, l)=>fs.truncateSync(p, l),
ftruncate: (fd, l)=>fs.ftruncateSync(fd, l),
rename:
(o, n)=>{
var n_stat;
if
(on_windows && (n_stat = fs.statSync(n, {throwIfNoEntry: false}))
&& fs.statSync(o, {throwIfNoEntry: false})?.isDirectory())
if(n_stat.isDirectory()){
if(! n.startsWith(o)) try{fs.rmdirSync(n);}catch{}
}
else{
var
e =
new Error(`ENOTDIR: not a directory, rename '${o}' -> '${n}'`);
throw Object.assign
(e,
{errno: - 20, code: "ENOTDIR", syscall: "rename", path: n});
}
fs.renameSync(o, n);},
tmpdir: ()=>require("node:os").tmpdir(),
start_fiber: x=>start_fiber(x),
suspend_fiber: make_suspending((f, env)=>new Promise(k=>f(k, env))),
resume_fiber: (k, v)=>k(v),
weak_new: v=>new WeakRef(v),
weak_deref:
w=>{var v = w.deref(); return v === undefined ? null : v;},
weak_map_new: ()=>new WeakMap(),
map_new: ()=>new Map(),
map_get:
(m, x)=>{var v = m.get(x); return v === undefined ? null : v;},
map_set: (m, x, v)=>m.set(x, v),
map_delete: (m, x)=>m.delete(x),
hash_string: hash_string,
log: x=>console.log(x)},
string_ops =
{test: v=>+ (typeof v === "string"),
compare: (s1, s2)=>s1 < s2 ? - 1 : + (s1 > s2),
decodeStringFromUTF8Array: ()=>"",
encodeStringToUTF8Array: ()=>0,
fromCharCodeArray: ()=>""},
imports =
Object.assign
({Math: math,
bindings: bindings,
js: js,
"wasm:js-string": string_ops,
"wasm:text-decoder": string_ops,
"wasm:text-encoder": string_ops,
str: new globalThis.Proxy({}, {get(_, prop){return prop;}}),
env: {}},
generated),
options =
{builtins: ["js-string", "text-decoder", "text-encoder"],
importedStringConstants: "str"};
function loadRelative(src){
const
path = require("node:path"),
f = path.join(path.dirname(require.main.filename), src);
return require("node:fs/promises").readFile(f);
}
const fetchBase = globalThis?.document?.currentScript?.src;
function fetchRelative(src){
const url = fetchBase ? new URL(src, fetchBase) : src;
return fetch(url);
}
const loadCode = isNode ? loadRelative : fetchRelative;
async function instantiateModule(code){
return isNode
? WebAssembly.instantiate(await code, imports, options)
: WebAssembly.instantiateStreaming(code, imports, options);
}
async function instantiateFromDir(){
imports.OCaml = {};
const deps = [];
async function loadModule(module, isRuntime){
const sync = module[1].constructor !== Array;
async function instantiate(){
const code = loadCode(src + "/" + module[0] + ".wasm");
await Promise.all(sync ? deps : module[1].map(i=>deps[i]));
const wasmModule = await instantiateModule(code);
Object.assign
(isRuntime ? imports.env : imports.OCaml,
wasmModule.instance.exports);
}
const promise = instantiate();
deps.push(promise);
return promise;
}
async function loadModules(lst){
for(const module of lst) await loadModule(module);
}
await loadModule(link[0], 1);
if(link.length > 1){
await loadModule(link[1]);
const
workers = new Array(20).fill(link.slice(2).values()).map(loadModules);
await Promise.all(workers);
}
return {instance: {exports: Object.assign(imports.env, imports.OCaml)}};
}
const wasmModule = await instantiateFromDir();
var
{caml_callback,
caml_alloc_times,
caml_alloc_tm,
caml_alloc_stat,
caml_start_fiber,
caml_handle_uncaught_exception,
caml_buffer,
caml_extract_bytes,
bytes_get,
bytes_set,
_initialize}
= wasmModule.instance.exports,
buffer = caml_buffer?.buffer,
out_buffer = buffer && new Uint8Array(buffer, 0, buffer.length);
start_fiber = make_promising(caml_start_fiber);
var _initialize = make_promising(_initialize);
if(globalThis.process?.on)
globalThis.process.on
("uncaughtException",
(err, _origin)=>caml_handle_uncaught_exception(err));
else if(globalThis.addEventListener)
globalThis.addEventListener
("error",
event=>event.error && caml_handle_uncaught_exception(event.error));
await _initialize();})
(function(globalThis){
"use strict";
var
blake2b =
function(){
function ADD64AA(v, a, b){
const o0 = v[a] + v[b];
let o1 = v[a + 1] + v[b + 1];
if(o0 >= 0x100000000) o1++;
v[a] = o0;
v[a + 1] = o1;
}
function ADD64AC(v, a, b0, b1){
let o0 = v[a] + b0;
if(b0 < 0) o0 += 0x100000000;
let o1 = v[a + 1] + b1;
if(o0 >= 0x100000000) o1++;
v[a] = o0;
v[a + 1] = o1;
}
function B2B_GET32(arr, i){
return arr[i] ^ arr[i + 1] << 8 ^ arr[i + 2] << 16
^ arr[i + 3] << 24;
}
function B2B_G(a, b, c, d, ix, iy){
const x0 = m[ix], x1 = m[ix + 1], y0 = m[iy], y1 = m[iy + 1];
ADD64AA(v, a, b);
ADD64AC(v, a, x0, x1);
let xor0 = v[d] ^ v[a], xor1 = v[d + 1] ^ v[a + 1];
v[d] = xor1;
v[d + 1] = xor0;
ADD64AA(v, c, d);
xor0 = v[b] ^ v[c];
xor1 = v[b + 1] ^ v[c + 1];
v[b] = xor0 >>> 24 ^ xor1 << 8;
v[b + 1] = xor1 >>> 24 ^ xor0 << 8;
ADD64AA(v, a, b);
ADD64AC(v, a, y0, y1);
xor0 = v[d] ^ v[a];
xor1 = v[d + 1] ^ v[a + 1];
v[d] = xor0 >>> 16 ^ xor1 << 16;
v[d + 1] = xor1 >>> 16 ^ xor0 << 16;
ADD64AA(v, c, d);
xor0 = v[b] ^ v[c];
xor1 = v[b + 1] ^ v[c + 1];
v[b] = xor1 >>> 31 ^ xor0 << 1;
v[b + 1] = xor0 >>> 31 ^ xor1 << 1;
}
const
BLAKE2B_IV32 =
new
Uint32Array
([0xf3bcc908,
0x6a09e667,
0x84caa73b,
0xbb67ae85,
0xfe94f82b,
0x3c6ef372,
0x5f1d36f1,
0xa54ff53a,
0xade682d1,
0x510e527f,
0x2b3e6c1f,
0x9b05688c,
0xfb41bd6b,
0x1f83d9ab,
0x137e2179,
0x5be0cd19]),
SIGMA8 =
[0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
14,
10,
4,
8,
9,
15,
13,
6,
1,
12,
0,
2,
11,
7,
5,
3,
11,
8,
12,
0,
5,
2,
15,
13,
10,
14,
3,
6,
7,
1,
9,
4,
7,
9,
3,
1,
13,
12,
11,
14,
2,
6,
5,
10,
4,
0,
15,
8,
9,
0,
5,
7,
2,
4,
10,
15,
14,
1,
11,
12,
6,
8,
3,
13,
2,
12,
6,
10,
0,
11,
8,
3,
4,
13,
7,
5,
15,
14,
1,
9,
12,
5,
1,
15,
14,
13,
4,
10,
0,
7,
6,
3,
9,
2,
8,
11,
13,
11,
7,
14,
12,
1,
3,
9,
5,
0,
15,
4,
8,
6,
2,
10,
6,
15,
14,
9,
11,
3,
0,
8,
12,
2,
13,
7,
1,
4,
10,
5,
10,
2,
8,
4,
7,
6,
1,
5,
15,
11,
9,
14,
3,
12,
13,
0,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
14,
10,
4,
8,
9,
15,
13,
6,
1,
12,
0,
2,
11,
7,
5,
3],
SIGMA82 = new Uint8Array(SIGMA8.map(function(x){return x * 2;})),
v = new Uint32Array(32),
m = new Uint32Array(32);
function blake2bCompress(ctx, last){
let i = 0;
for(i = 0; i < 16; i++){
v[i] = ctx.h[i];
v[i + 16] = BLAKE2B_IV32[i];
}
v[24] = v[24] ^ ctx.t;
v[25] = v[25] ^ ctx.t / 0x100000000;
if(last){v[28] = ~ v[28]; v[29] = ~ v[29];}
for(i = 0; i < 32; i++) m[i] = B2B_GET32(ctx.b, 4 * i);
for(i = 0; i < 12; i++){
B2B_G(0, 8, 16, 24, SIGMA82[i * 16 + 0], SIGMA82[i * 16 + 1]);
B2B_G(2, 10, 18, 26, SIGMA82[i * 16 + 2], SIGMA82[i * 16 + 3]);
B2B_G(4, 12, 20, 28, SIGMA82[i * 16 + 4], SIGMA82[i * 16 + 5]);
B2B_G(6, 14, 22, 30, SIGMA82[i * 16 + 6], SIGMA82[i * 16 + 7]);
B2B_G(0, 10, 20, 30, SIGMA82[i * 16 + 8], SIGMA82[i * 16 + 9]);
B2B_G(2, 12, 22, 24, SIGMA82[i * 16 + 10], SIGMA82[i * 16 + 11]);
B2B_G(4, 14, 16, 26, SIGMA82[i * 16 + 12], SIGMA82[i * 16 + 13]);
B2B_G(6, 8, 18, 28, SIGMA82[i * 16 + 14], SIGMA82[i * 16 + 15]);
}
for(i = 0; i < 16; i++) ctx.h[i] = ctx.h[i] ^ v[i] ^ v[i + 16];
}
const
parameterBlock =
new
Uint8Array
([0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0]);
function blake2bInit(outlen, key){
if(outlen === 0 || outlen > 64)
throw new Error("Illegal output length, expected 0 < length <= 64");
if(key.length > 64)
throw new
Error
("Illegal key, expected Uint8Array with 0 < length <= 64");
const
ctx =
{b: new Uint8Array(128),
h: new Uint32Array(16),
t: 0,
c: 0,
outlen: outlen};
parameterBlock.fill(0);
parameterBlock[0] = outlen;
parameterBlock[1] = key.length;
parameterBlock[2] = 1;
parameterBlock[3] = 1;
for(let i = 0; i < 16; i++)
ctx.h[i] = BLAKE2B_IV32[i] ^ B2B_GET32(parameterBlock, i * 4);
if(key.length > 0){blake2bUpdate(ctx, key); ctx.c = 128;}
return ctx;
}
function blake2bUpdate(ctx, input){
for(let i = 0; i < input.length; i++){
if(ctx.c === 128){
ctx.t += ctx.c;
blake2bCompress(ctx, false);
ctx.c = 0;
}
ctx.b[ctx.c++] = input[i];
}
}
function blake2bFinal(ctx){
ctx.t += ctx.c;
while(ctx.c < 128) ctx.b[ctx.c++] = 0;
blake2bCompress(ctx, true);
const out = new Uint8Array(ctx.outlen);
for(let i = 0; i < ctx.outlen; i++)
out[i] = ctx.h[i >> 2] >> 8 * (i & 3);
return out;
}
return {Init: blake2bInit,
Update: blake2bUpdate,
Final: blake2bFinal};
}
();
function caml_ml_string_length(s){return s.length;}
function caml_string_unsafe_get(s, i){return s.charCodeAt(i);}
function caml_uint8_array_of_string(s){
var l = caml_ml_string_length(s), a = new Uint8Array(l), i = 0;
for(; i < l; i++) a[i] = caml_string_unsafe_get(s, i);
return a;
}
function caml_blake2_create(hashlen, key){
key = caml_uint8_array_of_string(key);
if(key.length > 64) key.subarray(0, 64);
return blake2b.Init(hashlen, key);
}
function caml_string_of_jsbytes(x){return x;}
function blake2_js_for_wasm_create(hashlen, key){
const key_jsoo_string = caml_string_of_jsbytes(key);
return caml_blake2_create(hashlen, key_jsoo_string);
}
function caml_sub_uint8_array_to_jsbytes(a, i, len){
var f = String.fromCharCode;
if(i === 0 && len <= 4096 && len === a.length) return f.apply(null, a);
var s = "";
for(; 0 < len; i += 1024, len -= 1024)
s += f.apply(null, a.subarray(i, i + Math.min(len, 1024)));
return s;
}
function caml_string_of_uint8_array(a){
return caml_sub_uint8_array_to_jsbytes(a, 0, a.length);
}
function caml_blake2_final(ctx, _hashlen){
var r = blake2b.Final(ctx);
return caml_string_of_uint8_array(r);
}
function caml_jsbytes_of_string(x){return x;}
function blake2_js_for_wasm_final(ctx, hashlen){
return caml_jsbytes_of_string(caml_blake2_final(ctx, hashlen));
}
function caml_blake2_update(ctx, buf, ofs, len){
var input = caml_uint8_array_of_string(buf);
input = input.subarray(ofs, ofs + len);
blake2b.Update(ctx, input);
return 0;
}
function blake2_js_for_wasm_update(ctx, buf, ofs, len){
const buf_jsoo_string = caml_string_of_jsbytes(buf);
return caml_blake2_update(ctx, buf_jsoo_string, ofs, len);
}
function caml_js_html_entities(s){
var entity = /^&#?[0-9a-zA-Z]+;$/;
if(s.match(entity)){
var str, temp = document.createElement("p");
temp.innerHTML = s;
str = temp.textContent || temp.innerText;
temp = null;
return str;
}
else
return null;
}
var caml_js_regexps = {amp: /&/g, lt: /</g, quot: /"/g, all: /[&<"]/};
function caml_js_html_escape(s){
if(! caml_js_regexps.all.test(s)) return s;
return s.replace(caml_js_regexps.amp, "&amp;").replace
(caml_js_regexps.lt, "&lt;").replace
(caml_js_regexps.quot, "&quot;");
}
var
unix_error =
["E2BIG",
"EACCES",
"EAGAIN",
"EBADF",
"EBUSY",
"ECHILD",
"EDEADLK",
"EDOM",
"EEXIST",
"EFAULT",
"EFBIG",
"EINTR",
"EINVAL",
"EIO",
"EISDIR",
"EMFILE",
"EMLINK",
"ENAMETOOLONG",
"ENFILE",
"ENODEV",
"ENOENT",
"ENOEXEC",
"ENOLCK",
"ENOMEM",
"ENOSPC",
"ENOSYS",
"ENOTDIR",
"ENOTEMPTY",
"ENOTTY",
"ENXIO",
"EPERM",
"EPIPE",
"ERANGE",
"EROFS",
"ESPIPE",
"ESRCH",
"EXDEV",
"EWOULDBLOCK",
"EINPROGRESS",
"EALREADY",
"ENOTSOCK",
"EDESTADDRREQ",
"EMSGSIZE",
"EPROTOTYPE",
"ENOPROTOOPT",
"EPROTONOSUPPORT",
"ESOCKTNOSUPPORT",
"EOPNOTSUPP",
"EPFNOSUPPORT",
"EAFNOSUPPORT",
"EADDRINUSE",
"EADDRNOTAVAIL",
"ENETDOWN",
"ENETUNREACH",
"ENETRESET",
"ECONNABORTED",
"ECONNRESET",
"ENOBUFS",
"EISCONN",
"ENOTCONN",
"ESHUTDOWN",
"ETOOMANYREFS",
"ETIMEDOUT",
"ECONNREFUSED",
"EHOSTDOWN",
"EHOSTUNREACH",
"ELOOP",
"EOVERFLOW"];
function caml_strerror(errno){
const util = require("node:util");
if(errno >= 0){
const code = unix_error[errno];
return util.getSystemErrorMap().entries().find(x=>x[1][0] === code)[1]
[1];
}
else
return util.getSystemErrorMessage(errno);
}
var
zstd_decompress =
function(){
var
ab = ArrayBuffer,
u8 = Uint8Array,
u16 = Uint16Array,
i16 = Int16Array,
i32 = Int32Array;
function slc(v, s, e){
if(u8.prototype.slice) return u8.prototype.slice.call(v, s, e);
if(s == null || s < 0) s = 0;
if(e == null || e > v.length) e = v.length;
var n = new u8(e - s);
n.set(v.subarray(s, e));
return n;
}
function fill(v, n, s, e){
if(u8.prototype.fill) return u8.prototype.fill.call(v, n, s, e);
if(s == null || s < 0) s = 0;
if(e == null || e > v.length) e = v.length;
for(; s < e; ++s) v[s] = n;
return v;
}
function cpw(v, t, s, e){
if(u8.prototype.copyWithin)
return u8.prototype.copyWithin.call(v, t, s, e);
if(s == null || s < 0) s = 0;
if(e == null || e > v.length) e = v.length;
while(s < e) v[t++] = v[s++];
}
var
ec =
["invalid zstd data",
"window size too large (>2046MB)",
"invalid block type",
"FSE accuracy too high",
"match distance too far back",
"unexpected EOF"];
function err(ind, msg, nt){
var e = new Error(msg || ec[ind]);
e.code = ind;
if(! nt) throw e;
return e;
}
function rb(d, b, n){
var i = 0, o = 0;
for(; i < n; ++i) o |= d[b++] << (i << 3);
return o;
}
function b4(d, b){
return (d[b] | d[b + 1] << 8 | d[b + 2] << 16 | d[b + 3] << 24)
>>> 0;
}
function rzfh(dat, w){
var n3 = dat[0] | dat[1] << 8 | dat[2] << 16;
if(n3 === 0x2fb528 && dat[3] === 253){
var
flg = dat[4],
ss = flg >> 5 & 1,
cc = flg >> 2 & 1,
df = flg & 3,
fcf = flg >> 6;
if(flg & 8) err(0);
var bt = 6 - ss, db = df === 3 ? 4 : df, di = rb(dat, bt, db);
bt += db;
var
fsb = fcf ? 1 << fcf : ss,
fss = rb(dat, bt, fsb) + (fcf === 1 && 256),
ws = fss;
if(! ss){
var wb = 1 << 10 + (dat[5] >> 3);
ws = wb + (wb >> 3) * (dat[5] & 7);
}
if(ws > 2145386496) err(1);
var buf = new u8((w === 1 ? fss || ws : w ? 0 : ws) + 12);
buf[0] = 1, buf[4] = 4, buf[8] = 8;
return {b: bt + fsb,
y: 0,
l: 0,
d: di,
w: w && w !== 1 ? w : buf.subarray(12),
e: ws,
o: new i32(buf.buffer, 0, 3),
u: fss,
c: cc,
m: Math.min(131072, ws)};
}
else if((n3 >> 4 | dat[3] << 20) === 0x184d2a5)
return b4(dat, 4) + 8;
err(0);
}
function msb(val){
var bits = 0;
for(; 1 << bits <= val; ++bits) ;
return bits - 1;
}
function rfse(dat, bt, mal){
var tpos = (bt << 3) + 4, al = (dat[bt] & 15) + 5;
if(al > mal) err(3);
var
sz = 1 << al,
probs = sz,
sym = - 1,
re = - 1,
i = - 1,
ht = sz,
buf = new ab(512 + (sz << 2)),
freq = new i16(buf, 0, 256),
dstate = new u16(buf, 0, 256),
nstate = new u16(buf, 512, sz),
bb1 = 512 + (sz << 1),
syms = new u8(buf, bb1, sz),
nbits = new u8(buf, bb1 + sz);
while(sym < 255 && probs > 0){
var
bits = msb(probs + 1),
cbt = tpos >> 3,
msk = (1 << bits + 1) - 1,
val =
(dat[cbt] | dat[cbt + 1] << 8 | dat[cbt + 2] << 16)
>> (tpos & 7)
& msk,
msk1fb = (1 << bits) - 1,
msv = msk - probs - 1,
sval = val & msk1fb;
if(sval < msv)
tpos += bits, val = sval;
else{tpos += bits + 1; if(val > msk1fb) val -= msv;}
freq[++sym] = --val;
if(val === - 1){probs += val; syms[--ht] = sym;} else probs -= val;
if(! val)
do{
var rbt = tpos >> 3;
re = (dat[rbt] | dat[rbt + 1] << 8) >> (tpos & 7) & 3;
tpos += 2;
sym += re;
}
while
(re === 3);
}
if(sym > 255 || probs) err(0);
var sympos = 0, sstep = (sz >> 1) + (sz >> 3) + 3, smask = sz - 1;
for(var s = 0; s <= sym; ++s){
var sf = freq[s];
if(sf < 1){dstate[s] = - sf; continue;}
for(i = 0; i < sf; ++i){
syms[sympos] = s;
do sympos = sympos + sstep & smask;while(sympos >= ht);
}
}
if(sympos) err(0);
for(i = 0; i < sz; ++i){
var ns = dstate[syms[i]]++, nb = nbits[i] = al - msb(ns);
nstate[i] = (ns << nb) - sz;
}
return [tpos + 7 >> 3, {b: al, s: syms, n: nbits, t: nstate}];
}
function rhu(dat, bt){
var
i = 0,
wc = - 1,
buf = new u8(292),
hb = dat[bt],
hw = buf.subarray(0, 256),
rc = buf.subarray(256, 268),
ri = new u16(buf.buffer, 268);
if(hb < 128){
var _a = rfse(dat, bt + 1, 6), ebt = _a[0], fdt = _a[1];
bt += hb;
var epos = ebt << 3, lb = dat[bt];
if(! lb) err(0);
var
st1 = 0,
st2 = 0,
btr1 = fdt.b,
btr2 = btr1,
fpos = (++bt << 3) - 8 + msb(lb);
for(;;){
fpos -= btr1;
if(fpos < epos) break;
var cbt = fpos >> 3;
st1 +=
(dat[cbt] | dat[cbt + 1] << 8) >> (fpos & 7) & (1 << btr1) - 1;
hw[++wc] = fdt.s[st1];
fpos -= btr2;
if(fpos < epos) break;
cbt = fpos >> 3;
st2 +=
(dat[cbt] | dat[cbt + 1] << 8) >> (fpos & 7) & (1 << btr2) - 1;
hw[++wc] = fdt.s[st2];
btr1 = fdt.n[st1];
st1 = fdt.t[st1];
btr2 = fdt.n[st2];
st2 = fdt.t[st2];
}
if(++wc > 255) err(0);
}
else{
wc = hb - 127;
for(; i < wc; i += 2){
var byte = dat[++bt];
hw[i] = byte >> 4;
hw[i + 1] = byte & 15;
}
++bt;
}
var wes = 0;
for(i = 0; i < wc; ++i){
var wt = hw[i];
if(wt > 11) err(0);
wes += wt && 1 << wt - 1;
}
var mb = msb(wes) + 1, ts = 1 << mb, rem = ts - wes;
if(rem & rem - 1) err(0);
hw[wc++] = msb(rem) + 1;
for(i = 0; i < wc; ++i){
var wt = hw[i];
++rc[hw[i] = wt && mb + 1 - wt];
}
var
hbuf = new u8(ts << 1),
syms = hbuf.subarray(0, ts),
nb = hbuf.subarray(ts);
ri[mb] = 0;
for(i = mb; i > 0; --i){
var pv = ri[i];
fill(nb, i, pv, ri[i - 1] = pv + rc[i] * (1 << mb - i));
}
if(ri[0] !== ts) err(0);
for(i = 0; i < wc; ++i){
var bits = hw[i];
if(bits){
var code = ri[bits];
fill(syms, i, code, ri[bits] = code + (1 << mb - bits));
}
}
return [bt, {n: nb, b: mb, s: syms}];
}
var
dllt =
rfse
(new
u8
([81,
16,
99,
140,
49,
198,
24,
99,
12,
33,
196,
24,
99,
102,
102,
134,
70,
146,
4]),
0,
6)
[1],
dmlt =
rfse
(new
u8
([33,
20,
196,
24,
99,
140,
33,
132,
16,
66,
8,
33,
132,
16,
66,
8,
33,
68,
68,
68,
68,
68,
68,
68,
68,
36,
9]),
0,
6)
[1],
doct =
rfse
(new u8([32, 132, 16, 66, 102, 70, 68, 68, 68, 68, 36, 73, 2]),
0,
5)
[1];
function b2bl(b, s){
var len = b.length, bl = new i32(len);
for(var i = 0; i < len; ++i){bl[i] = s; s += 1 << b[i];}
return bl;
}
var
llb =
new
u8
(new
i32
([0,
0,
0,
0,
16843009,
50528770,
134678020,
202050057,
269422093]).buffer,
0,
36),
llbl = b2bl(llb, 0),
mlb =
new
u8
(new
i32
([0,
0,
0,
0,
0,
0,
0,
0,
16843009,
50528770,
117769220,
185207048,
252579084,
16]).buffer,
0,
53),
mlbl = b2bl(mlb, 3);
function dhu(dat, out, hu){
var
len = dat.length,
ss = out.length,
lb = dat[len - 1],
msk = (1 << hu.b) - 1,
eb = - hu.b;
if(! lb) err(0);
var
st = 0,
btr = hu.b,
pos = (len << 3) - 8 + msb(lb) - btr,
i = - 1;
while(pos > eb && i < ss){
var
cbt = pos >> 3,
val =
(dat[cbt] | dat[cbt + 1] << 8 | dat[cbt + 2] << 16) >> (pos & 7);
st = (st << btr | val) & msk;
out[++i] = hu.s[st];
pos -= btr = hu.n[st];
}
if(pos !== eb || i + 1 !== ss) err(0);
}
function dhu4(dat, out, hu){
var
bt = 6,
ss = out.length,
sz1 = ss + 3 >> 2,
sz2 = sz1 << 1,
sz3 = sz1 + sz2;
dhu
(dat.subarray(bt, bt += dat[0] | dat[1] << 8),
out.subarray(0, sz1),
hu);
dhu
(dat.subarray(bt, bt += dat[2] | dat[3] << 8),
out.subarray(sz1, sz2),
hu);
dhu
(dat.subarray(bt, bt += dat[4] | dat[5] << 8),
out.subarray(sz2, sz3),
hu);
dhu(dat.subarray(bt), out.subarray(sz3), hu);
}
function rzb(dat, st, out){
var _a, bt = st.b, b0 = dat[bt], btype = b0 >> 1 & 3;
st.l = b0 & 1;
var
sz = b0 >> 3 | dat[bt + 1] << 5 | dat[bt + 2] << 13,
ebt = (bt += 3) + sz;
if(btype === 1){
if(bt >= dat.length) return;
st.b = bt + 1;
if(out){fill(out, dat[bt], st.y, st.y += sz); return out;}
return fill(new u8(sz), dat[bt]);
}
if(ebt > dat.length) return;
if(btype === 0){
st.b = ebt;
if(out){
out.set(dat.subarray(bt, ebt), st.y);
st.y += sz;
return out;
}
return slc(dat, bt, ebt);
}
if(btype === 2){
var
b3 = dat[bt],
lbt = b3 & 3,
sf = b3 >> 2 & 3,
lss = b3 >> 4,
lcs = 0,
s4 = 0;
if(lbt < 2)
if(sf & 1)
lss |= dat[++bt] << 4 | (sf & 2 && dat[++bt] << 12);
else
lss = b3 >> 3;
else{
s4 = sf;
if(sf < 2)
lss |= (dat[++bt] & 63) << 4,
lcs = dat[bt] >> 6 | dat[++bt] << 2;
else if(sf === 2)
lss |= dat[++bt] << 4 | (dat[++bt] & 3) << 12,
lcs = dat[bt] >> 2 | dat[++bt] << 6;
else
lss |= dat[++bt] << 4 | (dat[++bt] & 63) << 12,
lcs = dat[bt] >> 6 | dat[++bt] << 2 | dat[++bt] << 10;
}
++bt;
var
buf = out ? out.subarray(st.y, st.y + st.m) : new u8(st.m),
spl = buf.length - lss;
if(lbt === 0)
buf.set(dat.subarray(bt, bt += lss), spl);
else if(lbt === 1)
fill(buf, dat[bt++], spl);
else{
var hu = st.h;
if(lbt === 2){
var hud = rhu(dat, bt);
lcs += bt - (bt = hud[0]);
st.h = hu = hud[1];
}
else if(! hu) err(0);
(s4 ? dhu4 : dhu)
(dat.subarray(bt, bt += lcs), buf.subarray(spl), hu);
}
var ns = dat[bt++];
if(ns){
if(ns === 255)
ns = (dat[bt++] | dat[bt++] << 8) + 0x7f00;
else if(ns > 127) ns = ns - 128 << 8 | dat[bt++];
var scm = dat[bt++];
if(scm & 3) err(0);
var dts = [dmlt, doct, dllt];
for(var i = 2; i > - 1; --i){
var md = scm >> (i << 1) + 2 & 3;
if(md === 1){
var rbuf = new u8([0, 0, dat[bt++]]);
dts[i] =
{s: rbuf.subarray(2, 3),
n: rbuf.subarray(0, 1),
t: new u16(rbuf.buffer, 0, 1),
b: 0};
}
else if(md === 2)
_a = rfse(dat, bt, 9 - (i & 1)), bt = _a[0], dts[i] = _a[1];
else if(md === 3){if(! st.t) err(0); dts[i] = st.t[i];}
}
var
_b = st.t = dts,
mlt = _b[0],
oct = _b[1],
llt = _b[2],
lb = dat[ebt - 1];
if(! lb) err(0);
var
spos = (ebt << 3) - 8 + msb(lb) - llt.b,
cbt = spos >> 3,
oubt = 0,
lst =
(dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7) & (1 << llt.b) - 1;
cbt = (spos -= oct.b) >> 3;
var
ost =
(dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7) & (1 << oct.b) - 1;
cbt = (spos -= mlt.b) >> 3;
var
mst =
(dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7) & (1 << mlt.b) - 1;
for(++ns; --ns;){
var
llc = llt.s[lst],
lbtr = llt.n[lst],
mlc = mlt.s[mst],
mbtr = mlt.n[mst],
ofc = oct.s[ost],
obtr = oct.n[ost];
cbt = (spos -= ofc) >> 3;
var
ofp = 1 << ofc,
off =
ofp
+
((dat[cbt] | dat[cbt + 1] << 8 | dat[cbt + 2] << 16
| dat[cbt + 3] << 24)
>>> (spos & 7)
& ofp - 1);
cbt = (spos -= mlb[mlc]) >> 3;
var
ml =
mlbl[mlc]
+
((dat[cbt] | dat[cbt + 1] << 8 | dat[cbt + 2] << 16)
>> (spos & 7)
& (1 << mlb[mlc]) - 1);
cbt = (spos -= llb[llc]) >> 3;
var
ll =
llbl[llc]
+
((dat[cbt] | dat[cbt + 1] << 8 | dat[cbt + 2] << 16)
>> (spos & 7)
& (1 << llb[llc]) - 1);
cbt = (spos -= lbtr) >> 3;
lst =
llt.t[lst]
+
((dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7)
& (1 << lbtr) - 1);
cbt = (spos -= mbtr) >> 3;
mst =
mlt.t[mst]
+
((dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7)
& (1 << mbtr) - 1);
cbt = (spos -= obtr) >> 3;
ost =
oct.t[ost]
+
((dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7)
& (1 << obtr) - 1);
if(off > 3){
st.o[2] = st.o[1];
st.o[1] = st.o[0];
st.o[0] = off -= 3;
}
else{
var idx = off - (ll !== 0);
if(idx){
off = idx === 3 ? st.o[0] - 1 : st.o[idx];
if(idx > 1) st.o[2] = st.o[1];
st.o[1] = st.o[0];
st.o[0] = off;
}
else
off = st.o[0];
}
for(var i = 0; i < ll; ++i) buf[oubt + i] = buf[spl + i];
oubt += ll, spl += ll;
var stin = oubt - off;
if(stin < 0){
var len = - stin, bs = st.e + stin;
if(len > ml) len = ml;
for(var i = 0; i < len; ++i) buf[oubt + i] = st.w[bs + i];
oubt += len, ml -= len, stin = 0;
}
for(var i = 0; i < ml; ++i) buf[oubt + i] = buf[stin + i];
oubt += ml;
}
if(oubt !== spl)
while(spl < buf.length) buf[oubt++] = buf[spl++];
else
oubt = buf.length;
if(out) st.y += oubt; else buf = slc(buf, 0, oubt);
}
else if(out){
st.y += lss;
if(spl) for(var i = 0; i < lss; ++i) buf[i] = buf[spl + i];
}
else if(spl) buf = slc(buf, spl);
st.b = ebt;
return buf;
}
err(2);
}
function cct(bufs, ol){
if(bufs.length === 1) return bufs[0];
var buf = new u8(ol);
for(var i = 0, b = 0; i < bufs.length; ++i){
var chk = bufs[i];
buf.set(chk, b);
b += chk.length;
}
return buf;
}
return function(dat, buf){
var bt = 0, bufs = [], nb = + ! buf, ol = 0;
while(dat.length){
var st = rzfh(dat, nb || buf);
if(typeof st === "object"){
if(nb){
buf = null;
if(st.w.length === st.u){bufs.push(buf = st.w); ol += st.u;}
}
else{bufs.push(buf); st.e = 0;}
while(! st.l){
var blk = rzb(dat, st, buf);
if(! blk) err(5);
if(buf)
st.e = st.y;
else{
bufs.push(blk);
ol += blk.length;
cpw(st.w, 0, blk.length);
st.w.set(blk, st.w.length - blk.length);
}
}
bt = st.b + st.c * 4;
}
else
bt = st;
dat = dat.subarray(bt);
}
return cct(bufs, ol);};
}
();
return {zstd_decompress: zstd_decompress,
unix_error: unix_error,
caml_strerror: caml_strerror,
caml_js_html_escape: caml_js_html_escape,
caml_js_html_entities: caml_js_html_entities,
blake2_js_for_wasm_update: blake2_js_for_wasm_update,
blake2_js_for_wasm_final: blake2_js_for_wasm_final,
blake2_js_for_wasm_create: blake2_js_for_wasm_create};
}
(globalThis))
({"link":[["runtime-0db9b496",0],["prelude-d7e4b000",0],["stdlib-23ce0836",[]],["sx-a462ed04",[2]],["jsoo_runtime-f96b44a8",[2]],["js_of_ocaml-651f6707",[2,4]],["dune__exe__Sx_browser-5f19f371",[2,3,5]],["std_exit-10fb8830",[2]],["start-9afa06f6",0]],"generated":(b=>{var
c=b,a=b?.module?.export||b;return{"env":{"caml_ba_kind_of_typed_array":()=>{throw new
Error("caml_ba_kind_of_typed_array not implemented")},"caml_exn_with_js_backtrace":()=>{throw new
Error("caml_exn_with_js_backtrace not implemented")},"caml_int64_create_lo_mi_hi":()=>{throw new
Error("caml_int64_create_lo_mi_hi not implemented")},"caml_jsoo_flags_effects":()=>{throw new
Error("caml_jsoo_flags_effects not implemented")},"caml_list_mount_point":()=>{throw new
Error("caml_list_mount_point not implemented")},"caml_ml_set_channel_output":()=>{throw new
Error("caml_ml_set_channel_output not implemented")},"caml_ml_set_channel_refill":()=>{throw new
Error("caml_ml_set_channel_refill not implemented")},"caml_unmount":()=>{throw new
Error("caml_unmount not implemented")}},"Js_of_ocaml__Js.fragments":{"fun_call_1":(a,b)=>a(b),"get_Array":a=>a.Array,"get_Date":a=>a.Date,"get_Error":a=>a.Error,"get_JSON":a=>a.JSON,"get_Math":a=>a.Math,"get_Object":a=>a.Object,"get_RegExp":a=>a.RegExp,"get_String":a=>a.String,"get_decodeURI":a=>a.decodeURI,"get_decodeURIComponent":a=>a.decodeURIComponent,"get_encodeURI":a=>a.encodeURI,"get_encodeURIComponent":a=>a.encodeURIComponent,"get_escape":a=>a.escape,"get_isNaN":a=>a.isNaN,"get_length":a=>a.length,"get_message":a=>a.message,"get_name":a=>a.name,"get_parseFloat":a=>a.parseFloat,"get_parseInt":a=>a.parseInt,"get_stack":a=>a.stack,"get_unescape":a=>a.unescape,"js_expr_12c48ca8":()=>b,"js_expr_21711c2a":()=>a,"js_expr_26f07992":()=>null,"js_expr_28647a4c":()=>false,"js_expr_34edcf72":()=>true,"js_expr_ba692c1":()=>undefined,"meth_call_0_toString":a=>a.toString(),"meth_call_1_forEach":(a,b)=>a.forEach(b),"meth_call_1_keys":(a,b)=>a.keys(b),"meth_call_1_map":(a,b)=>a.map(b)},"Js_of_ocaml__Dom.fragments":{"call_1":(a,b,c)=>a.call(b,c),"get_CustomEvent":a=>a.CustomEvent,"get_addEventListener":a=>a.addEventListener,"get_length":a=>a.length,"get_nodeType":a=>a.nodeType,"get_srcElement":a=>a.srcElement,"get_target":a=>a.target,"meth_call_0_preventDefault":a=>a.preventDefault(),"meth_call_1_appendChild":(a,b)=>a.appendChild(b),"meth_call_1_concat":(a,b)=>a.concat(b),"meth_call_1_item":(a,b)=>a.item(b),"meth_call_1_removeChild":(a,b)=>a.removeChild(b),"meth_call_2_attachEvent":(a,b,c)=>a.attachEvent(b,c),"meth_call_2_detachEvent":(a,b,c)=>a.detachEvent(b,c),"meth_call_2_insertBefore":(a,b,c)=>a.insertBefore(b,c),"meth_call_2_replaceChild":(a,b,c)=>a.replaceChild(b,c),"meth_call_3_addEventListener":(a,b,c,d)=>a.addEventListener(b,c,d),"meth_call_3_removeEventListener":(a,b,c,d)=>a.removeEventListener(b,c,d),"new_2":(a,b,c)=>new
a(b,c),"obj_0":()=>({}),"obj_1":()=>({}),"set_bubbles":(a,b)=>a.bubbles=b,"set_cancelable":(a,b)=>a.cancelable=b,"set_capture":(a,b)=>a.capture=b,"set_detail":(a,b)=>a.detail=b,"set_once":(a,b)=>a.once=b,"set_passive":(a,b)=>a.passive=b},"Js_of_ocaml__Typed_array.fragments":{"get_ArrayBuffer":a=>a.ArrayBuffer,"get_DataView":a=>a.DataView,"get_Float32Array":a=>a.Float32Array,"get_Float64Array":a=>a.Float64Array,"get_Int16Array":a=>a.Int16Array,"get_Int32Array":a=>a.Int32Array,"get_Int8Array":a=>a.Int8Array,"get_Uint16Array":a=>a.Uint16Array,"get_Uint32Array":a=>a.Uint32Array,"get_Uint8Array":a=>a.Uint8Array,"new_1":(a,b)=>new
a(b)},"Js_of_ocaml__File.fragments":{"get_Blob":a=>a.Blob,"get_Document":a=>a.Document,"get_FileReader":a=>a.FileReader,"get_fileName":a=>a.fileName,"get_name":a=>a.name,"new_2":(a,b,c)=>new
a(b,c)},"Js_of_ocaml__Dom_html.fragments":{"fun_call_1":(a,b)=>a(b),"get_HTMLElement":a=>a.HTMLElement,"get_KeyboardEvent":a=>a.KeyboardEvent,"get_MessageEvent":a=>a.MessageEvent,"get_MouseEvent":a=>a.MouseEvent,"get_MouseScrollEvent":a=>a.MouseScrollEvent,"get_PopStateEvent":a=>a.PopStateEvent,"get_WheelEvent":a=>a.WheelEvent,"get_body":a=>a.body,"get_button":a=>a.button,"get_charCode":a=>a.charCode,"get_clientLeft":a=>a.clientLeft,"get_clientTop":a=>a.clientTop,"get_clientX":a=>a.clientX,"get_clientY":a=>a.clientY,"get_code":a=>a.code,"get_document":a=>a.document,"get_documentElement":a=>a.documentElement,"get_getContext":a=>a.getContext,"get_history":a=>a.history,"get_key":a=>a.key,"get_keyCode":a=>a.keyCode,"get_left":a=>a.left,"get_length":a=>a.length,"get_location":a=>a.location,"get_mozRequestAnimationFrame":a=>a.mozRequestAnimationFrame,"get_msRequestAnimationFrame":a=>a.msRequestAnimationFrame,"get_name":a=>a.name,"get_oRequestAnimationFrame":a=>a.oRequestAnimationFrame,"get_origin":a=>a.origin,"get_pageX":a=>a.pageX,"get_pageY":a=>a.pageY,"get_placeholder":a=>a.placeholder,"get_pushState":a=>a.pushState,"get_readyState":a=>a.readyState,"get_relatedTarget":a=>a.relatedTarget,"get_requestAnimationFrame":a=>a.requestAnimationFrame,"get_required":a=>a.required,"get_scrollLeft":a=>a.scrollLeft,"get_scrollTop":a=>a.scrollTop,"get_stopPropagation":a=>a.stopPropagation,"get_tagName":a=>a.tagName,"get_top":a=>a.top,"get_webkitRequestAnimationFrame":a=>a.webkitRequestAnimationFrame,"get_wheelDelta":a=>a.wheelDelta,"get_wheelDeltaX":a=>a.wheelDeltaX,"get_wheelDeltaY":a=>a.wheelDeltaY,"get_which":a=>a.which,"js_expr_4c8b1c6":()=>[].slice,"meth_call_0_getBoundingClientRect":a=>a.getBoundingClientRect(),"meth_call_0_getTime":a=>a.getTime(),"meth_call_0_stopPropagation":a=>a.stopPropagation(),"meth_call_0_toLowerCase":a=>a.toLowerCase(),"meth_call_1_call":(a,b)=>a.call(b),"meth_call_1_charCodeAt":(a,b)=>a.charCodeAt(b),"meth_call_1_clearTimeout":(a,b)=>a.clearTimeout(b),"meth_call_1_createElement":(a,b)=>a.createElement(b),"meth_call_1_getElementById":(a,b)=>a.getElementById(b),"meth_call_1_join":(a,b)=>a.join(b),"meth_call_1_push":(a,b)=>a.push(b),"meth_call_2_push":(a,b,c)=>a.push(b,c),"meth_call_2_setTimeout":(a,b,c)=>a.setTimeout(b,c),"meth_call_3_push":(a,b,c,d)=>a.push(b,c,d),"new_0":a=>new
a(),"set_cancelBubble":(a,b)=>a.cancelBubble=b,"set_name":(a,b)=>a.name=b,"set_type":(a,b)=>a.type=b},"Js_of_ocaml__Form.fragments":{"get_FormData":a=>a.FormData,"get_checked":a=>a.checked,"get_disabled":a=>a.disabled,"get_elements":a=>a.elements,"get_files":a=>a.files,"get_length":a=>a.length,"get_multiple":a=>a.multiple,"get_name":a=>a.name,"get_options":a=>a.options,"get_selected":a=>a.selected,"get_type":a=>a.type,"get_value":a=>a.value,"meth_call_0_toLowerCase":a=>a.toLowerCase(),"meth_call_1_item":(a,b)=>a.item(b),"meth_call_2_append":(a,b,c)=>a.append(b,c),"new_0":a=>new
a()},"Js_of_ocaml__Worker.fragments":{"get_Worker":a=>a.Worker,"get_data":a=>a.data,"get_importScripts":a=>a.importScripts,"get_onmessage":a=>a.onmessage,"get_postMessage":a=>a.postMessage,"meth_call_1_postMessage":(a,b)=>a.postMessage(b),"new_1":(a,b)=>new
a(b),"set_onmessage":(a,b)=>a.onmessage=b},"Js_of_ocaml__WebSockets.fragments":{"get_WebSocket":a=>a.WebSocket},"Js_of_ocaml__WebGL.fragments":{"meth_call_1_getContext":(a,b)=>a.getContext(b),"meth_call_2_getContext":(a,b,c)=>a.getContext(b,c),"obj_2":(a,b,c,d,e,f,g,h)=>({alpha:a,depth:b,stencil:c,antialias:d,premultipliedAlpha:e,preserveDrawingBuffer:f,preferLowPowerToHighPerformance:g,failIfMajorPerformanceCaveat:h})},"Js_of_ocaml__Regexp.fragments":{"get_ignoreCase":a=>a.ignoreCase,"get_index":a=>a.index,"get_length":a=>a.length,"get_multiline":a=>a.multiline,"get_source":a=>a.source,"meth_call_1_exec":(a,b)=>a.exec(b),"meth_call_1_split":(a,b)=>a.split(b),"meth_call_2_replace":(a,b,c)=>a.replace(b,c),"meth_call_2_split":(a,b,c)=>a.split(b,c),"new_2":(a,b,c)=>new
a(b,c),"set_lastIndex":(a,b)=>a.lastIndex=b},"Js_of_ocaml__Url.fragments":{"get_hash":a=>a.hash,"get_hostname":a=>a.hostname,"get_href":a=>a.href,"get_length":a=>a.length,"get_location":a=>a.location,"get_pathname":a=>a.pathname,"get_port":a=>a.port,"get_protocol":a=>a.protocol,"get_search":a=>a.search,"meth_call_0_toLowerCase":a=>a.toLowerCase(),"meth_call_1_charAt":(a,b)=>a.charAt(b),"meth_call_1_exec":(a,b)=>a.exec(b),"meth_call_1_indexOf":(a,b)=>a.indexOf(b),"meth_call_1_slice":(a,b)=>a.slice(b),"meth_call_1_split":(a,b)=>a.split(b),"meth_call_2_replace":(a,b,c)=>a.replace(b,c),"meth_call_2_slice":(a,b,c)=>a.slice(b,c),"new_1":(a,b)=>new
a(b),"new_2":(a,b,c)=>new
a(b,c),"obj_3":(a,b,c,d,e,f,g,h,i,j,k,l)=>({href:a,protocol:b,host:c,hostname:d,port:e,pathname:f,search:g,hash:h,origin:i,reload:j,replace:k,assign:l}),"set_hash":(a,b)=>a.hash=b,"set_href":(a,b)=>a.href=b,"set_lastIndex":(a,b)=>a.lastIndex=b},"Js_of_ocaml__ResizeObserver.fragments":{"get_ResizeObserver":a=>a.ResizeObserver,"meth_call_1_observe":(a,b)=>a.observe(b),"meth_call_2_observe":(a,b,c)=>a.observe(b,c),"new_1":(a,b)=>new
a(b),"obj_4":()=>({}),"obj_5":()=>({}),"set_box":(a,b)=>a.box=b},"Js_of_ocaml__PerformanceObserver.fragments":{"get_PerformanceObserver":a=>a.PerformanceObserver,"meth_call_1_observe":(a,b)=>a.observe(b),"new_1":(a,b)=>new
a(b),"obj_6":()=>({}),"set_entryTypes":(a,b)=>a.entryTypes=b},"Js_of_ocaml__MutationObserver.fragments":{"get_MutationObserver":a=>a.MutationObserver,"meth_call_2_observe":(a,b,c)=>a.observe(b,c),"new_1":(a,b)=>new
a(b),"obj_7":()=>({}),"obj_8":()=>({}),"set_attributeFilter":(a,b)=>a.attributeFilter=b,"set_attributeOldValue":(a,b)=>a.attributeOldValue=b,"set_attributes":(a,b)=>a.attributes=b,"set_characterData":(a,b)=>a.characterData=b,"set_characterDataOldValue":(a,b)=>a.characterDataOldValue=b,"set_childList":(a,b)=>a.childList=b,"set_subtree":(a,b)=>a.subtree=b},"Js_of_ocaml__Jstable.fragments":{"get_Object":a=>a.Object,"get_length":a=>a.length,"meth_call_1_concat":(a,b)=>a.concat(b),"meth_call_1_keys":(a,b)=>a.keys(b),"meth_call_2_substring":(a,b,c)=>a.substring(b,c),"new_0":a=>new
a()},"Js_of_ocaml__Json.fragments":{"get_JSON":a=>a.JSON,"get_constructor":a=>a.constructor,"get_hi":a=>a.hi,"get_length":a=>a.length,"get_lo":a=>a.lo,"get_mi":a=>a.mi,"meth_call_1_stringify":(a,b)=>a.stringify(b),"meth_call_2_parse":(a,b,c)=>a.parse(b,c),"meth_call_2_stringify":(a,b,c)=>a.stringify(b,c)},"Js_of_ocaml__CSS.fragments":{"meth_call_1_test":(a,b)=>a.test(b),"new_1":(a,b)=>new
a(b)},"Js_of_ocaml__Dom_svg.fragments":{"get_SVGElement":a=>a.SVGElement,"get_document":a=>a.document,"get_tagName":a=>a.tagName,"meth_call_0_toLowerCase":a=>a.toLowerCase(),"meth_call_1_getElementById":(a,b)=>a.getElementById(b),"meth_call_2_createElementNS":(a,b,c)=>a.createElementNS(b,c)},"Js_of_ocaml__EventSource.fragments":{"get_EventSource":a=>a.EventSource,"obj_9":()=>({}),"set_withCredentials":(a,b)=>a.withCredentials=b},"Js_of_ocaml__Geolocation.fragments":{"get_geolocation":a=>a.geolocation,"get_navigator":a=>a.navigator,"obj_10":()=>({})},"Js_of_ocaml__IntersectionObserver.fragments":{"get_IntersectionObserver":a=>a.IntersectionObserver,"obj_11":()=>({})},"Js_of_ocaml__Intl.fragments":{"get_Collator":a=>a.Collator,"get_DateTimeFormat":a=>a.DateTimeFormat,"get_Intl":a=>a.Intl,"get_NumberFormat":a=>a.NumberFormat,"get_PluralRules":a=>a.PluralRules,"obj_12":a=>({localeMatcher:a}),"obj_13":(a,b,c,d,e,f)=>({localeMatcher:a,usage:b,sensitivity:c,ignorePunctuation:d,numeric:e,caseFirst:f}),"obj_14":(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t)=>({dateStyle:a,timeStyle:b,calendar:c,dayPeriod:d,numberingSystem:e,localeMatcher:f,timeZone:g,hour12:h,hourCycle:i,formatMatcher:j,weekday:k,era:l,year:m,month:n,day:o,hour:p,minute:q,second:r,fractionalSecondDigits:s,timeZoneName:t}),"obj_15":(a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u)=>({compactDisplay:a,currency:b,currencyDisplay:c,currencySign:d,localeMatcher:e,notation:f,numberingSystem:g,signDisplay:h,style:i,unit:j,unitDisplay:k,useGrouping:l,roundingMode:m,roundingPriority:n,roundingIncrement:o,trailingZeroDisplay:p,minimumIntegerDigits:q,minimumFractionDigits:r,maximumFractionDigits:s,minimumSignificantDigits:t,maximumSignificantDigits:u}),"obj_16":(a,b)=>({localeMatcher:a,type:b})},"Dune__exe__Sx_browser.fragments":{"fun_call_1":(a,b)=>a(b),"fun_call_3":(a,b,c,d)=>a(b,c,d),"get_Array":a=>a.Array,"get_Object":a=>a.Object,"get___sxTagFn":a=>a.__sxTagFn,"get___sx_handle":a=>a.__sx_handle,"get__type":a=>a._type,"get_console":a=>a.console,"get_items":a=>a.items,"get_length":a=>a.length,"get_name":a=>a.name,"js_expr_288a41e0":()=>function(a){return function(){b.__sxR=undefined;var
c=a.apply(null,arguments);return b.__sxR!==undefined?b.__sxR:c}},"js_expr_36506fc1":()=>function(a,b,c){a.__sx_handle=b;a._type=c;return a},"meth_call_1_error":(a,b)=>a.error(b),"meth_call_1_isArray":(a,b)=>a.isArray(b),"meth_call_1_keys":(a,b)=>a.keys(b),"obj_0":()=>({}),"obj_1":(a,b)=>({_type:a,items:b}),"obj_2":(a,b)=>({_type:a,__sx_handle:b}),"obj_3":(a,b,c)=>({_type:a,name:b,__sx_handle:c}),"obj_4":(a,b,c)=>({_type:a,name:b,__sx_handle:c}),"obj_5":()=>({}),"obj_6":(a,b)=>({_type:a,items:b}),"obj_7":(a,b)=>({_type:a,name:b}),"obj_8":(a,b)=>({_type:a,name:b}),"obj_9":(a,b)=>({_type:a,__sx_handle:b}),"set_SxKernel":(a,b)=>a.SxKernel=b,"set___sxR":(a,b)=>a.__sxR=b,"set___sxTagFn":(a,b)=>a.__sxTagFn=b,"set__type":(a,b)=>a._type=b,"set_callFn":(a,b)=>a.callFn=b,"set_cekRun":(a,b)=>a.cekRun=b,"set_engine":(a,b)=>a.engine=b,"set_eval":(a,b)=>a.eval=b,"set_evalExpr":(a,b)=>a.evalExpr=b,"set_fnArity":(a,b)=>a.fnArity=b,"set_inspect":(a,b)=>a.inspect=b,"set_isCallable":(a,b)=>a.isCallable=b,"set_load":(a,b)=>a.load=b,"set_loadSource":(a,b)=>a.loadSource=b,"set_parse":(a,b)=>a.parse=b,"set_registerNative":(a,b)=>a.registerNative=b,"set_renderToHtml":(a,b)=>a.renderToHtml=b,"set_stringify":(a,b)=>a.stringify=b,"set_typeOf":(a,b)=>a.typeOf=b}}})(globalThis),"src":"sx-wasm-assets"});
/**
* sx-platform.js — Thin JS platform layer for the OCaml SX WASM engine.
*
* This file provides browser-native primitives (DOM, fetch, timers, etc.)
* to the WASM-compiled OCaml CEK machine. It:
* 1. Loads the WASM module (SxKernel)
* 2. Registers ~80 native browser functions via registerNative
* 3. Loads web adapters (.sx files) into the engine
* 4. Exports the public Sx API
*
* Both wasm_of_ocaml and js_of_ocaml targets bind to this same layer.
*/
(function(global) {
"use strict";
function initPlatform() {
var K = global.SxKernel;
if (!K) {
// WASM loader is async — wait and retry
setTimeout(initPlatform, 20);
return;
}
var _hasDom = typeof document !== "undefined";
var NIL = null;
var SVG_NS = "http://www.w3.org/2000/svg";
// =========================================================================
// Helper: wrap SX lambda for use as JS callback
// =========================================================================
function wrapLambda(fn) {
// For now, SX lambdas from registerNative are opaque — we can't call them
// directly from JS. They need to go through the engine.
// TODO: add callLambda API to SxKernel
return fn;
}
// =========================================================================
// 1. DOM Creation & Manipulation
// =========================================================================
K.registerNative("dom-create-element", function(args) {
if (!_hasDom) return NIL;
var tag = args[0], ns = args[1];
if (ns && ns !== NIL) return document.createElementNS(ns, tag);
return document.createElement(tag);
});
K.registerNative("create-text-node", function(args) {
return _hasDom ? document.createTextNode(args[0] || "") : NIL;
});
K.registerNative("create-comment", function(args) {
return _hasDom ? document.createComment(args[0] || "") : NIL;
});
K.registerNative("create-fragment", function(_args) {
return _hasDom ? document.createDocumentFragment() : NIL;
});
K.registerNative("dom-clone", function(args) {
var node = args[0];
return node && node.cloneNode ? node.cloneNode(true) : node;
});
K.registerNative("dom-parse-html", function(args) {
if (!_hasDom) return NIL;
var tpl = document.createElement("template");
tpl.innerHTML = args[0] || "";
return tpl.content;
});
K.registerNative("dom-parse-html-document", function(args) {
if (!_hasDom) return NIL;
var parser = new DOMParser();
return parser.parseFromString(args[0] || "", "text/html");
});
// =========================================================================
// 2. DOM Queries
// =========================================================================
K.registerNative("dom-query", function(args) {
return _hasDom ? document.querySelector(args[0]) || NIL : NIL;
});
K.registerNative("dom-query-all", function(args) {
var root = args[0] || (_hasDom ? document : null);
if (!root || !root.querySelectorAll) return [];
return Array.prototype.slice.call(root.querySelectorAll(args[1] || args[0]));
});
K.registerNative("dom-query-by-id", function(args) {
return _hasDom ? document.getElementById(args[0]) || NIL : NIL;
});
K.registerNative("dom-body", function(_args) {
return _hasDom ? document.body : NIL;
});
K.registerNative("dom-ensure-element", function(args) {
if (!_hasDom) return NIL;
var sel = args[0];
var el = document.querySelector(sel);
if (el) return el;
if (sel.charAt(0) === "#") {
el = document.createElement("div");
el.id = sel.slice(1);
document.body.appendChild(el);
return el;
}
return NIL;
});
// =========================================================================
// 3. DOM Attributes
// =========================================================================
K.registerNative("dom-get-attr", function(args) {
var el = args[0], name = args[1];
if (!el || !el.getAttribute) return NIL;
var v = el.getAttribute(name);
return v === null ? NIL : v;
});
K.registerNative("dom-set-attr", function(args) {
var el = args[0], name = args[1], val = args[2];
if (el && el.setAttribute) el.setAttribute(name, val);
return NIL;
});
K.registerNative("dom-remove-attr", function(args) {
if (args[0] && args[0].removeAttribute) args[0].removeAttribute(args[1]);
return NIL;
});
K.registerNative("dom-has-attr?", function(args) {
return !!(args[0] && args[0].hasAttribute && args[0].hasAttribute(args[1]));
});
K.registerNative("dom-attr-list", function(args) {
var el = args[0];
if (!el || !el.attributes) return [];
var r = [];
for (var i = 0; i < el.attributes.length; i++) {
r.push([el.attributes[i].name, el.attributes[i].value]);
}
return r;
});
// =========================================================================
// 4. DOM Content
// =========================================================================
K.registerNative("dom-text-content", function(args) {
var el = args[0];
return el ? el.textContent || el.nodeValue || "" : "";
});
K.registerNative("dom-set-text-content", function(args) {
var el = args[0], s = args[1];
if (el) {
if (el.nodeType === 3 || el.nodeType === 8) el.nodeValue = s;
else el.textContent = s;
}
return NIL;
});
K.registerNative("dom-inner-html", function(args) {
return args[0] && args[0].innerHTML != null ? args[0].innerHTML : "";
});
K.registerNative("dom-set-inner-html", function(args) {
if (args[0]) args[0].innerHTML = args[1] || "";
return NIL;
});
K.registerNative("dom-insert-adjacent-html", function(args) {
var el = args[0], pos = args[1], html = args[2];
if (el && el.insertAdjacentHTML) el.insertAdjacentHTML(pos, html);
return NIL;
});
K.registerNative("dom-body-inner-html", function(args) {
var doc = args[0];
return doc && doc.body ? doc.body.innerHTML : "";
});
// =========================================================================
// 5. DOM Structure & Navigation
// =========================================================================
K.registerNative("dom-parent", function(args) { return args[0] ? args[0].parentNode || NIL : NIL; });
K.registerNative("dom-first-child", function(args) { return args[0] ? args[0].firstChild || NIL : NIL; });
K.registerNative("dom-next-sibling", function(args) { return args[0] ? args[0].nextSibling || NIL : NIL; });
K.registerNative("dom-id", function(args) { return args[0] && args[0].id ? args[0].id : NIL; });
K.registerNative("dom-node-type", function(args) { return args[0] ? args[0].nodeType : 0; });
K.registerNative("dom-node-name", function(args) { return args[0] ? args[0].nodeName : ""; });
K.registerNative("dom-tag-name", function(args) { return args[0] && args[0].tagName ? args[0].tagName : ""; });
K.registerNative("dom-child-list", function(args) {
var el = args[0];
if (!el || !el.childNodes) return [];
return Array.prototype.slice.call(el.childNodes);
});
K.registerNative("dom-child-nodes", function(args) {
var el = args[0];
if (!el || !el.childNodes) return [];
return Array.prototype.slice.call(el.childNodes);
});
// =========================================================================
// 6. DOM Insertion & Removal
// =========================================================================
K.registerNative("dom-append", function(args) {
var parent = args[0], child = args[1];
if (parent && child) parent.appendChild(child);
return NIL;
});
K.registerNative("dom-prepend", function(args) {
var parent = args[0], child = args[1];
if (parent && child) parent.insertBefore(child, parent.firstChild);
return NIL;
});
K.registerNative("dom-insert-before", function(args) {
var parent = args[0], node = args[1], ref = args[2];
if (parent && node) parent.insertBefore(node, ref || null);
return NIL;
});
K.registerNative("dom-insert-after", function(args) {
var ref = args[0], node = args[1];
if (ref && ref.parentNode && node) {
ref.parentNode.insertBefore(node, ref.nextSibling);
}
return NIL;
});
K.registerNative("dom-remove", function(args) {
var node = args[0];
if (node && node.parentNode) node.parentNode.removeChild(node);
return NIL;
});
K.registerNative("dom-remove-child", function(args) {
var parent = args[0], child = args[1];
if (parent && child && child.parentNode === parent) parent.removeChild(child);
return NIL;
});
K.registerNative("dom-replace-child", function(args) {
var parent = args[0], newC = args[1], oldC = args[2];
if (parent && newC && oldC) parent.replaceChild(newC, oldC);
return NIL;
});
K.registerNative("dom-remove-children-after", function(args) {
var marker = args[0];
if (!marker || !marker.parentNode) return NIL;
var parent = marker.parentNode;
while (marker.nextSibling) parent.removeChild(marker.nextSibling);
return NIL;
});
K.registerNative("dom-append-to-head", function(args) {
if (_hasDom && args[0]) document.head.appendChild(args[0]);
return NIL;
});
// =========================================================================
// 7. DOM Type Checks
// =========================================================================
K.registerNative("dom-is-fragment?", function(args) { return args[0] ? args[0].nodeType === 11 : false; });
K.registerNative("dom-is-child-of?", function(args) { return !!(args[1] && args[0] && args[0].parentNode === args[1]); });
K.registerNative("dom-is-active-element?", function(args) { return _hasDom && args[0] === document.activeElement; });
K.registerNative("dom-is-input-element?", function(args) {
if (!args[0] || !args[0].tagName) return false;
var t = args[0].tagName;
return t === "INPUT" || t === "TEXTAREA" || t === "SELECT";
});
// =========================================================================
// 8. DOM Styles & Classes
// =========================================================================
K.registerNative("dom-get-style", function(args) {
return args[0] && args[0].style ? args[0].style[args[1]] || "" : "";
});
K.registerNative("dom-set-style", function(args) {
if (args[0] && args[0].style) args[0].style[args[1]] = args[2];
return NIL;
});
K.registerNative("dom-add-class", function(args) {
if (args[0] && args[0].classList) args[0].classList.add(args[1]);
return NIL;
});
K.registerNative("dom-remove-class", function(args) {
if (args[0] && args[0].classList) args[0].classList.remove(args[1]);
return NIL;
});
K.registerNative("dom-has-class?", function(args) {
return !!(args[0] && args[0].classList && args[0].classList.contains(args[1]));
});
// =========================================================================
// 9. DOM Properties & Data
// =========================================================================
K.registerNative("dom-get-prop", function(args) { return args[0] ? args[0][args[1]] : NIL; });
K.registerNative("dom-set-prop", function(args) { if (args[0]) args[0][args[1]] = args[2]; return NIL; });
K.registerNative("dom-set-data", function(args) {
var el = args[0], key = args[1], val = args[2];
if (el) { if (!el._sxData) el._sxData = {}; el._sxData[key] = val; }
return NIL;
});
K.registerNative("dom-get-data", function(args) {
var el = args[0], key = args[1];
return (el && el._sxData) ? (el._sxData[key] != null ? el._sxData[key] : NIL) : NIL;
});
K.registerNative("dom-call-method", function(args) {
var obj = args[0], method = args[1];
var callArgs = args.slice(2);
if (obj && typeof obj[method] === "function") {
try { return obj[method].apply(obj, callArgs); }
catch(e) { return NIL; }
}
return NIL;
});
// =========================================================================
// 10. DOM Events
// =========================================================================
K.registerNative("dom-listen", function(args) {
var el = args[0], name = args[1], handler = args[2];
if (!_hasDom || !el) return function() {};
// handler is a wrapped SX lambda (JS function with __sx_handle).
// Wrap it to:
// - Pass the event object as arg (or no args for 0-arity handlers)
// - Catch errors from the CEK machine
var arity = K.fnArity(handler);
var wrapped;
if (arity === 0) {
wrapped = function(_e) {
try { K.callFn(handler, []); }
catch(err) { console.error("[sx] event handler error:", name, err); }
};
} else {
wrapped = function(e) {
try { K.callFn(handler, [e]); }
catch(err) { console.error("[sx] event handler error:", name, err); }
};
}
el.addEventListener(name, wrapped);
return function() { el.removeEventListener(name, wrapped); };
});
K.registerNative("dom-dispatch", function(args) {
if (!_hasDom || !args[0]) return false;
var evt = new CustomEvent(args[1], { bubbles: true, cancelable: true, detail: args[2] || {} });
return args[0].dispatchEvent(evt);
});
K.registerNative("event-detail", function(args) {
return (args[0] && args[0].detail != null) ? args[0].detail : NIL;
});
// =========================================================================
// 11. Browser Navigation & History
// =========================================================================
K.registerNative("browser-location-href", function(_args) {
return typeof location !== "undefined" ? location.href : "";
});
K.registerNative("browser-same-origin?", function(args) {
try { return new URL(args[0], location.href).origin === location.origin; }
catch (e) { return true; }
});
K.registerNative("browser-push-state", function(args) {
if (typeof history !== "undefined") {
try { history.pushState({ sxUrl: args[0], scrollY: typeof window !== "undefined" ? window.scrollY : 0 }, "", args[0]); }
catch (e) {}
}
return NIL;
});
K.registerNative("browser-replace-state", function(args) {
if (typeof history !== "undefined") {
try { history.replaceState({ sxUrl: args[0], scrollY: typeof window !== "undefined" ? window.scrollY : 0 }, "", args[0]); }
catch (e) {}
}
return NIL;
});
K.registerNative("browser-navigate", function(args) {
if (typeof location !== "undefined") location.assign(args[0]);
return NIL;
});
K.registerNative("browser-reload", function(_args) {
if (typeof location !== "undefined") location.reload();
return NIL;
});
K.registerNative("browser-scroll-to", function(args) {
if (typeof window !== "undefined") window.scrollTo(args[0] || 0, args[1] || 0);
return NIL;
});
K.registerNative("browser-media-matches?", function(args) {
if (typeof window === "undefined") return false;
return window.matchMedia(args[0]).matches;
});
K.registerNative("browser-confirm", function(args) {
if (typeof window === "undefined") return false;
return window.confirm(args[0]);
});
K.registerNative("browser-prompt", function(args) {
if (typeof window === "undefined") return NIL;
var r = window.prompt(args[0]);
return r === null ? NIL : r;
});
// =========================================================================
// 12. Timers
// =========================================================================
K.registerNative("set-timeout", function(args) {
var fn = args[0], ms = args[1] || 0;
var cb = (typeof fn === "function" && fn.__sx_handle != null)
? function() { try { K.callFn(fn, []); } catch(e) { console.error("[sx] timeout error:", e); } }
: fn;
return setTimeout(cb, ms);
});
K.registerNative("set-interval", function(args) {
var fn = args[0], ms = args[1] || 1000;
var cb = (typeof fn === "function" && fn.__sx_handle != null)
? function() { try { K.callFn(fn, []); } catch(e) { console.error("[sx] interval error:", e); } }
: fn;
return setInterval(cb, ms);
});
K.registerNative("clear-timeout", function(args) { clearTimeout(args[0]); return NIL; });
K.registerNative("clear-interval", function(args) { clearInterval(args[0]); return NIL; });
K.registerNative("now-ms", function(_args) {
return (typeof performance !== "undefined") ? performance.now() : Date.now();
});
K.registerNative("request-animation-frame", function(args) {
var fn = args[0];
var cb = (typeof fn === "function" && fn.__sx_handle != null)
? function() { try { K.callFn(fn, []); } catch(e) { console.error("[sx] raf error:", e); } }
: fn;
if (typeof requestAnimationFrame !== "undefined") requestAnimationFrame(cb);
else setTimeout(cb, 16);
return NIL;
});
// =========================================================================
// 13. Promises
// =========================================================================
K.registerNative("promise-resolve", function(args) { return Promise.resolve(args[0]); });
K.registerNative("promise-then", function(args) {
var p = args[0];
if (!p || !p.then) return p;
var onResolve = function(v) { return K.callFn(args[1], [v]); };
var onReject = args[2] ? function(e) { return K.callFn(args[2], [e]); } : undefined;
return onReject ? p.then(onResolve, onReject) : p.then(onResolve);
});
K.registerNative("promise-catch", function(args) {
if (!args[0] || !args[0].catch) return args[0];
return args[0].catch(function(e) { return K.callFn(args[1], [e]); });
});
K.registerNative("promise-delayed", function(args) {
return new Promise(function(resolve) {
setTimeout(function() { resolve(args[1]); }, args[0]);
});
});
// =========================================================================
// 14. Abort Controllers
// =========================================================================
var _controllers = typeof WeakMap !== "undefined" ? new WeakMap() : null;
var _targetControllers = typeof WeakMap !== "undefined" ? new WeakMap() : null;
K.registerNative("new-abort-controller", function(_args) {
return typeof AbortController !== "undefined" ? new AbortController() : { signal: null, abort: function() {} };
});
K.registerNative("abort-previous", function(args) {
if (_controllers) { var prev = _controllers.get(args[0]); if (prev) prev.abort(); }
return NIL;
});
K.registerNative("track-controller", function(args) {
if (_controllers) _controllers.set(args[0], args[1]);
return NIL;
});
K.registerNative("abort-previous-target", function(args) {
if (_targetControllers) { var prev = _targetControllers.get(args[0]); if (prev) prev.abort(); }
return NIL;
});
K.registerNative("track-controller-target", function(args) {
if (_targetControllers) _targetControllers.set(args[0], args[1]);
return NIL;
});
K.registerNative("controller-signal", function(args) { return args[0] ? args[0].signal : NIL; });
K.registerNative("is-abort-error", function(args) { return args[0] && args[0].name === "AbortError"; });
// =========================================================================
// 15. Fetch
// =========================================================================
K.registerNative("fetch-request", function(args) {
var config = args[0], successFn = args[1], errorFn = args[2];
var opts = { method: config.method, headers: config.headers };
if (config.signal) opts.signal = config.signal;
if (config.body && config.method !== "GET") opts.body = config.body;
if (config["cross-origin"]) opts.credentials = "include";
return fetch(config.url, opts).then(function(resp) {
return resp.text().then(function(text) {
var getHeader = function(name) {
var v = resp.headers.get(name);
return v === null ? NIL : v;
};
return K.callFn(successFn, [resp.ok, resp.status, getHeader, text]);
});
}).catch(function(err) {
return K.callFn(errorFn, [err]);
});
});
K.registerNative("csrf-token", function(_args) {
if (!_hasDom) return NIL;
var m = document.querySelector('meta[name="csrf-token"]');
return m ? m.getAttribute("content") : NIL;
});
K.registerNative("is-cross-origin", function(args) {
try {
var h = new URL(args[0], location.href).hostname;
return h !== location.hostname &&
(h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0);
} catch (e) { return false; }
});
// =========================================================================
// 16. localStorage
// =========================================================================
K.registerNative("local-storage-get", function(args) {
try { var v = localStorage.getItem(args[0]); return v === null ? NIL : v; }
catch(e) { return NIL; }
});
K.registerNative("local-storage-set", function(args) {
try { localStorage.setItem(args[0], args[1]); } catch(e) {}
return NIL;
});
K.registerNative("local-storage-remove", function(args) {
try { localStorage.removeItem(args[0]); } catch(e) {}
return NIL;
});
// =========================================================================
// 17. Document Head & Title
// =========================================================================
K.registerNative("set-document-title", function(args) {
if (_hasDom) document.title = args[0] || "";
return NIL;
});
K.registerNative("remove-head-element", function(args) {
if (_hasDom) {
var el = document.head.querySelector(args[0]);
if (el) el.remove();
}
return NIL;
});
// =========================================================================
// 18. Logging
// =========================================================================
K.registerNative("log-info", function(args) { console.log("[sx]", args[0]); return NIL; });
K.registerNative("log-warn", function(args) { console.warn("[sx]", args[0]); return NIL; });
K.registerNative("log-error", function(args) { console.error("[sx]", args[0]); return NIL; });
// =========================================================================
// 19. JSON
// =========================================================================
K.registerNative("json-parse", function(args) {
try { return JSON.parse(args[0]); } catch(e) { return {}; }
});
K.registerNative("try-parse-json", function(args) {
try { return JSON.parse(args[0]); } catch(e) { return NIL; }
});
// =========================================================================
// 20. Processing markers
// =========================================================================
K.registerNative("mark-processed!", function(args) {
var el = args[0], key = args[1] || "sx";
if (el) { if (!el._sxProcessed) el._sxProcessed = {}; el._sxProcessed[key] = true; }
return NIL;
});
K.registerNative("is-processed?", function(args) {
var el = args[0], key = args[1] || "sx";
return !!(el && el._sxProcessed && el._sxProcessed[key]);
});
// =========================================================================
// Public Sx API (wraps SxKernel for compatibility with existing code)
// =========================================================================
var Sx = {
// Core (delegated to WASM engine)
parse: K.parse,
eval: K.eval,
evalExpr: K.evalExpr,
load: K.load,
loadSource: K.loadSource,
renderToHtml: K.renderToHtml,
typeOf: K.typeOf,
inspect: K.inspect,
engine: K.engine,
// Will be populated after web adapters load:
// mount, hydrate, processElements, etc.
};
global.Sx = Sx;
global.SxKernel = K; // Keep kernel available for direct access
console.log("[sx-platform] registered, engine:", K.engine());
} // end initPlatform
initPlatform();
})(typeof globalThis !== "undefined" ? globalThis : this);
// =========================================================================
// Embedded web adapters (loaded into WASM engine at boot)
// =========================================================================
globalThis.__sxAdapters = {};
globalThis.__sxAdapters["signals"] = ";; ==========================================================================\n;; signals.sx \u2014 Reactive signal runtime specification\n;;\n;; Defines the signal primitive: a container for a value that notifies\n;; subscribers when it changes. Signals are the reactive state primitive\n;; for SX islands.\n;;\n;; Signals are pure computation \u2014 no DOM, no IO. The reactive rendering\n;; layer (adapter-dom.sx) subscribes DOM nodes to signals. The server\n;; adapter (adapter-html.sx) reads signal values without subscribing.\n;;\n;; Signals are plain dicts with a \"__signal\" marker key. No platform\n;; primitives needed \u2014 all signal operations are pure SX.\n;;\n;; Reactive tracking and island lifecycle use the general scoped effects\n;; system (scope-push!/scope-pop!/context) instead of separate globals.\n;; Two scope names:\n;; \"sx-reactive\" \u2014 tracking context for computed/effect dep discovery\n;; \"sx-island-scope\" \u2014 island disposable collector\n;;\n;; Scope-based tracking:\n;; (scope-push! \"sx-reactive\" {:deps (list) :notify fn}) \u2192 void\n;; (scope-pop! \"sx-reactive\") \u2192 void\n;; (context \"sx-reactive\" nil) \u2192 dict or nil\n;;\n;; CEK callable dispatch:\n;; (cek-call f args) \u2192 any \u2014 call f with args list via CEK.\n;; Dispatches through cek-run for SX\n;; lambdas, apply for native callables.\n;; Defined in cek.sx.\n;;\n;; ==========================================================================\n\n\n;; --------------------------------------------------------------------------\n;; Signal container \u2014 plain dict with marker key\n;; --------------------------------------------------------------------------\n;;\n;; A signal is a dict: {\"__signal\" true, \"value\" v, \"subscribers\" [], \"deps\" []}\n;; type-of returns \"dict\". Use signal? to distinguish from regular dicts.\n\n(define make-signal (fn (value)\n (dict \"__signal\" true \"value\" value \"subscribers\" (list) \"deps\" (list))))\n\n(define signal? (fn (x)\n (and (dict? x) (has-key? x \"__signal\"))))\n\n(define signal-value (fn (s) (get s \"value\")))\n(define signal-set-value! (fn (s v) (dict-set! s \"value\" v)))\n(define signal-subscribers (fn (s) (get s \"subscribers\")))\n\n(define signal-add-sub! (fn (s f)\n (when (not (contains? (get s \"subscribers\") f))\n (append! (get s \"subscribers\") f))))\n\n(define signal-remove-sub! (fn (s f)\n (dict-set! s \"subscribers\"\n (filter (fn (sub) (not (identical? sub f)))\n (get s \"subscribers\")))))\n\n(define signal-deps (fn (s) (get s \"deps\")))\n(define signal-set-deps! (fn (s deps) (dict-set! s \"deps\" deps)))\n\n\n;; --------------------------------------------------------------------------\n;; 1. signal \u2014 create a reactive container\n;; --------------------------------------------------------------------------\n\n(define signal :effects []\n (fn ((initial-value :as any))\n (make-signal initial-value)))\n\n\n;; --------------------------------------------------------------------------\n;; 2. deref \u2014 read signal value, subscribe current reactive context\n;; --------------------------------------------------------------------------\n;;\n;; In a reactive context (inside effect or computed), deref registers the\n;; signal as a dependency. Outside reactive context, deref just returns\n;; the current value \u2014 no subscription, no overhead.\n\n(define deref :effects []\n (fn ((s :as any))\n (if (not (signal? s))\n s ;; non-signal values pass through\n (let ((ctx (context \"sx-reactive\" nil)))\n (when ctx\n ;; Register this signal as a dependency of the current context\n (let ((dep-list (get ctx \"deps\"))\n (notify-fn (get ctx \"notify\")))\n (when (not (contains? dep-list s))\n (append! dep-list s)\n (signal-add-sub! s notify-fn))))\n (signal-value s)))))\n\n\n;; --------------------------------------------------------------------------\n;; 3. reset! \u2014 write a new value, notify subscribers\n;; --------------------------------------------------------------------------\n\n(define reset! :effects [mutation]\n (fn ((s :as signal) value)\n (when (signal? s)\n (let ((old (signal-value s)))\n (when (not (identical? old value))\n (signal-set-value! s value)\n (notify-subscribers s))))))\n\n\n;; --------------------------------------------------------------------------\n;; 4. swap! \u2014 update signal via function\n;; --------------------------------------------------------------------------\n\n(define swap! :effects [mutation]\n (fn ((s :as signal) (f :as lambda) &rest args)\n (when (signal? s)\n (let ((old (signal-value s))\n (new-val (apply f (cons old args))))\n (when (not (identical? old new-val))\n (signal-set-value! s new-val)\n (notify-subscribers s))))))\n\n\n;; --------------------------------------------------------------------------\n;; 5. computed \u2014 derived signal with automatic dependency tracking\n;; --------------------------------------------------------------------------\n;;\n;; A computed signal wraps a zero-arg function. It re-evaluates when any\n;; of its dependencies change. The dependency set is discovered automatically\n;; by tracking deref calls during evaluation.\n\n(define computed :effects [mutation]\n (fn ((compute-fn :as lambda))\n (let ((s (make-signal nil))\n (deps (list))\n (compute-ctx nil))\n\n ;; The notify function \u2014 called when a dependency changes\n (let ((recompute\n (fn ()\n ;; Unsubscribe from old deps\n (for-each\n (fn ((dep :as signal)) (signal-remove-sub! dep recompute))\n (signal-deps s))\n (signal-set-deps! s (list))\n\n ;; Push scope-based tracking context for this computed\n (let ((ctx (dict \"deps\" (list) \"notify\" recompute)))\n (scope-push! \"sx-reactive\" ctx)\n (let ((new-val (cek-call compute-fn nil)))\n (scope-pop! \"sx-reactive\")\n ;; Save discovered deps\n (signal-set-deps! s (get ctx \"deps\"))\n ;; Update value + notify downstream\n (let ((old (signal-value s)))\n (signal-set-value! s new-val)\n (when (not (identical? old new-val))\n (notify-subscribers s))))))))\n\n ;; Initial computation\n (recompute)\n ;; Auto-register disposal with island scope\n (register-in-scope (fn () (dispose-computed s)))\n s))))\n\n\n;; --------------------------------------------------------------------------\n;; 6. effect \u2014 side effect that runs when dependencies change\n;; --------------------------------------------------------------------------\n;;\n;; Like computed, but doesn't produce a signal value. Returns a dispose\n;; function that tears down the effect.\n\n(define effect :effects [mutation]\n (fn ((effect-fn :as lambda))\n (let ((deps (list))\n (disposed false)\n (cleanup-fn nil))\n\n (let ((run-effect\n (fn ()\n (when (not disposed)\n ;; Run previous cleanup if any\n (when cleanup-fn (cek-call cleanup-fn nil))\n\n ;; Unsubscribe from old deps\n (for-each\n (fn ((dep :as signal)) (signal-remove-sub! dep run-effect))\n deps)\n (set! deps (list))\n\n ;; Push scope-based tracking context\n (let ((ctx (dict \"deps\" (list) \"notify\" run-effect)))\n (scope-push! \"sx-reactive\" ctx)\n (let ((result (cek-call effect-fn nil)))\n (scope-pop! \"sx-reactive\")\n (set! deps (get ctx \"deps\"))\n ;; If effect returns a function, it's the cleanup\n (when (callable? result)\n (set! cleanup-fn result))))))))\n\n ;; Initial run\n (run-effect)\n\n ;; Return dispose function\n (let ((dispose-fn\n (fn ()\n (set! disposed true)\n (when cleanup-fn (cek-call cleanup-fn nil))\n (for-each\n (fn ((dep :as signal)) (signal-remove-sub! dep run-effect))\n deps)\n (set! deps (list)))))\n ;; Auto-register with island scope so disposal happens on swap\n (register-in-scope dispose-fn)\n dispose-fn)))))\n\n\n;; --------------------------------------------------------------------------\n;; 7. batch \u2014 group multiple signal writes into one notification pass\n;; --------------------------------------------------------------------------\n;;\n;; During a batch, signal writes are deferred. Subscribers are notified\n;; once at the end, after all values have been updated.\n\n(define *batch-depth* 0)\n(define *batch-queue* (list))\n\n(define batch :effects [mutation]\n (fn ((thunk :as lambda))\n (set! *batch-depth* (+ *batch-depth* 1))\n (cek-call thunk nil)\n (set! *batch-depth* (- *batch-depth* 1))\n (when (= *batch-depth* 0)\n (let ((queue *batch-queue*))\n (set! *batch-queue* (list))\n ;; Collect unique subscribers across all queued signals,\n ;; then notify each exactly once.\n (let ((seen (list))\n (pending (list)))\n (for-each\n (fn ((s :as signal))\n (for-each\n (fn ((sub :as lambda))\n (when (not (contains? seen sub))\n (append! seen sub)\n (append! pending sub)))\n (signal-subscribers s)))\n queue)\n (for-each (fn ((sub :as lambda)) (sub)) pending))))))\n\n\n;; --------------------------------------------------------------------------\n;; 8. notify-subscribers \u2014 internal notification dispatch\n;; --------------------------------------------------------------------------\n;;\n;; If inside a batch, queues the signal. Otherwise, notifies immediately.\n\n(define notify-subscribers :effects [mutation]\n (fn ((s :as signal))\n (if (> *batch-depth* 0)\n (when (not (contains? *batch-queue* s))\n (append! *batch-queue* s))\n (flush-subscribers s))))\n\n(define flush-subscribers :effects [mutation]\n (fn ((s :as signal))\n (for-each\n (fn ((sub :as lambda)) (sub))\n (signal-subscribers s))))\n\n\n;; --------------------------------------------------------------------------\n;; 9. Reactive tracking context\n;; --------------------------------------------------------------------------\n;;\n;; Tracking is now scope-based. computed/effect push a dict\n;; {:deps (list) :notify fn} onto the \"sx-reactive\" scope stack via\n;; scope-push!/scope-pop!. deref reads it via (context \"sx-reactive\" nil).\n;; No platform primitives needed \u2014 uses the existing scope infrastructure.\n\n\n;; --------------------------------------------------------------------------\n;; 10. dispose \u2014 tear down a computed signal\n;; --------------------------------------------------------------------------\n;;\n;; For computed signals, unsubscribe from all dependencies.\n;; For effects, the dispose function is returned by effect itself.\n\n(define dispose-computed :effects [mutation]\n (fn ((s :as signal))\n (when (signal? s)\n (for-each\n (fn ((dep :as signal)) (signal-remove-sub! dep nil))\n (signal-deps s))\n (signal-set-deps! s (list)))))\n\n\n;; --------------------------------------------------------------------------\n;; 11. Island scope \u2014 automatic cleanup of signals within an island\n;; --------------------------------------------------------------------------\n;;\n;; When an island is created, all signals, effects, and computeds created\n;; within it are tracked. When the island is removed from the DOM, they\n;; are all disposed.\n;;\n;; Uses \"sx-island-scope\" scope name. The scope value is a collector\n;; function (fn (disposable) ...) that appends to the island's disposer list.\n\n(define with-island-scope :effects [mutation]\n (fn ((scope-fn :as lambda) (body-fn :as lambda))\n (scope-push! \"sx-island-scope\" scope-fn)\n (let ((result (body-fn)))\n (scope-pop! \"sx-island-scope\")\n result)))\n\n;; Hook into signal/effect/computed creation for scope tracking.\n\n(define register-in-scope :effects [mutation]\n (fn ((disposable :as lambda))\n (let ((collector (context \"sx-island-scope\" nil)))\n (when collector\n (cek-call collector (list disposable))))))\n\n\n;; ==========================================================================\n;; 12. Marsh scopes \u2014 child scopes within islands\n;; ==========================================================================\n;;\n;; Marshes are zones inside islands where server content is re-evaluated\n;; in the island's reactive context. When a marsh is re-morphed with new\n;; content, its old effects and computeds must be disposed WITHOUT disturbing\n;; the island's own reactive graph.\n;;\n;; Scope hierarchy: island \u2192 marsh \u2192 effects/computeds\n;; Disposing a marsh disposes its subscope. Disposing an island disposes\n;; all its marshes. The signal graph is a tree, not a flat list.\n;;\n;; Platform interface required:\n;; (dom-set-data el key val) \u2192 void \u2014 store JS value on element\n;; (dom-get-data el key) \u2192 any \u2014 retrieve stored value\n\n(define with-marsh-scope :effects [mutation io]\n (fn (marsh-el (body-fn :as lambda))\n ;; Execute body-fn collecting all disposables into a marsh-local list.\n ;; Nested under the current island scope \u2014 if the island is disposed,\n ;; the marsh is disposed too (because island scope collected the marsh's\n ;; own dispose function).\n (let ((disposers (list)))\n (with-island-scope\n (fn (d) (append! disposers d))\n body-fn)\n ;; Store disposers on the marsh element for later cleanup\n (dom-set-data marsh-el \"sx-marsh-disposers\" disposers))))\n\n(define dispose-marsh-scope :effects [mutation io]\n (fn (marsh-el)\n ;; Dispose all effects/computeds registered in this marsh's scope.\n ;; Parent island scope and sibling marshes are unaffected.\n (let ((disposers (dom-get-data marsh-el \"sx-marsh-disposers\")))\n (when disposers\n (for-each (fn ((d :as lambda)) (cek-call d nil)) disposers)\n (dom-set-data marsh-el \"sx-marsh-disposers\" nil)))))\n\n\n;; ==========================================================================\n;; 13. Named stores \u2014 page-level signal containers (L3)\n;; ==========================================================================\n;;\n;; Stores persist across island creation/destruction. They live at page\n;; scope, not island scope. When an island is swapped out and re-created,\n;; it reconnects to the same store instance.\n;;\n;; The store registry is global page-level state. It survives island\n;; disposal but is cleared on full page navigation.\n\n(define *store-registry* (dict))\n\n(define def-store :effects [mutation]\n (fn ((name :as string) (init-fn :as lambda))\n (let ((registry *store-registry*))\n ;; Only create the store once \u2014 subsequent calls return existing\n (when (not (has-key? registry name))\n (set! *store-registry* (assoc registry name (cek-call init-fn nil))))\n (get *store-registry* name))))\n\n(define use-store :effects []\n (fn ((name :as string))\n (if (has-key? *store-registry* name)\n (get *store-registry* name)\n (error (str \"Store not found: \" name\n \". Call (def-store ...) before (use-store ...).\")))))\n\n(define clear-stores :effects [mutation]\n (fn ()\n (set! *store-registry* (dict))))\n\n\n;; ==========================================================================\n;; 13. Event bridge \u2014 DOM event communication for lake\u2192island\n;; ==========================================================================\n;;\n;; Server-rendered content (\"htmx lakes\") inside reactive islands can\n;; communicate with island signals via DOM custom events. The bridge\n;; pattern:\n;;\n;; 1. Server renders a button/link with data-sx-emit=\"event-name\"\n;; 2. When clicked, the client dispatches a CustomEvent on the element\n;; 3. The event bubbles up to the island container\n;; 4. An island effect listens for the event and updates signals\n;;\n;; This keeps server content pure HTML \u2014 no signal references needed.\n;; The island effect is the only reactive code.\n;;\n;; Platform interface required:\n;; (dom-listen el event-name handler) \u2192 remove-fn\n;; (dom-dispatch el event-name detail) \u2192 void\n;; (event-detail e) \u2192 any\n;;\n;; These are platform primitives because they require browser DOM APIs.\n\n(define emit-event :effects [io]\n (fn (el (event-name :as string) detail)\n (dom-dispatch el event-name detail)))\n\n(define on-event :effects [io]\n (fn (el (event-name :as string) (handler :as lambda))\n (dom-listen el event-name handler)))\n\n;; Convenience: create an effect that listens for a DOM event on an\n;; element and writes the event detail (or a transformed value) into\n;; a target signal. Returns the effect's dispose function.\n;; When the effect is disposed (island teardown), the listener is\n;; removed automatically via the cleanup return.\n\n(define bridge-event :effects [mutation io]\n (fn (el (event-name :as string) (target-signal :as signal) transform-fn)\n (effect (fn ()\n (let ((remove (dom-listen el event-name\n (fn (e)\n (let ((detail (event-detail e))\n (new-val (if transform-fn\n (cek-call transform-fn (list detail))\n detail)))\n (reset! target-signal new-val))))))\n ;; Return cleanup \u2014 removes listener on dispose/re-run\n remove)))))\n\n\n;; ==========================================================================\n;; 14. Resource \u2014 async signal with loading/resolved/error states\n;; ==========================================================================\n;;\n;; A resource wraps an async operation (fetch, computation) and exposes\n;; its state as a signal. The signal transitions through:\n;; {:loading true :data nil :error nil} \u2014 initial/loading\n;; {:loading false :data result :error nil} \u2014 success\n;; {:loading false :data nil :error err} \u2014 failure\n;;\n;; Usage:\n;; (let ((user (resource (fn () (fetch-json \"/api/user\")))))\n;; (cond\n;; (get (deref user) \"loading\") (div \"Loading...\")\n;; (get (deref user) \"error\") (div \"Error: \" (get (deref user) \"error\"))\n;; :else (div (get (deref user) \"data\"))))\n;;\n;; Platform interface required:\n;; (promise-then promise on-resolve on-reject) \u2192 void\n\n(define resource :effects [mutation io]\n (fn ((fetch-fn :as lambda))\n (let ((state (signal (dict \"loading\" true \"data\" nil \"error\" nil))))\n ;; Kick off the async operation\n (promise-then (cek-call fetch-fn nil)\n (fn (data) (reset! state (dict \"loading\" false \"data\" data \"error\" nil)))\n (fn (err) (reset! state (dict \"loading\" false \"data\" nil \"error\" err))))\n state)))\n\n\n";
globalThis.__sxAdapters["deps"] = ";; ==========================================================================\n;; deps.sx \u2014 Component dependency analysis specification\n;;\n;; Pure functions for analyzing component dependency graphs.\n;; Used by the bundling system to compute per-page component bundles\n;; instead of sending every definition to every page.\n;;\n;; All functions are pure \u2014 no IO, no platform-specific operations.\n;; Each host bootstraps this to native code alongside eval.sx/render.sx.\n;;\n;; From eval.sx platform (already provided by every host):\n;; (type-of x) \u2192 type string\n;; (symbol-name s) \u2192 string name of symbol\n;; (component-body c) \u2192 unevaluated AST of component body\n;; (component-name c) \u2192 string name (without ~)\n;; (macro-body m) \u2192 macro body AST\n;; (env-get env k) \u2192 value or nil\n;;\n;; New platform functions for deps (each host implements):\n;; (component-deps c) \u2192 cached deps list (may be empty)\n;; (component-set-deps! c d)\u2192 cache deps on component\n;; (component-css-classes c)\u2192 pre-scanned CSS class list\n;; (regex-find-all pat src) \u2192 list of capture group 1 matches\n;; (scan-css-classes src) \u2192 list of CSS class strings from source\n;; ==========================================================================\n\n\n;; --------------------------------------------------------------------------\n;; 1. AST scanning \u2014 collect ~component references from an AST node\n;; --------------------------------------------------------------------------\n;; Walks all branches of control flow (if/when/cond/case) to find\n;; every component that *could* be rendered.\n\n(define scan-refs :effects []\n (fn (node)\n (let ((refs (list)))\n (scan-refs-walk node refs)\n refs)))\n\n\n(define scan-refs-walk :effects []\n (fn (node (refs :as list))\n (cond\n ;; Symbol starting with ~ \u2192 component reference\n (= (type-of node) \"symbol\")\n (let ((name (symbol-name node)))\n (when (starts-with? name \"~\")\n (when (not (contains? refs name))\n (append! refs name))))\n\n ;; List \u2192 recurse into all elements (covers all control flow branches)\n (= (type-of node) \"list\")\n (for-each (fn (item) (scan-refs-walk item refs)) node)\n\n ;; Dict \u2192 recurse into values\n (= (type-of node) \"dict\")\n (for-each (fn (key) (scan-refs-walk (dict-get node key) refs))\n (keys node))\n\n ;; Literals (number, string, boolean, nil, keyword) \u2192 no refs\n :else nil)))\n\n\n;; --------------------------------------------------------------------------\n;; 2. Transitive dependency closure\n;; --------------------------------------------------------------------------\n;; Given a component name and an environment, compute all components\n;; that it can transitively render. Handles cycles via seen-set.\n\n(define transitive-deps-walk :effects []\n (fn ((n :as string) (seen :as list) (env :as dict))\n (when (not (contains? seen n))\n (append! seen n)\n (let ((val (env-get env n)))\n (cond\n (or (= (type-of val) \"component\") (= (type-of val) \"island\"))\n (for-each (fn ((ref :as string)) (transitive-deps-walk ref seen env))\n (scan-refs (component-body val)))\n (= (type-of val) \"macro\")\n (for-each (fn ((ref :as string)) (transitive-deps-walk ref seen env))\n (scan-refs (macro-body val)))\n :else nil)))))\n\n\n(define transitive-deps :effects []\n (fn ((name :as string) (env :as dict))\n (let ((seen (list))\n (key (if (starts-with? name \"~\") name (str \"~\" name))))\n (transitive-deps-walk key seen env)\n (filter (fn ((x :as string)) (not (= x key))) seen))))\n\n\n;; --------------------------------------------------------------------------\n;; 3. Compute deps for all components in an environment\n;; --------------------------------------------------------------------------\n;; Iterates env, calls transitive-deps for each component, and\n;; stores the result via the platform's component-set-deps! function.\n;;\n;; Platform interface:\n;; (env-components env) \u2192 list of component names in env\n;; (component-set-deps! comp deps) \u2192 store deps on component\n\n(define compute-all-deps :effects [mutation]\n (fn ((env :as dict))\n (for-each\n (fn ((name :as string))\n (let ((val (env-get env name)))\n (when (or (= (type-of val) \"component\") (= (type-of val) \"island\"))\n (component-set-deps! val (transitive-deps name env)))))\n (env-components env))))\n\n\n;; --------------------------------------------------------------------------\n;; 4. Scan serialized SX source for component references\n;; --------------------------------------------------------------------------\n;; Regex-based extraction of (~name patterns from SX wire format.\n;; Returns list of names WITH ~ prefix.\n;;\n;; Platform interface:\n;; (regex-find-all pattern source) \u2192 list of matched group strings\n\n(define scan-components-from-source :effects []\n (fn ((source :as string))\n (let ((matches (regex-find-all \"\\\\(~([a-zA-Z_][a-zA-Z0-9_\\\\-:/]*)\" source)))\n (map (fn ((m :as string)) (str \"~\" m)) matches))))\n\n\n;; --------------------------------------------------------------------------\n;; 5. Components needed for a page\n;; --------------------------------------------------------------------------\n;; Scans page source for direct component references, then computes\n;; the transitive closure. Returns list of ~names.\n\n(define components-needed :effects []\n (fn ((page-source :as string) (env :as dict))\n (let ((direct (scan-components-from-source page-source))\n (all-needed (list)))\n\n ;; Add each direct ref + its transitive deps\n (for-each\n (fn ((name :as string))\n (when (not (contains? all-needed name))\n (append! all-needed name))\n (let ((val (env-get env name)))\n (let ((deps (if (and (= (type-of val) \"component\")\n (not (empty? (component-deps val))))\n (component-deps val)\n (transitive-deps name env))))\n (for-each\n (fn ((dep :as string))\n (when (not (contains? all-needed dep))\n (append! all-needed dep)))\n deps))))\n direct)\n\n all-needed)))\n\n\n;; --------------------------------------------------------------------------\n;; 6. Build per-page component bundle\n;; --------------------------------------------------------------------------\n;; Given page source and env, returns list of component names needed.\n;; The host uses this list to serialize only the needed definitions\n;; and compute a page-specific hash.\n;;\n;; This replaces the \"send everything\" approach with per-page bundles.\n\n(define page-component-bundle :effects []\n (fn ((page-source :as string) (env :as dict))\n (components-needed page-source env)))\n\n\n;; --------------------------------------------------------------------------\n;; 7. CSS classes for a page\n;; --------------------------------------------------------------------------\n;; Returns the union of CSS classes from components this page uses,\n;; plus classes from the page source itself.\n;;\n;; Platform interface:\n;; (component-css-classes c) \u2192 set/list of class strings\n;; (scan-css-classes source) \u2192 set/list of class strings from source\n\n(define page-css-classes :effects []\n (fn ((page-source :as string) (env :as dict))\n (let ((needed (components-needed page-source env))\n (classes (list)))\n\n ;; Collect classes from needed components\n (for-each\n (fn ((name :as string))\n (let ((val (env-get env name)))\n (when (= (type-of val) \"component\")\n (for-each\n (fn ((cls :as string))\n (when (not (contains? classes cls))\n (append! classes cls)))\n (component-css-classes val)))))\n needed)\n\n ;; Add classes from page source\n (for-each\n (fn ((cls :as string))\n (when (not (contains? classes cls))\n (append! classes cls)))\n (scan-css-classes page-source))\n\n classes)))\n\n\n;; --------------------------------------------------------------------------\n;; 8. IO detection \u2014 scan component ASTs for IO primitive references\n;; --------------------------------------------------------------------------\n;; Extends the dependency walker to detect references to IO primitives.\n;; IO names are provided by the host (from boundary.sx declarations).\n;; A component is \"pure\" if it (transitively) references no IO primitives.\n;;\n;; Platform interface additions:\n;; (component-io-refs c) \u2192 cached IO ref list (may be empty)\n;; (component-set-io-refs! c r) \u2192 cache IO refs on component\n\n(define scan-io-refs-walk :effects []\n (fn (node (io-names :as list) (refs :as list))\n (cond\n ;; Symbol \u2192 check if name is in the IO set\n (= (type-of node) \"symbol\")\n (let ((name (symbol-name node)))\n (when (contains? io-names name)\n (when (not (contains? refs name))\n (append! refs name))))\n\n ;; List \u2192 recurse into all elements\n (= (type-of node) \"list\")\n (for-each (fn (item) (scan-io-refs-walk item io-names refs)) node)\n\n ;; Dict \u2192 recurse into values\n (= (type-of node) \"dict\")\n (for-each (fn (key) (scan-io-refs-walk (dict-get node key) io-names refs))\n (keys node))\n\n ;; Literals \u2192 no IO refs\n :else nil)))\n\n\n(define scan-io-refs :effects []\n (fn (node (io-names :as list))\n (let ((refs (list)))\n (scan-io-refs-walk node io-names refs)\n refs)))\n\n\n;; --------------------------------------------------------------------------\n;; 9. Transitive IO refs \u2014 follow component deps and union IO refs\n;; --------------------------------------------------------------------------\n\n(define transitive-io-refs-walk :effects []\n (fn ((n :as string) (seen :as list) (all-refs :as list) (env :as dict) (io-names :as list))\n (when (not (contains? seen n))\n (append! seen n)\n (let ((val (env-get env n)))\n (cond\n (= (type-of val) \"component\")\n (do\n ;; Scan this component's body for IO refs\n (for-each\n (fn ((ref :as string))\n (when (not (contains? all-refs ref))\n (append! all-refs ref)))\n (scan-io-refs (component-body val) io-names))\n ;; Recurse into component deps\n (for-each\n (fn ((dep :as string)) (transitive-io-refs-walk dep seen all-refs env io-names))\n (scan-refs (component-body val))))\n\n (= (type-of val) \"macro\")\n (do\n (for-each\n (fn ((ref :as string))\n (when (not (contains? all-refs ref))\n (append! all-refs ref)))\n (scan-io-refs (macro-body val) io-names))\n (for-each\n (fn ((dep :as string)) (transitive-io-refs-walk dep seen all-refs env io-names))\n (scan-refs (macro-body val))))\n\n :else nil)))))\n\n\n(define transitive-io-refs :effects []\n (fn ((name :as string) (env :as dict) (io-names :as list))\n (let ((all-refs (list))\n (seen (list))\n (key (if (starts-with? name \"~\") name (str \"~\" name))))\n (transitive-io-refs-walk key seen all-refs env io-names)\n all-refs)))\n\n\n;; --------------------------------------------------------------------------\n;; 10. Compute IO refs for all components in an environment\n;; --------------------------------------------------------------------------\n\n(define compute-all-io-refs :effects [mutation]\n (fn ((env :as dict) (io-names :as list))\n (for-each\n (fn ((name :as string))\n (let ((val (env-get env name)))\n (when (= (type-of val) \"component\")\n (component-set-io-refs! val (transitive-io-refs name env io-names)))))\n (env-components env))))\n\n\n(define component-io-refs-cached :effects []\n (fn ((name :as string) (env :as dict) (io-names :as list))\n (let ((key (if (starts-with? name \"~\") name (str \"~\" name))))\n (let ((val (env-get env key)))\n (if (and (= (type-of val) \"component\")\n (not (nil? (component-io-refs val)))\n (not (empty? (component-io-refs val))))\n (component-io-refs val)\n ;; Fallback: not yet cached (shouldn't happen after compute-all-io-refs)\n (transitive-io-refs name env io-names))))))\n\n(define component-pure? :effects []\n (fn ((name :as string) (env :as dict) (io-names :as list))\n (let ((key (if (starts-with? name \"~\") name (str \"~\" name))))\n (let ((val (env-get env key)))\n (if (and (= (type-of val) \"component\")\n (not (nil? (component-io-refs val))))\n ;; Use cached io-refs (empty list = pure)\n (empty? (component-io-refs val))\n ;; Fallback\n (empty? (transitive-io-refs name env io-names)))))))\n\n\n;; --------------------------------------------------------------------------\n;; 5. Render target \u2014 boundary decision per component\n;; --------------------------------------------------------------------------\n;; Combines IO analysis with affinity annotations to decide where a\n;; component should render:\n;;\n;; :affinity :server \u2192 always \"server\" (auth-sensitive, secrets)\n;; :affinity :client \u2192 \"client\" even if IO-dependent (IO proxy)\n;; :affinity :auto \u2192 \"server\" if IO-dependent, \"client\" if pure\n;;\n;; Returns: \"server\" | \"client\"\n\n(define render-target :effects []\n (fn ((name :as string) (env :as dict) (io-names :as list))\n (let ((key (if (starts-with? name \"~\") name (str \"~\" name))))\n (let ((val (env-get env key)))\n (if (not (= (type-of val) \"component\"))\n \"server\"\n (let ((affinity (component-affinity val)))\n (cond\n (= affinity \"server\") \"server\"\n (= affinity \"client\") \"client\"\n ;; auto: decide from IO analysis\n (not (component-pure? name env io-names)) \"server\"\n :else \"client\")))))))\n\n\n;; --------------------------------------------------------------------------\n;; 6. Page render plan \u2014 pre-computed boundary decisions for a page\n;; --------------------------------------------------------------------------\n;; Given page source + env + IO names, returns a render plan dict:\n;;\n;; {:components {~name \"server\"|\"client\" ...}\n;; :server (list of ~names that render server-side)\n;; :client (list of ~names that render client-side)\n;; :io-deps (list of IO primitives needed by server components)}\n;;\n;; This is computed once at page registration and cached on the page def.\n;; The async evaluator and client router both use it to make decisions\n;; without recomputing at every request.\n\n(define page-render-plan :effects []\n (fn ((page-source :as string) (env :as dict) (io-names :as list))\n (let ((needed (components-needed page-source env))\n (comp-targets (dict))\n (server-list (list))\n (client-list (list))\n (io-deps (list)))\n\n (for-each\n (fn ((name :as string))\n (let ((target (render-target name env io-names)))\n (dict-set! comp-targets name target)\n (if (= target \"server\")\n (do\n (append! server-list name)\n ;; Collect IO deps from server components (use cache)\n (for-each\n (fn ((io-ref :as string))\n (when (not (contains? io-deps io-ref))\n (append! io-deps io-ref)))\n (component-io-refs-cached name env io-names)))\n (append! client-list name))))\n needed)\n\n {:components comp-targets\n :server server-list\n :client client-list\n :io-deps io-deps})))\n\n\n;; --------------------------------------------------------------------------\n;; Host obligation: selective expansion in async partial evaluation\n;; --------------------------------------------------------------------------\n;; The spec classifies components as pure or IO-dependent and provides\n;; per-component render-target decisions. Each host's async partial\n;; evaluator (the server-side rendering path that bridges sync evaluation\n;; with async IO) must use this classification:\n;;\n;; render-target \"server\" \u2192 expand server-side (IO must resolve)\n;; render-target \"client\" \u2192 serialize for client (can render anywhere)\n;; Layout slot context \u2192 expand all (server needs full HTML)\n;;\n;; The spec provides: component-io-refs, component-pure?, render-target,\n;; component-affinity. The host provides the async runtime that acts on it.\n;; This is not SX semantics \u2014 it is host infrastructure. Every host\n;; with a server-side async evaluator implements the same rule.\n;; --------------------------------------------------------------------------\n\n\n;; --------------------------------------------------------------------------\n;; Platform interface summary\n;; --------------------------------------------------------------------------\n;;\n;; From eval.sx (already provided):\n;; (type-of x) \u2192 type string\n;; (symbol-name s) \u2192 string name of symbol\n;; (env-get env k) \u2192 value or nil\n;;\n;; New for deps.sx (each host implements):\n;; (component-body c) \u2192 AST body of component\n;; (component-name c) \u2192 name string\n;; (component-deps c) \u2192 cached deps list (may be empty)\n;; (component-set-deps! c d)\u2192 cache deps on component\n;; (component-css-classes c)\u2192 pre-scanned CSS class list\n;; (component-io-refs c) \u2192 cached IO ref list (may be empty)\n;; (component-set-io-refs! c r)\u2192 cache IO refs on component\n;; (component-affinity c) \u2192 \"auto\" | \"client\" | \"server\"\n;; (macro-body m) \u2192 AST body of macro\n;; (regex-find-all pat src) \u2192 list of capture group matches\n;; (scan-css-classes src) \u2192 list of CSS class strings from source\n;; --------------------------------------------------------------------------\n\n\n;; --------------------------------------------------------------------------\n;; env-components \u2014 list component/macro names in an environment\n;; --------------------------------------------------------------------------\n;; Moved from platform to spec: pure logic using type predicates.\n\n(define env-components :effects []\n (fn ((env :as dict))\n (filter\n (fn ((k :as string))\n (let ((v (env-get env k)))\n (or (component? v) (macro? v))))\n (keys env))))\n";
globalThis.__sxAdapters["page-helpers"] = ";; ==========================================================================\n;; page-helpers.sx \u2014 Pure data-transformation page helpers\n;;\n;; These functions take raw data (from Python I/O edge) and return\n;; structured dicts for page rendering. No I/O \u2014 pure transformations\n;; only. Bootstrapped to every host.\n;; ==========================================================================\n\n\n;; --------------------------------------------------------------------------\n;; categorize-special-forms\n;;\n;; Parses define-special-form declarations from special-forms.sx AST,\n;; categorizes each form by name lookup, returns dict of category \u2192 forms.\n;; --------------------------------------------------------------------------\n\n(define special-form-category-map\n {\"if\" \"Control Flow\" \"when\" \"Control Flow\" \"cond\" \"Control Flow\"\n \"case\" \"Control Flow\" \"and\" \"Control Flow\" \"or\" \"Control Flow\"\n \"let\" \"Binding\" \"let*\" \"Binding\" \"letrec\" \"Binding\"\n \"define\" \"Binding\" \"set!\" \"Binding\"\n \"lambda\" \"Functions & Components\" \"fn\" \"Functions & Components\"\n \"defcomp\" \"Functions & Components\" \"defmacro\" \"Functions & Components\"\n \"begin\" \"Sequencing & Threading\" \"do\" \"Sequencing & Threading\"\n \"->\" \"Sequencing & Threading\"\n \"quote\" \"Quoting\" \"quasiquote\" \"Quoting\"\n \"reset\" \"Continuations\" \"shift\" \"Continuations\"\n \"dynamic-wind\" \"Guards\"\n \"map\" \"Higher-Order Forms\" \"map-indexed\" \"Higher-Order Forms\"\n \"filter\" \"Higher-Order Forms\" \"reduce\" \"Higher-Order Forms\"\n \"some\" \"Higher-Order Forms\" \"every?\" \"Higher-Order Forms\"\n \"for-each\" \"Higher-Order Forms\"\n \"defstyle\" \"Domain Definitions\"\n \"defhandler\" \"Domain Definitions\" \"defpage\" \"Domain Definitions\"\n \"defquery\" \"Domain Definitions\" \"defaction\" \"Domain Definitions\"})\n\n\n(define extract-define-kwargs\n (fn ((expr :as list))\n ;; Extract keyword args from a define-special-form expression.\n ;; Returns dict of keyword-name \u2192 string value.\n ;; Walks items pairwise: when item[i] is a keyword, item[i+1] is its value.\n (let ((result {})\n (items (slice expr 2))\n (n (len items)))\n (for-each\n (fn ((idx :as number))\n (when (and (< (+ idx 1) n)\n (= (type-of (nth items idx)) \"keyword\"))\n (let ((key (keyword-name (nth items idx)))\n (val (nth items (+ idx 1))))\n (dict-set! result key\n (if (= (type-of val) \"list\")\n (str \"(\" (join \" \" (map serialize val)) \")\")\n (str val))))))\n (range 0 n))\n result)))\n\n\n(define categorize-special-forms\n (fn ((parsed-exprs :as list))\n ;; parsed-exprs: result of parse-all on special-forms.sx\n ;; Returns dict of category-name \u2192 list of form dicts.\n (let ((categories {}))\n (for-each\n (fn (expr)\n (when (and (= (type-of expr) \"list\")\n (>= (len expr) 2)\n (= (type-of (first expr)) \"symbol\")\n (= (symbol-name (first expr)) \"define-special-form\"))\n (let ((name (nth expr 1))\n (kwargs (extract-define-kwargs expr))\n (category (or (get special-form-category-map name) \"Other\")))\n (when (not (has-key? categories category))\n (dict-set! categories category (list)))\n (append! (get categories category)\n {\"name\" name\n \"syntax\" (or (get kwargs \"syntax\") \"\")\n \"doc\" (or (get kwargs \"doc\") \"\")\n \"tail-position\" (or (get kwargs \"tail-position\") \"\")\n \"example\" (or (get kwargs \"example\") \"\")}))))\n parsed-exprs)\n categories)))\n\n\n;; --------------------------------------------------------------------------\n;; build-reference-data\n;;\n;; Takes a slug and raw reference data, returns structured dict for rendering.\n;; --------------------------------------------------------------------------\n\n(define build-ref-items-with-href\n (fn ((items :as list) (base-path :as string) (detail-keys :as list) (n-fields :as number))\n ;; items: list of lists (tuples), each with n-fields elements\n ;; base-path: e.g. \"/geography/hypermedia/reference/attributes/\"\n ;; detail-keys: list of strings (keys that have detail pages)\n ;; n-fields: 2 or 3 (number of fields per tuple)\n (map\n (fn ((item :as list))\n (if (= n-fields 3)\n ;; [name, desc/value, exists/desc]\n (let ((name (nth item 0))\n (field2 (nth item 1))\n (field3 (nth item 2)))\n {\"name\" name\n \"desc\" field2\n \"exists\" field3\n \"href\" (if (and field3 (some (fn ((k :as string)) (= k name)) detail-keys))\n (str base-path name)\n nil)})\n ;; [name, desc]\n (let ((name (nth item 0))\n (desc (nth item 1)))\n {\"name\" name\n \"desc\" desc\n \"href\" (if (some (fn ((k :as string)) (= k name)) detail-keys)\n (str base-path name)\n nil)})))\n items)))\n\n\n(define build-reference-data\n (fn ((slug :as string) (raw-data :as dict) (detail-keys :as list))\n ;; slug: \"attributes\", \"headers\", \"events\", \"js-api\"\n ;; raw-data: dict with the raw data lists for this slug\n ;; detail-keys: list of names that have detail pages\n (case slug\n \"attributes\"\n {\"req-attrs\" (build-ref-items-with-href\n (get raw-data \"req-attrs\")\n \"/geography/hypermedia/reference/attributes/\" detail-keys 3)\n \"beh-attrs\" (build-ref-items-with-href\n (get raw-data \"beh-attrs\")\n \"/geography/hypermedia/reference/attributes/\" detail-keys 3)\n \"uniq-attrs\" (build-ref-items-with-href\n (get raw-data \"uniq-attrs\")\n \"/geography/hypermedia/reference/attributes/\" detail-keys 3)}\n\n \"headers\"\n {\"req-headers\" (build-ref-items-with-href\n (get raw-data \"req-headers\")\n \"/geography/hypermedia/reference/headers/\" detail-keys 3)\n \"resp-headers\" (build-ref-items-with-href\n (get raw-data \"resp-headers\")\n \"/geography/hypermedia/reference/headers/\" detail-keys 3)}\n\n \"events\"\n {\"events-list\" (build-ref-items-with-href\n (get raw-data \"events-list\")\n \"/geography/hypermedia/reference/events/\" detail-keys 2)}\n\n \"js-api\"\n {\"js-api-list\" (map (fn ((item :as list)) {\"name\" (nth item 0) \"desc\" (nth item 1)})\n (get raw-data \"js-api-list\"))}\n\n ;; default: attributes\n :else\n {\"req-attrs\" (build-ref-items-with-href\n (get raw-data \"req-attrs\")\n \"/geography/hypermedia/reference/attributes/\" detail-keys 3)\n \"beh-attrs\" (build-ref-items-with-href\n (get raw-data \"beh-attrs\")\n \"/geography/hypermedia/reference/attributes/\" detail-keys 3)\n \"uniq-attrs\" (build-ref-items-with-href\n (get raw-data \"uniq-attrs\")\n \"/geography/hypermedia/reference/attributes/\" detail-keys 3)})))\n\n\n;; --------------------------------------------------------------------------\n;; build-attr-detail / build-header-detail / build-event-detail\n;;\n;; Lookup a slug in a detail dict, reshape for page rendering.\n;; --------------------------------------------------------------------------\n\n(define build-attr-detail\n (fn ((slug :as string) detail)\n ;; detail: dict with \"description\", \"example\", \"handler\", \"demo\" keys or nil\n (if (nil? detail)\n {\"attr-not-found\" true}\n {\"attr-not-found\" nil\n \"attr-title\" slug\n \"attr-description\" (get detail \"description\")\n \"attr-example\" (get detail \"example\")\n \"attr-handler\" (get detail \"handler\")\n \"attr-demo\" (get detail \"demo\")\n \"attr-wire-id\" (if (has-key? detail \"handler\")\n (str \"ref-wire-\"\n (replace (replace slug \":\" \"-\") \"*\" \"star\"))\n nil)})))\n\n\n(define build-header-detail\n (fn ((slug :as string) detail)\n (if (nil? detail)\n {\"header-not-found\" true}\n {\"header-not-found\" nil\n \"header-title\" slug\n \"header-direction\" (get detail \"direction\")\n \"header-description\" (get detail \"description\")\n \"header-example\" (get detail \"example\")\n \"header-demo\" (get detail \"demo\")})))\n\n\n(define build-event-detail\n (fn ((slug :as string) detail)\n (if (nil? detail)\n {\"event-not-found\" true}\n {\"event-not-found\" nil\n \"event-title\" slug\n \"event-description\" (get detail \"description\")\n \"event-example\" (get detail \"example\")\n \"event-demo\" (get detail \"demo\")})))\n\n\n;; --------------------------------------------------------------------------\n;; build-component-source\n;;\n;; Reconstruct defcomp/defisland source from component metadata.\n;; --------------------------------------------------------------------------\n\n(define build-component-source\n (fn ((comp-data :as dict))\n ;; comp-data: dict with \"type\", \"name\", \"params\", \"has-children\", \"body-sx\", \"affinity\"\n (let ((comp-type (get comp-data \"type\"))\n (name (get comp-data \"name\"))\n (params (get comp-data \"params\"))\n (has-children (get comp-data \"has-children\"))\n (body-sx (get comp-data \"body-sx\"))\n (affinity (get comp-data \"affinity\")))\n (if (= comp-type \"not-found\")\n (str \";; component \" name \" not found\")\n (let ((param-strs (if (empty? params)\n (if has-children\n (list \"&rest\" \"children\")\n (list))\n (if has-children\n (append (cons \"&key\" params) (list \"&rest\" \"children\"))\n (cons \"&key\" params))))\n (params-sx (str \"(\" (join \" \" param-strs) \")\"))\n (form-name (if (= comp-type \"island\") \"defisland\" \"defcomp\"))\n (affinity-str (if (and (= comp-type \"component\")\n (not (nil? affinity))\n (not (= affinity \"auto\")))\n (str \" :affinity \" affinity)\n \"\")))\n (str \"(\" form-name \" \" name \" \" params-sx affinity-str \"\\n \" body-sx \")\"))))))\n\n\n;; --------------------------------------------------------------------------\n;; build-bundle-analysis\n;;\n;; Compute per-page bundle stats from pre-extracted component data.\n;; --------------------------------------------------------------------------\n\n(define build-bundle-analysis\n (fn ((pages-raw :as list) (components-raw :as dict) (total-components :as number) (total-macros :as number) (pure-count :as number) (io-count :as number))\n ;; pages-raw: list of {:name :path :direct :needed-names}\n ;; components-raw: dict of name \u2192 {:is-pure :affinity :render-target :io-refs :deps :source}\n (let ((pages-data (list)))\n (for-each\n (fn ((page :as dict))\n (let ((needed-names (get page \"needed-names\"))\n (n (len needed-names))\n (pct (if (> total-components 0)\n (round (* (/ n total-components) 100))\n 0))\n (savings (- 100 pct))\n (pure-in-page 0)\n (io-in-page 0)\n (page-io-refs (list))\n (comp-details (list)))\n ;; Walk needed components\n (for-each\n (fn ((comp-name :as string))\n (let ((info (get components-raw comp-name)))\n (when (not (nil? info))\n (if (get info \"is-pure\")\n (set! pure-in-page (+ pure-in-page 1))\n (do\n (set! io-in-page (+ io-in-page 1))\n (for-each\n (fn ((ref :as string)) (when (not (some (fn ((r :as string)) (= r ref)) page-io-refs))\n (append! page-io-refs ref)))\n (or (get info \"io-refs\") (list)))))\n (append! comp-details\n {\"name\" comp-name\n \"is-pure\" (get info \"is-pure\")\n \"affinity\" (get info \"affinity\")\n \"render-target\" (get info \"render-target\")\n \"io-refs\" (or (get info \"io-refs\") (list))\n \"deps\" (or (get info \"deps\") (list))\n \"source\" (get info \"source\")}))))\n needed-names)\n (append! pages-data\n {\"name\" (get page \"name\")\n \"path\" (get page \"path\")\n \"direct\" (get page \"direct\")\n \"needed\" n\n \"pct\" pct\n \"savings\" savings\n \"io-refs\" (len page-io-refs)\n \"pure-in-page\" pure-in-page\n \"io-in-page\" io-in-page\n \"components\" comp-details})))\n pages-raw)\n {\"pages\" pages-data\n \"total-components\" total-components\n \"total-macros\" total-macros\n \"pure-count\" pure-count\n \"io-count\" io-count})))\n\n\n;; --------------------------------------------------------------------------\n;; build-routing-analysis\n;;\n;; Classify pages by routing mode (client vs server).\n;; --------------------------------------------------------------------------\n\n(define build-routing-analysis\n (fn ((pages-raw :as list))\n ;; pages-raw: list of {:name :path :has-data :content-src}\n (let ((pages-data (list))\n (client-count 0)\n (server-count 0))\n (for-each\n (fn ((page :as dict))\n (let ((has-data (get page \"has-data\"))\n (content-src (or (get page \"content-src\") \"\"))\n (mode nil)\n (reason \"\"))\n (cond\n has-data\n (do (set! mode \"server\")\n (set! reason \"Has :data expression \u2014 needs server IO\")\n (set! server-count (+ server-count 1)))\n (empty? content-src)\n (do (set! mode \"server\")\n (set! reason \"No content expression\")\n (set! server-count (+ server-count 1)))\n :else\n (do (set! mode \"client\")\n (set! client-count (+ client-count 1))))\n (append! pages-data\n {\"name\" (get page \"name\")\n \"path\" (get page \"path\")\n \"mode\" mode\n \"has-data\" has-data\n \"content-expr\" (if (> (len content-src) 80)\n (str (slice content-src 0 80) \"...\")\n content-src)\n \"reason\" reason})))\n pages-raw)\n {\"pages\" pages-data\n \"total-pages\" (+ client-count server-count)\n \"client-count\" client-count\n \"server-count\" server-count})))\n\n\n;; --------------------------------------------------------------------------\n;; build-affinity-analysis\n;;\n;; Package component affinity info + page render plans for display.\n;; --------------------------------------------------------------------------\n\n(define build-affinity-analysis\n (fn ((demo-components :as list) (page-plans :as list))\n {\"components\" demo-components\n \"page-plans\" page-plans}))\n";
globalThis.__sxAdapters["router"] = ";; ==========================================================================\n;; router.sx \u2014 Client-side route matching specification\n;;\n;; Pure functions for matching URL paths against Flask-style route patterns.\n;; Used by client-side routing to determine if a page can be rendered\n;; locally without a server roundtrip.\n;;\n;; All functions are pure \u2014 no IO, no platform-specific operations.\n;; Uses only primitives from primitives.sx (string ops, list ops).\n;; ==========================================================================\n\n\n;; --------------------------------------------------------------------------\n;; 1. Split path into segments\n;; --------------------------------------------------------------------------\n;; \"/docs/hello\" \u2192 (\"docs\" \"hello\")\n;; \"/\" \u2192 ()\n;; \"/docs/\" \u2192 (\"docs\")\n\n(define split-path-segments :effects []\n (fn ((path :as string))\n (let ((trimmed (if (starts-with? path \"/\") (slice path 1) path)))\n (let ((trimmed2 (if (and (not (empty? trimmed))\n (ends-with? trimmed \"/\"))\n (slice trimmed 0 (- (len trimmed) 1))\n trimmed)))\n (if (empty? trimmed2)\n (list)\n (split trimmed2 \"/\"))))))\n\n\n;; --------------------------------------------------------------------------\n;; 2. Parse Flask-style route pattern into segment descriptors\n;; --------------------------------------------------------------------------\n;; \"/docs/<slug>\" \u2192 ({\"type\" \"literal\" \"value\" \"docs\"}\n;; {\"type\" \"param\" \"value\" \"slug\"})\n\n(define make-route-segment :effects []\n (fn ((seg :as string))\n (if (and (starts-with? seg \"<\") (ends-with? seg \">\"))\n (let ((param-name (slice seg 1 (- (len seg) 1))))\n (let ((d {}))\n (dict-set! d \"type\" \"param\")\n (dict-set! d \"value\" param-name)\n d))\n (let ((d {}))\n (dict-set! d \"type\" \"literal\")\n (dict-set! d \"value\" seg)\n d))))\n\n(define parse-route-pattern :effects []\n (fn ((pattern :as string))\n (let ((segments (split-path-segments pattern)))\n (map make-route-segment segments))))\n\n\n;; --------------------------------------------------------------------------\n;; 3. Match path segments against parsed pattern\n;; --------------------------------------------------------------------------\n;; Returns params dict if match, nil if no match.\n\n(define match-route-segments :effects []\n (fn ((path-segs :as list) (parsed-segs :as list))\n (if (not (= (len path-segs) (len parsed-segs)))\n nil\n (let ((params {})\n (matched true))\n (for-each-indexed\n (fn ((i :as number) (parsed-seg :as dict))\n (when matched\n (let ((path-seg (nth path-segs i))\n (seg-type (get parsed-seg \"type\")))\n (cond\n (= seg-type \"literal\")\n (when (not (= path-seg (get parsed-seg \"value\")))\n (set! matched false))\n (= seg-type \"param\")\n (dict-set! params (get parsed-seg \"value\") path-seg)\n :else\n (set! matched false)))))\n parsed-segs)\n (if matched params nil)))))\n\n\n;; --------------------------------------------------------------------------\n;; 4. Public API: match a URL path against a pattern string\n;; --------------------------------------------------------------------------\n;; Returns params dict (may be empty for exact matches) or nil.\n\n(define match-route :effects []\n (fn ((path :as string) (pattern :as string))\n (let ((path-segs (split-path-segments path))\n (parsed-segs (parse-route-pattern pattern)))\n (match-route-segments path-segs parsed-segs))))\n\n\n;; --------------------------------------------------------------------------\n;; 5. Search a list of route entries for first match\n;; --------------------------------------------------------------------------\n;; Each entry: {\"pattern\" \"/docs/<slug>\" \"parsed\" [...] \"name\" \"docs-page\" ...}\n;; Returns matching entry with \"params\" added, or nil.\n\n(define find-matching-route :effects []\n (fn ((path :as string) (routes :as list))\n ;; If path is an SX expression URL, convert to old-style for matching.\n (let ((match-path (if (starts-with? path \"/(\")\n (or (sx-url-to-path path) path)\n path)))\n (let ((path-segs (split-path-segments match-path))\n (result nil))\n (for-each\n (fn ((route :as dict))\n (when (nil? result)\n (let ((params (match-route-segments path-segs (get route \"parsed\"))))\n (when (not (nil? params))\n (let ((matched (merge route {})))\n (dict-set! matched \"params\" params)\n (set! result matched))))))\n routes)\n result))))\n\n\n;; --------------------------------------------------------------------------\n;; 6. SX expression URL \u2192 old-style path conversion\n;; --------------------------------------------------------------------------\n;; Converts /(language.(doc.introduction)) \u2192 /language/docs/introduction\n;; so client-side routing can match SX URLs against Flask-style patterns.\n\n(define _fn-to-segment :effects []\n (fn ((name :as string))\n (case name\n \"doc\" \"docs\"\n \"spec\" \"specs\"\n \"bootstrapper\" \"bootstrappers\"\n \"test\" \"testing\"\n \"example\" \"examples\"\n \"protocol\" \"protocols\"\n \"essay\" \"essays\"\n \"plan\" \"plans\"\n \"reference-detail\" \"reference\"\n :else name)))\n\n(define sx-url-to-path :effects []\n (fn ((url :as string))\n ;; Convert an SX expression URL to an old-style slash path.\n ;; \"/(language.(doc.introduction))\" \u2192 \"/language/docs/introduction\"\n ;; Returns nil for non-SX URLs (those not starting with \"/(\" ).\n (if (not (and (starts-with? url \"/(\") (ends-with? url \")\")))\n nil\n (let ((inner (slice url 2 (- (len url) 1))))\n ;; \"language.(doc.introduction)\" \u2192 dots to slashes, strip parens\n (let ((s (replace (replace (replace inner \".\" \"/\") \"(\" \"\") \")\" \"\")))\n ;; \"language/doc/introduction\" \u2192 split, map names, rejoin\n (let ((segs (filter (fn (s) (not (empty? s))) (split s \"/\"))))\n (str \"/\" (join \"/\" (map _fn-to-segment segs)))))))))\n\n\n;; --------------------------------------------------------------------------\n;; 7. Relative SX URL resolution\n;; --------------------------------------------------------------------------\n;; Resolves relative SX URLs against the current absolute URL.\n;; This is a macro in the deepest sense: SX transforming SX into SX.\n;; The URL is code. Relative resolution is code transformation.\n;;\n;; Relative URLs start with ( or . :\n;; (.slug) \u2192 append slug as argument to innermost call\n;; (..section) \u2192 up 1: replace innermost with new nested call\n;; (...section) \u2192 up 2: replace 2 innermost levels\n;;\n;; Bare-dot shorthand (parens optional):\n;; .slug \u2192 same as (.slug)\n;; .. \u2192 same as (..) \u2014 go up one level\n;; ... \u2192 same as (...) \u2014 go up two levels\n;; .:page.4 \u2192 same as (.:page.4) \u2014 set keyword\n;;\n;; Dot count semantics (parallels filesystem . and ..):\n;; 1 dot = current level (append argument / modify keyword)\n;; 2 dots = up 1 level (sibling call)\n;; 3 dots = up 2 levels\n;; N dots = up N-1 levels\n;;\n;; Keyword operations (set, delta):\n;; (.:page.4) \u2192 set :page to 4 at current level\n;; (.:page.+1) \u2192 increment :page by 1 (delta)\n;; (.:page.-1) \u2192 decrement :page by 1 (delta)\n;; (.slug.:page.1) \u2192 append slug AND set :page=1\n;;\n;; Examples (current = \"/(geography.(hypermedia.(example)))\"):\n;; (.progress-bar) \u2192 /(geography.(hypermedia.(example.progress-bar)))\n;; (..reactive.demo) \u2192 /(geography.(hypermedia.(reactive.demo)))\n;; (...marshes) \u2192 /(geography.(marshes))\n;; (..) \u2192 /(geography.(hypermedia))\n;; (...) \u2192 /(geography)\n;;\n;; Keyword examples (current = \"/(language.(spec.(explore.signals.:page.3)))\"):\n;; (.:page.4) \u2192 /(language.(spec.(explore.signals.:page.4)))\n;; (.:page.+1) \u2192 /(language.(spec.(explore.signals.:page.4)))\n;; (.:page.-1) \u2192 /(language.(spec.(explore.signals.:page.2)))\n;; (..eval) \u2192 /(language.(spec.(eval)))\n;; (..eval.:page.1) \u2192 /(language.(spec.(eval.:page.1)))\n\n(define _count-leading-dots :effects []\n (fn ((s :as string))\n (if (empty? s)\n 0\n (if (starts-with? s \".\")\n (+ 1 (_count-leading-dots (slice s 1)))\n 0))))\n\n(define _strip-trailing-close :effects []\n (fn ((s :as string))\n ;; Strip trailing ) characters: \"/(a.(b.(c\" from \"/(a.(b.(c)))\"\n (if (ends-with? s \")\")\n (_strip-trailing-close (slice s 0 (- (len s) 1)))\n s)))\n\n(define _index-of-safe :effects []\n (fn ((s :as string) (needle :as string))\n ;; Wrapper around index-of that normalizes -1 to nil.\n ;; (index-of returns -1 on some platforms, nil on others.)\n (let ((idx (index-of s needle)))\n (if (or (nil? idx) (< idx 0)) nil idx))))\n\n(define _last-index-of :effects []\n (fn ((s :as string) (needle :as string))\n ;; Find the last occurrence of needle in s. Returns nil if not found.\n (let ((idx (_index-of-safe s needle)))\n (if (nil? idx)\n nil\n (let ((rest-idx (_last-index-of (slice s (+ idx 1)) needle)))\n (if (nil? rest-idx)\n idx\n (+ (+ idx 1) rest-idx)))))))\n\n(define _pop-sx-url-level :effects []\n (fn ((url :as string))\n ;; Remove the innermost nesting level from an absolute SX URL.\n ;; \"/(a.(b.(c)))\" \u2192 \"/(a.(b))\"\n ;; \"/(a.(b))\" \u2192 \"/(a)\"\n ;; \"/(a)\" \u2192 \"/\"\n (let ((stripped (_strip-trailing-close url))\n (close-count (- (len url) (len (_strip-trailing-close url)))))\n (if (<= close-count 1)\n \"/\" ;; at root, popping goes to bare root\n (let ((last-dp (_last-index-of stripped \".(\")))\n (if (nil? last-dp)\n \"/\" ;; single-level URL, pop to root\n ;; Remove from .( to end of stripped, drop one closing paren\n (str (slice stripped 0 last-dp)\n (slice url (- (len url) (- close-count 1))))))))))\n\n(define _pop-sx-url-levels :effects []\n (fn ((url :as string) (n :as number))\n (if (<= n 0)\n url\n (_pop-sx-url-levels (_pop-sx-url-level url) (- n 1)))))\n\n\n;; --------------------------------------------------------------------------\n;; 8. Relative URL body parsing \u2014 positional vs keyword tokens\n;; --------------------------------------------------------------------------\n;; Body \"slug.:page.4\" \u2192 positional \"slug\", keywords ((:page 4))\n;; Body \":page.+1\" \u2192 positional \"\", keywords ((:page +1))\n\n(define _split-pos-kw :effects []\n (fn ((tokens :as list) (i :as number) (pos :as list) (kw :as list))\n ;; Walk tokens: non-: tokens are positional, : tokens consume next as value\n (if (>= i (len tokens))\n {\"positional\" (join \".\" pos) \"keywords\" kw}\n (let ((tok (nth tokens i)))\n (if (starts-with? tok \":\")\n ;; Keyword: take this + next token as a pair\n (let ((val (if (< (+ i 1) (len tokens))\n (nth tokens (+ i 1))\n \"\")))\n (_split-pos-kw tokens (+ i 2) pos\n (append kw (list (list tok val)))))\n ;; Positional token\n (_split-pos-kw tokens (+ i 1)\n (append pos (list tok))\n kw))))))\n\n(define _parse-relative-body :effects []\n (fn ((body :as string))\n ;; Returns {\"positional\" <string> \"keywords\" <list of (kw val) pairs>}\n (if (empty? body)\n {\"positional\" \"\" \"keywords\" (list)}\n (_split-pos-kw (split body \".\") 0 (list) (list)))))\n\n\n;; --------------------------------------------------------------------------\n;; 9. Keyword operations on URL expressions\n;; --------------------------------------------------------------------------\n;; Extract, find, and modify keyword arguments in the innermost expression.\n\n(define _extract-innermost :effects []\n (fn ((url :as string))\n ;; Returns {\"before\" ... \"content\" ... \"suffix\" ...}\n ;; where before + content + suffix = url\n ;; content = the innermost expression's dot-separated tokens\n (let ((stripped (_strip-trailing-close url))\n (suffix (slice url (len (_strip-trailing-close url)))))\n (let ((last-dp (_last-index-of stripped \".(\")))\n (if (nil? last-dp)\n ;; Single-level: /(content)\n {\"before\" \"/(\"\n \"content\" (slice stripped 2)\n \"suffix\" suffix}\n ;; Multi-level: .../.(content)...)\n {\"before\" (slice stripped 0 (+ last-dp 2))\n \"content\" (slice stripped (+ last-dp 2))\n \"suffix\" suffix})))))\n\n(define _find-kw-in-tokens :effects []\n (fn ((tokens :as list) (i :as number) (kw :as string))\n ;; Find value of keyword kw in token list. Returns nil if not found.\n (if (>= i (len tokens))\n nil\n (if (and (= (nth tokens i) kw)\n (< (+ i 1) (len tokens)))\n (nth tokens (+ i 1))\n (_find-kw-in-tokens tokens (+ i 1) kw)))))\n\n(define _find-keyword-value :effects []\n (fn ((content :as string) (kw :as string))\n ;; Find keyword's value in dot-separated content string.\n ;; \"explore.signals.:page.3\" \":page\" \u2192 \"3\"\n (_find-kw-in-tokens (split content \".\") 0 kw)))\n\n(define _replace-kw-in-tokens :effects []\n (fn ((tokens :as list) (i :as number) (kw :as string) (value :as string))\n ;; Replace keyword's value in token list. Returns new token list.\n (if (>= i (len tokens))\n (list)\n (if (and (= (nth tokens i) kw)\n (< (+ i 1) (len tokens)))\n ;; Found \u2014 keep keyword, replace value, concat rest\n (append (list kw value)\n (_replace-kw-in-tokens tokens (+ i 2) kw value))\n ;; Not this keyword \u2014 keep token, continue\n (cons (nth tokens i)\n (_replace-kw-in-tokens tokens (+ i 1) kw value))))))\n\n(define _set-keyword-in-content :effects []\n (fn ((content :as string) (kw :as string) (value :as string))\n ;; Set or replace keyword value in dot-separated content.\n ;; \"a.b.:page.3\" \":page\" \"4\" \u2192 \"a.b.:page.4\"\n ;; \"a.b\" \":page\" \"1\" \u2192 \"a.b.:page.1\"\n (let ((current (_find-keyword-value content kw)))\n (if (nil? current)\n ;; Not found \u2014 append\n (str content \".\" kw \".\" value)\n ;; Found \u2014 replace\n (join \".\" (_replace-kw-in-tokens (split content \".\") 0 kw value))))))\n\n(define _is-delta-value? :effects []\n (fn ((s :as string))\n ;; \"+1\", \"-2\", \"+10\" are deltas. \"-\" alone is not.\n (and (not (empty? s))\n (> (len s) 1)\n (or (starts-with? s \"+\") (starts-with? s \"-\")))))\n\n(define _apply-delta :effects []\n (fn ((current-str :as string) (delta-str :as string))\n ;; Apply numeric delta to current value string.\n ;; \"3\" \"+1\" \u2192 \"4\", \"3\" \"-1\" \u2192 \"2\"\n (let ((cur (parse-int current-str nil))\n (delta (parse-int delta-str nil)))\n (if (or (nil? cur) (nil? delta))\n delta-str ;; fallback: use delta as literal value\n (str (+ cur delta))))))\n\n(define _apply-kw-pairs :effects []\n (fn ((content :as string) (kw-pairs :as list))\n ;; Apply keyword modifications to content, one at a time.\n (if (empty? kw-pairs)\n content\n (let ((pair (first kw-pairs))\n (kw (first pair))\n (raw-val (nth pair 1)))\n (let ((actual-val\n (if (_is-delta-value? raw-val)\n (let ((current (_find-keyword-value content kw)))\n (if (nil? current)\n raw-val ;; no current value, treat delta as literal\n (_apply-delta current raw-val)))\n raw-val)))\n (_apply-kw-pairs\n (_set-keyword-in-content content kw actual-val)\n (rest kw-pairs)))))))\n\n(define _apply-keywords-to-url :effects []\n (fn ((url :as string) (kw-pairs :as list))\n ;; Apply keyword modifications to the innermost expression of a URL.\n (if (empty? kw-pairs)\n url\n (let ((parts (_extract-innermost url)))\n (let ((new-content (_apply-kw-pairs (get parts \"content\") kw-pairs)))\n (str (get parts \"before\") new-content (get parts \"suffix\")))))))\n\n\n;; --------------------------------------------------------------------------\n;; 10. Public API: resolve-relative-url (structural + keywords)\n;; --------------------------------------------------------------------------\n\n(define _normalize-relative :effects []\n (fn ((url :as string))\n ;; Normalize bare-dot shorthand to paren form.\n ;; \"..\" \u2192 \"(..)\"\n ;; \".slug\" \u2192 \"(.slug)\"\n ;; \".:page.4\" \u2192 \"(.:page.4)\"\n ;; \"(.slug)\" \u2192 \"(.slug)\" (already canonical)\n (if (starts-with? url \"(\")\n url\n (str \"(\" url \")\"))))\n\n(define resolve-relative-url :effects []\n (fn ((current :as string) (relative :as string))\n ;; current: absolute SX URL \"/(geography.(hypermedia.(example)))\"\n ;; relative: relative SX URL \"(.progress-bar)\" or \"..\" or \".:page.+1\"\n ;; Returns: absolute SX URL\n (let ((canonical (_normalize-relative relative)))\n (let ((rel-inner (slice canonical 1 (- (len canonical) 1))))\n (let ((dots (_count-leading-dots rel-inner))\n (body (slice rel-inner (_count-leading-dots rel-inner))))\n (if (= dots 0)\n current ;; no dots \u2014 not a relative URL\n ;; Parse body into positional part + keyword pairs\n (let ((parsed (_parse-relative-body body))\n (pos-body (get parsed \"positional\"))\n (kw-pairs (get parsed \"keywords\")))\n ;; Step 1: structural navigation\n (let ((after-nav\n (if (= dots 1)\n ;; One dot = current level\n (if (empty? pos-body)\n current ;; no positional \u2192 stay here (keyword-only)\n ;; Append positional part at current level\n (let ((stripped (_strip-trailing-close current))\n (suffix (slice current (len (_strip-trailing-close current)))))\n (str stripped \".\" pos-body suffix)))\n ;; Two+ dots = pop (dots-1) levels\n (let ((base (_pop-sx-url-levels current (- dots 1))))\n (if (empty? pos-body)\n base ;; no positional \u2192 just pop (cd ..)\n (if (= base \"/\")\n (str \"/(\" pos-body \")\")\n (let ((stripped (_strip-trailing-close base))\n (suffix (slice base (len (_strip-trailing-close base)))))\n (str stripped \".(\" pos-body \")\" suffix))))))))\n ;; Step 2: apply keyword modifications\n (_apply-keywords-to-url after-nav kw-pairs)))))))))\n\n;; Check if a URL is relative (starts with ( but not /( , or starts with .)\n(define relative-sx-url? :effects []\n (fn ((url :as string))\n (or (and (starts-with? url \"(\")\n (not (starts-with? url \"/(\")))\n (starts-with? url \".\"))))\n\n\n;; --------------------------------------------------------------------------\n;; 11. URL special forms (! prefix)\n;; --------------------------------------------------------------------------\n;; Special forms are meta-operations on URL expressions.\n;; Distinguished by `!` prefix to avoid name collisions with sections/pages.\n;;\n;; Known forms:\n;; !source \u2014 show defcomp source code\n;; !inspect \u2014 deps, CSS footprint, render plan, IO\n;; !diff \u2014 side-by-side comparison of two expressions\n;; !search \u2014 grep within a page/spec\n;; !raw \u2014 skip ~sx-doc wrapping, return raw content\n;; !json \u2014 return content as JSON data\n;;\n;; URL examples:\n;; /(!source.(~essay-sx-sucks))\n;; /(!inspect.(language.(doc.primitives)))\n;; /(!diff.(language.(spec.signals)).(language.(spec.eval)))\n;; /(!search.\"define\".:in.(language.(spec.signals)))\n;; /(!raw.(~some-component))\n;; /(!json.(language.(doc.primitives)))\n\n(define _url-special-forms :effects []\n (fn ()\n ;; Returns the set of known URL special form names.\n (list \"!source\" \"!inspect\" \"!diff\" \"!search\" \"!raw\" \"!json\")))\n\n(define url-special-form? :effects []\n (fn ((name :as string))\n ;; Check if a name is a URL special form (starts with ! and is known).\n (and (starts-with? name \"!\")\n (contains? (_url-special-forms) name))))\n\n(define parse-sx-url :effects []\n (fn ((url :as string))\n ;; Parse an SX URL into a structured descriptor.\n ;; Returns a dict with:\n ;; \"type\" \u2014 \"home\" | \"absolute\" | \"relative\" | \"special-form\" | \"direct-component\"\n ;; \"form\" \u2014 special form name (for special-form type), e.g. \"!source\"\n ;; \"inner\" \u2014 inner URL expression string (without the special form wrapper)\n ;; \"raw\" \u2014 original URL string\n ;;\n ;; Examples:\n ;; \"/\" \u2192 {\"type\" \"home\" \"raw\" \"/\"}\n ;; \"/(language.(doc.intro))\" \u2192 {\"type\" \"absolute\" \"raw\" ...}\n ;; \"(.slug)\" \u2192 {\"type\" \"relative\" \"raw\" ...}\n ;; \"..slug\" \u2192 {\"type\" \"relative\" \"raw\" ...}\n ;; \"/(!source.(~essay))\" \u2192 {\"type\" \"special-form\" \"form\" \"!source\" \"inner\" \"(~essay)\" \"raw\" ...}\n ;; \"/(~essay-sx-sucks)\" \u2192 {\"type\" \"direct-component\" \"name\" \"~essay-sx-sucks\" \"raw\" ...}\n (cond\n (= url \"/\")\n {\"type\" \"home\" \"raw\" url}\n (relative-sx-url? url)\n {\"type\" \"relative\" \"raw\" url}\n (and (starts-with? url \"/(!\")\n (ends-with? url \")\"))\n ;; Special form: /(!source.(~essay)) or /(!diff.a.b)\n ;; Extract the form name (first dot-separated token after /()\n (let ((inner (slice url 2 (- (len url) 1))))\n ;; inner = \"!source.(~essay)\" or \"!diff.(a).(b)\"\n (let ((dot-pos (_index-of-safe inner \".\"))\n (paren-pos (_index-of-safe inner \"(\")))\n ;; Form name ends at first . or ( (whichever comes first)\n (let ((end-pos (cond\n (and (nil? dot-pos) (nil? paren-pos)) (len inner)\n (nil? dot-pos) paren-pos\n (nil? paren-pos) dot-pos\n :else (min dot-pos paren-pos))))\n (let ((form-name (slice inner 0 end-pos))\n (rest-part (slice inner end-pos)))\n ;; rest-part starts with \".\" \u2192 strip leading dot\n (let ((inner-expr (if (starts-with? rest-part \".\")\n (slice rest-part 1)\n rest-part)))\n {\"type\" \"special-form\"\n \"form\" form-name\n \"inner\" inner-expr\n \"raw\" url})))))\n (and (starts-with? url \"/(~\")\n (ends-with? url \")\"))\n ;; Direct component: /(~essay-sx-sucks)\n (let ((name (slice url 2 (- (len url) 1))))\n {\"type\" \"direct-component\" \"name\" name \"raw\" url})\n (and (starts-with? url \"/(\")\n (ends-with? url \")\"))\n {\"type\" \"absolute\" \"raw\" url}\n :else\n {\"type\" \"path\" \"raw\" url})))\n\n(define url-special-form-name :effects []\n (fn ((url :as string))\n ;; Extract the special form name from a URL, or nil if not a special form.\n ;; \"/(!source.(~essay))\" \u2192 \"!source\"\n ;; \"/(language.(doc))\" \u2192 nil\n (let ((parsed (parse-sx-url url)))\n (if (= (get parsed \"type\") \"special-form\")\n (get parsed \"form\")\n nil))))\n\n(define url-special-form-inner :effects []\n (fn ((url :as string))\n ;; Extract the inner expression from a special form URL, or nil.\n ;; \"/(!source.(~essay))\" \u2192 \"(~essay)\"\n ;; \"/(!diff.(a).(b))\" \u2192 \"(a).(b)\"\n (let ((parsed (parse-sx-url url)))\n (if (= (get parsed \"type\") \"special-form\")\n (get parsed \"inner\")\n nil))))\n\n\n;; --------------------------------------------------------------------------\n;; 12. URL expression evaluation\n;; --------------------------------------------------------------------------\n;; A URL is an expression. The system is the environment.\n;; eval(url, env) \u2014 that's it.\n;;\n;; The only URL-specific pre-processing:\n;; 1. Surface syntax \u2192 AST (dots to spaces, parse as SX)\n;; 2. Auto-quote unknowns (symbols not in env become strings)\n;;\n;; After that, it's standard eval. The host wires these into its route\n;; handlers (Python catch-all, JS client-side navigation). The same\n;; functions serve both.\n\n(define url-to-expr :effects []\n (fn ((url-path :as string))\n ;; Convert a URL path to an SX expression (AST).\n ;;\n ;; \"/sx/(language.(doc.introduction))\" \u2192 (language (doc introduction))\n ;; \"/(language.(doc.introduction))\" \u2192 (language (doc introduction))\n ;; \"/\" \u2192 (list) ; empty \u2014 home\n ;;\n ;; Steps:\n ;; 1. Strip URL prefix (\"/sx/\" or \"/\") \u2014 host passes the path after prefix\n ;; 2. Dots \u2192 spaces (URL-safe whitespace encoding)\n ;; 3. Parse as SX expression\n ;;\n ;; The caller is responsible for stripping any app-level prefix.\n ;; This function receives the raw expression portion: \"(language.(doc.intro))\"\n ;; or \"/\" for home.\n (if (or (= url-path \"/\") (empty? url-path))\n (list)\n (let ((trimmed (if (starts-with? url-path \"/\")\n (slice url-path 1)\n url-path)))\n ;; Dots \u2192 spaces\n (let ((sx-source (replace trimmed \".\" \" \")))\n ;; Parse \u2014 returns list of expressions, take the first\n (let ((exprs (sx-parse sx-source)))\n (if (empty? exprs)\n (list)\n (first exprs))))))))\n\n\n(define auto-quote-unknowns :effects []\n (fn ((expr :as list) (env :as dict))\n ;; Walk an AST and replace symbols not in env with their name as a string.\n ;; This makes URL slugs work without quoting:\n ;; (language (doc introduction)) ; introduction is not a function\n ;; \u2192 (language (doc \"introduction\"))\n ;;\n ;; Rules:\n ;; - List head (call position) stays as-is \u2014 it's a function name\n ;; - Tail symbols: if in env, keep as symbol; otherwise, string\n ;; - Keywords, strings, numbers, nested lists: pass through\n ;; - Non-list expressions: pass through unchanged\n (if (not (list? expr))\n expr\n (if (empty? expr)\n expr\n ;; Head stays as symbol (function position), quote the rest\n (cons (first expr)\n (map (fn (child)\n (cond\n ;; Nested list \u2014 recurse\n (list? child)\n (auto-quote-unknowns child env)\n ;; Symbol \u2014 check env\n (= (type-of child) \"symbol\")\n (let ((name (symbol-name child)))\n (if (or (env-has? env name)\n ;; Keep keywords, component refs, special forms\n (starts-with? name \":\")\n (starts-with? name \"~\")\n (starts-with? name \"!\"))\n child\n name)) ;; unknown \u2192 string\n ;; Everything else passes through\n :else child))\n (rest expr)))))))\n\n\n(define prepare-url-expr :effects []\n (fn ((url-path :as string) (env :as dict))\n ;; Full pipeline: URL path \u2192 ready-to-eval AST.\n ;;\n ;; \"(language.(doc.introduction))\" + env\n ;; \u2192 (language (doc \"introduction\"))\n ;;\n ;; The result can be fed directly to eval:\n ;; (eval (prepare-url-expr path env) env)\n (let ((expr (url-to-expr url-path)))\n (if (empty? expr)\n expr\n (auto-quote-unknowns expr env)))))\n\n\n;; --------------------------------------------------------------------------\n;; Platform interface\n;; --------------------------------------------------------------------------\n;; Pure primitives used:\n;; split, slice, starts-with?, ends-with?, len, empty?, replace,\n;; map, filter, for-each, for-each-indexed, nth, get, dict-set!, merge,\n;; list, nil?, not, =, case, join, str, index-of, and, or, cons,\n;; first, rest, append, parse-int, contains?, min, cond,\n;; symbol?, symbol-name, list?, env-has?, type-of\n;;\n;; From parser.sx: sx-parse, sx-serialize\n;; --------------------------------------------------------------------------\n";
globalThis.__sxAdapters["adapter-html"] = ";; ==========================================================================\n;; adapter-html.sx \u2014 HTML string rendering adapter\n;;\n;; Renders evaluated SX expressions to HTML strings. Used server-side.\n;;\n;; Depends on:\n;; render.sx \u2014 HTML_TAGS, VOID_ELEMENTS, BOOLEAN_ATTRS,\n;; parse-element-args, render-attrs, definition-form?\n;; eval.sx \u2014 eval-expr, trampoline, expand-macro, process-bindings,\n;; eval-cond, env-has?, env-get, env-set!, env-merge,\n;; lambda?, component?, island?, macro?,\n;; lambda-closure, lambda-params, lambda-body\n;; ==========================================================================\n\n\n(define render-to-html :effects [render]\n (fn (expr (env :as dict))\n (set-render-active! true)\n (case (type-of expr)\n ;; Literals \u2014 render directly\n \"nil\" \"\"\n \"string\" (escape-html expr)\n \"number\" (str expr)\n \"boolean\" (if expr \"true\" \"false\")\n ;; List \u2014 dispatch to render-list which handles HTML tags, special forms, etc.\n \"list\" (if (empty? expr) \"\" (render-list-to-html expr env))\n ;; Symbol \u2014 evaluate then render\n \"symbol\" (render-value-to-html (trampoline (eval-expr expr env)) env)\n ;; Keyword \u2014 render as text\n \"keyword\" (escape-html (keyword-name expr))\n ;; Raw HTML passthrough\n \"raw-html\" (raw-html-content expr)\n ;; Spread \u2014 emit attrs to nearest element provider\n \"spread\" (do (emit! \"element-attrs\" (spread-attrs expr)) \"\")\n ;; Everything else \u2014 evaluate first\n :else (render-value-to-html (trampoline (eval-expr expr env)) env))))\n\n(define render-value-to-html :effects [render]\n (fn (val (env :as dict))\n (case (type-of val)\n \"nil\" \"\"\n \"string\" (escape-html val)\n \"number\" (str val)\n \"boolean\" (if val \"true\" \"false\")\n \"list\" (render-list-to-html val env)\n \"raw-html\" (raw-html-content val)\n \"spread\" (do (emit! \"element-attrs\" (spread-attrs val)) \"\")\n :else (escape-html (str val)))))\n\n\n;; --------------------------------------------------------------------------\n;; Render-aware form classification\n;; --------------------------------------------------------------------------\n\n(define RENDER_HTML_FORMS\n (list \"if\" \"when\" \"cond\" \"case\" \"let\" \"let*\" \"begin\" \"do\"\n \"define\" \"defcomp\" \"defisland\" \"defmacro\" \"defstyle\" \"defhandler\"\n \"deftype\" \"defeffect\"\n \"map\" \"map-indexed\" \"filter\" \"for-each\" \"scope\" \"provide\"))\n\n(define render-html-form? :effects []\n (fn ((name :as string))\n (contains? RENDER_HTML_FORMS name)))\n\n\n;; --------------------------------------------------------------------------\n;; render-list-to-html \u2014 dispatch on list head\n;; --------------------------------------------------------------------------\n\n(define render-list-to-html :effects [render]\n (fn ((expr :as list) (env :as dict))\n (if (empty? expr)\n \"\"\n (let ((head (first expr)))\n (if (not (= (type-of head) \"symbol\"))\n ;; Data list \u2014 render each item\n (join \"\" (map (fn (x) (render-value-to-html x env)) expr))\n (let ((name (symbol-name head))\n (args (rest expr)))\n (cond\n ;; Fragment\n (= name \"<>\")\n (join \"\" (map (fn (x) (render-to-html x env)) args))\n\n ;; Raw HTML passthrough\n (= name \"raw!\")\n (join \"\" (map (fn (x) (str (trampoline (eval-expr x env)))) args))\n\n ;; Lake \u2014 server-morphable slot within an island\n (= name \"lake\")\n (render-html-lake args env)\n\n ;; Marsh \u2014 reactive server-morphable slot within an island\n (= name \"marsh\")\n (render-html-marsh args env)\n\n ;; HTML tag\n (contains? HTML_TAGS name)\n (render-html-element name args env)\n\n ;; Island (~name) \u2014 reactive component, SSR with hydration markers\n (and (starts-with? name \"~\")\n (env-has? env name)\n (island? (env-get env name)))\n (render-html-island (env-get env name) args env)\n\n ;; Component or macro call (~name)\n (starts-with? name \"~\")\n (let ((val (env-get env name)))\n (cond\n (component? val)\n (render-html-component val args env)\n (macro? val)\n (render-to-html\n (expand-macro val args env)\n env)\n :else\n (error (str \"Unknown component: \" name))))\n\n ;; Render-aware special forms\n (render-html-form? name)\n (dispatch-html-form name expr env)\n\n ;; Macro expansion\n (and (env-has? env name) (macro? (env-get env name)))\n (render-to-html\n (expand-macro (env-get env name) args env)\n env)\n\n ;; Fallback \u2014 evaluate then render result\n :else\n (render-value-to-html\n (trampoline (eval-expr expr env))\n env))))))))\n\n\n;; --------------------------------------------------------------------------\n;; dispatch-html-form \u2014 render-aware special form handling for HTML output\n;; --------------------------------------------------------------------------\n\n(define dispatch-html-form :effects [render]\n (fn ((name :as string) (expr :as list) (env :as dict))\n (cond\n ;; if\n (= name \"if\")\n (let ((cond-val (trampoline (eval-expr (nth expr 1) env))))\n (if cond-val\n (render-to-html (nth expr 2) env)\n (if (> (len expr) 3)\n (render-to-html (nth expr 3) env)\n \"\")))\n\n ;; when \u2014 single body: pass through. Multi: join strings.\n (= name \"when\")\n (if (not (trampoline (eval-expr (nth expr 1) env)))\n \"\"\n (if (= (len expr) 3)\n (render-to-html (nth expr 2) env)\n (join \"\" (map (fn (i) (render-to-html (nth expr i) env))\n (range 2 (len expr))))))\n\n ;; cond\n (= name \"cond\")\n (let ((branch (eval-cond (rest expr) env)))\n (if branch\n (render-to-html branch env)\n \"\"))\n\n ;; case\n (= name \"case\")\n (render-to-html (trampoline (eval-expr expr env)) env)\n\n ;; let / let* \u2014 single body: pass through. Multi: join strings.\n (or (= name \"let\") (= name \"let*\"))\n (let ((local (process-bindings (nth expr 1) env)))\n (if (= (len expr) 3)\n (render-to-html (nth expr 2) local)\n (join \"\" (map (fn (i) (render-to-html (nth expr i) local))\n (range 2 (len expr))))))\n\n ;; begin / do \u2014 single body: pass through. Multi: join strings.\n (or (= name \"begin\") (= name \"do\"))\n (if (= (len expr) 2)\n (render-to-html (nth expr 1) env)\n (join \"\" (map (fn (i) (render-to-html (nth expr i) env))\n (range 1 (len expr)))))\n\n ;; Definition forms \u2014 eval for side effects\n (definition-form? name)\n (do (trampoline (eval-expr expr env)) \"\")\n\n ;; map\n (= name \"map\")\n (let ((f (trampoline (eval-expr (nth expr 1) env)))\n (coll (trampoline (eval-expr (nth expr 2) env))))\n (join \"\"\n (map\n (fn (item)\n (if (lambda? f)\n (render-lambda-html f (list item) env)\n (render-to-html (apply f (list item)) env)))\n coll)))\n\n ;; map-indexed\n (= name \"map-indexed\")\n (let ((f (trampoline (eval-expr (nth expr 1) env)))\n (coll (trampoline (eval-expr (nth expr 2) env))))\n (join \"\"\n (map-indexed\n (fn (i item)\n (if (lambda? f)\n (render-lambda-html f (list i item) env)\n (render-to-html (apply f (list i item)) env)))\n coll)))\n\n ;; filter \u2014 evaluate fully then render\n (= name \"filter\")\n (render-to-html (trampoline (eval-expr expr env)) env)\n\n ;; for-each (render variant)\n (= name \"for-each\")\n (let ((f (trampoline (eval-expr (nth expr 1) env)))\n (coll (trampoline (eval-expr (nth expr 2) env))))\n (join \"\"\n (map\n (fn (item)\n (if (lambda? f)\n (render-lambda-html f (list item) env)\n (render-to-html (apply f (list item)) env)))\n coll)))\n\n ;; scope \u2014 unified render-time dynamic scope\n (= name \"scope\")\n (let ((scope-name (trampoline (eval-expr (nth expr 1) env)))\n (rest-args (slice expr 2))\n (scope-val nil)\n (body-exprs nil))\n ;; Check for :value keyword\n (if (and (>= (len rest-args) 2)\n (= (type-of (first rest-args)) \"keyword\")\n (= (keyword-name (first rest-args)) \"value\"))\n (do (set! scope-val (trampoline (eval-expr (nth rest-args 1) env)))\n (set! body-exprs (slice rest-args 2)))\n (set! body-exprs rest-args))\n (scope-push! scope-name scope-val)\n (let ((result (if (= (len body-exprs) 1)\n (render-to-html (first body-exprs) env)\n (join \"\" (map (fn (e) (render-to-html e env)) body-exprs)))))\n (scope-pop! scope-name)\n result))\n\n ;; provide \u2014 sugar for scope with value\n (= name \"provide\")\n (let ((prov-name (trampoline (eval-expr (nth expr 1) env)))\n (prov-val (trampoline (eval-expr (nth expr 2) env)))\n (body-start 3)\n (body-count (- (len expr) 3)))\n (scope-push! prov-name prov-val)\n (let ((result (if (= body-count 1)\n (render-to-html (nth expr body-start) env)\n (join \"\" (map (fn (i) (render-to-html (nth expr i) env))\n (range body-start (+ body-start body-count)))))))\n (scope-pop! prov-name)\n result))\n\n ;; Fallback\n :else\n (render-value-to-html (trampoline (eval-expr expr env)) env))))\n\n\n;; --------------------------------------------------------------------------\n;; render-lambda-html \u2014 render a lambda body in HTML context\n;; --------------------------------------------------------------------------\n\n(define render-lambda-html :effects [render]\n (fn ((f :as lambda) (args :as list) (env :as dict))\n (let ((local (env-merge (lambda-closure f) env)))\n (for-each-indexed\n (fn (i p)\n (env-bind! local p (nth args i)))\n (lambda-params f))\n (render-to-html (lambda-body f) local))))\n\n\n;; --------------------------------------------------------------------------\n;; render-html-component \u2014 expand and render a component\n;; --------------------------------------------------------------------------\n\n(define render-html-component :effects [render]\n (fn ((comp :as component) (args :as list) (env :as dict))\n ;; Expand component and render body through HTML adapter.\n ;; Component body contains rendering forms (HTML tags) that only the\n ;; adapter understands, so expansion must happen here, not in eval-expr.\n (let ((kwargs (dict))\n (children (list)))\n ;; Separate keyword args from positional children\n (reduce\n (fn (state arg)\n (let ((skip (get state \"skip\")))\n (if skip\n (assoc state \"skip\" false \"i\" (inc (get state \"i\")))\n (if (and (= (type-of arg) \"keyword\")\n (< (inc (get state \"i\")) (len args)))\n (let ((val (trampoline\n (eval-expr (nth args (inc (get state \"i\"))) env))))\n (dict-set! kwargs (keyword-name arg) val)\n (assoc state \"skip\" true \"i\" (inc (get state \"i\"))))\n (do\n (append! children arg)\n (assoc state \"i\" (inc (get state \"i\"))))))))\n (dict \"i\" 0 \"skip\" false)\n args)\n ;; Build component env: closure + caller env + params\n (let ((local (env-merge (component-closure comp) env)))\n ;; Bind params from kwargs\n (for-each\n (fn (p)\n (env-bind! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil)))\n (component-params comp))\n ;; If component accepts children, pre-render them to raw HTML\n (when (component-has-children? comp)\n (env-bind! local \"children\"\n (make-raw-html (join \"\" (map (fn (c) (render-to-html c env)) children)))))\n (render-to-html (component-body comp) local)))))\n\n\n(define render-html-element :effects [render]\n (fn ((tag :as string) (args :as list) (env :as dict))\n (let ((parsed (parse-element-args args env))\n (attrs (first parsed))\n (children (nth parsed 1))\n (is-void (contains? VOID_ELEMENTS tag)))\n (if is-void\n (str \"<\" tag (render-attrs attrs) \" />\")\n ;; Provide scope for spread emit!\n (do\n (scope-push! \"element-attrs\" nil)\n (let ((content (join \"\" (map (fn (c) (render-to-html c env)) children))))\n (for-each\n (fn (spread-dict) (merge-spread-attrs attrs spread-dict))\n (emitted \"element-attrs\"))\n (scope-pop! \"element-attrs\")\n (str \"<\" tag (render-attrs attrs) \">\"\n content\n \"</\" tag \">\")))))))\n\n\n;; --------------------------------------------------------------------------\n;; render-html-lake \u2014 SSR rendering of a server-morphable slot\n;; --------------------------------------------------------------------------\n;;\n;; (lake :id \"name\" children...) \u2192 <div data-sx-lake=\"name\">children</div>\n;;\n;; Lakes are server territory inside islands. The morph can update lake\n;; content while preserving surrounding reactive DOM.\n\n(define render-html-lake :effects [render]\n (fn ((args :as list) (env :as dict))\n (let ((lake-id nil)\n (lake-tag \"div\")\n (children (list)))\n (reduce\n (fn (state arg)\n (let ((skip (get state \"skip\")))\n (if skip\n (assoc state \"skip\" false \"i\" (inc (get state \"i\")))\n (if (and (= (type-of arg) \"keyword\")\n (< (inc (get state \"i\")) (len args)))\n (let ((kname (keyword-name arg))\n (kval (trampoline (eval-expr (nth args (inc (get state \"i\"))) env))))\n (cond\n (= kname \"id\") (set! lake-id kval)\n (= kname \"tag\") (set! lake-tag kval))\n (assoc state \"skip\" true \"i\" (inc (get state \"i\"))))\n (do\n (append! children arg)\n (assoc state \"i\" (inc (get state \"i\"))))))))\n (dict \"i\" 0 \"skip\" false)\n args)\n ;; Provide scope for spread emit!\n (let ((lake-attrs (dict \"data-sx-lake\" (or lake-id \"\"))))\n (scope-push! \"element-attrs\" nil)\n (let ((content (join \"\" (map (fn (c) (render-to-html c env)) children))))\n (for-each\n (fn (spread-dict) (merge-spread-attrs lake-attrs spread-dict))\n (emitted \"element-attrs\"))\n (scope-pop! \"element-attrs\")\n (str \"<\" lake-tag (render-attrs lake-attrs) \">\"\n content\n \"</\" lake-tag \">\"))))))\n\n\n;; --------------------------------------------------------------------------\n;; render-html-marsh \u2014 SSR rendering of a reactive server-morphable slot\n;; --------------------------------------------------------------------------\n;;\n;; (marsh :id \"name\" :tag \"div\" :transform fn children...)\n;; \u2192 <div data-sx-marsh=\"name\">children</div>\n;;\n;; Like a lake but reactive: during morph, new content is parsed as SX and\n;; re-evaluated in the island's signal scope. Server renders children normally;\n;; the :transform is a client-only concern.\n\n(define render-html-marsh :effects [render]\n (fn ((args :as list) (env :as dict))\n (let ((marsh-id nil)\n (marsh-tag \"div\")\n (children (list)))\n (reduce\n (fn (state arg)\n (let ((skip (get state \"skip\")))\n (if skip\n (assoc state \"skip\" false \"i\" (inc (get state \"i\")))\n (if (and (= (type-of arg) \"keyword\")\n (< (inc (get state \"i\")) (len args)))\n (let ((kname (keyword-name arg))\n (kval (trampoline (eval-expr (nth args (inc (get state \"i\"))) env))))\n (cond\n (= kname \"id\") (set! marsh-id kval)\n (= kname \"tag\") (set! marsh-tag kval)\n (= kname \"transform\") nil)\n (assoc state \"skip\" true \"i\" (inc (get state \"i\"))))\n (do\n (append! children arg)\n (assoc state \"i\" (inc (get state \"i\"))))))))\n (dict \"i\" 0 \"skip\" false)\n args)\n ;; Provide scope for spread emit!\n (let ((marsh-attrs (dict \"data-sx-marsh\" (or marsh-id \"\"))))\n (scope-push! \"element-attrs\" nil)\n (let ((content (join \"\" (map (fn (c) (render-to-html c env)) children))))\n (for-each\n (fn (spread-dict) (merge-spread-attrs marsh-attrs spread-dict))\n (emitted \"element-attrs\"))\n (scope-pop! \"element-attrs\")\n (str \"<\" marsh-tag (render-attrs marsh-attrs) \">\"\n content\n \"</\" marsh-tag \">\"))))))\n\n\n;; --------------------------------------------------------------------------\n;; render-html-island \u2014 SSR rendering of a reactive island\n;; --------------------------------------------------------------------------\n;;\n;; Renders the island body as static HTML wrapped in a container element\n;; with data-sx-island and data-sx-state attributes. The client hydrates\n;; this by finding these elements and re-rendering with reactive context.\n;;\n;; On the server, signal/deref/reset!/swap! are simple passthrough:\n;; (signal val) \u2192 returns val (no container needed server-side)\n;; (deref s) \u2192 returns s (signal values are plain values server-side)\n;; (reset! s v) \u2192 no-op\n;; (swap! s f) \u2192 no-op\n\n(define render-html-island :effects [render]\n (fn ((island :as island) (args :as list) (env :as dict))\n ;; Parse kwargs and children (same pattern as render-html-component)\n (let ((kwargs (dict))\n (children (list)))\n (reduce\n (fn (state arg)\n (let ((skip (get state \"skip\")))\n (if skip\n (assoc state \"skip\" false \"i\" (inc (get state \"i\")))\n (if (and (= (type-of arg) \"keyword\")\n (< (inc (get state \"i\")) (len args)))\n (let ((val (trampoline\n (eval-expr (nth args (inc (get state \"i\"))) env))))\n (dict-set! kwargs (keyword-name arg) val)\n (assoc state \"skip\" true \"i\" (inc (get state \"i\"))))\n (do\n (append! children arg)\n (assoc state \"i\" (inc (get state \"i\"))))))))\n (dict \"i\" 0 \"skip\" false)\n args)\n\n ;; Build island env: closure + caller env + params\n (let ((local (env-merge (component-closure island) env))\n (island-name (component-name island)))\n\n ;; Bind params from kwargs\n (for-each\n (fn (p)\n (env-bind! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil)))\n (component-params island))\n\n ;; If island accepts children, pre-render them to raw HTML\n (when (component-has-children? island)\n (env-bind! local \"children\"\n (make-raw-html (join \"\" (map (fn (c) (render-to-html c env)) children)))))\n\n ;; Render the island body as HTML\n (let ((body-html (render-to-html (component-body island) local))\n (state-sx (serialize-island-state kwargs)))\n ;; Wrap in container with hydration attributes\n (str \"<span data-sx-island=\\\"\" (escape-attr island-name) \"\\\"\"\n (if state-sx\n (str \" data-sx-state=\\\"\" (escape-attr state-sx) \"\\\"\")\n \"\")\n \">\"\n body-html\n \"</span>\"))))))\n\n\n;; --------------------------------------------------------------------------\n;; serialize-island-state \u2014 serialize kwargs to SX for hydration\n;; --------------------------------------------------------------------------\n;;\n;; Uses the SX serializer (not JSON) so the client can parse with sx-parse.\n;; Handles all SX types natively: numbers, strings, booleans, nil, lists, dicts.\n\n(define serialize-island-state :effects []\n (fn ((kwargs :as dict))\n (if (empty-dict? kwargs)\n nil\n (sx-serialize kwargs))))\n\n\n;; --------------------------------------------------------------------------\n;; Platform interface \u2014 HTML adapter\n;; --------------------------------------------------------------------------\n;;\n;; Inherited from render.sx:\n;; escape-html, escape-attr, raw-html-content\n;;\n;; From eval.sx:\n;; eval-expr, trampoline, expand-macro, process-bindings, eval-cond\n;; env-has?, env-get, env-set!, env-merge\n;; lambda?, component?, island?, macro?\n;; lambda-closure, lambda-params, lambda-body\n;; component-params, component-body, component-closure,\n;; component-has-children?, component-name\n;;\n;; Raw HTML construction:\n;; (make-raw-html s) \u2192 wrap string as raw HTML (not double-escaped)\n;;\n;; Island state serialization:\n;; (sx-serialize val) \u2192 SX source string (from parser.sx)\n;; (empty-dict? d) \u2192 boolean\n;; (escape-attr s) \u2192 HTML attribute escape\n;;\n;; Iteration:\n;; (for-each-indexed fn coll) \u2192 call fn(index, item) for each element\n;; (map-indexed fn coll) \u2192 map fn(index, item) over each element\n;; --------------------------------------------------------------------------\n";
// =========================================================================
// WASM Boot: load adapters, then process inline <script type="text/sx">
// =========================================================================
(function() {
"use strict";
if (typeof document === "undefined") return;
function sxWasmBoot() {
var K = globalThis.SxKernel;
if (!K || !globalThis.Sx) { setTimeout(sxWasmBoot, 50); return; }
console.log("[sx-wasm] booting, engine:", K.engine());
// Load embedded web adapters
var adapters = globalThis.__sxAdapters || {};
var adapterOrder = ["signals", "deps", "page-helpers", "router", "adapter-html"];
for (var j = 0; j < adapterOrder.length; j++) {
var name = adapterOrder[j];
if (adapters[name]) {
var r = K.loadSource(adapters[name]);
if (typeof r === "string" && r.startsWith("Error:")) {
console.error("[sx-wasm] adapter " + name + " error:", r);
} else {
console.log("[sx-wasm] loaded " + name + " (" + r + " defs)");
}
}
}
delete globalThis.__sxAdapters; // Free memory
// Process <script type="text/sx" data-components>
var scripts = document.querySelectorAll('script[type="text/sx"]');
for (var i = 0; i < scripts.length; i++) {
var s = scripts[i], src = s.textContent.trim();
if (!src) continue;
if (s.hasAttribute("data-components")) {
var result = K.loadSource(src);
if (typeof result === "string" && result.startsWith("Error:"))
console.error("[sx-wasm] component load error:", result);
}
}
// Process <script type="text/sx" data-init>
for (var i = 0; i < scripts.length; i++) {
var s = scripts[i];
if (s.hasAttribute("data-init")) {
var src = s.textContent.trim();
if (src) K.loadSource(src);
}
}
// Process <script type="text/sx" data-mount="...">
for (var i = 0; i < scripts.length; i++) {
var s = scripts[i];
if (s.hasAttribute("data-mount")) {
var mount = s.getAttribute("data-mount"), src = s.textContent.trim();
if (!src) continue;
var target = mount === "body" ? document.body : document.querySelector(mount);
if (!target) continue;
try {
var parsed = K.parse(src);
if (parsed && parsed.length > 0) {
var html = K.renderToHtml(parsed[0]);
if (html && typeof html === "string") {
target.innerHTML = html;
console.log("[sx-wasm] mounted to", mount);
}
}
} catch(e) { console.error("[sx-wasm] mount error:", e); }
}
}
console.log("[sx-wasm] boot complete");
}
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", sxWasmBoot);
else sxWasmBoot();
})();