All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 23m17s
- 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>
2585 lines
201 KiB
JavaScript
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, "&").replace
|
|
(caml_js_regexps.lt, "<").replace
|
|
(caml_js_regexps.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();
|
|
})();
|