Files
rose-ash/shared/static/scripts/sx-browser.js
giles 03ba8e58e5
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 11m13s
Implement reader macros (#;, #|...|, #', #name) and #z3 demo
Reader macros in parser.sx spec, Python parser.py, and hand-written sx.js:
- #; datum comment: read and discard next expression
- #|...|  raw string: no escape processing
- #' quote shorthand: (quote expr)
- #name extensible dispatch: registered handler transforms next expression

#z3 reader macro demo (reader_z3.py): translates define-primitive
declarations from primitives.sx into SMT-LIB verification conditions.
Same source, two interpretations — bootstrappers compile to executable
code, #z3 extracts proof obligations.

48 parser tests (SX spec + Python), all passing. Rebootstrapped JS+Python.
Demo page at /plans/reader-macro-demo with side-by-side examples.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 20:21:40 +00:00

8274 lines
346 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* sx-ref.js — Generated from reference SX evaluator specification.
*
* Bootstrap-compiled from shared/sx/ref/{eval,render,primitives}.sx
* Compare against hand-written sx.js for correctness verification.
*
* DO NOT EDIT — regenerate with: python bootstrap_js.py
*/
;(function(global) {
"use strict";
// =========================================================================
// Types
// =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-03-08T20:20:11Z";
function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); }
function Symbol(name) { this.name = name; }
Symbol.prototype.toString = function() { return this.name; };
Symbol.prototype._sym = true;
function Keyword(name) { this.name = name; }
Keyword.prototype.toString = function() { return ":" + this.name; };
Keyword.prototype._kw = true;
function Lambda(params, body, closure, name) {
this.params = params;
this.body = body;
this.closure = closure || {};
this.name = name || null;
}
Lambda.prototype._lambda = true;
function Component(name, params, hasChildren, body, closure, affinity) {
this.name = name;
this.params = params;
this.hasChildren = hasChildren;
this.body = body;
this.closure = closure || {};
this.affinity = affinity || "auto";
}
Component.prototype._component = true;
function Island(name, params, hasChildren, body, closure) {
this.name = name;
this.params = params;
this.hasChildren = hasChildren;
this.body = body;
this.closure = closure || {};
}
Island.prototype._island = true;
function SxSignal(value) {
this.value = value;
this.subscribers = [];
this.deps = [];
}
SxSignal.prototype._signal = true;
function TrackingCtx(notifyFn) {
this.notifyFn = notifyFn;
this.deps = [];
}
var _trackingContext = null;
function Macro(params, restParam, body, closure, name) {
this.params = params;
this.restParam = restParam;
this.body = body;
this.closure = closure || {};
this.name = name || null;
}
Macro.prototype._macro = true;
function Thunk(expr, env) { this.expr = expr; this.env = env; }
Thunk.prototype._thunk = true;
function RawHTML(html) { this.html = html; }
RawHTML.prototype._raw = true;
function isSym(x) { return x != null && x._sym === true; }
function isKw(x) { return x != null && x._kw === true; }
function merge() {
var out = {};
for (var i = 0; i < arguments.length; i++) {
var d = arguments[i];
if (d) for (var k in d) out[k] = d[k];
}
return out;
}
function sxOr() {
for (var i = 0; i < arguments.length; i++) {
if (isSxTruthy(arguments[i])) return arguments[i];
}
return arguments.length ? arguments[arguments.length - 1] : false;
}
// =========================================================================
// Platform interface — JS implementation
// =========================================================================
function typeOf(x) {
if (isNil(x)) return "nil";
if (typeof x === "number") return "number";
if (typeof x === "string") return "string";
if (typeof x === "boolean") return "boolean";
if (x._sym) return "symbol";
if (x._kw) return "keyword";
if (x._thunk) return "thunk";
if (x._lambda) return "lambda";
if (x._component) return "component";
if (x._island) return "island";
if (x._signal) return "signal";
if (x._macro) return "macro";
if (x._raw) return "raw-html";
if (typeof Node !== "undefined" && x instanceof Node) return "dom-node";
if (Array.isArray(x)) return "list";
if (typeof x === "object") return "dict";
return "unknown";
}
function symbolName(s) { return s.name; }
function keywordName(k) { return k.name; }
function makeSymbol(n) { return new Symbol(n); }
function makeKeyword(n) { return new Keyword(n); }
function makeLambda(params, body, env) { return new Lambda(params, body, merge(env)); }
function makeComponent(name, params, hasChildren, body, env, affinity) {
return new Component(name, params, hasChildren, body, merge(env), affinity);
}
function makeMacro(params, restParam, body, env, name) {
return new Macro(params, restParam, body, merge(env), name);
}
function makeThunk(expr, env) { return new Thunk(expr, env); }
function lambdaParams(f) { return f.params; }
function lambdaBody(f) { return f.body; }
function lambdaClosure(f) { return f.closure; }
function lambdaName(f) { return f.name; }
function setLambdaName(f, n) { f.name = n; }
function componentParams(c) { return c.params; }
function componentBody(c) { return c.body; }
function componentClosure(c) { return c.closure; }
function componentHasChildren(c) { return c.hasChildren; }
function componentName(c) { return c.name; }
function componentAffinity(c) { return c.affinity || "auto"; }
function macroParams(m) { return m.params; }
function macroRestParam(m) { return m.restParam; }
function macroBody(m) { return m.body; }
function macroClosure(m) { return m.closure; }
function isThunk(x) { return x != null && x._thunk === true; }
function thunkExpr(t) { return t.expr; }
function thunkEnv(t) { return t.env; }
function isCallable(x) { return typeof x === "function" || (x != null && x._lambda === true); }
function isLambda(x) { return x != null && x._lambda === true; }
function isComponent(x) { return x != null && x._component === true; }
function isIsland(x) { return x != null && x._island === true; }
function isMacro(x) { return x != null && x._macro === true; }
function isIdentical(a, b) { return a === b; }
// Island platform
function makeIsland(name, params, hasChildren, body, env) {
return new Island(name, params, hasChildren, body, merge(env));
}
// Signal platform
function makeSignal(value) { return new SxSignal(value); }
function isSignal(x) { return x != null && x._signal === true; }
function signalValue(s) { return s.value; }
function signalSetValue(s, v) { s.value = v; }
function signalSubscribers(s) { return s.subscribers.slice(); }
function signalAddSub(s, fn) { if (s.subscribers.indexOf(fn) < 0) s.subscribers.push(fn); }
function signalRemoveSub(s, fn) { var i = s.subscribers.indexOf(fn); if (i >= 0) s.subscribers.splice(i, 1); }
function signalDeps(s) { return s.deps.slice(); }
function signalSetDeps(s, deps) { s.deps = Array.isArray(deps) ? deps.slice() : []; }
function setTrackingContext(ctx) { _trackingContext = ctx; }
function getTrackingContext() { return _trackingContext || NIL; }
function makeTrackingContext(notifyFn) { return new TrackingCtx(notifyFn); }
function trackingContextDeps(ctx) { return ctx ? ctx.deps : []; }
function trackingContextAddDep(ctx, s) { if (ctx && ctx.deps.indexOf(s) < 0) ctx.deps.push(s); }
function trackingContextNotifyFn(ctx) { return ctx ? ctx.notifyFn : NIL; }
// invoke — call any callable (native fn or SX lambda) with args.
// Transpiled code emits direct calls f(args) which fail on SX lambdas
// from runtime-evaluated island bodies. invoke dispatches correctly.
function invoke() {
var f = arguments[0];
var args = Array.prototype.slice.call(arguments, 1);
if (isLambda(f)) return trampoline(callLambda(f, args, lambdaClosure(f)));
if (typeof f === 'function') return f.apply(null, args);
return NIL;
}
// JSON / dict helpers for island state serialization
function jsonSerialize(obj) {
try { return JSON.stringify(obj); } catch(e) { return "{}"; }
}
function isEmptyDict(d) {
if (!d || typeof d !== "object") return true;
for (var k in d) if (d.hasOwnProperty(k)) return false;
return true;
}
function envHas(env, name) { return name in env; }
function envGet(env, name) { return env[name]; }
function envSet(env, name, val) { env[name] = val; }
function envExtend(env) { return Object.create(env); }
function envMerge(base, overlay) {
var child = Object.create(base);
if (overlay) for (var k in overlay) if (overlay.hasOwnProperty(k)) child[k] = overlay[k];
return child;
}
function dictSet(d, k, v) { d[k] = v; return v; }
function dictGet(d, k) { var v = d[k]; return v !== undefined ? v : NIL; }
// Render-expression detection — lets the evaluator delegate to the active adapter.
// Matches HTML tags, SVG tags, <>, raw!, ~components, html: prefix, custom elements.
// Placeholder — overridden by transpiled version from render.sx
function isRenderExpr(expr) { return false; }
// Render dispatch — call the active adapter's render function.
// Set by each adapter when loaded; defaults to identity (no rendering).
var _renderExprFn = null;
function renderExpr(expr, env) {
if (_renderExprFn) return _renderExprFn(expr, env);
// No adapter loaded — just return the expression as-is
return expr;
}
function stripPrefix(s, prefix) {
return s.indexOf(prefix) === 0 ? s.slice(prefix.length) : s;
}
function error(msg) { throw new Error(msg); }
function inspect(x) { return JSON.stringify(x); }
// =========================================================================
// Primitives
// =========================================================================
var PRIMITIVES = {};
// core.arithmetic
PRIMITIVES["+"] = function() { var s = 0; for (var i = 0; i < arguments.length; i++) s += arguments[i]; return s; };
PRIMITIVES["-"] = function(a, b) { return arguments.length === 1 ? -a : a - b; };
PRIMITIVES["*"] = function() { var s = 1; for (var i = 0; i < arguments.length; i++) s *= arguments[i]; return s; };
PRIMITIVES["/"] = function(a, b) { return a / b; };
PRIMITIVES["mod"] = function(a, b) { return a % b; };
PRIMITIVES["inc"] = function(n) { return n + 1; };
PRIMITIVES["dec"] = function(n) { return n - 1; };
PRIMITIVES["abs"] = Math.abs;
PRIMITIVES["floor"] = Math.floor;
PRIMITIVES["ceil"] = Math.ceil;
PRIMITIVES["round"] = function(x, n) {
if (n === undefined || n === 0) return Math.round(x);
var f = Math.pow(10, n); return Math.round(x * f) / f;
};
PRIMITIVES["min"] = Math.min;
PRIMITIVES["max"] = Math.max;
PRIMITIVES["sqrt"] = Math.sqrt;
PRIMITIVES["pow"] = Math.pow;
PRIMITIVES["clamp"] = function(x, lo, hi) { return Math.max(lo, Math.min(hi, x)); };
// core.comparison
PRIMITIVES["="] = function(a, b) { return a === b; };
PRIMITIVES["!="] = function(a, b) { return a !== b; };
PRIMITIVES["<"] = function(a, b) { return a < b; };
PRIMITIVES[">"] = function(a, b) { return a > b; };
PRIMITIVES["<="] = function(a, b) { return a <= b; };
PRIMITIVES[">="] = function(a, b) { return a >= b; };
// core.logic
PRIMITIVES["not"] = function(x) { return !isSxTruthy(x); };
// core.predicates
PRIMITIVES["nil?"] = isNil;
PRIMITIVES["number?"] = function(x) { return typeof x === "number"; };
PRIMITIVES["string?"] = function(x) { return typeof x === "string"; };
PRIMITIVES["list?"] = Array.isArray;
PRIMITIVES["dict?"] = function(x) { return x !== null && typeof x === "object" && !Array.isArray(x) && !x._sym && !x._kw; };
PRIMITIVES["empty?"] = function(c) { return isNil(c) || (Array.isArray(c) ? c.length === 0 : typeof c === "string" ? c.length === 0 : Object.keys(c).length === 0); };
PRIMITIVES["contains?"] = function(c, k) {
if (typeof c === "string") return c.indexOf(String(k)) !== -1;
if (Array.isArray(c)) return c.indexOf(k) !== -1;
return k in c;
};
PRIMITIVES["odd?"] = function(n) { return n % 2 !== 0; };
PRIMITIVES["even?"] = function(n) { return n % 2 === 0; };
PRIMITIVES["zero?"] = function(n) { return n === 0; };
PRIMITIVES["boolean?"] = function(x) { return x === true || x === false; };
// core.strings
PRIMITIVES["str"] = function() {
var p = [];
for (var i = 0; i < arguments.length; i++) {
var v = arguments[i]; if (isNil(v)) continue; p.push(String(v));
}
return p.join("");
};
PRIMITIVES["upper"] = function(s) { return String(s).toUpperCase(); };
PRIMITIVES["lower"] = function(s) { return String(s).toLowerCase(); };
PRIMITIVES["trim"] = function(s) { return String(s).trim(); };
PRIMITIVES["split"] = function(s, sep) { return String(s).split(sep || " "); };
PRIMITIVES["join"] = function(sep, coll) { return coll.join(sep); };
PRIMITIVES["replace"] = function(s, old, nw) { return s.split(old).join(nw); };
PRIMITIVES["index-of"] = function(s, needle, from) { return String(s).indexOf(needle, from || 0); };
PRIMITIVES["starts-with?"] = function(s, p) { return String(s).indexOf(p) === 0; };
PRIMITIVES["ends-with?"] = function(s, p) { var str = String(s); return str.indexOf(p, str.length - p.length) !== -1; };
PRIMITIVES["slice"] = function(c, a, b) { return b !== undefined ? c.slice(a, b) : c.slice(a); };
PRIMITIVES["substring"] = function(s, a, b) { return String(s).substring(a, b); };
PRIMITIVES["string-length"] = function(s) { return String(s).length; };
PRIMITIVES["string-contains?"] = function(s, sub) { return String(s).indexOf(String(sub)) !== -1; };
PRIMITIVES["concat"] = function() {
var out = [];
for (var i = 0; i < arguments.length; i++) if (!isNil(arguments[i])) out = out.concat(arguments[i]);
return out;
};
// core.collections
PRIMITIVES["list"] = function() { return Array.prototype.slice.call(arguments); };
PRIMITIVES["dict"] = function() {
var d = {};
for (var i = 0; i < arguments.length - 1; i += 2) d[arguments[i]] = arguments[i + 1];
return d;
};
PRIMITIVES["range"] = function(a, b, step) {
var r = []; step = step || 1;
for (var i = a; step > 0 ? i < b : i > b; i += step) r.push(i);
return r;
};
PRIMITIVES["get"] = function(c, k, def) { var v = (c && c[k]); return v !== undefined ? v : (def !== undefined ? def : NIL); };
PRIMITIVES["len"] = function(c) { return Array.isArray(c) ? c.length : typeof c === "string" ? c.length : Object.keys(c).length; };
PRIMITIVES["first"] = function(c) { return c && c.length > 0 ? c[0] : NIL; };
PRIMITIVES["last"] = function(c) { return c && c.length > 0 ? c[c.length - 1] : NIL; };
PRIMITIVES["rest"] = function(c) { return c ? c.slice(1) : []; };
PRIMITIVES["nth"] = function(c, n) { return c && n >= 0 && n < c.length ? c[n] : NIL; };
PRIMITIVES["cons"] = function(x, c) { return [x].concat(c || []); };
PRIMITIVES["append"] = function(c, x) { return (c || []).concat([x]); };
PRIMITIVES["append!"] = function(arr, x) { arr.push(x); return arr; };
PRIMITIVES["chunk-every"] = function(c, n) {
var r = []; for (var i = 0; i < c.length; i += n) r.push(c.slice(i, i + n)); return r;
};
PRIMITIVES["zip-pairs"] = function(c) {
var r = []; for (var i = 0; i < c.length - 1; i++) r.push([c[i], c[i + 1]]); return r;
};
PRIMITIVES["reverse"] = function(c) { return Array.isArray(c) ? c.slice().reverse() : String(c).split("").reverse().join(""); };
PRIMITIVES["flatten"] = function(c) {
var out = [];
function walk(a) { for (var i = 0; i < a.length; i++) Array.isArray(a[i]) ? walk(a[i]) : out.push(a[i]); }
walk(c || []); return out;
};
// core.dict
PRIMITIVES["keys"] = function(d) { return Object.keys(d || {}); };
PRIMITIVES["vals"] = function(d) { var r = []; for (var k in d) r.push(d[k]); return r; };
PRIMITIVES["merge"] = function() {
var out = {};
for (var i = 0; i < arguments.length; i++) { var d = arguments[i]; if (d && !isNil(d)) for (var k in d) out[k] = d[k]; }
return out;
};
PRIMITIVES["assoc"] = function(d) {
var out = {}; if (d && !isNil(d)) for (var k in d) out[k] = d[k];
for (var i = 1; i < arguments.length - 1; i += 2) out[arguments[i]] = arguments[i + 1];
return out;
};
PRIMITIVES["dissoc"] = function(d) {
var out = {}; for (var k in d) out[k] = d[k];
for (var i = 1; i < arguments.length; i++) delete out[arguments[i]];
return out;
};
PRIMITIVES["dict-set!"] = function(d, k, v) { d[k] = v; return v; };
PRIMITIVES["has-key?"] = function(d, k) { return d !== null && d !== undefined && k in d; };
PRIMITIVES["into"] = function(target, coll) {
if (Array.isArray(target)) return Array.isArray(coll) ? coll.slice() : Object.entries(coll);
var r = {}; for (var i = 0; i < coll.length; i++) { var p = coll[i]; if (Array.isArray(p) && p.length >= 2) r[p[0]] = p[1]; }
return r;
};
// stdlib.format
PRIMITIVES["format-decimal"] = function(v, p) { return Number(v).toFixed(p || 2); };
PRIMITIVES["parse-int"] = function(v, d) { var n = parseInt(v, 10); return isNaN(n) ? (d || 0) : n; };
PRIMITIVES["format-date"] = function(s, fmt) {
if (!s) return "";
try {
var d = new Date(s);
if (isNaN(d.getTime())) return String(s);
var months = ["January","February","March","April","May","June","July","August","September","October","November","December"];
var short_months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
return fmt.replace(/%-d/g, d.getDate()).replace(/%d/g, ("0"+d.getDate()).slice(-2))
.replace(/%B/g, months[d.getMonth()]).replace(/%b/g, short_months[d.getMonth()])
.replace(/%Y/g, d.getFullYear()).replace(/%m/g, ("0"+(d.getMonth()+1)).slice(-2))
.replace(/%H/g, ("0"+d.getHours()).slice(-2)).replace(/%M/g, ("0"+d.getMinutes()).slice(-2));
} catch (e) { return String(s); }
};
PRIMITIVES["parse-datetime"] = function(s) { return s ? String(s) : NIL; };
// stdlib.text
PRIMITIVES["pluralize"] = function(n, s, p) {
if (s || (p && p !== "s")) return n == 1 ? (s || "") : (p || "s");
return n == 1 ? "" : "s";
};
PRIMITIVES["escape"] = function(s) {
return String(s).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;");
};
PRIMITIVES["strip-tags"] = function(s) { return String(s).replace(/<[^>]+>/g, ""); };
// stdlib.debug
PRIMITIVES["assert"] = function(cond, msg) {
if (!isSxTruthy(cond)) throw new Error("Assertion error: " + (msg || "Assertion failed"));
return true;
};
function isPrimitive(name) { return name in PRIMITIVES; }
function getPrimitive(name) { return PRIMITIVES[name]; }
// Higher-order helpers used by the transpiled code
function map(fn, coll) { return coll.map(fn); }
function mapIndexed(fn, coll) { return coll.map(function(item, i) { return fn(i, item); }); }
function filter(fn, coll) { return coll.filter(function(x) { return isSxTruthy(fn(x)); }); }
function reduce(fn, init, coll) {
var acc = init;
for (var i = 0; i < coll.length; i++) acc = fn(acc, coll[i]);
return acc;
}
function some(fn, coll) {
for (var i = 0; i < coll.length; i++) { var r = fn(coll[i]); if (isSxTruthy(r)) return r; }
return NIL;
}
function forEach(fn, coll) { for (var i = 0; i < coll.length; i++) fn(coll[i]); return NIL; }
function isEvery(fn, coll) {
for (var i = 0; i < coll.length; i++) { if (!isSxTruthy(fn(coll[i]))) return false; }
return true;
}
function mapDict(fn, d) { var r = {}; for (var k in d) r[k] = fn(k, d[k]); return r; }
// List primitives used directly by transpiled code
var len = PRIMITIVES["len"];
var first = PRIMITIVES["first"];
var last = PRIMITIVES["last"];
var rest = PRIMITIVES["rest"];
var nth = PRIMITIVES["nth"];
var cons = PRIMITIVES["cons"];
var append = PRIMITIVES["append"];
var isEmpty = PRIMITIVES["empty?"];
var contains = PRIMITIVES["contains?"];
var startsWith = PRIMITIVES["starts-with?"];
var slice = PRIMITIVES["slice"];
var concat = PRIMITIVES["concat"];
var str = PRIMITIVES["str"];
var join = PRIMITIVES["join"];
var keys = PRIMITIVES["keys"];
var get = PRIMITIVES["get"];
var assoc = PRIMITIVES["assoc"];
var range = PRIMITIVES["range"];
function zip(a, b) { var r = []; for (var i = 0; i < Math.min(a.length, b.length); i++) r.push([a[i], b[i]]); return r; }
function append_b(arr, x) { arr.push(x); return arr; }
var apply = function(f, args) {
if (isLambda(f)) return trampoline(callLambda(f, args, lambdaClosure(f)));
return f.apply(null, args);
};
// Additional primitive aliases used by adapter/engine transpiled code
var split = PRIMITIVES["split"];
var trim = PRIMITIVES["trim"];
var upper = PRIMITIVES["upper"];
var lower = PRIMITIVES["lower"];
var replace_ = function(s, old, nw) { return s.split(old).join(nw); };
var endsWith = PRIMITIVES["ends-with?"];
var parseInt_ = PRIMITIVES["parse-int"];
var dict_fn = PRIMITIVES["dict"];
// HTML rendering helpers
function escapeHtml(s) {
return String(s).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;");
}
function escapeAttr(s) { return escapeHtml(s); }
function rawHtmlContent(r) { return r.html; }
function makeRawHtml(s) { return { _raw: true, html: s }; }
function sxExprSource(x) { return x && x.source ? x.source : String(x); }
// Placeholders — overridden by transpiled spec from parser.sx / adapter-sx.sx
function serialize(val) { return String(val); }
function isSpecialForm(n) { return false; }
function isHoForm(n) { return false; }
// processBindings and evalCond — now specced in render.sx, bootstrapped above
function isDefinitionForm(name) {
return name === "define" || name === "defcomp" || name === "defmacro" ||
name === "defstyle" || name === "defhandler";
}
function indexOf_(s, ch) {
return typeof s === "string" ? s.indexOf(ch) : -1;
}
function dictHas(d, k) { return d != null && k in d; }
function dictDelete(d, k) { delete d[k]; }
function forEachIndexed(fn, coll) {
for (var i = 0; i < coll.length; i++) fn(i, coll[i]);
return NIL;
}
// =========================================================================
// Performance overrides — evaluator hot path
// =========================================================================
// Override parseKeywordArgs: imperative loop instead of reduce+assoc
parseKeywordArgs = function(rawArgs, env) {
var kwargs = {};
var children = [];
for (var i = 0; i < rawArgs.length; i++) {
var arg = rawArgs[i];
if (arg && arg._kw && (i + 1) < rawArgs.length) {
kwargs[arg.name] = trampoline(evalExpr(rawArgs[i + 1], env));
i++;
} else {
children.push(trampoline(evalExpr(arg, env)));
}
}
return [kwargs, children];
};
// Override callComponent: use prototype chain env, imperative kwarg binding
callComponent = function(comp, rawArgs, env) {
var kwargs = {};
var children = [];
for (var i = 0; i < rawArgs.length; i++) {
var arg = rawArgs[i];
if (arg && arg._kw && (i + 1) < rawArgs.length) {
kwargs[arg.name] = trampoline(evalExpr(rawArgs[i + 1], env));
i++;
} else {
children.push(trampoline(evalExpr(arg, env)));
}
}
var local = Object.create(componentClosure(comp));
for (var k in env) if (env.hasOwnProperty(k)) local[k] = env[k];
var params = componentParams(comp);
for (var j = 0; j < params.length; j++) {
var p = params[j];
local[p] = p in kwargs ? kwargs[p] : NIL;
}
if (componentHasChildren(comp)) {
local["children"] = children;
}
return makeThunk(componentBody(comp), local);
};
// =========================================================================
// Platform interface — Parser
// =========================================================================
// Character classification derived from the grammar:
// ident-start → [a-zA-Z_~*+\-><=/!?&]
// ident-char → ident-start + [0-9.:\/\[\]#,]
var _identStartRe = /[a-zA-Z_~*+\-><=/!?&]/;
var _identCharRe = /[a-zA-Z0-9_~*+\-><=/!?.:&/\[\]#,]/;
function isIdentStart(ch) { return _identStartRe.test(ch); }
function isIdentChar(ch) { return _identCharRe.test(ch); }
function parseNumber(s) { return Number(s); }
function escapeString(s) {
return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\t/g, "\\t");
}
function sxExprSource(e) { return typeof e === "string" ? e : String(e); }
// === Transpiled from eval ===
// trampoline
var trampoline = function(val) { return (function() {
var result = val;
return (isSxTruthy(isThunk(result)) ? trampoline(evalExpr(thunkExpr(result), thunkEnv(result))) : result);
})(); };
// eval-expr
var evalExpr = function(expr, env) { return (function() { var _m = typeOf(expr); if (_m == "number") return expr; if (_m == "string") return expr; if (_m == "boolean") return expr; if (_m == "nil") return NIL; if (_m == "symbol") return (function() {
var name = symbolName(expr);
return (isSxTruthy(envHas(env, name)) ? envGet(env, name) : (isSxTruthy(isPrimitive(name)) ? getPrimitive(name) : (isSxTruthy((name == "true")) ? true : (isSxTruthy((name == "false")) ? false : (isSxTruthy((name == "nil")) ? NIL : error((String("Undefined symbol: ") + String(name))))))));
})(); if (_m == "keyword") return keywordName(expr); if (_m == "dict") return mapDict(function(k, v) { return trampoline(evalExpr(v, env)); }, expr); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? [] : evalList(expr, env)); return expr; })(); };
// eval-list
var evalList = function(expr, env) { return (function() {
var head = first(expr);
var args = rest(expr);
return (isSxTruthy(!isSxTruthy(sxOr((typeOf(head) == "symbol"), (typeOf(head) == "lambda"), (typeOf(head) == "list")))) ? map(function(x) { return trampoline(evalExpr(x, env)); }, expr) : (isSxTruthy((typeOf(head) == "symbol")) ? (function() {
var name = symbolName(head);
return (isSxTruthy((name == "if")) ? sfIf(args, env) : (isSxTruthy((name == "when")) ? sfWhen(args, env) : (isSxTruthy((name == "cond")) ? sfCond(args, env) : (isSxTruthy((name == "case")) ? sfCase(args, env) : (isSxTruthy((name == "and")) ? sfAnd(args, env) : (isSxTruthy((name == "or")) ? sfOr(args, env) : (isSxTruthy((name == "let")) ? sfLet(args, env) : (isSxTruthy((name == "let*")) ? sfLet(args, env) : (isSxTruthy((name == "letrec")) ? sfLetrec(args, env) : (isSxTruthy((name == "lambda")) ? sfLambda(args, env) : (isSxTruthy((name == "fn")) ? sfLambda(args, env) : (isSxTruthy((name == "define")) ? sfDefine(args, env) : (isSxTruthy((name == "defcomp")) ? sfDefcomp(args, env) : (isSxTruthy((name == "defisland")) ? sfDefisland(args, env) : (isSxTruthy((name == "defmacro")) ? sfDefmacro(args, env) : (isSxTruthy((name == "defstyle")) ? sfDefstyle(args, env) : (isSxTruthy((name == "defhandler")) ? sfDefhandler(args, env) : (isSxTruthy((name == "defpage")) ? sfDefpage(args, env) : (isSxTruthy((name == "defquery")) ? sfDefquery(args, env) : (isSxTruthy((name == "defaction")) ? sfDefaction(args, env) : (isSxTruthy((name == "begin")) ? sfBegin(args, env) : (isSxTruthy((name == "do")) ? sfBegin(args, env) : (isSxTruthy((name == "quote")) ? sfQuote(args, env) : (isSxTruthy((name == "quasiquote")) ? sfQuasiquote(args, env) : (isSxTruthy((name == "->")) ? sfThreadFirst(args, env) : (isSxTruthy((name == "set!")) ? sfSetBang(args, env) : (isSxTruthy((name == "reset")) ? sfReset(args, env) : (isSxTruthy((name == "shift")) ? sfShift(args, env) : (isSxTruthy((name == "dynamic-wind")) ? sfDynamicWind(args, env) : (isSxTruthy((name == "map")) ? hoMap(args, env) : (isSxTruthy((name == "map-indexed")) ? hoMapIndexed(args, env) : (isSxTruthy((name == "filter")) ? hoFilter(args, env) : (isSxTruthy((name == "reduce")) ? hoReduce(args, env) : (isSxTruthy((name == "some")) ? hoSome(args, env) : (isSxTruthy((name == "every?")) ? hoEvery(args, env) : (isSxTruthy((name == "for-each")) ? hoForEach(args, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? (function() {
var mac = envGet(env, name);
return makeThunk(expandMacro(mac, args, env), env);
})() : (isSxTruthy(isRenderExpr(expr)) ? renderExpr(expr, env) : evalCall(head, args, env)))))))))))))))))))))))))))))))))))))));
})() : evalCall(head, args, env)));
})(); };
// eval-call
var evalCall = function(head, args, env) { return (function() {
var f = trampoline(evalExpr(head, env));
var evaluatedArgs = map(function(a) { return trampoline(evalExpr(a, env)); }, args);
return (isSxTruthy((isSxTruthy(isCallable(f)) && isSxTruthy(!isSxTruthy(isLambda(f))) && isSxTruthy(!isSxTruthy(isComponent(f))) && !isSxTruthy(isIsland(f)))) ? apply(f, evaluatedArgs) : (isSxTruthy(isLambda(f)) ? callLambda(f, evaluatedArgs, env) : (isSxTruthy(isComponent(f)) ? callComponent(f, args, env) : (isSxTruthy(isIsland(f)) ? callComponent(f, args, env) : error((String("Not callable: ") + String(inspect(f))))))));
})(); };
// call-lambda
var callLambda = function(f, args, callerEnv) { return (function() {
var params = lambdaParams(f);
var local = envMerge(lambdaClosure(f), callerEnv);
return (isSxTruthy((len(args) != len(params))) ? error((String(sxOr(lambdaName(f), "lambda")) + String(" expects ") + String(len(params)) + String(" args, got ") + String(len(args)))) : (forEach(function(pair) { return envSet(local, first(pair), nth(pair, 1)); }, zip(params, args)), makeThunk(lambdaBody(f), local)));
})(); };
// call-component
var callComponent = function(comp, rawArgs, env) { return (function() {
var parsed = parseKeywordArgs(rawArgs, env);
var kwargs = first(parsed);
var children = nth(parsed, 1);
var local = envMerge(componentClosure(comp), env);
{ var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; local[p] = sxOr(dictGet(kwargs, p), NIL); } }
if (isSxTruthy(componentHasChildren(comp))) {
local["children"] = children;
}
return makeThunk(componentBody(comp), local);
})(); };
// parse-keyword-args
var parseKeywordArgs = function(rawArgs, env) { return (function() {
var kwargs = {};
var children = [];
var i = 0;
reduce(function(state, arg) { return (function() {
var idx = get(state, "i");
var skip = get(state, "skip");
return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (idx + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((idx + 1) < len(rawArgs)))) ? (dictSet(kwargs, keywordName(arg), trampoline(evalExpr(nth(rawArgs, (idx + 1)), env))), assoc(state, "skip", true, "i", (idx + 1))) : (append_b(children, trampoline(evalExpr(arg, env))), assoc(state, "i", (idx + 1)))));
})(); }, {["i"]: 0, ["skip"]: false}, rawArgs);
return [kwargs, children];
})(); };
// sf-if
var sfIf = function(args, env) { return (function() {
var condition = trampoline(evalExpr(first(args), env));
return (isSxTruthy((isSxTruthy(condition) && !isSxTruthy(isNil(condition)))) ? makeThunk(nth(args, 1), env) : (isSxTruthy((len(args) > 2)) ? makeThunk(nth(args, 2), env) : NIL));
})(); };
// sf-when
var sfWhen = function(args, env) { return (function() {
var condition = trampoline(evalExpr(first(args), env));
return (isSxTruthy((isSxTruthy(condition) && !isSxTruthy(isNil(condition)))) ? (forEach(function(e) { return trampoline(evalExpr(e, env)); }, slice(args, 1, (len(args) - 1))), makeThunk(last(args), env)) : NIL);
})(); };
// sf-cond
var sfCond = function(args, env) { return (isSxTruthy((isSxTruthy((typeOf(first(args)) == "list")) && (len(first(args)) == 2))) ? sfCondScheme(args, env) : sfCondClojure(args, env)); };
// sf-cond-scheme
var sfCondScheme = function(clauses, env) { return (isSxTruthy(isEmpty(clauses)) ? NIL : (function() {
var clause = first(clauses);
var test = first(clause);
var body = nth(clause, 1);
return (isSxTruthy(sxOr((isSxTruthy((typeOf(test) == "symbol")) && sxOr((symbolName(test) == "else"), (symbolName(test) == ":else"))), (isSxTruthy((typeOf(test) == "keyword")) && (keywordName(test) == "else")))) ? makeThunk(body, env) : (isSxTruthy(trampoline(evalExpr(test, env))) ? makeThunk(body, env) : sfCondScheme(rest(clauses), env)));
})()); };
// sf-cond-clojure
var sfCondClojure = function(clauses, env) { return (isSxTruthy((len(clauses) < 2)) ? NIL : (function() {
var test = first(clauses);
var body = nth(clauses, 1);
return (isSxTruthy(sxOr((isSxTruthy((typeOf(test) == "keyword")) && (keywordName(test) == "else")), (isSxTruthy((typeOf(test) == "symbol")) && sxOr((symbolName(test) == "else"), (symbolName(test) == ":else"))))) ? makeThunk(body, env) : (isSxTruthy(trampoline(evalExpr(test, env))) ? makeThunk(body, env) : sfCondClojure(slice(clauses, 2), env)));
})()); };
// sf-case
var sfCase = function(args, env) { return (function() {
var matchVal = trampoline(evalExpr(first(args), env));
var clauses = rest(args);
return sfCaseLoop(matchVal, clauses, env);
})(); };
// sf-case-loop
var sfCaseLoop = function(matchVal, clauses, env) { return (isSxTruthy((len(clauses) < 2)) ? NIL : (function() {
var test = first(clauses);
var body = nth(clauses, 1);
return (isSxTruthy(sxOr((isSxTruthy((typeOf(test) == "keyword")) && (keywordName(test) == "else")), (isSxTruthy((typeOf(test) == "symbol")) && sxOr((symbolName(test) == "else"), (symbolName(test) == ":else"))))) ? makeThunk(body, env) : (isSxTruthy((matchVal == trampoline(evalExpr(test, env)))) ? makeThunk(body, env) : sfCaseLoop(matchVal, slice(clauses, 2), env)));
})()); };
// sf-and
var sfAnd = function(args, env) { return (isSxTruthy(isEmpty(args)) ? true : (function() {
var val = trampoline(evalExpr(first(args), env));
return (isSxTruthy(!isSxTruthy(val)) ? val : (isSxTruthy((len(args) == 1)) ? val : sfAnd(rest(args), env)));
})()); };
// sf-or
var sfOr = function(args, env) { return (isSxTruthy(isEmpty(args)) ? false : (function() {
var val = trampoline(evalExpr(first(args), env));
return (isSxTruthy(val) ? val : sfOr(rest(args), env));
})()); };
// sf-let
var sfLet = function(args, env) { return (isSxTruthy((typeOf(first(args)) == "symbol")) ? sfNamedLet(args, env) : (function() {
var bindings = first(args);
var body = rest(args);
var local = envExtend(env);
(isSxTruthy((isSxTruthy((typeOf(first(bindings)) == "list")) && (len(first(bindings)) == 2))) ? forEach(function(binding) { return (function() {
var vname = (isSxTruthy((typeOf(first(binding)) == "symbol")) ? symbolName(first(binding)) : first(binding));
return envSet(local, vname, trampoline(evalExpr(nth(binding, 1), local)));
})(); }, bindings) : (function() {
var i = 0;
return reduce(function(acc, pairIdx) { return (function() {
var vname = (isSxTruthy((typeOf(nth(bindings, (pairIdx * 2))) == "symbol")) ? symbolName(nth(bindings, (pairIdx * 2))) : nth(bindings, (pairIdx * 2)));
var valExpr = nth(bindings, ((pairIdx * 2) + 1));
return envSet(local, vname, trampoline(evalExpr(valExpr, local)));
})(); }, NIL, range(0, (len(bindings) / 2)));
})());
{ var _c = slice(body, 0, (len(body) - 1)); for (var _i = 0; _i < _c.length; _i++) { var e = _c[_i]; trampoline(evalExpr(e, local)); } }
return makeThunk(last(body), local);
})()); };
// sf-named-let
var sfNamedLet = function(args, env) { return (function() {
var loopName = symbolName(first(args));
var bindings = nth(args, 1);
var body = slice(args, 2);
var params = [];
var inits = [];
(isSxTruthy((isSxTruthy((typeOf(first(bindings)) == "list")) && (len(first(bindings)) == 2))) ? forEach(function(binding) { params.push((isSxTruthy((typeOf(first(binding)) == "symbol")) ? symbolName(first(binding)) : first(binding)));
return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pairIdx) { return (append_b(params, (isSxTruthy((typeOf(nth(bindings, (pairIdx * 2))) == "symbol")) ? symbolName(nth(bindings, (pairIdx * 2))) : nth(bindings, (pairIdx * 2)))), append_b(inits, nth(bindings, ((pairIdx * 2) + 1)))); }, NIL, range(0, (len(bindings) / 2))));
return (function() {
var loopBody = (isSxTruthy((len(body) == 1)) ? first(body) : cons(makeSymbol("begin"), body));
var loopFn = makeLambda(params, loopBody, env);
loopFn.name = loopName;
lambdaClosure(loopFn)[loopName] = loopFn;
return (function() {
var initVals = map(function(e) { return trampoline(evalExpr(e, env)); }, inits);
return callLambda(loopFn, initVals, env);
})();
})();
})(); };
// sf-lambda
var sfLambda = function(args, env) { return (function() {
var paramsExpr = first(args);
var bodyExprs = rest(args);
var body = (isSxTruthy((len(bodyExprs) == 1)) ? first(bodyExprs) : cons(makeSymbol("begin"), bodyExprs));
var paramNames = map(function(p) { return (isSxTruthy((typeOf(p) == "symbol")) ? symbolName(p) : p); }, paramsExpr);
return makeLambda(paramNames, body, env);
})(); };
// sf-define
var sfDefine = function(args, env) { return (function() {
var nameSym = first(args);
var value = trampoline(evalExpr(nth(args, 1), env));
if (isSxTruthy((isSxTruthy(isLambda(value)) && isNil(lambdaName(value))))) {
value.name = symbolName(nameSym);
}
env[symbolName(nameSym)] = value;
return value;
})(); };
// sf-defcomp
var sfDefcomp = function(args, env) { return (function() {
var nameSym = first(args);
var paramsRaw = nth(args, 1);
var body = last(args);
var compName = stripPrefix(symbolName(nameSym), "~");
var parsed = parseCompParams(paramsRaw);
var params = first(parsed);
var hasChildren = nth(parsed, 1);
var affinity = defcompKwarg(args, "affinity", "auto");
return (function() {
var comp = makeComponent(compName, params, hasChildren, body, env, affinity);
env[symbolName(nameSym)] = comp;
return comp;
})();
})(); };
// defcomp-kwarg
var defcompKwarg = function(args, key, default_) { return (function() {
var end = (len(args) - 1);
var result = default_;
{ var _c = range(2, end, 1); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; if (isSxTruthy((isSxTruthy((typeOf(nth(args, i)) == "keyword")) && isSxTruthy((keywordName(nth(args, i)) == key)) && ((i + 1) < end)))) {
(function() {
var val = nth(args, (i + 1));
return (result = (isSxTruthy((typeOf(val) == "keyword")) ? keywordName(val) : val));
})();
} } }
return result;
})(); };
// parse-comp-params
var parseCompParams = function(paramsExpr) { return (function() {
var params = [];
var hasChildren = false;
var inKey = false;
{ var _c = paramsExpr; for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; if (isSxTruthy((typeOf(p) == "symbol"))) {
(function() {
var name = symbolName(p);
return (isSxTruthy((name == "&key")) ? (inKey = true) : (isSxTruthy((name == "&rest")) ? (hasChildren = true) : (isSxTruthy((name == "&children")) ? (hasChildren = true) : (isSxTruthy(hasChildren) ? NIL : (isSxTruthy(inKey) ? append_b(params, name) : append_b(params, name))))));
})();
} } }
return [params, hasChildren];
})(); };
// sf-defisland
var sfDefisland = function(args, env) { return (function() {
var nameSym = first(args);
var paramsRaw = nth(args, 1);
var body = last(args);
var compName = stripPrefix(symbolName(nameSym), "~");
var parsed = parseCompParams(paramsRaw);
var params = first(parsed);
var hasChildren = nth(parsed, 1);
return (function() {
var island = makeIsland(compName, params, hasChildren, body, env);
env[symbolName(nameSym)] = island;
return island;
})();
})(); };
// sf-defmacro
var sfDefmacro = function(args, env) { return (function() {
var nameSym = first(args);
var paramsRaw = nth(args, 1);
var body = nth(args, 2);
var parsed = parseMacroParams(paramsRaw);
var params = first(parsed);
var restParam = nth(parsed, 1);
return (function() {
var mac = makeMacro(params, restParam, body, env, symbolName(nameSym));
env[symbolName(nameSym)] = mac;
return mac;
})();
})(); };
// parse-macro-params
var parseMacroParams = function(paramsExpr) { return (function() {
var params = [];
var restParam = NIL;
reduce(function(state, p) { return (isSxTruthy((isSxTruthy((typeOf(p) == "symbol")) && (symbolName(p) == "&rest"))) ? assoc(state, "in-rest", true) : (isSxTruthy(get(state, "in-rest")) ? ((restParam = (isSxTruthy((typeOf(p) == "symbol")) ? symbolName(p) : p)), state) : (append_b(params, (isSxTruthy((typeOf(p) == "symbol")) ? symbolName(p) : p)), state))); }, {["in-rest"]: false}, paramsExpr);
return [params, restParam];
})(); };
// sf-defstyle
var sfDefstyle = function(args, env) { return (function() {
var nameSym = first(args);
var value = trampoline(evalExpr(nth(args, 1), env));
env[symbolName(nameSym)] = value;
return value;
})(); };
// sf-begin
var sfBegin = function(args, env) { return (isSxTruthy(isEmpty(args)) ? NIL : (forEach(function(e) { return trampoline(evalExpr(e, env)); }, slice(args, 0, (len(args) - 1))), makeThunk(last(args), env))); };
// sf-quote
var sfQuote = function(args, env) { return (isSxTruthy(isEmpty(args)) ? NIL : first(args)); };
// sf-quasiquote
var sfQuasiquote = function(args, env) { return qqExpand(first(args), env); };
// qq-expand
var qqExpand = function(template, env) { return (isSxTruthy(!isSxTruthy((typeOf(template) == "list"))) ? template : (isSxTruthy(isEmpty(template)) ? [] : (function() {
var head = first(template);
return (isSxTruthy((isSxTruthy((typeOf(head) == "symbol")) && (symbolName(head) == "unquote"))) ? trampoline(evalExpr(nth(template, 1), env)) : reduce(function(result, item) { return (isSxTruthy((isSxTruthy((typeOf(item) == "list")) && isSxTruthy((len(item) == 2)) && isSxTruthy((typeOf(first(item)) == "symbol")) && (symbolName(first(item)) == "splice-unquote"))) ? (function() {
var spliced = trampoline(evalExpr(nth(item, 1), env));
return (isSxTruthy((typeOf(spliced) == "list")) ? concat(result, spliced) : (isSxTruthy(isNil(spliced)) ? result : append(result, spliced)));
})() : append(result, qqExpand(item, env))); }, [], template));
})())); };
// sf-thread-first
var sfThreadFirst = function(args, env) { return (function() {
var val = trampoline(evalExpr(first(args), env));
return reduce(function(result, form) { return (isSxTruthy((typeOf(form) == "list")) ? (function() {
var f = trampoline(evalExpr(first(form), env));
var restArgs = map(function(a) { return trampoline(evalExpr(a, env)); }, rest(form));
var allArgs = cons(result, restArgs);
return (isSxTruthy((isSxTruthy(isCallable(f)) && !isSxTruthy(isLambda(f)))) ? apply(f, allArgs) : (isSxTruthy(isLambda(f)) ? trampoline(callLambda(f, allArgs, env)) : error((String("-> form not callable: ") + String(inspect(f))))));
})() : (function() {
var f = trampoline(evalExpr(form, env));
return (isSxTruthy((isSxTruthy(isCallable(f)) && !isSxTruthy(isLambda(f)))) ? f(result) : (isSxTruthy(isLambda(f)) ? trampoline(callLambda(f, [result], env)) : error((String("-> form not callable: ") + String(inspect(f))))));
})()); }, val, rest(args));
})(); };
// sf-set!
var sfSetBang = function(args, env) { return (function() {
var name = symbolName(first(args));
var value = trampoline(evalExpr(nth(args, 1), env));
env[name] = value;
return value;
})(); };
// sf-letrec
var sfLetrec = function(args, env) { return (function() {
var bindings = first(args);
var body = rest(args);
var local = envExtend(env);
var names = [];
var valExprs = [];
(isSxTruthy((isSxTruthy((typeOf(first(bindings)) == "list")) && (len(first(bindings)) == 2))) ? forEach(function(binding) { return (function() {
var vname = (isSxTruthy((typeOf(first(binding)) == "symbol")) ? symbolName(first(binding)) : first(binding));
names.push(vname);
valExprs.push(nth(binding, 1));
return envSet(local, vname, NIL);
})(); }, bindings) : reduce(function(acc, pairIdx) { return (function() {
var vname = (isSxTruthy((typeOf(nth(bindings, (pairIdx * 2))) == "symbol")) ? symbolName(nth(bindings, (pairIdx * 2))) : nth(bindings, (pairIdx * 2)));
var valExpr = nth(bindings, ((pairIdx * 2) + 1));
names.push(vname);
valExprs.push(valExpr);
return envSet(local, vname, NIL);
})(); }, NIL, range(0, (len(bindings) / 2))));
(function() {
var values = map(function(e) { return trampoline(evalExpr(e, local)); }, valExprs);
{ var _c = zip(names, values); for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; local[first(pair)] = nth(pair, 1); } }
return forEach(function(val) { return (isSxTruthy(isLambda(val)) ? forEach(function(n) { return envSet(lambdaClosure(val), n, envGet(local, n)); }, names) : NIL); }, values);
})();
{ var _c = slice(body, 0, (len(body) - 1)); for (var _i = 0; _i < _c.length; _i++) { var e = _c[_i]; trampoline(evalExpr(e, local)); } }
return makeThunk(last(body), local);
})(); };
// sf-dynamic-wind
var sfDynamicWind = function(args, env) { return (function() {
var before = trampoline(evalExpr(first(args), env));
var body = trampoline(evalExpr(nth(args, 1), env));
var after = trampoline(evalExpr(nth(args, 2), env));
callThunk(before, env);
pushWind(before, after);
return (function() {
var result = callThunk(body, env);
popWind();
callThunk(after, env);
return result;
})();
})(); };
// expand-macro
var expandMacro = function(mac, rawArgs, env) { return (function() {
var local = envMerge(macroClosure(mac), env);
{ var _c = mapIndexed(function(i, p) { return [p, i]; }, macroParams(mac)); for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; local[first(pair)] = (isSxTruthy((nth(pair, 1) < len(rawArgs))) ? nth(rawArgs, nth(pair, 1)) : NIL); } }
if (isSxTruthy(macroRestParam(mac))) {
local[macroRestParam(mac)] = slice(rawArgs, len(macroParams(mac)));
}
return trampoline(evalExpr(macroBody(mac), local));
})(); };
// call-fn
var callFn = function(f, args, env) { return (isSxTruthy(isLambda(f)) ? trampoline(callLambda(f, args, env)) : (isSxTruthy(isCallable(f)) ? apply(f, args) : error((String("Not callable in HO form: ") + String(inspect(f)))))); };
// ho-map
var hoMap = function(args, env) { return (function() {
var f = trampoline(evalExpr(first(args), env));
var coll = trampoline(evalExpr(nth(args, 1), env));
return map(function(item) { return callFn(f, [item], env); }, coll);
})(); };
// ho-map-indexed
var hoMapIndexed = function(args, env) { return (function() {
var f = trampoline(evalExpr(first(args), env));
var coll = trampoline(evalExpr(nth(args, 1), env));
return mapIndexed(function(i, item) { return callFn(f, [i, item], env); }, coll);
})(); };
// ho-filter
var hoFilter = function(args, env) { return (function() {
var f = trampoline(evalExpr(first(args), env));
var coll = trampoline(evalExpr(nth(args, 1), env));
return filter(function(item) { return callFn(f, [item], env); }, coll);
})(); };
// ho-reduce
var hoReduce = function(args, env) { return (function() {
var f = trampoline(evalExpr(first(args), env));
var init = trampoline(evalExpr(nth(args, 1), env));
var coll = trampoline(evalExpr(nth(args, 2), env));
return reduce(function(acc, item) { return callFn(f, [acc, item], env); }, init, coll);
})(); };
// ho-some
var hoSome = function(args, env) { return (function() {
var f = trampoline(evalExpr(first(args), env));
var coll = trampoline(evalExpr(nth(args, 1), env));
return some(function(item) { return callFn(f, [item], env); }, coll);
})(); };
// ho-every
var hoEvery = function(args, env) { return (function() {
var f = trampoline(evalExpr(first(args), env));
var coll = trampoline(evalExpr(nth(args, 1), env));
return isEvery(function(item) { return callFn(f, [item], env); }, coll);
})(); };
// ho-for-each
var hoForEach = function(args, env) { return (function() {
var f = trampoline(evalExpr(first(args), env));
var coll = trampoline(evalExpr(nth(args, 1), env));
return forEach(function(item) { return callFn(f, [item], env); }, coll);
})(); };
// === Transpiled from render (core) ===
// HTML_TAGS
var HTML_TAGS = ["html", "head", "body", "title", "meta", "link", "script", "style", "noscript", "header", "nav", "main", "section", "article", "aside", "footer", "h1", "h2", "h3", "h4", "h5", "h6", "hgroup", "div", "p", "blockquote", "pre", "figure", "figcaption", "address", "details", "summary", "a", "span", "em", "strong", "small", "b", "i", "u", "s", "mark", "sub", "sup", "abbr", "cite", "code", "time", "br", "wbr", "hr", "ul", "ol", "li", "dl", "dt", "dd", "table", "thead", "tbody", "tfoot", "tr", "th", "td", "caption", "colgroup", "col", "form", "input", "textarea", "select", "option", "optgroup", "button", "label", "fieldset", "legend", "output", "datalist", "img", "video", "audio", "source", "picture", "canvas", "iframe", "svg", "math", "path", "circle", "ellipse", "rect", "line", "polyline", "polygon", "text", "tspan", "g", "defs", "use", "clipPath", "mask", "pattern", "linearGradient", "radialGradient", "stop", "filter", "feGaussianBlur", "feOffset", "feBlend", "feColorMatrix", "feComposite", "feMerge", "feMergeNode", "feTurbulence", "feComponentTransfer", "feFuncR", "feFuncG", "feFuncB", "feFuncA", "feDisplacementMap", "feFlood", "feImage", "feMorphology", "feSpecularLighting", "feDiffuseLighting", "fePointLight", "feSpotLight", "feDistantLight", "animate", "animateTransform", "foreignObject", "template", "slot", "dialog", "menu"];
// VOID_ELEMENTS
var VOID_ELEMENTS = ["area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param", "source", "track", "wbr"];
// BOOLEAN_ATTRS
var BOOLEAN_ATTRS = ["async", "autofocus", "autoplay", "checked", "controls", "default", "defer", "disabled", "formnovalidate", "hidden", "inert", "ismap", "loop", "multiple", "muted", "nomodule", "novalidate", "open", "playsinline", "readonly", "required", "reversed", "selected"];
// definition-form?
var isDefinitionForm = function(name) { return sxOr((name == "define"), (name == "defcomp"), (name == "defisland"), (name == "defmacro"), (name == "defstyle"), (name == "defhandler")); };
// parse-element-args
var parseElementArgs = function(args, env) { return (function() {
var attrs = {};
var children = [];
reduce(function(state, arg) { return (function() {
var skip = get(state, "skip");
return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() {
var val = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env));
attrs[keywordName(arg)] = val;
return assoc(state, "skip", true, "i", (get(state, "i") + 1));
})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1)))));
})(); }, {["i"]: 0, ["skip"]: false}, args);
return [attrs, children];
})(); };
// render-attrs
var renderAttrs = function(attrs) { return join("", map(function(key) { return (function() {
var val = dictGet(attrs, key);
return (isSxTruthy((isSxTruthy(contains(BOOLEAN_ATTRS, key)) && val)) ? (String(" ") + String(key)) : (isSxTruthy((isSxTruthy(contains(BOOLEAN_ATTRS, key)) && !isSxTruthy(val))) ? "" : (isSxTruthy(isNil(val)) ? "" : (String(" ") + String(key) + String("=\"") + String(escapeAttr((String(val)))) + String("\"")))));
})(); }, keys(attrs))); };
// eval-cond
var evalCond = function(clauses, env) { return (isSxTruthy((isSxTruthy(!isSxTruthy(isEmpty(clauses))) && isSxTruthy((typeOf(first(clauses)) == "list")) && (len(first(clauses)) == 2))) ? evalCondScheme(clauses, env) : evalCondClojure(clauses, env)); };
// eval-cond-scheme
var evalCondScheme = function(clauses, env) { return (isSxTruthy(isEmpty(clauses)) ? NIL : (function() {
var clause = first(clauses);
var test = first(clause);
var body = nth(clause, 1);
return (isSxTruthy(sxOr((isSxTruthy((typeOf(test) == "symbol")) && sxOr((symbolName(test) == "else"), (symbolName(test) == ":else"))), (isSxTruthy((typeOf(test) == "keyword")) && (keywordName(test) == "else")))) ? body : (isSxTruthy(trampoline(evalExpr(test, env))) ? body : evalCondScheme(rest(clauses), env)));
})()); };
// eval-cond-clojure
var evalCondClojure = function(clauses, env) { return (isSxTruthy((len(clauses) < 2)) ? NIL : (function() {
var test = first(clauses);
var body = nth(clauses, 1);
return (isSxTruthy(sxOr((isSxTruthy((typeOf(test) == "keyword")) && (keywordName(test) == "else")), (isSxTruthy((typeOf(test) == "symbol")) && sxOr((symbolName(test) == "else"), (symbolName(test) == ":else"))))) ? body : (isSxTruthy(trampoline(evalExpr(test, env))) ? body : evalCondClojure(slice(clauses, 2), env)));
})()); };
// process-bindings
var processBindings = function(bindings, env) { return (function() {
var local = merge(env);
{ var _c = bindings; for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; if (isSxTruthy((isSxTruthy((typeOf(pair) == "list")) && (len(pair) >= 2)))) {
(function() {
var name = (isSxTruthy((typeOf(first(pair)) == "symbol")) ? symbolName(first(pair)) : (String(first(pair))));
return envSet(local, name, trampoline(evalExpr(nth(pair, 1), local)));
})();
} } }
return local;
})(); };
// is-render-expr?
var isRenderExpr = function(expr) { return (isSxTruthy(sxOr(!isSxTruthy((typeOf(expr) == "list")), isEmpty(expr))) ? false : (function() {
var h = first(expr);
return (isSxTruthy(!isSxTruthy((typeOf(h) == "symbol"))) ? false : (function() {
var n = symbolName(h);
return sxOr((n == "<>"), (n == "raw!"), startsWith(n, "~"), startsWith(n, "html:"), contains(HTML_TAGS, n), (isSxTruthy((indexOf_(n, "-") > 0)) && isSxTruthy((len(expr) > 1)) && (typeOf(nth(expr, 1)) == "keyword")));
})());
})()); };
// === Transpiled from parser ===
// sx-parse
var sxParse = function(source) { return (function() {
var pos = 0;
var lenSrc = len(source);
var skipComment = function() { while(true) { if (isSxTruthy((isSxTruthy((pos < lenSrc)) && !isSxTruthy((nth(source, pos) == "\n"))))) { pos = (pos + 1);
continue; } else { return NIL; } } };
var skipWs = function() { while(true) { if (isSxTruthy((pos < lenSrc))) { { var ch = nth(source, pos);
if (isSxTruthy(sxOr((ch == " "), (ch == "\t"), (ch == "\n"), (ch == "\r")))) { pos = (pos + 1);
continue; } else if (isSxTruthy((ch == ";"))) { pos = (pos + 1);
skipComment();
continue; } else { return NIL; } } } else { return NIL; } } };
var readString = function() { pos = (pos + 1);
return (function() {
var buf = "";
var readStrLoop = function() { while(true) { if (isSxTruthy((pos >= lenSrc))) { return error("Unterminated string"); } else { { var ch = nth(source, pos);
if (isSxTruthy((ch == "\""))) { pos = (pos + 1);
return NIL; } else if (isSxTruthy((ch == "\\"))) { pos = (pos + 1);
{ var esc = nth(source, pos);
buf = (String(buf) + String((isSxTruthy((esc == "n")) ? "\n" : (isSxTruthy((esc == "t")) ? "\t" : (isSxTruthy((esc == "r")) ? "\r" : esc)))));
pos = (pos + 1);
continue; } } else { buf = (String(buf) + String(ch));
pos = (pos + 1);
continue; } } } } };
readStrLoop();
return buf;
})(); };
var readIdent = function() { return (function() {
var start = pos;
var readIdentLoop = function() { while(true) { if (isSxTruthy((isSxTruthy((pos < lenSrc)) && isIdentChar(nth(source, pos))))) { pos = (pos + 1);
continue; } else { return NIL; } } };
readIdentLoop();
return slice(source, start, pos);
})(); };
var readKeyword = function() { pos = (pos + 1);
return makeKeyword(readIdent()); };
var readNumber = function() { return (function() {
var start = pos;
if (isSxTruthy((isSxTruthy((pos < lenSrc)) && (nth(source, pos) == "-")))) {
pos = (pos + 1);
}
var readDigits = function() { while(true) { if (isSxTruthy((isSxTruthy((pos < lenSrc)) && (function() {
var c = nth(source, pos);
return (isSxTruthy((c >= "0")) && (c <= "9"));
})()))) { pos = (pos + 1);
continue; } else { return NIL; } } };
readDigits();
if (isSxTruthy((isSxTruthy((pos < lenSrc)) && (nth(source, pos) == ".")))) {
pos = (pos + 1);
readDigits();
}
if (isSxTruthy((isSxTruthy((pos < lenSrc)) && sxOr((nth(source, pos) == "e"), (nth(source, pos) == "E"))))) {
pos = (pos + 1);
if (isSxTruthy((isSxTruthy((pos < lenSrc)) && sxOr((nth(source, pos) == "+"), (nth(source, pos) == "-"))))) {
pos = (pos + 1);
}
readDigits();
}
return parseNumber(slice(source, start, pos));
})(); };
var readSymbol = function() { return (function() {
var name = readIdent();
return (isSxTruthy((name == "true")) ? true : (isSxTruthy((name == "false")) ? false : (isSxTruthy((name == "nil")) ? NIL : makeSymbol(name))));
})(); };
var readList = function(closeCh) { return (function() {
var items = [];
var readListLoop = function() { while(true) { skipWs();
if (isSxTruthy((pos >= lenSrc))) { return error("Unterminated list"); } else { if (isSxTruthy((nth(source, pos) == closeCh))) { pos = (pos + 1);
return NIL; } else { items.push(readExpr());
continue; } } } };
readListLoop();
return items;
})(); };
var readMap = function() { return (function() {
var result = {};
var readMapLoop = function() { while(true) { skipWs();
if (isSxTruthy((pos >= lenSrc))) { return error("Unterminated map"); } else { if (isSxTruthy((nth(source, pos) == "}"))) { pos = (pos + 1);
return NIL; } else { { var keyExpr = readExpr();
var keyStr = (isSxTruthy((typeOf(keyExpr) == "keyword")) ? keywordName(keyExpr) : (String(keyExpr)));
var valExpr = readExpr();
result[keyStr] = valExpr;
continue; } } } } };
readMapLoop();
return result;
})(); };
var readRawString = function() { return (function() {
var buf = "";
var rawLoop = function() { while(true) { if (isSxTruthy((pos >= lenSrc))) { return error("Unterminated raw string"); } else { { var ch = nth(source, pos);
if (isSxTruthy((ch == "|"))) { pos = (pos + 1);
return NIL; } else { buf = (String(buf) + String(ch));
pos = (pos + 1);
continue; } } } } };
rawLoop();
return buf;
})(); };
var readExpr = function() { while(true) { skipWs();
if (isSxTruthy((pos >= lenSrc))) { return error("Unexpected end of input"); } else { { var ch = nth(source, pos);
if (isSxTruthy((ch == "("))) { pos = (pos + 1);
return readList(")"); } else if (isSxTruthy((ch == "["))) { pos = (pos + 1);
return readList("]"); } else if (isSxTruthy((ch == "{"))) { pos = (pos + 1);
return readMap(); } else if (isSxTruthy((ch == "\""))) { return readString(); } else if (isSxTruthy((ch == ":"))) { return readKeyword(); } else if (isSxTruthy((ch == "`"))) { pos = (pos + 1);
return [makeSymbol("quasiquote"), readExpr()]; } else if (isSxTruthy((ch == ","))) { pos = (pos + 1);
if (isSxTruthy((isSxTruthy((pos < lenSrc)) && (nth(source, pos) == "@")))) { pos = (pos + 1);
return [makeSymbol("splice-unquote"), readExpr()]; } else { return [makeSymbol("unquote"), readExpr()]; } } else if (isSxTruthy((ch == "#"))) { pos = (pos + 1);
if (isSxTruthy((pos >= lenSrc))) { return error("Unexpected end of input after #"); } else { { var dispatchCh = nth(source, pos);
if (isSxTruthy((dispatchCh == ";"))) { pos = (pos + 1);
readExpr();
continue; } else if (isSxTruthy((dispatchCh == "|"))) { pos = (pos + 1);
return readRawString(); } else if (isSxTruthy((dispatchCh == "'"))) { pos = (pos + 1);
return [makeSymbol("quote"), readExpr()]; } else if (isSxTruthy(isIdentStart(dispatchCh))) { { var macroName = readIdent();
{ var handler = readerMacroGet(macroName);
if (isSxTruthy(handler)) { return handler(readExpr()); } else { return error((String("Unknown reader macro: #") + String(macroName))); } } } } else { return error((String("Unknown reader macro: #") + String(dispatchCh))); } } } } else if (isSxTruthy(sxOr((isSxTruthy((ch >= "0")) && (ch <= "9")), (isSxTruthy((ch == "-")) && isSxTruthy(((pos + 1) < lenSrc)) && (function() {
var nextCh = nth(source, (pos + 1));
return (isSxTruthy((nextCh >= "0")) && (nextCh <= "9"));
})())))) { return readNumber(); } else if (isSxTruthy((isSxTruthy((ch == ".")) && isSxTruthy(((pos + 2) < lenSrc)) && isSxTruthy((nth(source, (pos + 1)) == ".")) && (nth(source, (pos + 2)) == ".")))) { pos = (pos + 3);
return makeSymbol("..."); } else if (isSxTruthy(isIdentStart(ch))) { return readSymbol(); } else { return error((String("Unexpected character: ") + String(ch))); } } } } };
return (function() {
var exprs = [];
var parseLoop = function() { while(true) { skipWs();
if (isSxTruthy((pos < lenSrc))) { exprs.push(readExpr());
continue; } else { return NIL; } } };
parseLoop();
return exprs;
})();
})(); };
// sx-serialize
var sxSerialize = function(val) { return (function() { var _m = typeOf(val); if (_m == "nil") return "nil"; if (_m == "boolean") return (isSxTruthy(val) ? "true" : "false"); if (_m == "number") return (String(val)); if (_m == "string") return (String("\"") + String(escapeString(val)) + String("\"")); if (_m == "symbol") return symbolName(val); if (_m == "keyword") return (String(":") + String(keywordName(val))); if (_m == "list") return (String("(") + String(join(" ", map(sxSerialize, val))) + String(")")); if (_m == "dict") return sxSerializeDict(val); if (_m == "sx-expr") return sxExprSource(val); return (String(val)); })(); };
// sx-serialize-dict
var sxSerializeDict = function(d) { return (String("{") + String(join(" ", reduce(function(acc, key) { return concat(acc, [(String(":") + String(key)), sxSerialize(dictGet(d, key))]); }, [], keys(d)))) + String("}")); };
// serialize
var serialize = sxSerialize;
// === Transpiled from adapter-html ===
// render-to-html
var renderToHtml = function(expr, env) { return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(expr); if (_m == "number") return (String(expr)); if (_m == "boolean") return (isSxTruthy(expr) ? "true" : "false"); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? "" : renderListToHtml(expr, env)); if (_m == "symbol") return renderValueToHtml(trampoline(evalExpr(expr, env)), env); if (_m == "keyword") return escapeHtml(keywordName(expr)); if (_m == "raw-html") return rawHtmlContent(expr); return renderValueToHtml(trampoline(evalExpr(expr, env)), env); })(); };
// render-value-to-html
var renderValueToHtml = function(val, env) { return (function() { var _m = typeOf(val); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(val); if (_m == "number") return (String(val)); if (_m == "boolean") return (isSxTruthy(val) ? "true" : "false"); if (_m == "list") return renderListToHtml(val, env); if (_m == "raw-html") return rawHtmlContent(val); return escapeHtml((String(val))); })(); };
// RENDER_HTML_FORMS
var RENDER_HTML_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "defhandler", "map", "map-indexed", "filter", "for-each"];
// render-html-form?
var isRenderHtmlForm = function(name) { return contains(RENDER_HTML_FORMS, name); };
// render-list-to-html
var renderListToHtml = function(expr, env) { return (isSxTruthy(isEmpty(expr)) ? "" : (function() {
var head = first(expr);
return (isSxTruthy(!isSxTruthy((typeOf(head) == "symbol"))) ? join("", map(function(x) { return renderValueToHtml(x, env); }, expr)) : (function() {
var name = symbolName(head);
var args = rest(expr);
return (isSxTruthy((name == "<>")) ? join("", map(function(x) { return renderToHtml(x, env); }, args)) : (isSxTruthy((name == "raw!")) ? join("", map(function(x) { return (String(trampoline(evalExpr(x, env)))); }, args)) : (isSxTruthy(contains(HTML_TAGS, name)) ? renderHtmlElement(name, args, env) : (isSxTruthy((isSxTruthy(startsWith(name, "~")) && isSxTruthy(envHas(env, name)) && isIsland(envGet(env, name)))) ? renderHtmlIsland(envGet(env, name), args, env) : (isSxTruthy(startsWith(name, "~")) ? (function() {
var val = envGet(env, name);
return (isSxTruthy(isComponent(val)) ? renderHtmlComponent(val, args, env) : (isSxTruthy(isMacro(val)) ? renderToHtml(expandMacro(val, args, env), env) : error((String("Unknown component: ") + String(name)))));
})() : (isSxTruthy(isRenderHtmlForm(name)) ? dispatchHtmlForm(name, expr, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? renderToHtml(expandMacro(envGet(env, name), args, env), env) : renderValueToHtml(trampoline(evalExpr(expr, env)), env))))))));
})());
})()); };
// dispatch-html-form
var dispatchHtmlForm = function(name, expr, env) { return (isSxTruthy((name == "if")) ? (function() {
var condVal = trampoline(evalExpr(nth(expr, 1), env));
return (isSxTruthy(condVal) ? renderToHtml(nth(expr, 2), env) : (isSxTruthy((len(expr) > 3)) ? renderToHtml(nth(expr, 3), env) : ""));
})() : (isSxTruthy((name == "when")) ? (isSxTruthy(!isSxTruthy(trampoline(evalExpr(nth(expr, 1), env)))) ? "" : join("", map(function(i) { return renderToHtml(nth(expr, i), env); }, range(2, len(expr))))) : (isSxTruthy((name == "cond")) ? (function() {
var branch = evalCond(rest(expr), env);
return (isSxTruthy(branch) ? renderToHtml(branch, env) : "");
})() : (isSxTruthy((name == "case")) ? renderToHtml(trampoline(evalExpr(expr, env)), env) : (isSxTruthy(sxOr((name == "let"), (name == "let*"))) ? (function() {
var local = processBindings(nth(expr, 1), env);
return join("", map(function(i) { return renderToHtml(nth(expr, i), local); }, range(2, len(expr))));
})() : (isSxTruthy(sxOr((name == "begin"), (name == "do"))) ? join("", map(function(i) { return renderToHtml(nth(expr, i), env); }, range(1, len(expr)))) : (isSxTruthy(isDefinitionForm(name)) ? (trampoline(evalExpr(expr, env)), "") : (isSxTruthy((name == "map")) ? (function() {
var f = trampoline(evalExpr(nth(expr, 1), env));
var coll = trampoline(evalExpr(nth(expr, 2), env));
return join("", map(function(item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [item], env) : renderToHtml(apply(f, [item]), env)); }, coll));
})() : (isSxTruthy((name == "map-indexed")) ? (function() {
var f = trampoline(evalExpr(nth(expr, 1), env));
var coll = trampoline(evalExpr(nth(expr, 2), env));
return join("", mapIndexed(function(i, item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [i, item], env) : renderToHtml(apply(f, [i, item]), env)); }, coll));
})() : (isSxTruthy((name == "filter")) ? renderToHtml(trampoline(evalExpr(expr, env)), env) : (isSxTruthy((name == "for-each")) ? (function() {
var f = trampoline(evalExpr(nth(expr, 1), env));
var coll = trampoline(evalExpr(nth(expr, 2), env));
return join("", map(function(item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [item], env) : renderToHtml(apply(f, [item]), env)); }, coll));
})() : renderValueToHtml(trampoline(evalExpr(expr, env)), env)))))))))))); };
// render-lambda-html
var renderLambdaHtml = function(f, args, env) { return (function() {
var local = envMerge(lambdaClosure(f), env);
forEachIndexed(function(i, p) { return envSet(local, p, nth(args, i)); }, lambdaParams(f));
return renderToHtml(lambdaBody(f), local);
})(); };
// render-html-component
var renderHtmlComponent = function(comp, args, env) { return (function() {
var kwargs = {};
var children = [];
reduce(function(state, arg) { return (function() {
var skip = get(state, "skip");
return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() {
var val = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env));
kwargs[keywordName(arg)] = val;
return assoc(state, "skip", true, "i", (get(state, "i") + 1));
})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1)))));
})(); }, {["i"]: 0, ["skip"]: false}, args);
return (function() {
var local = envMerge(componentClosure(comp), env);
{ var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; local[p] = (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL); } }
if (isSxTruthy(componentHasChildren(comp))) {
local["children"] = makeRawHtml(join("", map(function(c) { return renderToHtml(c, env); }, children)));
}
return renderToHtml(componentBody(comp), local);
})();
})(); };
// render-html-element
var renderHtmlElement = function(tag, args, env) { return (function() {
var parsed = parseElementArgs(args, env);
var attrs = first(parsed);
var children = nth(parsed, 1);
var isVoid = contains(VOID_ELEMENTS, tag);
return (String("<") + String(tag) + String(renderAttrs(attrs)) + String((isSxTruthy(isVoid) ? " />" : (String(">") + String(join("", map(function(c) { return renderToHtml(c, env); }, children))) + String("</") + String(tag) + String(">")))));
})(); };
// render-html-island
var renderHtmlIsland = function(island, args, env) { return (function() {
var kwargs = {};
var children = [];
reduce(function(state, arg) { return (function() {
var skip = get(state, "skip");
return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() {
var val = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env));
kwargs[keywordName(arg)] = val;
return assoc(state, "skip", true, "i", (get(state, "i") + 1));
})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1)))));
})(); }, {["i"]: 0, ["skip"]: false}, args);
return (function() {
var local = envMerge(componentClosure(island), env);
var islandName = componentName(island);
{ var _c = componentParams(island); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; local[p] = (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL); } }
if (isSxTruthy(componentHasChildren(island))) {
local["children"] = makeRawHtml(join("", map(function(c) { return renderToHtml(c, env); }, children)));
}
return (function() {
var bodyHtml = renderToHtml(componentBody(island), local);
var stateJson = serializeIslandState(kwargs);
return (String("<div data-sx-island=\"") + String(escapeAttr(islandName)) + String("\"") + String((isSxTruthy(stateJson) ? (String(" data-sx-state=\"") + String(escapeAttr(stateJson)) + String("\"")) : "")) + String(">") + String(bodyHtml) + String("</div>"));
})();
})();
})(); };
// serialize-island-state
var serializeIslandState = function(kwargs) { return (isSxTruthy(isEmptyDict(kwargs)) ? NIL : jsonSerialize(kwargs)); };
// === Transpiled from adapter-sx ===
// render-to-sx
var renderToSx = function(expr, env) { return (function() {
var result = aser(expr, env);
return (isSxTruthy((typeOf(result) == "string")) ? result : serialize(result));
})(); };
// aser
var aser = function(expr, env) { return (function() { var _m = typeOf(expr); if (_m == "number") return expr; if (_m == "string") return expr; if (_m == "boolean") return expr; if (_m == "nil") return NIL; if (_m == "symbol") return (function() {
var name = symbolName(expr);
return (isSxTruthy(envHas(env, name)) ? envGet(env, name) : (isSxTruthy(isPrimitive(name)) ? getPrimitive(name) : (isSxTruthy((name == "true")) ? true : (isSxTruthy((name == "false")) ? false : (isSxTruthy((name == "nil")) ? NIL : error((String("Undefined symbol: ") + String(name))))))));
})(); if (_m == "keyword") return keywordName(expr); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? [] : aserList(expr, env)); return expr; })(); };
// aser-list
var aserList = function(expr, env) { return (function() {
var head = first(expr);
var args = rest(expr);
return (isSxTruthy(!isSxTruthy((typeOf(head) == "symbol"))) ? map(function(x) { return aser(x, env); }, expr) : (function() {
var name = symbolName(head);
return (isSxTruthy((name == "<>")) ? aserFragment(args, env) : (isSxTruthy(startsWith(name, "~")) ? aserCall(name, args, env) : (isSxTruthy(contains(HTML_TAGS, name)) ? aserCall(name, args, env) : (isSxTruthy(sxOr(isSpecialForm(name), isHoForm(name))) ? aserSpecial(name, expr, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? aser(expandMacro(envGet(env, name), args, env), env) : (function() {
var f = trampoline(evalExpr(head, env));
var evaledArgs = map(function(a) { return trampoline(evalExpr(a, env)); }, args);
return (isSxTruthy((isSxTruthy(isCallable(f)) && isSxTruthy(!isSxTruthy(isLambda(f))) && isSxTruthy(!isSxTruthy(isComponent(f))) && !isSxTruthy(isIsland(f)))) ? apply(f, evaledArgs) : (isSxTruthy(isLambda(f)) ? trampoline(callLambda(f, evaledArgs, env)) : (isSxTruthy(isComponent(f)) ? aserCall((String("~") + String(componentName(f))), args, env) : (isSxTruthy(isIsland(f)) ? aserCall((String("~") + String(componentName(f))), args, env) : error((String("Not callable: ") + String(inspect(f))))))));
})())))));
})());
})(); };
// aser-fragment
var aserFragment = function(children, env) { return (function() {
var parts = filter(function(x) { return !isSxTruthy(isNil(x)); }, map(function(c) { return aser(c, env); }, children));
return (isSxTruthy(isEmpty(parts)) ? "" : (String("(<> ") + String(join(" ", map(serialize, parts))) + String(")")));
})(); };
// aser-call
var aserCall = function(name, args, env) { return (function() {
var parts = [name];
reduce(function(state, arg) { return (function() {
var skip = get(state, "skip");
return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() {
var val = aser(nth(args, (get(state, "i") + 1)), env);
if (isSxTruthy(!isSxTruthy(isNil(val)))) {
parts.push((String(":") + String(keywordName(arg))));
parts.push(serialize(val));
}
return assoc(state, "skip", true, "i", (get(state, "i") + 1));
})() : (function() {
var val = aser(arg, env);
if (isSxTruthy(!isSxTruthy(isNil(val)))) {
parts.push(serialize(val));
}
return assoc(state, "i", (get(state, "i") + 1));
})()));
})(); }, {["i"]: 0, ["skip"]: false}, args);
return (String("(") + String(join(" ", parts)) + String(")"));
})(); };
// SPECIAL_FORM_NAMES
var SPECIAL_FORM_NAMES = ["if", "when", "cond", "case", "and", "or", "let", "let*", "lambda", "fn", "define", "defcomp", "defmacro", "defstyle", "defhandler", "defpage", "defquery", "defaction", "defrelation", "begin", "do", "quote", "quasiquote", "->", "set!", "letrec", "dynamic-wind", "defisland"];
// HO_FORM_NAMES
var HO_FORM_NAMES = ["map", "map-indexed", "filter", "reduce", "some", "every?", "for-each"];
// special-form?
var isSpecialForm = function(name) { return contains(SPECIAL_FORM_NAMES, name); };
// ho-form?
var isHoForm = function(name) { return contains(HO_FORM_NAMES, name); };
// aser-special
var aserSpecial = function(name, expr, env) { return (function() {
var args = rest(expr);
return (isSxTruthy((name == "if")) ? (isSxTruthy(trampoline(evalExpr(first(args), env))) ? aser(nth(args, 1), env) : (isSxTruthy((len(args) > 2)) ? aser(nth(args, 2), env) : NIL)) : (isSxTruthy((name == "when")) ? (isSxTruthy(!isSxTruthy(trampoline(evalExpr(first(args), env)))) ? NIL : (function() {
var result = NIL;
{ var _c = rest(args); for (var _i = 0; _i < _c.length; _i++) { var body = _c[_i]; result = aser(body, env); } }
return result;
})()) : (isSxTruthy((name == "cond")) ? (function() {
var branch = evalCond(args, env);
return (isSxTruthy(branch) ? aser(branch, env) : NIL);
})() : (isSxTruthy((name == "case")) ? (function() {
var matchVal = trampoline(evalExpr(first(args), env));
var clauses = rest(args);
return evalCaseAser(matchVal, clauses, env);
})() : (isSxTruthy(sxOr((name == "let"), (name == "let*"))) ? (function() {
var local = processBindings(first(args), env);
var result = NIL;
{ var _c = rest(args); for (var _i = 0; _i < _c.length; _i++) { var body = _c[_i]; result = aser(body, local); } }
return result;
})() : (isSxTruthy(sxOr((name == "begin"), (name == "do"))) ? (function() {
var result = NIL;
{ var _c = args; for (var _i = 0; _i < _c.length; _i++) { var body = _c[_i]; result = aser(body, env); } }
return result;
})() : (isSxTruthy((name == "and")) ? (function() {
var result = true;
some(function(arg) { result = trampoline(evalExpr(arg, env));
return !isSxTruthy(result); }, args);
return result;
})() : (isSxTruthy((name == "or")) ? (function() {
var result = false;
some(function(arg) { result = trampoline(evalExpr(arg, env));
return result; }, args);
return result;
})() : (isSxTruthy((name == "map")) ? (function() {
var f = trampoline(evalExpr(first(args), env));
var coll = trampoline(evalExpr(nth(args, 1), env));
return map(function(item) { return (isSxTruthy(isLambda(f)) ? (function() {
var local = envMerge(lambdaClosure(f), env);
local[first(lambdaParams(f))] = item;
return aser(lambdaBody(f), local);
})() : invoke(f, item)); }, coll);
})() : (isSxTruthy((name == "map-indexed")) ? (function() {
var f = trampoline(evalExpr(first(args), env));
var coll = trampoline(evalExpr(nth(args, 1), env));
return mapIndexed(function(i, item) { return (isSxTruthy(isLambda(f)) ? (function() {
var local = envMerge(lambdaClosure(f), env);
local[first(lambdaParams(f))] = i;
local[nth(lambdaParams(f), 1)] = item;
return aser(lambdaBody(f), local);
})() : invoke(f, i, item)); }, coll);
})() : (isSxTruthy((name == "for-each")) ? (function() {
var f = trampoline(evalExpr(first(args), env));
var coll = trampoline(evalExpr(nth(args, 1), env));
var results = [];
{ var _c = coll; for (var _i = 0; _i < _c.length; _i++) { var item = _c[_i]; (isSxTruthy(isLambda(f)) ? (function() {
var local = envMerge(lambdaClosure(f), env);
local[first(lambdaParams(f))] = item;
return append_b(results, aser(lambdaBody(f), local));
})() : invoke(f, item)); } }
return (isSxTruthy(isEmpty(results)) ? NIL : results);
})() : (isSxTruthy((name == "defisland")) ? (trampoline(evalExpr(expr, env)), serialize(expr)) : (isSxTruthy(sxOr((name == "define"), (name == "defcomp"), (name == "defmacro"), (name == "defstyle"), (name == "defhandler"), (name == "defpage"), (name == "defquery"), (name == "defaction"), (name == "defrelation"))) ? (trampoline(evalExpr(expr, env)), NIL) : trampoline(evalExpr(expr, env)))))))))))))));
})(); };
// eval-case-aser
var evalCaseAser = function(matchVal, clauses, env) { return (isSxTruthy((len(clauses) < 2)) ? NIL : (function() {
var test = first(clauses);
var body = nth(clauses, 1);
return (isSxTruthy(sxOr((isSxTruthy((typeOf(test) == "keyword")) && (keywordName(test) == "else")), (isSxTruthy((typeOf(test) == "symbol")) && sxOr((symbolName(test) == ":else"), (symbolName(test) == "else"))))) ? aser(body, env) : (isSxTruthy((matchVal == trampoline(evalExpr(test, env)))) ? aser(body, env) : evalCaseAser(matchVal, slice(clauses, 2), env)));
})()); };
// === Transpiled from adapter-dom ===
// SVG_NS
var SVG_NS = "http://www.w3.org/2000/svg";
// MATH_NS
var MATH_NS = "http://www.w3.org/1998/Math/MathML";
// render-to-dom
var renderToDom = function(expr, env, ns) { return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragment(); if (_m == "boolean") return createFragment(); if (_m == "raw-html") return domParseHtml(rawHtmlContent(expr)); if (_m == "string") return createTextNode(expr); if (_m == "number") return createTextNode((String(expr))); if (_m == "symbol") return renderToDom(trampoline(evalExpr(expr, env)), env, ns); if (_m == "keyword") return createTextNode(keywordName(expr)); if (_m == "dom-node") return expr; if (_m == "dict") return createFragment(); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? createFragment() : renderDomList(expr, env, ns)); return createTextNode((String(expr))); })(); };
// render-dom-list
var renderDomList = function(expr, env, ns) { return (function() {
var head = first(expr);
return (isSxTruthy((typeOf(head) == "symbol")) ? (function() {
var name = symbolName(head);
var args = rest(expr);
return (isSxTruthy((name == "raw!")) ? renderDomRaw(args, env) : (isSxTruthy((name == "<>")) ? renderDomFragment(args, env, ns) : (isSxTruthy(startsWith(name, "html:")) ? renderDomElement(slice(name, 5), args, env, ns) : (isSxTruthy(isRenderDomForm(name)) ? (isSxTruthy((isSxTruthy(contains(HTML_TAGS, name)) && sxOr((isSxTruthy((len(args) > 0)) && (typeOf(first(args)) == "keyword")), ns))) ? renderDomElement(name, args, env, ns) : dispatchRenderForm(name, expr, env, ns)) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? renderToDom(expandMacro(envGet(env, name), args, env), env, ns) : (isSxTruthy(contains(HTML_TAGS, name)) ? renderDomElement(name, args, env, ns) : (isSxTruthy((isSxTruthy(startsWith(name, "~")) && isSxTruthy(envHas(env, name)) && isIsland(envGet(env, name)))) ? renderDomIsland(envGet(env, name), args, env, ns) : (isSxTruthy(startsWith(name, "~")) ? (function() {
var comp = envGet(env, name);
return (isSxTruthy(isComponent(comp)) ? renderDomComponent(comp, args, env, ns) : renderDomUnknownComponent(name));
})() : (isSxTruthy((isSxTruthy((indexOf_(name, "-") > 0)) && isSxTruthy((len(args) > 0)) && (typeOf(first(args)) == "keyword"))) ? renderDomElement(name, args, env, ns) : (isSxTruthy(ns) ? renderDomElement(name, args, env, ns) : (isSxTruthy((isSxTruthy((name == "deref")) && _islandScope)) ? (function() {
var sigOrVal = trampoline(evalExpr(first(args), env));
return (isSxTruthy(isSignal(sigOrVal)) ? reactiveText(sigOrVal) : createTextNode((String(deref(sigOrVal)))));
})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns))))))))))));
})() : (isSxTruthy(sxOr(isLambda(head), (typeOf(head) == "list"))) ? renderToDom(trampoline(evalExpr(expr, env)), env, ns) : (function() {
var frag = createFragment();
{ var _c = expr; for (var _i = 0; _i < _c.length; _i++) { var x = _c[_i]; domAppend(frag, renderToDom(x, env, ns)); } }
return frag;
})()));
})(); };
// render-dom-element
var renderDomElement = function(tag, args, env, ns) { return (function() {
var newNs = (isSxTruthy((tag == "svg")) ? SVG_NS : (isSxTruthy((tag == "math")) ? MATH_NS : ns));
var el = domCreateElement(tag, newNs);
reduce(function(state, arg) { return (function() {
var skip = get(state, "skip");
return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() {
var attrName = keywordName(arg);
var attrExpr = nth(args, (get(state, "i") + 1));
(isSxTruthy(startsWith(attrName, "on-")) ? (function() {
var attrVal = trampoline(evalExpr(attrExpr, env));
return (isSxTruthy(isCallable(attrVal)) ? domListen(el, slice(attrName, 3), attrVal) : NIL);
})() : (isSxTruthy((attrName == "bind")) ? (function() {
var attrVal = trampoline(evalExpr(attrExpr, env));
return (isSxTruthy(isSignal(attrVal)) ? bindInput(el, attrVal) : NIL);
})() : (isSxTruthy((attrName == "ref")) ? (function() {
var attrVal = trampoline(evalExpr(attrExpr, env));
return dictSet(attrVal, "current", el);
})() : (isSxTruthy((attrName == "key")) ? (function() {
var attrVal = trampoline(evalExpr(attrExpr, env));
return domSetAttr(el, "key", (String(attrVal)));
})() : (isSxTruthy(_islandScope) ? reactiveAttr(el, attrName, function() { return trampoline(evalExpr(attrExpr, env)); }) : (function() {
var attrVal = trampoline(evalExpr(attrExpr, env));
return (isSxTruthy(sxOr(isNil(attrVal), (attrVal == false))) ? NIL : (isSxTruthy(contains(BOOLEAN_ATTRS, attrName)) ? (isSxTruthy(attrVal) ? domSetAttr(el, attrName, "") : NIL) : (isSxTruthy((attrVal == true)) ? domSetAttr(el, attrName, "") : domSetAttr(el, attrName, (String(attrVal))))));
})())))));
return assoc(state, "skip", true, "i", (get(state, "i") + 1));
})() : ((isSxTruthy(!isSxTruthy(contains(VOID_ELEMENTS, tag))) ? domAppend(el, renderToDom(arg, env, newNs)) : NIL), assoc(state, "i", (get(state, "i") + 1)))));
})(); }, {["i"]: 0, ["skip"]: false}, args);
return el;
})(); };
// render-dom-component
var renderDomComponent = function(comp, args, env, ns) { return (function() {
var kwargs = {};
var children = [];
reduce(function(state, arg) { return (function() {
var skip = get(state, "skip");
return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() {
var val = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env));
kwargs[keywordName(arg)] = val;
return assoc(state, "skip", true, "i", (get(state, "i") + 1));
})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1)))));
})(); }, {["i"]: 0, ["skip"]: false}, args);
return (function() {
var local = envMerge(componentClosure(comp), env);
{ var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; local[p] = (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL); } }
if (isSxTruthy(componentHasChildren(comp))) {
(function() {
var childFrag = createFragment();
{ var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; domAppend(childFrag, renderToDom(c, env, ns)); } }
return envSet(local, "children", childFrag);
})();
}
return renderToDom(componentBody(comp), local, ns);
})();
})(); };
// render-dom-fragment
var renderDomFragment = function(args, env, ns) { return (function() {
var frag = createFragment();
{ var _c = args; for (var _i = 0; _i < _c.length; _i++) { var x = _c[_i]; domAppend(frag, renderToDom(x, env, ns)); } }
return frag;
})(); };
// render-dom-raw
var renderDomRaw = function(args, env) { return (function() {
var frag = createFragment();
{ var _c = args; for (var _i = 0; _i < _c.length; _i++) { var arg = _c[_i]; (function() {
var val = trampoline(evalExpr(arg, env));
return (isSxTruthy((typeOf(val) == "string")) ? domAppend(frag, domParseHtml(val)) : (isSxTruthy((typeOf(val) == "dom-node")) ? domAppend(frag, domClone(val)) : (isSxTruthy(!isSxTruthy(isNil(val))) ? domAppend(frag, createTextNode((String(val)))) : NIL)));
})(); } }
return frag;
})(); };
// render-dom-unknown-component
var renderDomUnknownComponent = function(name) { return error((String("Unknown component: ") + String(name))); };
// RENDER_DOM_FORMS
var RENDER_DOM_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "defhandler", "map", "map-indexed", "filter", "for-each", "portal", "error-boundary"];
// render-dom-form?
var isRenderDomForm = function(name) { return contains(RENDER_DOM_FORMS, name); };
// dispatch-render-form
var dispatchRenderForm = function(name, expr, env, ns) { return (isSxTruthy((name == "if")) ? (isSxTruthy(_islandScope) ? (function() {
var marker = createComment("r-if");
var currentNodes = [];
var initialResult = NIL;
effect(function() { return (function() {
var result = (function() {
var condVal = trampoline(evalExpr(nth(expr, 1), env));
return (isSxTruthy(condVal) ? renderToDom(nth(expr, 2), env, ns) : (isSxTruthy((len(expr) > 3)) ? renderToDom(nth(expr, 3), env, ns) : createFragment()));
})();
return (isSxTruthy(domParent(marker)) ? (forEach(function(n) { return domRemove(n); }, currentNodes), (currentNodes = (isSxTruthy(domIsFragment(result)) ? domChildNodes(result) : [result])), domInsertAfter(marker, result)) : (initialResult = result));
})(); });
return (function() {
var frag = createFragment();
domAppend(frag, marker);
if (isSxTruthy(initialResult)) {
currentNodes = (isSxTruthy(domIsFragment(initialResult)) ? domChildNodes(initialResult) : [initialResult]);
domAppend(frag, initialResult);
}
return frag;
})();
})() : (function() {
var condVal = trampoline(evalExpr(nth(expr, 1), env));
return (isSxTruthy(condVal) ? renderToDom(nth(expr, 2), env, ns) : (isSxTruthy((len(expr) > 3)) ? renderToDom(nth(expr, 3), env, ns) : createFragment()));
})()) : (isSxTruthy((name == "when")) ? (isSxTruthy(_islandScope) ? (function() {
var marker = createComment("r-when");
var currentNodes = [];
var initialResult = NIL;
effect(function() { return (isSxTruthy(domParent(marker)) ? (forEach(function(n) { return domRemove(n); }, currentNodes), (currentNodes = []), (isSxTruthy(trampoline(evalExpr(nth(expr, 1), env))) ? (function() {
var frag = createFragment();
{ var _c = range(2, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; domAppend(frag, renderToDom(nth(expr, i), env, ns)); } }
currentNodes = domChildNodes(frag);
return domInsertAfter(marker, frag);
})() : NIL)) : (isSxTruthy(trampoline(evalExpr(nth(expr, 1), env))) ? (function() {
var frag = createFragment();
{ var _c = range(2, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; domAppend(frag, renderToDom(nth(expr, i), env, ns)); } }
currentNodes = domChildNodes(frag);
return (initialResult = frag);
})() : NIL)); });
return (function() {
var frag = createFragment();
domAppend(frag, marker);
if (isSxTruthy(initialResult)) {
domAppend(frag, initialResult);
}
return frag;
})();
})() : (isSxTruthy(!isSxTruthy(trampoline(evalExpr(nth(expr, 1), env)))) ? createFragment() : (function() {
var frag = createFragment();
{ var _c = range(2, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; domAppend(frag, renderToDom(nth(expr, i), env, ns)); } }
return frag;
})())) : (isSxTruthy((name == "cond")) ? (isSxTruthy(_islandScope) ? (function() {
var marker = createComment("r-cond");
var currentNodes = [];
var initialResult = NIL;
effect(function() { return (function() {
var branch = evalCond(rest(expr), env);
return (isSxTruthy(domParent(marker)) ? (forEach(function(n) { return domRemove(n); }, currentNodes), (currentNodes = []), (isSxTruthy(branch) ? (function() {
var result = renderToDom(branch, env, ns);
currentNodes = (isSxTruthy(domIsFragment(result)) ? domChildNodes(result) : [result]);
return domInsertAfter(marker, result);
})() : NIL)) : (isSxTruthy(branch) ? (function() {
var result = renderToDom(branch, env, ns);
currentNodes = (isSxTruthy(domIsFragment(result)) ? domChildNodes(result) : [result]);
return (initialResult = result);
})() : NIL));
})(); });
return (function() {
var frag = createFragment();
domAppend(frag, marker);
if (isSxTruthy(initialResult)) {
domAppend(frag, initialResult);
}
return frag;
})();
})() : (function() {
var branch = evalCond(rest(expr), env);
return (isSxTruthy(branch) ? renderToDom(branch, env, ns) : createFragment());
})()) : (isSxTruthy((name == "case")) ? renderToDom(trampoline(evalExpr(expr, env)), env, ns) : (isSxTruthy(sxOr((name == "let"), (name == "let*"))) ? (function() {
var local = processBindings(nth(expr, 1), env);
var frag = createFragment();
{ var _c = range(2, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; domAppend(frag, renderToDom(nth(expr, i), local, ns)); } }
return frag;
})() : (isSxTruthy(sxOr((name == "begin"), (name == "do"))) ? (function() {
var frag = createFragment();
{ var _c = range(1, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; domAppend(frag, renderToDom(nth(expr, i), env, ns)); } }
return frag;
})() : (isSxTruthy(isDefinitionForm(name)) ? (trampoline(evalExpr(expr, env)), createFragment()) : (isSxTruthy((name == "map")) ? (function() {
var collExpr = nth(expr, 2);
return (isSxTruthy((isSxTruthy(_islandScope) && isSxTruthy((typeOf(collExpr) == "list")) && isSxTruthy((len(collExpr) > 1)) && (first(collExpr) == "deref"))) ? (function() {
var f = trampoline(evalExpr(nth(expr, 1), env));
var sig = trampoline(evalExpr(nth(collExpr, 1), env));
return (isSxTruthy(isSignal(sig)) ? reactiveList(f, sig, env, ns) : (function() {
var coll = deref(sig);
var frag = createFragment();
{ var _c = coll; for (var _i = 0; _i < _c.length; _i++) { var item = _c[_i]; (function() {
var val = (isSxTruthy(isLambda(f)) ? renderLambdaDom(f, [item], env, ns) : renderToDom(apply(f, [item]), env, ns));
return domAppend(frag, val);
})(); } }
return frag;
})());
})() : (function() {
var f = trampoline(evalExpr(nth(expr, 1), env));
var coll = trampoline(evalExpr(nth(expr, 2), env));
var frag = createFragment();
{ var _c = coll; for (var _i = 0; _i < _c.length; _i++) { var item = _c[_i]; (function() {
var val = (isSxTruthy(isLambda(f)) ? renderLambdaDom(f, [item], env, ns) : renderToDom(apply(f, [item]), env, ns));
return domAppend(frag, val);
})(); } }
return frag;
})());
})() : (isSxTruthy((name == "map-indexed")) ? (function() {
var f = trampoline(evalExpr(nth(expr, 1), env));
var coll = trampoline(evalExpr(nth(expr, 2), env));
var frag = createFragment();
forEachIndexed(function(i, item) { return (function() {
var val = (isSxTruthy(isLambda(f)) ? renderLambdaDom(f, [i, item], env, ns) : renderToDom(apply(f, [i, item]), env, ns));
return domAppend(frag, val);
})(); }, coll);
return frag;
})() : (isSxTruthy((name == "filter")) ? renderToDom(trampoline(evalExpr(expr, env)), env, ns) : (isSxTruthy((name == "portal")) ? renderDomPortal(rest(expr), env, ns) : (isSxTruthy((name == "error-boundary")) ? renderDomErrorBoundary(rest(expr), env, ns) : (isSxTruthy((name == "for-each")) ? (function() {
var f = trampoline(evalExpr(nth(expr, 1), env));
var coll = trampoline(evalExpr(nth(expr, 2), env));
var frag = createFragment();
{ var _c = coll; for (var _i = 0; _i < _c.length; _i++) { var item = _c[_i]; (function() {
var val = (isSxTruthy(isLambda(f)) ? renderLambdaDom(f, [item], env, ns) : renderToDom(apply(f, [item]), env, ns));
return domAppend(frag, val);
})(); } }
return frag;
})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns)))))))))))))); };
// render-lambda-dom
var renderLambdaDom = function(f, args, env, ns) { return (function() {
var local = envMerge(lambdaClosure(f), env);
forEachIndexed(function(i, p) { return envSet(local, p, nth(args, i)); }, lambdaParams(f));
return renderToDom(lambdaBody(f), local, ns);
})(); };
// render-dom-island
var renderDomIsland = function(island, args, env, ns) { return (function() {
var kwargs = {};
var children = [];
reduce(function(state, arg) { return (function() {
var skip = get(state, "skip");
return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() {
var val = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env));
kwargs[keywordName(arg)] = val;
return assoc(state, "skip", true, "i", (get(state, "i") + 1));
})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1)))));
})(); }, {["i"]: 0, ["skip"]: false}, args);
return (function() {
var local = envMerge(componentClosure(island), env);
var islandName = componentName(island);
{ var _c = componentParams(island); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; local[p] = (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL); } }
if (isSxTruthy(componentHasChildren(island))) {
(function() {
var childFrag = createFragment();
{ var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; domAppend(childFrag, renderToDom(c, env, ns)); } }
return envSet(local, "children", childFrag);
})();
}
return (function() {
var container = domCreateElement("div", NIL);
var disposers = [];
domSetAttr(container, "data-sx-island", islandName);
return (function() {
var bodyDom = withIslandScope(function(disposable) { return append_b(disposers, disposable); }, function() { return renderToDom(componentBody(island), local, ns); });
domAppend(container, bodyDom);
domSetData(container, "sx-disposers", disposers);
return container;
})();
})();
})();
})(); };
// reactive-text
var reactiveText = function(sig) { return (function() {
var node = createTextNode((String(deref(sig))));
effect(function() { return domSetTextContent(node, (String(deref(sig)))); });
return node;
})(); };
// reactive-attr
var reactiveAttr = function(el, attrName, computeFn) { return effect(function() { return (function() {
var val = computeFn();
return (isSxTruthy(sxOr(isNil(val), (val == false))) ? domRemoveAttr(el, attrName) : (isSxTruthy((val == true)) ? domSetAttr(el, attrName, "") : domSetAttr(el, attrName, (String(val)))));
})(); }); };
// reactive-fragment
var reactiveFragment = function(testFn, renderFn, env, ns) { return (function() {
var marker = createComment("island-fragment");
var currentNodes = [];
effect(function() { { var _c = currentNodes; for (var _i = 0; _i < _c.length; _i++) { var n = _c[_i]; domRemove(n); } }
currentNodes = [];
return (isSxTruthy(testFn()) ? (function() {
var frag = renderFn();
currentNodes = domChildNodes(frag);
return domInsertAfter(marker, frag);
})() : NIL); });
return marker;
})(); };
// render-list-item
var renderListItem = function(mapFn, item, env, ns) { return (isSxTruthy(isLambda(mapFn)) ? renderLambdaDom(mapFn, [item], env, ns) : renderToDom(apply(mapFn, [item]), env, ns)); };
// extract-key
var extractKey = function(node, index) { return (function() {
var k = domGetAttr(node, "key");
return (isSxTruthy(k) ? (domRemoveAttr(node, "key"), k) : (function() {
var dk = domGetData(node, "key");
return (isSxTruthy(dk) ? (String(dk)) : (String("__idx_") + String(index)));
})());
})(); };
// reactive-list
var reactiveList = function(mapFn, itemsSig, env, ns) { return (function() {
var container = createFragment();
var marker = createComment("island-list");
var keyMap = {};
var keyOrder = [];
domAppend(container, marker);
effect(function() { return (function() {
var items = deref(itemsSig);
return (isSxTruthy(domParent(marker)) ? (function() {
var newMap = {};
var newKeys = [];
var hasKeys = false;
forEachIndexed(function(idx, item) { return (function() {
var rendered = renderListItem(mapFn, item, env, ns);
var key = extractKey(rendered, idx);
if (isSxTruthy((isSxTruthy(!isSxTruthy(hasKeys)) && !isSxTruthy(startsWith(key, "__idx_"))))) {
hasKeys = true;
}
(isSxTruthy(dictHas(keyMap, key)) ? dictSet(newMap, key, dictGet(keyMap, key)) : dictSet(newMap, key, rendered));
return append_b(newKeys, key);
})(); }, items);
(isSxTruthy(!isSxTruthy(hasKeys)) ? (domRemoveChildrenAfter(marker), (function() {
var frag = createFragment();
{ var _c = newKeys; for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; domAppend(frag, dictGet(newMap, k)); } }
return domInsertAfter(marker, frag);
})()) : (forEach(function(oldKey) { return (isSxTruthy(!isSxTruthy(dictHas(newMap, oldKey))) ? domRemove(dictGet(keyMap, oldKey)) : NIL); }, keyOrder), (function() {
var cursor = marker;
return forEach(function(k) { return (function() {
var node = dictGet(newMap, k);
var next = domNextSibling(cursor);
if (isSxTruthy(!isSxTruthy(isIdentical(node, next)))) {
domInsertAfter(cursor, node);
}
return (cursor = node);
})(); }, newKeys);
})()));
keyMap = newMap;
return (keyOrder = newKeys);
})() : forEachIndexed(function(idx, item) { return (function() {
var rendered = renderListItem(mapFn, item, env, ns);
var key = extractKey(rendered, idx);
keyMap[key] = rendered;
keyOrder.push(key);
return domAppend(container, rendered);
})(); }, items));
})(); });
return container;
})(); };
// bind-input
var bindInput = function(el, sig) { return (function() {
var inputType = lower(sxOr(domGetAttr(el, "type"), ""));
var isCheckbox = sxOr((inputType == "checkbox"), (inputType == "radio"));
(isSxTruthy(isCheckbox) ? domSetProp(el, "checked", deref(sig)) : domSetProp(el, "value", (String(deref(sig)))));
effect(function() { return (isSxTruthy(isCheckbox) ? domSetProp(el, "checked", deref(sig)) : (function() {
var v = (String(deref(sig)));
return (isSxTruthy((domGetProp(el, "value") != v)) ? domSetProp(el, "value", v) : NIL);
})()); });
return domListen(el, (isSxTruthy(isCheckbox) ? "change" : "input"), function(e) { return (isSxTruthy(isCheckbox) ? reset_b(sig, domGetProp(el, "checked")) : reset_b(sig, domGetProp(el, "value"))); });
})(); };
// render-dom-portal
var renderDomPortal = function(args, env, ns) { return (function() {
var selector = trampoline(evalExpr(first(args), env));
var target = sxOr(domQuery(selector), domEnsureElement(selector));
return (isSxTruthy(!isSxTruthy(target)) ? createComment((String("portal: ") + String(selector) + String(" (not found)"))) : (function() {
var marker = createComment((String("portal: ") + String(selector)));
var frag = createFragment();
{ var _c = rest(args); for (var _i = 0; _i < _c.length; _i++) { var child = _c[_i]; domAppend(frag, renderToDom(child, env, ns)); } }
(function() {
var portalNodes = domChildNodes(frag);
domAppend(target, frag);
return registerInScope(function() { return forEach(function(n) { return domRemove(n); }, portalNodes); });
})();
return marker;
})());
})(); };
// render-dom-error-boundary
var renderDomErrorBoundary = function(args, env, ns) { return (function() {
var fallbackExpr = first(args);
var bodyExprs = rest(args);
var container = domCreateElement("div", NIL);
var retryVersion = signal(0);
domSetAttr(container, "data-sx-boundary", "true");
effect(function() { deref(retryVersion);
domSetProp(container, "innerHTML", "");
return (function() {
var savedScope = _islandScope;
_islandScope = NIL;
return tryCatch(function() { (function() {
var frag = createFragment();
{ var _c = bodyExprs; for (var _i = 0; _i < _c.length; _i++) { var child = _c[_i]; domAppend(frag, renderToDom(child, env, ns)); } }
return domAppend(container, frag);
})();
return (_islandScope = savedScope); }, function(err) { _islandScope = savedScope;
return (function() {
var fallbackFn = trampoline(evalExpr(fallbackExpr, env));
var retryFn = function() { return swap_b(retryVersion, function(n) { return (n + 1); }); };
return (function() {
var fallbackDom = (isSxTruthy(isLambda(fallbackFn)) ? renderLambdaDom(fallbackFn, [err, retryFn], env, ns) : renderToDom(apply(fallbackFn, [err, retryFn]), env, ns));
return domAppend(container, fallbackDom);
})();
})(); });
})(); });
return container;
})(); };
// === Transpiled from engine ===
// ENGINE_VERBS
var ENGINE_VERBS = ["get", "post", "put", "delete", "patch"];
// DEFAULT_SWAP
var DEFAULT_SWAP = "outerHTML";
// parse-time
var parseTime = function(s) { return (isSxTruthy(isNil(s)) ? 0 : (isSxTruthy(endsWith(s, "ms")) ? parseInt_(s, 0) : (isSxTruthy(endsWith(s, "s")) ? (parseInt_(replace_(s, "s", ""), 0) * 1000) : parseInt_(s, 0)))); };
// parse-trigger-spec
var parseTriggerSpec = function(spec) { return (isSxTruthy(isNil(spec)) ? NIL : (function() {
var rawParts = split(spec, ",");
return filter(function(x) { return !isSxTruthy(isNil(x)); }, map(function(part) { return (function() {
var tokens = split(trim(part), " ");
return (isSxTruthy(isEmpty(tokens)) ? NIL : (isSxTruthy((isSxTruthy((first(tokens) == "every")) && (len(tokens) >= 2))) ? {["event"]: "every", ["modifiers"]: {["interval"]: parseTime(nth(tokens, 1))}} : (function() {
var mods = {};
{ var _c = rest(tokens); for (var _i = 0; _i < _c.length; _i++) { var tok = _c[_i]; (isSxTruthy((tok == "once")) ? dictSet(mods, "once", true) : (isSxTruthy((tok == "changed")) ? dictSet(mods, "changed", true) : (isSxTruthy(startsWith(tok, "delay:")) ? dictSet(mods, "delay", parseTime(slice(tok, 6))) : (isSxTruthy(startsWith(tok, "from:")) ? dictSet(mods, "from", slice(tok, 5)) : NIL)))); } }
return {["event"]: first(tokens), ["modifiers"]: mods};
})()));
})(); }, rawParts));
})()); };
// default-trigger
var defaultTrigger = function(tagName) { return (isSxTruthy((tagName == "FORM")) ? [{["event"]: "submit", ["modifiers"]: {}}] : (isSxTruthy(sxOr((tagName == "INPUT"), (tagName == "SELECT"), (tagName == "TEXTAREA"))) ? [{["event"]: "change", ["modifiers"]: {}}] : [{["event"]: "click", ["modifiers"]: {}}])); };
// get-verb-info
var getVerbInfo = function(el) { return some(function(verb) { return (function() {
var url = domGetAttr(el, (String("sx-") + String(verb)));
return (isSxTruthy(url) ? {["method"]: upper(verb), ["url"]: url} : NIL);
})(); }, ENGINE_VERBS); };
// build-request-headers
var buildRequestHeaders = function(el, loadedComponents, cssHash) { return (function() {
var headers = {["SX-Request"]: "true", ["SX-Current-URL"]: browserLocationHref()};
(function() {
var targetSel = domGetAttr(el, "sx-target");
return (isSxTruthy(targetSel) ? dictSet(headers, "SX-Target", targetSel) : NIL);
})();
if (isSxTruthy(!isSxTruthy(isEmpty(loadedComponents)))) {
headers["SX-Components"] = join(",", loadedComponents);
}
if (isSxTruthy(cssHash)) {
headers["SX-Css"] = cssHash;
}
(function() {
var extraH = domGetAttr(el, "sx-headers");
return (isSxTruthy(extraH) ? (function() {
var parsed = parseHeaderValue(extraH);
return (isSxTruthy(parsed) ? forEach(function(key) { return dictSet(headers, key, (String(get(parsed, key)))); }, keys(parsed)) : NIL);
})() : NIL);
})();
return headers;
})(); };
// process-response-headers
var processResponseHeaders = function(getHeader) { return {["redirect"]: getHeader("SX-Redirect"), ["refresh"]: getHeader("SX-Refresh"), ["trigger"]: getHeader("SX-Trigger"), ["retarget"]: getHeader("SX-Retarget"), ["reswap"]: getHeader("SX-Reswap"), ["location"]: getHeader("SX-Location"), ["replace-url"]: getHeader("SX-Replace-Url"), ["css-hash"]: getHeader("SX-Css-Hash"), ["trigger-swap"]: getHeader("SX-Trigger-After-Swap"), ["trigger-settle"]: getHeader("SX-Trigger-After-Settle"), ["content-type"]: getHeader("Content-Type"), ["cache-invalidate"]: getHeader("SX-Cache-Invalidate"), ["cache-update"]: getHeader("SX-Cache-Update")}; };
// parse-swap-spec
var parseSwapSpec = function(rawSwap, globalTransitions_p) { return (function() {
var parts = split(sxOr(rawSwap, DEFAULT_SWAP), " ");
var style = first(parts);
var useTransition = globalTransitions_p;
{ var _c = rest(parts); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; (isSxTruthy((p == "transition:true")) ? (useTransition = true) : (isSxTruthy((p == "transition:false")) ? (useTransition = false) : NIL)); } }
return {["style"]: style, ["transition"]: useTransition};
})(); };
// parse-retry-spec
var parseRetrySpec = function(retryAttr) { return (isSxTruthy(isNil(retryAttr)) ? NIL : (function() {
var parts = split(retryAttr, ":");
return {["strategy"]: first(parts), ["start-ms"]: parseInt_(nth(parts, 1), 1000), ["cap-ms"]: parseInt_(nth(parts, 2), 30000)};
})()); };
// next-retry-ms
var nextRetryMs = function(currentMs, capMs) { return min((currentMs * 2), capMs); };
// filter-params
var filterParams = function(paramsSpec, allParams) { return (isSxTruthy(isNil(paramsSpec)) ? allParams : (isSxTruthy((paramsSpec == "none")) ? [] : (isSxTruthy((paramsSpec == "*")) ? allParams : (isSxTruthy(startsWith(paramsSpec, "not ")) ? (function() {
var excluded = map(trim, split(slice(paramsSpec, 4), ","));
return filter(function(p) { return !isSxTruthy(contains(excluded, first(p))); }, allParams);
})() : (function() {
var allowed = map(trim, split(paramsSpec, ","));
return filter(function(p) { return contains(allowed, first(p)); }, allParams);
})())))); };
// resolve-target
var resolveTarget = function(el) { return (function() {
var sel = domGetAttr(el, "sx-target");
return (isSxTruthy(sxOr(isNil(sel), (sel == "this"))) ? el : (isSxTruthy((sel == "closest")) ? domParent(el) : domQuery(sel)));
})(); };
// apply-optimistic
var applyOptimistic = function(el) { return (function() {
var directive = domGetAttr(el, "sx-optimistic");
return (isSxTruthy(isNil(directive)) ? NIL : (function() {
var target = sxOr(resolveTarget(el), el);
var state = {["target"]: target, ["directive"]: directive};
(isSxTruthy((directive == "remove")) ? (dictSet(state, "opacity", domGetStyle(target, "opacity")), domSetStyle(target, "opacity", "0"), domSetStyle(target, "pointer-events", "none")) : (isSxTruthy((directive == "disable")) ? (dictSet(state, "disabled", domGetProp(target, "disabled")), domSetProp(target, "disabled", true)) : (isSxTruthy(startsWith(directive, "add-class:")) ? (function() {
var cls = slice(directive, 10);
state["add-class"] = cls;
return domAddClass(target, cls);
})() : NIL)));
return state;
})());
})(); };
// revert-optimistic
var revertOptimistic = function(state) { return (isSxTruthy(state) ? (function() {
var target = get(state, "target");
var directive = get(state, "directive");
return (isSxTruthy((directive == "remove")) ? (domSetStyle(target, "opacity", sxOr(get(state, "opacity"), "")), domSetStyle(target, "pointer-events", "")) : (isSxTruthy((directive == "disable")) ? domSetProp(target, "disabled", sxOr(get(state, "disabled"), false)) : (isSxTruthy(get(state, "add-class")) ? domRemoveClass(target, get(state, "add-class")) : NIL)));
})() : NIL); };
// find-oob-swaps
var findOobSwaps = function(container) { return (function() {
var results = [];
{ var _c = ["sx-swap-oob", "hx-swap-oob"]; for (var _i = 0; _i < _c.length; _i++) { var attr = _c[_i]; (function() {
var oobEls = domQueryAll(container, (String("[") + String(attr) + String("]")));
return forEach(function(oob) { return (function() {
var swapType = sxOr(domGetAttr(oob, attr), "outerHTML");
var targetId = domId(oob);
domRemoveAttr(oob, attr);
return (isSxTruthy(targetId) ? append_b(results, {["element"]: oob, ["swap-type"]: swapType, ["target-id"]: targetId}) : NIL);
})(); }, oobEls);
})(); } }
return results;
})(); };
// morph-node
var morphNode = function(oldNode, newNode) { return (isSxTruthy(sxOr(domHasAttr(oldNode, "sx-preserve"), domHasAttr(oldNode, "sx-ignore"))) ? NIL : (isSxTruthy(sxOr(!isSxTruthy((domNodeType(oldNode) == domNodeType(newNode))), !isSxTruthy((domNodeName(oldNode) == domNodeName(newNode))))) ? domReplaceChild(domParent(oldNode), domClone(newNode), oldNode) : (isSxTruthy(sxOr((domNodeType(oldNode) == 3), (domNodeType(oldNode) == 8))) ? (isSxTruthy(!isSxTruthy((domTextContent(oldNode) == domTextContent(newNode)))) ? domSetTextContent(oldNode, domTextContent(newNode)) : NIL) : (isSxTruthy((domNodeType(oldNode) == 1)) ? (syncAttrs(oldNode, newNode), (isSxTruthy(!isSxTruthy((isSxTruthy(domIsActiveElement(oldNode)) && domIsInputElement(oldNode)))) ? morphChildren(oldNode, newNode) : NIL)) : NIL)))); };
// sync-attrs
var syncAttrs = function(oldEl, newEl) { { var _c = domAttrList(newEl); for (var _i = 0; _i < _c.length; _i++) { var attr = _c[_i]; (function() {
var name = first(attr);
var val = nth(attr, 1);
return (isSxTruthy(!isSxTruthy((domGetAttr(oldEl, name) == val))) ? domSetAttr(oldEl, name, val) : NIL);
})(); } }
return forEach(function(attr) { return (isSxTruthy(!isSxTruthy(domHasAttr(newEl, first(attr)))) ? domRemoveAttr(oldEl, first(attr)) : NIL); }, domAttrList(oldEl)); };
// morph-children
var morphChildren = function(oldParent, newParent) { return (function() {
var oldKids = domChildList(oldParent);
var newKids = domChildList(newParent);
var oldById = reduce(function(acc, kid) { return (function() {
var id = domId(kid);
return (isSxTruthy(id) ? (dictSet(acc, id, kid), acc) : acc);
})(); }, {}, oldKids);
var oi = 0;
{ var _c = newKids; for (var _i = 0; _i < _c.length; _i++) { var newChild = _c[_i]; (function() {
var matchId = domId(newChild);
var matchById = (isSxTruthy(matchId) ? dictGet(oldById, matchId) : NIL);
return (isSxTruthy((isSxTruthy(matchById) && !isSxTruthy(isNil(matchById)))) ? ((isSxTruthy((isSxTruthy((oi < len(oldKids))) && !isSxTruthy((matchById == nth(oldKids, oi))))) ? domInsertBefore(oldParent, matchById, (isSxTruthy((oi < len(oldKids))) ? nth(oldKids, oi) : NIL)) : NIL), morphNode(matchById, newChild), (oi = (oi + 1))) : (isSxTruthy((oi < len(oldKids))) ? (function() {
var oldChild = nth(oldKids, oi);
return (isSxTruthy((isSxTruthy(domId(oldChild)) && !isSxTruthy(matchId))) ? domInsertBefore(oldParent, domClone(newChild), oldChild) : (morphNode(oldChild, newChild), (oi = (oi + 1))));
})() : domAppend(oldParent, domClone(newChild))));
})(); } }
return forEach(function(i) { return (isSxTruthy((i >= oi)) ? (function() {
var leftover = nth(oldKids, i);
return (isSxTruthy((isSxTruthy(domIsChildOf(leftover, oldParent)) && isSxTruthy(!isSxTruthy(domHasAttr(leftover, "sx-preserve"))) && !isSxTruthy(domHasAttr(leftover, "sx-ignore")))) ? domRemoveChild(oldParent, leftover) : NIL);
})() : NIL); }, range(oi, len(oldKids)));
})(); };
// swap-dom-nodes
var swapDomNodes = function(target, newNodes, strategy) { return (function() { var _m = strategy; if (_m == "innerHTML") return (isSxTruthy(domIsFragment(newNodes)) ? morphChildren(target, newNodes) : (function() {
var wrapper = domCreateElement("div", NIL);
domAppend(wrapper, newNodes);
return morphChildren(target, wrapper);
})()); if (_m == "outerHTML") return (function() {
var parent = domParent(target);
(isSxTruthy(domIsFragment(newNodes)) ? (function() {
var fc = domFirstChild(newNodes);
return (isSxTruthy(fc) ? (morphNode(target, fc), (function() {
var sib = domNextSibling(fc);
return insertRemainingSiblings(parent, target, sib);
})()) : domRemoveChild(parent, target));
})() : morphNode(target, newNodes));
return parent;
})(); if (_m == "afterend") return domInsertAfter(target, newNodes); if (_m == "beforeend") return domAppend(target, newNodes); if (_m == "afterbegin") return domPrepend(target, newNodes); if (_m == "beforebegin") return domInsertBefore(domParent(target), newNodes, target); if (_m == "delete") return domRemoveChild(domParent(target), target); if (_m == "none") return NIL; return (isSxTruthy(domIsFragment(newNodes)) ? morphChildren(target, newNodes) : (function() {
var wrapper = domCreateElement("div", NIL);
domAppend(wrapper, newNodes);
return morphChildren(target, wrapper);
})()); })(); };
// insert-remaining-siblings
var insertRemainingSiblings = function(parent, refNode, sib) { return (isSxTruthy(sib) ? (function() {
var next = domNextSibling(sib);
domInsertAfter(refNode, sib);
return insertRemainingSiblings(parent, sib, next);
})() : NIL); };
// swap-html-string
var swapHtmlString = function(target, html, strategy) { return (function() { var _m = strategy; if (_m == "innerHTML") return domSetInnerHtml(target, html); if (_m == "outerHTML") return (function() {
var parent = domParent(target);
domInsertAdjacentHtml(target, "afterend", html);
domRemoveChild(parent, target);
return parent;
})(); if (_m == "afterend") return domInsertAdjacentHtml(target, "afterend", html); if (_m == "beforeend") return domInsertAdjacentHtml(target, "beforeend", html); if (_m == "afterbegin") return domInsertAdjacentHtml(target, "afterbegin", html); if (_m == "beforebegin") return domInsertAdjacentHtml(target, "beforebegin", html); if (_m == "delete") return domRemoveChild(domParent(target), target); if (_m == "none") return NIL; return domSetInnerHtml(target, html); })(); };
// handle-history
var handleHistory = function(el, url, respHeaders) { return (function() {
var pushUrl = domGetAttr(el, "sx-push-url");
var replaceUrl = domGetAttr(el, "sx-replace-url");
var hdrReplace = get(respHeaders, "replace-url");
return (isSxTruthy(hdrReplace) ? browserReplaceState(hdrReplace) : (isSxTruthy((isSxTruthy(pushUrl) && !isSxTruthy((pushUrl == "false")))) ? browserPushState((isSxTruthy((pushUrl == "true")) ? url : pushUrl)) : (isSxTruthy((isSxTruthy(replaceUrl) && !isSxTruthy((replaceUrl == "false")))) ? browserReplaceState((isSxTruthy((replaceUrl == "true")) ? url : replaceUrl)) : NIL)));
})(); };
// PRELOAD_TTL
var PRELOAD_TTL = 30000;
// preload-cache-get
var preloadCacheGet = function(cache, url) { return (function() {
var entry = dictGet(cache, url);
return (isSxTruthy(isNil(entry)) ? NIL : (isSxTruthy(((nowMs() - get(entry, "timestamp")) > PRELOAD_TTL)) ? (dictDelete(cache, url), NIL) : (dictDelete(cache, url), entry)));
})(); };
// preload-cache-set
var preloadCacheSet = function(cache, url, text, contentType) { return dictSet(cache, url, {["text"]: text, ["content-type"]: contentType, ["timestamp"]: nowMs()}); };
// classify-trigger
var classifyTrigger = function(trigger) { return (function() {
var event = get(trigger, "event");
return (isSxTruthy((event == "every")) ? "poll" : (isSxTruthy((event == "intersect")) ? "intersect" : (isSxTruthy((event == "load")) ? "load" : (isSxTruthy((event == "revealed")) ? "revealed" : "event"))));
})(); };
// should-boost-link?
var shouldBoostLink = function(link) { return (function() {
var href = domGetAttr(link, "href");
return (isSxTruthy(href) && isSxTruthy(!isSxTruthy(startsWith(href, "#"))) && isSxTruthy(!isSxTruthy(startsWith(href, "javascript:"))) && isSxTruthy(!isSxTruthy(startsWith(href, "mailto:"))) && isSxTruthy(browserSameOrigin(href)) && isSxTruthy(!isSxTruthy(domHasAttr(link, "sx-get"))) && isSxTruthy(!isSxTruthy(domHasAttr(link, "sx-post"))) && !isSxTruthy(domHasAttr(link, "sx-disable")));
})(); };
// should-boost-form?
var shouldBoostForm = function(form) { return (isSxTruthy(!isSxTruthy(domHasAttr(form, "sx-get"))) && isSxTruthy(!isSxTruthy(domHasAttr(form, "sx-post"))) && !isSxTruthy(domHasAttr(form, "sx-disable"))); };
// parse-sse-swap
var parseSseSwap = function(el) { return sxOr(domGetAttr(el, "sx-sse-swap"), "message"); };
// === Transpiled from orchestration ===
// _preload-cache
var _preloadCache = {};
// _css-hash
var _cssHash = NIL;
// dispatch-trigger-events
var dispatchTriggerEvents = function(el, headerVal) { return (isSxTruthy(headerVal) ? (function() {
var parsed = tryParseJson(headerVal);
return (isSxTruthy(parsed) ? forEach(function(key) { return domDispatch(el, key, get(parsed, key)); }, keys(parsed)) : forEach(function(name) { return (function() {
var trimmed = trim(name);
return (isSxTruthy(!isSxTruthy(isEmpty(trimmed))) ? domDispatch(el, trimmed, {}) : NIL);
})(); }, split(headerVal, ",")));
})() : NIL); };
// init-css-tracking
var initCssTracking = function() { return (function() {
var meta = domQuery("meta[name=\"sx-css-classes\"]");
return (isSxTruthy(meta) ? (function() {
var content = domGetAttr(meta, "content");
return (isSxTruthy(content) ? (_cssHash = content) : NIL);
})() : NIL);
})(); };
// execute-request
var executeRequest = function(el, verbInfo, extraParams) { return (function() {
var info = sxOr(getVerbInfo(el), verbInfo);
return (isSxTruthy(isNil(info)) ? promiseResolve(NIL) : (function() {
var verb = get(info, "method");
var url = get(info, "url");
return (isSxTruthy((function() {
var media = domGetAttr(el, "sx-media");
return (isSxTruthy(media) && !isSxTruthy(browserMediaMatches(media)));
})()) ? promiseResolve(NIL) : (isSxTruthy((function() {
var confirmMsg = domGetAttr(el, "sx-confirm");
return (isSxTruthy(confirmMsg) && !isSxTruthy(browserConfirm(confirmMsg)));
})()) ? promiseResolve(NIL) : (function() {
var promptMsg = domGetAttr(el, "sx-prompt");
var promptVal = (isSxTruthy(promptMsg) ? browserPrompt(promptMsg) : NIL);
return (isSxTruthy((isSxTruthy(promptMsg) && isNil(promptVal))) ? promiseResolve(NIL) : (isSxTruthy(!isSxTruthy(validateForRequest(el))) ? promiseResolve(NIL) : doFetch(el, verb, verb, url, (isSxTruthy(promptVal) ? assoc(sxOr(extraParams, {}), "SX-Prompt", promptVal) : extraParams))));
})()));
})());
})(); };
// do-fetch
var doFetch = function(el, verb, method, url, extraParams) { return (function() {
var sync = domGetAttr(el, "sx-sync");
if (isSxTruthy((sync == "replace"))) {
abortPrevious(el);
}
return (function() {
var ctrl = newAbortController();
trackController(el, ctrl);
return (function() {
var bodyInfo = buildRequestBody(el, method, url);
var finalUrl = get(bodyInfo, "url");
var body = get(bodyInfo, "body");
var ct = get(bodyInfo, "content-type");
var headers = buildRequestHeaders(el, loadedComponentNames(), _cssHash);
var csrf = csrfToken();
if (isSxTruthy(extraParams)) {
{ var _c = keys(extraParams); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; headers[k] = get(extraParams, k); } }
}
if (isSxTruthy(ct)) {
headers["Content-Type"] = ct;
}
if (isSxTruthy(csrf)) {
headers["X-CSRFToken"] = csrf;
}
return (function() {
var cached = preloadCacheGet(_preloadCache, finalUrl);
var optimisticState = applyOptimistic(el);
var indicator = showIndicator(el);
var disabledElts = disableElements(el);
domAddClass(el, "sx-request");
domSetAttr(el, "aria-busy", "true");
domDispatch(el, "sx:beforeRequest", {["url"]: finalUrl, ["method"]: method});
return fetchRequest({["url"]: finalUrl, ["method"]: method, ["headers"]: headers, ["body"]: body, ["signal"]: controllerSignal(ctrl), ["cross-origin"]: isCrossOrigin(finalUrl), ["preloaded"]: cached}, function(respOk, status, getHeader, text) { return (clearLoadingState(el, indicator, disabledElts), revertOptimistic(optimisticState), (isSxTruthy(!isSxTruthy(respOk)) ? (domDispatch(el, "sx:responseError", {["status"]: status, ["text"]: text}), handleRetry(el, verb, method, finalUrl, extraParams)) : (domDispatch(el, "sx:afterRequest", {["status"]: status}), handleFetchSuccess(el, finalUrl, verb, extraParams, getHeader, text)))); }, function(err) { return (clearLoadingState(el, indicator, disabledElts), revertOptimistic(optimisticState), (isSxTruthy(!isSxTruthy(isAbortError(err))) ? domDispatch(el, "sx:requestError", {["error"]: err}) : NIL)); });
})();
})();
})();
})(); };
// handle-fetch-success
var handleFetchSuccess = function(el, url, verb, extraParams, getHeader, text) { return (function() {
var respHeaders = processResponseHeaders(getHeader);
(function() {
var newHash = get(respHeaders, "css-hash");
return (isSxTruthy(newHash) ? (_cssHash = newHash) : NIL);
})();
dispatchTriggerEvents(el, get(respHeaders, "trigger"));
processCacheDirectives(el, respHeaders, text);
return (isSxTruthy(get(respHeaders, "redirect")) ? browserNavigate(get(respHeaders, "redirect")) : (isSxTruthy(get(respHeaders, "refresh")) ? browserReload() : (isSxTruthy(get(respHeaders, "location")) ? fetchLocation(get(respHeaders, "location")) : (function() {
var targetEl = (isSxTruthy(get(respHeaders, "retarget")) ? domQuery(get(respHeaders, "retarget")) : resolveTarget(el));
var swapSpec = parseSwapSpec(sxOr(get(respHeaders, "reswap"), domGetAttr(el, "sx-swap")), domHasClass(domBody(), "sx-transitions"));
var swapStyle = get(swapSpec, "style");
var useTransition = get(swapSpec, "transition");
var ct = sxOr(get(respHeaders, "content-type"), "");
(isSxTruthy(contains(ct, "text/sx")) ? handleSxResponse(el, targetEl, text, swapStyle, useTransition) : handleHtmlResponse(el, targetEl, text, swapStyle, useTransition));
dispatchTriggerEvents(el, get(respHeaders, "trigger-swap"));
handleHistory(el, url, respHeaders);
if (isSxTruthy(get(respHeaders, "trigger-settle"))) {
setTimeout_(function() { return dispatchTriggerEvents(el, get(respHeaders, "trigger-settle")); }, 20);
}
return domDispatch(el, "sx:afterSwap", {["target"]: targetEl, ["swap"]: swapStyle});
})())));
})(); };
// handle-sx-response
var handleSxResponse = function(el, target, text, swapStyle, useTransition) { return (function() {
var cleaned = stripComponentScripts(text);
return (function() {
var final_ = extractResponseCss(cleaned);
return (function() {
var trimmed = trim(final_);
return (isSxTruthy(!isSxTruthy(isEmpty(trimmed))) ? (function() {
var rendered = sxRender(trimmed);
var container = domCreateElement("div", NIL);
domAppend(container, rendered);
processOobSwaps(container, function(t, oob, s) { disposeIslandsIn(t);
swapDomNodes(t, oob, s);
sxHydrate(t);
return processElements(t); });
return (function() {
var selectSel = domGetAttr(el, "sx-select");
var content = (isSxTruthy(selectSel) ? selectFromContainer(container, selectSel) : childrenToFragment(container));
disposeIslandsIn(target);
return withTransition(useTransition, function() { swapDomNodes(target, content, swapStyle);
return postSwap(target); });
})();
})() : NIL);
})();
})();
})(); };
// handle-html-response
var handleHtmlResponse = function(el, target, text, swapStyle, useTransition) { return (function() {
var doc = domParseHtmlDocument(text);
return (isSxTruthy(doc) ? (function() {
var selectSel = domGetAttr(el, "sx-select");
disposeIslandsIn(target);
return (isSxTruthy(selectSel) ? (function() {
var html = selectHtmlFromDoc(doc, selectSel);
return withTransition(useTransition, function() { swapHtmlString(target, html, swapStyle);
return postSwap(target); });
})() : (function() {
var container = domCreateElement("div", NIL);
domSetInnerHtml(container, domBodyInnerHtml(doc));
processOobSwaps(container, function(t, oob, s) { disposeIslandsIn(t);
swapDomNodes(t, oob, s);
return postSwap(t); });
hoistHeadElements(container);
return withTransition(useTransition, function() { swapDomNodes(target, childrenToFragment(container), swapStyle);
return postSwap(target); });
})());
})() : NIL);
})(); };
// handle-retry
var handleRetry = function(el, verb, method, url, extraParams) { return (function() {
var retryAttr = domGetAttr(el, "sx-retry");
var spec = parseRetrySpec(retryAttr);
return (isSxTruthy(spec) ? (function() {
var currentMs = sxOr(domGetAttr(el, "data-sx-retry-ms"), get(spec, "start-ms"));
return (function() {
var ms = parseInt_(currentMs, get(spec, "start-ms"));
domSetAttr(el, "data-sx-retry-ms", (String(nextRetryMs(ms, get(spec, "cap-ms")))));
return setTimeout_(function() { return doFetch(el, verb, method, url, extraParams); }, ms);
})();
})() : NIL);
})(); };
// bind-triggers
var bindTriggers = function(el, verbInfo) { return (function() {
var triggers = sxOr(parseTriggerSpec(domGetAttr(el, "sx-trigger")), defaultTrigger(domTagName(el)));
return forEach(function(trigger) { return (function() {
var kind = classifyTrigger(trigger);
var mods = get(trigger, "modifiers");
return (isSxTruthy((kind == "poll")) ? setInterval_(function() { return executeRequest(el, NIL, NIL); }, get(mods, "interval")) : (isSxTruthy((kind == "intersect")) ? observeIntersection(el, function() { return executeRequest(el, NIL, NIL); }, false, get(mods, "delay")) : (isSxTruthy((kind == "load")) ? setTimeout_(function() { return executeRequest(el, NIL, NIL); }, sxOr(get(mods, "delay"), 0)) : (isSxTruthy((kind == "revealed")) ? observeIntersection(el, function() { return executeRequest(el, NIL, NIL); }, true, get(mods, "delay")) : (isSxTruthy((kind == "event")) ? bindEvent(el, get(trigger, "event"), mods, verbInfo) : NIL)))));
})(); }, triggers);
})(); };
// bind-event
var bindEvent = function(el, eventName, mods, verbInfo) { return (function() {
var timer = NIL;
var lastVal = NIL;
var listenTarget = (isSxTruthy(get(mods, "from")) ? domQuery(get(mods, "from")) : el);
return (isSxTruthy(listenTarget) ? domAddListener(listenTarget, eventName, function(e) { return (function() {
var shouldFire = true;
if (isSxTruthy(get(mods, "changed"))) {
(function() {
var val = elementValue(el);
return (isSxTruthy((val == lastVal)) ? (shouldFire = false) : (lastVal = val));
})();
}
return (isSxTruthy(shouldFire) ? ((isSxTruthy(sxOr((eventName == "submit"), (isSxTruthy((eventName == "click")) && domHasAttr(el, "href")))) ? preventDefault_(e) : NIL), (function() {
var liveInfo = sxOr(getVerbInfo(el), verbInfo);
var isGetLink = (isSxTruthy((eventName == "click")) && isSxTruthy((get(liveInfo, "method") == "GET")) && isSxTruthy(domHasAttr(el, "href")) && !isSxTruthy(get(mods, "delay")));
var clientRouted = false;
if (isSxTruthy(isGetLink)) {
clientRouted = tryClientRoute(urlPathname(get(liveInfo, "url")), domGetAttr(el, "sx-target"));
}
return (isSxTruthy(clientRouted) ? (browserPushState(get(liveInfo, "url")), browserScrollTo(0, 0)) : ((isSxTruthy(isGetLink) ? logInfo((String("sx:route server fetch ") + String(get(liveInfo, "url")))) : NIL), (isSxTruthy(get(mods, "delay")) ? (clearTimeout_(timer), (timer = setTimeout_(function() { return executeRequest(el, NIL, NIL); }, get(mods, "delay")))) : executeRequest(el, NIL, NIL))));
})()) : NIL);
})(); }, (isSxTruthy(get(mods, "once")) ? {["once"]: true} : NIL)) : NIL);
})(); };
// post-swap
var postSwap = function(root) { activateScripts(root);
sxProcessScripts(root);
sxHydrate(root);
sxHydrateIslands(root);
return processElements(root); };
// activate-scripts
var activateScripts = function(root) { return (isSxTruthy(root) ? (function() {
var scripts = domQueryAll(root, "script");
return forEach(function(dead) { return (isSxTruthy((isSxTruthy(!isSxTruthy(domHasAttr(dead, "data-components"))) && !isSxTruthy(domHasAttr(dead, "data-sx-activated")))) ? (function() {
var live = createScriptClone(dead);
domSetAttr(live, "data-sx-activated", "true");
return domReplaceChild(domParent(dead), live, dead);
})() : NIL); }, scripts);
})() : NIL); };
// process-oob-swaps
var processOobSwaps = function(container, swapFn) { return (function() {
var oobs = findOobSwaps(container);
return forEach(function(oob) { return (function() {
var targetId = get(oob, "target-id");
var target = domQueryById(targetId);
var oobEl = get(oob, "element");
var swapType = get(oob, "swap-type");
if (isSxTruthy(domParent(oobEl))) {
domRemoveChild(domParent(oobEl), oobEl);
}
return (isSxTruthy(target) ? swapFn(target, oobEl, swapType) : NIL);
})(); }, oobs);
})(); };
// hoist-head-elements
var hoistHeadElements = function(container) { { var _c = domQueryAll(container, "style[data-sx-css]"); for (var _i = 0; _i < _c.length; _i++) { var style = _c[_i]; if (isSxTruthy(domParent(style))) {
domRemoveChild(domParent(style), style);
}
domAppendToHead(style); } }
return forEach(function(link) { if (isSxTruthy(domParent(link))) {
domRemoveChild(domParent(link), link);
}
return domAppendToHead(link); }, domQueryAll(container, "link[rel=\"stylesheet\"]")); };
// process-boosted
var processBoosted = function(root) { return forEach(function(container) { return boostDescendants(container); }, domQueryAll(sxOr(root, domBody()), "[sx-boost]")); };
// boost-descendants
var boostDescendants = function(container) { return (function() {
var boostTarget = domGetAttr(container, "sx-boost");
{ var _c = domQueryAll(container, "a[href]"); for (var _i = 0; _i < _c.length; _i++) { var link = _c[_i]; if (isSxTruthy((isSxTruthy(!isSxTruthy(isProcessed(link, "boost"))) && shouldBoostLink(link)))) {
markProcessed(link, "boost");
if (isSxTruthy((isSxTruthy(!isSxTruthy(domHasAttr(link, "sx-target"))) && isSxTruthy(boostTarget) && !isSxTruthy((boostTarget == "true"))))) {
domSetAttr(link, "sx-target", boostTarget);
}
if (isSxTruthy(!isSxTruthy(domHasAttr(link, "sx-swap")))) {
domSetAttr(link, "sx-swap", "innerHTML");
}
if (isSxTruthy(!isSxTruthy(domHasAttr(link, "sx-push-url")))) {
domSetAttr(link, "sx-push-url", "true");
}
bindClientRouteLink(link, domGetAttr(link, "href"));
} } }
return forEach(function(form) { return (isSxTruthy((isSxTruthy(!isSxTruthy(isProcessed(form, "boost"))) && shouldBoostForm(form))) ? (markProcessed(form, "boost"), (function() {
var method = upper(sxOr(domGetAttr(form, "method"), "GET"));
var action = sxOr(domGetAttr(form, "action"), browserLocationHref());
if (isSxTruthy((isSxTruthy(!isSxTruthy(domHasAttr(form, "sx-target"))) && isSxTruthy(boostTarget) && !isSxTruthy((boostTarget == "true"))))) {
domSetAttr(form, "sx-target", boostTarget);
}
if (isSxTruthy(!isSxTruthy(domHasAttr(form, "sx-swap")))) {
domSetAttr(form, "sx-swap", "innerHTML");
}
return bindBoostForm(form, method, action);
})()) : NIL); }, domQueryAll(container, "form"));
})(); };
// _page-data-cache
var _pageDataCache = {};
// _page-data-cache-ttl
var _pageDataCacheTtl = 30000;
// page-data-cache-key
var pageDataCacheKey = function(pageName, params) { return (function() {
var base = pageName;
return (isSxTruthy(sxOr(isNil(params), isEmpty(keys(params)))) ? base : (function() {
var parts = [];
{ var _c = keys(params); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; parts.push((String(k) + String("=") + String(get(params, k)))); } }
return (String(base) + String(":") + String(join("&", parts)));
})());
})(); };
// page-data-cache-get
var pageDataCacheGet = function(cacheKey) { return (function() {
var entry = get(_pageDataCache, cacheKey);
return (isSxTruthy(isNil(entry)) ? NIL : (isSxTruthy(((nowMs() - get(entry, "ts")) > _pageDataCacheTtl)) ? (dictSet(_pageDataCache, cacheKey, NIL), NIL) : get(entry, "data")));
})(); };
// page-data-cache-set
var pageDataCacheSet = function(cacheKey, data) { return dictSet(_pageDataCache, cacheKey, {"data": data, "ts": nowMs()}); };
// invalidate-page-cache
var invalidatePageCache = function(pageName) { { var _c = keys(_pageDataCache); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; if (isSxTruthy(sxOr((k == pageName), startsWith(k, (String(pageName) + String(":")))))) {
_pageDataCache[k] = NIL;
} } }
swPostMessage({"type": "invalidate", "page": pageName});
return logInfo((String("sx:cache invalidate ") + String(pageName))); };
// invalidate-all-page-cache
var invalidateAllPageCache = function() { _pageDataCache = {};
swPostMessage({"type": "invalidate", "page": "*"});
return logInfo("sx:cache invalidate *"); };
// update-page-cache
var updatePageCache = function(pageName, data) { return (function() {
var cacheKey = pageDataCacheKey(pageName, {});
pageDataCacheSet(cacheKey, data);
return logInfo((String("sx:cache update ") + String(pageName)));
})(); };
// process-cache-directives
var processCacheDirectives = function(el, respHeaders, responseText) { (function() {
var elInvalidate = domGetAttr(el, "sx-cache-invalidate");
return (isSxTruthy(elInvalidate) ? (isSxTruthy((elInvalidate == "*")) ? invalidateAllPageCache() : invalidatePageCache(elInvalidate)) : NIL);
})();
(function() {
var hdrInvalidate = get(respHeaders, "cache-invalidate");
return (isSxTruthy(hdrInvalidate) ? (isSxTruthy((hdrInvalidate == "*")) ? invalidateAllPageCache() : invalidatePageCache(hdrInvalidate)) : NIL);
})();
return (function() {
var hdrUpdate = get(respHeaders, "cache-update");
return (isSxTruthy(hdrUpdate) ? (function() {
var data = parseSxData(responseText);
return (isSxTruthy(data) ? updatePageCache(hdrUpdate, data) : NIL);
})() : NIL);
})(); };
// _optimistic-snapshots
var _optimisticSnapshots = {};
// optimistic-cache-update
var optimisticCacheUpdate = function(cacheKey, mutator) { return (function() {
var cached = pageDataCacheGet(cacheKey);
return (isSxTruthy(cached) ? (function() {
var predicted = mutator(cached);
_optimisticSnapshots[cacheKey] = cached;
pageDataCacheSet(cacheKey, predicted);
return predicted;
})() : NIL);
})(); };
// optimistic-cache-revert
var optimisticCacheRevert = function(cacheKey) { return (function() {
var snapshot = get(_optimisticSnapshots, cacheKey);
return (isSxTruthy(snapshot) ? (pageDataCacheSet(cacheKey, snapshot), dictDelete(_optimisticSnapshots, cacheKey), snapshot) : NIL);
})(); };
// optimistic-cache-confirm
var optimisticCacheConfirm = function(cacheKey) { return dictDelete(_optimisticSnapshots, cacheKey); };
// submit-mutation
var submitMutation = function(pageName, params, actionName, payload, mutatorFn, onComplete) { return (function() {
var cacheKey = pageDataCacheKey(pageName, params);
var predicted = optimisticCacheUpdate(cacheKey, mutatorFn);
if (isSxTruthy(predicted)) {
tryRerenderPage(pageName, params, predicted);
}
return executeAction(actionName, payload, function(result) { if (isSxTruthy(result)) {
pageDataCacheSet(cacheKey, result);
}
optimisticCacheConfirm(cacheKey);
if (isSxTruthy(result)) {
tryRerenderPage(pageName, params, result);
}
logInfo((String("sx:optimistic confirmed ") + String(pageName)));
return (isSxTruthy(onComplete) ? onComplete("confirmed") : NIL); }, function(error) { return (function() {
var reverted = optimisticCacheRevert(cacheKey);
if (isSxTruthy(reverted)) {
tryRerenderPage(pageName, params, reverted);
}
logWarn((String("sx:optimistic reverted ") + String(pageName) + String(": ") + String(error)));
return (isSxTruthy(onComplete) ? onComplete("reverted") : NIL);
})(); });
})(); };
// _is-online
var _isOnline = true;
// _offline-queue
var _offlineQueue = [];
// offline-is-online?
var offlineIsOnline_p = function() { return _isOnline; };
// offline-set-online!
var offlineSetOnline_b = function(val) { return (_isOnline = val); };
// offline-queue-mutation
var offlineQueueMutation = function(actionName, payload, pageName, params, mutatorFn) { return (function() {
var cacheKey = pageDataCacheKey(pageName, params);
var entry = {["action"]: actionName, ["payload"]: payload, ["page"]: pageName, ["params"]: params, ["timestamp"]: nowMs(), ["status"]: "pending"};
_offlineQueue.push(entry);
(function() {
var predicted = optimisticCacheUpdate(cacheKey, mutatorFn);
return (isSxTruthy(predicted) ? tryRerenderPage(pageName, params, predicted) : NIL);
})();
logInfo((String("sx:offline queued ") + String(actionName) + String(" (") + String(len(_offlineQueue)) + String(" pending)")));
return entry;
})(); };
// offline-sync
var offlineSync = function() { return (function() {
var pending = filter(function(e) { return (get(e, "status") == "pending"); }, _offlineQueue);
return (isSxTruthy(!isSxTruthy(isEmpty(pending))) ? (logInfo((String("sx:offline syncing ") + String(len(pending)) + String(" mutations"))), forEach(function(entry) { return executeAction(get(entry, "action"), get(entry, "payload"), function(result) { entry["status"] = "synced";
return logInfo((String("sx:offline synced ") + String(get(entry, "action")))); }, function(error) { entry["status"] = "failed";
return logWarn((String("sx:offline sync failed ") + String(get(entry, "action")) + String(": ") + String(error))); }); }, pending)) : NIL);
})(); };
// offline-pending-count
var offlinePendingCount = function() { return len(filter(function(e) { return (get(e, "status") == "pending"); }, _offlineQueue)); };
// offline-aware-mutation
var offlineAwareMutation = function(pageName, params, actionName, payload, mutatorFn, onComplete) { return (isSxTruthy(_isOnline) ? submitMutation(pageName, params, actionName, payload, mutatorFn, onComplete) : (offlineQueueMutation(actionName, payload, pageName, params, mutatorFn), (isSxTruthy(onComplete) ? onComplete("queued") : NIL))); };
// current-page-layout
var currentPageLayout = function() { return (function() {
var pathname = urlPathname(browserLocationHref());
var match = findMatchingRoute(pathname, _pageRoutes);
return (isSxTruthy(isNil(match)) ? "" : sxOr(get(match, "layout"), ""));
})(); };
// swap-rendered-content
var swapRenderedContent = function(target, rendered, pathname) { return (disposeIslandsIn(target), domSetTextContent(target, ""), domAppend(target, rendered), hoistHeadElementsFull(target), processElements(target), sxHydrateElements(target), domDispatch(target, "sx:clientRoute", {["pathname"]: pathname}), logInfo((String("sx:route client ") + String(pathname)))); };
// resolve-route-target
var resolveRouteTarget = function(targetSel) { return (isSxTruthy((isSxTruthy(targetSel) && !isSxTruthy((targetSel == "true")))) ? domQuery(targetSel) : NIL); };
// deps-satisfied?
var depsSatisfied_p = function(match) { return (function() {
var deps = get(match, "deps");
var loaded = loadedComponentNames();
return (isSxTruthy(sxOr(isNil(deps), isEmpty(deps))) ? true : isEvery(function(dep) { return contains(loaded, dep); }, deps));
})(); };
// try-client-route
var tryClientRoute = function(pathname, targetSel) { return (function() {
var match = findMatchingRoute(pathname, _pageRoutes);
return (isSxTruthy(isNil(match)) ? (logInfo((String("sx:route no match (") + String(len(_pageRoutes)) + String(" routes) ") + String(pathname))), false) : (function() {
var targetLayout = sxOr(get(match, "layout"), "");
var curLayout = currentPageLayout();
return (isSxTruthy(!isSxTruthy((targetLayout == curLayout))) ? (logInfo((String("sx:route server (layout: ") + String(curLayout) + String(" -> ") + String(targetLayout) + String(") ") + String(pathname))), false) : (function() {
var contentSrc = get(match, "content");
var closure = sxOr(get(match, "closure"), {});
var params = get(match, "params");
var pageName = get(match, "name");
return (isSxTruthy(sxOr(isNil(contentSrc), isEmpty(contentSrc))) ? (logWarn((String("sx:route no content for ") + String(pathname))), false) : (function() {
var target = resolveRouteTarget(targetSel);
return (isSxTruthy(isNil(target)) ? (logWarn((String("sx:route target not found: ") + String(targetSel))), false) : (isSxTruthy(!isSxTruthy(depsSatisfied_p(match))) ? (logInfo((String("sx:route deps miss for ") + String(pageName))), false) : (function() {
var ioDeps = get(match, "io-deps");
var hasIo = (isSxTruthy(ioDeps) && !isSxTruthy(isEmpty(ioDeps)));
var renderPlan = get(match, "render-plan");
if (isSxTruthy(renderPlan)) {
(function() {
var srv = sxOr(get(renderPlan, "server"), []);
var cli = sxOr(get(renderPlan, "client"), []);
return logInfo((String("sx:route plan ") + String(pageName) + String(" — ") + String(len(srv)) + String(" server, ") + String(len(cli)) + String(" client")));
})();
}
if (isSxTruthy(hasIo)) {
registerIoDeps(ioDeps);
}
return (isSxTruthy(get(match, "stream")) ? (logInfo((String("sx:route streaming ") + String(pathname))), fetchStreaming(target, pathname, buildRequestHeaders(target, loadedComponentNames(), _cssHash)), true) : (isSxTruthy(get(match, "has-data")) ? (function() {
var cacheKey = pageDataCacheKey(pageName, params);
var cached = pageDataCacheGet(cacheKey);
return (isSxTruthy(cached) ? (function() {
var env = merge(closure, params, cached);
return (isSxTruthy(hasIo) ? (logInfo((String("sx:route client+cache+async ") + String(pathname))), tryAsyncEvalContent(contentSrc, env, function(rendered) { return (isSxTruthy(isNil(rendered)) ? logWarn((String("sx:route async eval failed for ") + String(pathname))) : swapRenderedContent(target, rendered, pathname)); }), true) : (function() {
var rendered = tryEvalContent(contentSrc, env);
return (isSxTruthy(isNil(rendered)) ? (logWarn((String("sx:route cached eval failed for ") + String(pathname))), false) : (logInfo((String("sx:route client+cache ") + String(pathname))), swapRenderedContent(target, rendered, pathname), true));
})());
})() : (logInfo((String("sx:route client+data ") + String(pathname))), resolvePageData(pageName, params, function(data) { pageDataCacheSet(cacheKey, data);
return (function() {
var env = merge(closure, params, data);
return (isSxTruthy(hasIo) ? tryAsyncEvalContent(contentSrc, env, function(rendered) { return (isSxTruthy(isNil(rendered)) ? logWarn((String("sx:route data+async eval failed for ") + String(pathname))) : swapRenderedContent(target, rendered, pathname)); }) : (function() {
var rendered = tryEvalContent(contentSrc, env);
return (isSxTruthy(isNil(rendered)) ? logWarn((String("sx:route data eval failed for ") + String(pathname))) : swapRenderedContent(target, rendered, pathname));
})());
})(); }), true));
})() : (isSxTruthy(hasIo) ? (logInfo((String("sx:route client+async ") + String(pathname))), tryAsyncEvalContent(contentSrc, merge(closure, params), function(rendered) { return (isSxTruthy(isNil(rendered)) ? logWarn((String("sx:route async eval failed for ") + String(pathname))) : swapRenderedContent(target, rendered, pathname)); }), true) : (function() {
var env = merge(closure, params);
var rendered = tryEvalContent(contentSrc, env);
return (isSxTruthy(isNil(rendered)) ? (logInfo((String("sx:route server (eval failed) ") + String(pathname))), false) : (swapRenderedContent(target, rendered, pathname), true));
})())));
})()));
})());
})());
})());
})(); };
// bind-client-route-link
var bindClientRouteLink = function(link, href) { return bindClientRouteClick(link, href, function() { return bindBoostLink(link, href); }); };
// process-sse
var processSse = function(root) { return forEach(function(el) { return (isSxTruthy(!isSxTruthy(isProcessed(el, "sse"))) ? (markProcessed(el, "sse"), bindSse(el)) : NIL); }, domQueryAll(sxOr(root, domBody()), "[sx-sse]")); };
// bind-sse
var bindSse = function(el) { return (function() {
var url = domGetAttr(el, "sx-sse");
return (isSxTruthy(url) ? (function() {
var source = eventSourceConnect(url, el);
var eventName = parseSseSwap(el);
return eventSourceListen(source, eventName, function(data) { return bindSseSwap(el, data); });
})() : NIL);
})(); };
// bind-sse-swap
var bindSseSwap = function(el, data) { return (function() {
var target = resolveTarget(el);
var swapSpec = parseSwapSpec(domGetAttr(el, "sx-swap"), domHasClass(domBody(), "sx-transitions"));
var swapStyle = get(swapSpec, "style");
var useTransition = get(swapSpec, "transition");
var trimmed = trim(data);
return (isSxTruthy(!isSxTruthy(isEmpty(trimmed))) ? (disposeIslandsIn(target), (isSxTruthy(startsWith(trimmed, "(")) ? (function() {
var rendered = sxRender(trimmed);
var container = domCreateElement("div", NIL);
domAppend(container, rendered);
return withTransition(useTransition, function() { swapDomNodes(target, childrenToFragment(container), swapStyle);
return postSwap(target); });
})() : withTransition(useTransition, function() { swapHtmlString(target, trimmed, swapStyle);
return postSwap(target); }))) : NIL);
})(); };
// bind-inline-handlers
var bindInlineHandlers = function(root) { return forEach(function(el) { return forEach(function(attr) { return (function() {
var name = first(attr);
var body = nth(attr, 1);
return (isSxTruthy(startsWith(name, "sx-on:")) ? (function() {
var eventName = slice(name, 6);
return (isSxTruthy(!isSxTruthy(isProcessed(el, (String("on:") + String(eventName))))) ? (markProcessed(el, (String("on:") + String(eventName))), bindInlineHandler(el, eventName, body)) : NIL);
})() : NIL);
})(); }, domAttrList(el)); }, domQueryAll(sxOr(root, domBody()), "[sx-on\\:beforeRequest],[sx-on\\:afterRequest],[sx-on\\:afterSwap],[sx-on\\:afterSettle],[sx-on\\:load]")); };
// bind-preload-for
var bindPreloadFor = function(el) { return (function() {
var preloadAttr = domGetAttr(el, "sx-preload");
return (isSxTruthy(preloadAttr) ? (function() {
var events = (isSxTruthy((preloadAttr == "mousedown")) ? ["mousedown", "touchstart"] : ["mouseover"]);
var debounceMs = (isSxTruthy((preloadAttr == "mousedown")) ? 0 : 100);
return bindPreload(el, events, debounceMs, function() { return (function() {
var info = getVerbInfo(el);
return (isSxTruthy(info) ? doPreload(get(info, "url"), buildRequestHeaders(el, loadedComponentNames(), _cssHash)) : NIL);
})(); });
})() : NIL);
})(); };
// do-preload
var doPreload = function(url, headers) { return (isSxTruthy(isNil(preloadCacheGet(_preloadCache, url))) ? fetchPreload(url, headers, _preloadCache) : NIL); };
// VERB_SELECTOR
var VERB_SELECTOR = (String("[sx-get],[sx-post],[sx-put],[sx-delete],[sx-patch]"));
// process-elements
var processElements = function(root) { (function() {
var els = domQueryAll(sxOr(root, domBody()), VERB_SELECTOR);
return forEach(function(el) { return (isSxTruthy(!isSxTruthy(isProcessed(el, "verb"))) ? (markProcessed(el, "verb"), processOne(el)) : NIL); }, els);
})();
processBoosted(root);
processSse(root);
bindInlineHandlers(root);
return processEmitElements(root); };
// process-one
var processOne = function(el) { return (function() {
var verbInfo = getVerbInfo(el);
return (isSxTruthy(verbInfo) ? (isSxTruthy(!isSxTruthy(domHasAttr(el, "sx-disable"))) ? (bindTriggers(el, verbInfo), bindPreloadFor(el)) : NIL) : NIL);
})(); };
// process-emit-elements
var processEmitElements = function(root) { return (function() {
var els = domQueryAll(sxOr(root, domBody()), "[data-sx-emit]");
return forEach(function(el) { return (isSxTruthy(!isSxTruthy(isProcessed(el, "emit"))) ? (markProcessed(el, "emit"), (function() {
var eventName = domGetAttr(el, "data-sx-emit");
return (isSxTruthy(eventName) ? domListen(el, "click", function(e) { return (function() {
var detailJson = domGetAttr(el, "data-sx-emit-detail");
var detail = (isSxTruthy(detailJson) ? jsonParse(detailJson) : {});
return domDispatch(el, eventName, detail);
})(); }) : NIL);
})()) : NIL); }, els);
})(); };
// handle-popstate
var handlePopstate = function(scrollY) { return (function() {
var url = browserLocationHref();
var boostEl = domQuery("[sx-boost]");
var targetSel = (isSxTruthy(boostEl) ? (function() {
var attr = domGetAttr(boostEl, "sx-boost");
return (isSxTruthy((isSxTruthy(attr) && !isSxTruthy((attr == "true")))) ? attr : NIL);
})() : NIL);
var targetSel = sxOr(targetSel, "#main-panel");
var target = domQuery(targetSel);
var pathname = urlPathname(url);
return (isSxTruthy(target) ? (isSxTruthy(tryClientRoute(pathname, targetSel)) ? browserScrollTo(0, scrollY) : (function() {
var headers = buildRequestHeaders(target, loadedComponentNames(), _cssHash);
return fetchAndRestore(target, url, headers, scrollY);
})()) : NIL);
})(); };
// engine-init
var engineInit = function() { return (initCssTracking(), sxProcessScripts(NIL), sxHydrate(NIL), processElements(NIL)); };
// === Transpiled from boot ===
// HEAD_HOIST_SELECTOR
var HEAD_HOIST_SELECTOR = "meta, title, link[rel='canonical'], script[type='application/ld+json']";
// hoist-head-elements-full
var hoistHeadElementsFull = function(root) { return (function() {
var els = domQueryAll(root, HEAD_HOIST_SELECTOR);
return forEach(function(el) { return (function() {
var tag = lower(domTagName(el));
return (isSxTruthy((tag == "title")) ? (setDocumentTitle(domTextContent(el)), domRemoveChild(domParent(el), el)) : (isSxTruthy((tag == "meta")) ? ((function() {
var name = domGetAttr(el, "name");
var prop = domGetAttr(el, "property");
if (isSxTruthy(name)) {
removeHeadElement((String("meta[name=\"") + String(name) + String("\"]")));
}
return (isSxTruthy(prop) ? removeHeadElement((String("meta[property=\"") + String(prop) + String("\"]"))) : NIL);
})(), domRemoveChild(domParent(el), el), domAppendToHead(el)) : (isSxTruthy((isSxTruthy((tag == "link")) && (domGetAttr(el, "rel") == "canonical"))) ? (removeHeadElement("link[rel=\"canonical\"]"), domRemoveChild(domParent(el), el), domAppendToHead(el)) : (domRemoveChild(domParent(el), el), domAppendToHead(el)))));
})(); }, els);
})(); };
// sx-mount
var sxMount = function(target, source, extraEnv) { return (function() {
var el = resolveMountTarget(target);
return (isSxTruthy(el) ? (function() {
var node = sxRenderWithEnv(source, extraEnv);
domSetTextContent(el, "");
domAppend(el, node);
hoistHeadElementsFull(el);
processElements(el);
sxHydrateElements(el);
return sxHydrateIslands(el);
})() : NIL);
})(); };
// resolve-suspense
var resolveSuspense = function(id, sx) { processSxScripts(NIL);
return (function() {
var el = domQuery((String("[data-suspense=\"") + String(id) + String("\"]")));
return (isSxTruthy(el) ? (function() {
var exprs = parse(sx);
var env = getRenderEnv(NIL);
domSetTextContent(el, "");
{ var _c = exprs; for (var _i = 0; _i < _c.length; _i++) { var expr = _c[_i]; domAppend(el, renderToDom(expr, env, NIL)); } }
processElements(el);
sxHydrateElements(el);
sxHydrateIslands(el);
return domDispatch(el, "sx:resolved", {"id": id});
})() : logWarn((String("resolveSuspense: no element for id=") + String(id))));
})(); };
// sx-hydrate-elements
var sxHydrateElements = function(root) { return (function() {
var els = domQueryAll(sxOr(root, domBody()), "[data-sx]");
return forEach(function(el) { return (isSxTruthy(!isSxTruthy(isProcessed(el, "hydrated"))) ? (markProcessed(el, "hydrated"), sxUpdateElement(el, NIL)) : NIL); }, els);
})(); };
// sx-update-element
var sxUpdateElement = function(el, newEnv) { return (function() {
var target = resolveMountTarget(el);
return (isSxTruthy(target) ? (function() {
var source = domGetAttr(target, "data-sx");
return (isSxTruthy(source) ? (function() {
var baseEnv = parseEnvAttr(target);
var env = mergeEnvs(baseEnv, newEnv);
return (function() {
var node = sxRenderWithEnv(source, env);
domSetTextContent(target, "");
domAppend(target, node);
return (isSxTruthy(newEnv) ? storeEnvAttr(target, baseEnv, newEnv) : NIL);
})();
})() : NIL);
})() : NIL);
})(); };
// sx-render-component
var sxRenderComponent = function(name, kwargs, extraEnv) { return (function() {
var fullName = (isSxTruthy(startsWith(name, "~")) ? name : (String("~") + String(name)));
return (function() {
var env = getRenderEnv(extraEnv);
var comp = envGet(env, fullName);
return (isSxTruthy(!isSxTruthy(isComponent(comp))) ? error((String("Unknown component: ") + String(fullName))) : (function() {
var callExpr = [makeSymbol(fullName)];
{ var _c = keys(kwargs); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; callExpr.push(makeKeyword(toKebab(k)));
callExpr.push(dictGet(kwargs, k)); } }
return renderToDom(callExpr, env, NIL);
})());
})();
})(); };
// process-sx-scripts
var processSxScripts = function(root) { return (function() {
var scripts = querySxScripts(root);
return forEach(function(s) { return (isSxTruthy(!isSxTruthy(isProcessed(s, "script"))) ? (markProcessed(s, "script"), (function() {
var text = domTextContent(s);
return (isSxTruthy(domHasAttr(s, "data-components")) ? processComponentScript(s, text) : (isSxTruthy(sxOr(isNil(text), isEmpty(trim(text)))) ? NIL : (isSxTruthy(domHasAttr(s, "data-mount")) ? (function() {
var mountSel = domGetAttr(s, "data-mount");
var target = domQuery(mountSel);
return (isSxTruthy(target) ? sxMount(target, text, NIL) : NIL);
})() : sxLoadComponents(text))));
})()) : NIL); }, scripts);
})(); };
// process-component-script
var processComponentScript = function(script, text) { return (function() {
var hash = domGetAttr(script, "data-hash");
return (isSxTruthy(isNil(hash)) ? (isSxTruthy((isSxTruthy(text) && !isSxTruthy(isEmpty(trim(text))))) ? sxLoadComponents(text) : NIL) : (function() {
var hasInline = (isSxTruthy(text) && !isSxTruthy(isEmpty(trim(text))));
(function() {
var cachedHash = localStorageGet("sx-components-hash");
return (isSxTruthy((cachedHash == hash)) ? (isSxTruthy(hasInline) ? (localStorageSet("sx-components-hash", hash), localStorageSet("sx-components-src", text), sxLoadComponents(text), logInfo("components: downloaded (cookie stale)")) : (function() {
var cached = localStorageGet("sx-components-src");
return (isSxTruthy(cached) ? (sxLoadComponents(cached), logInfo((String("components: cached (") + String(hash) + String(")")))) : (clearSxCompCookie(), browserReload()));
})()) : (isSxTruthy(hasInline) ? (localStorageSet("sx-components-hash", hash), localStorageSet("sx-components-src", text), sxLoadComponents(text), logInfo((String("components: downloaded (") + String(hash) + String(")")))) : (localStorageRemove("sx-components-hash"), localStorageRemove("sx-components-src"), clearSxCompCookie(), browserReload())));
})();
return setSxCompCookie(hash);
})());
})(); };
// _page-routes
var _pageRoutes = [];
// process-page-scripts
var processPageScripts = function() { return (function() {
var scripts = queryPageScripts();
logInfo((String("pages: found ") + String(len(scripts)) + String(" script tags")));
{ var _c = scripts; for (var _i = 0; _i < _c.length; _i++) { var s = _c[_i]; if (isSxTruthy(!isSxTruthy(isProcessed(s, "pages")))) {
markProcessed(s, "pages");
(function() {
var text = domTextContent(s);
logInfo((String("pages: script text length=") + String((isSxTruthy(text) ? len(text) : 0))));
return (isSxTruthy((isSxTruthy(text) && !isSxTruthy(isEmpty(trim(text))))) ? (function() {
var pages = parse(text);
logInfo((String("pages: parsed ") + String(len(pages)) + String(" entries")));
return forEach(function(page) { return append_b(_pageRoutes, merge(page, {"parsed": parseRoutePattern(get(page, "path"))})); }, pages);
})() : logWarn("pages: script tag is empty"));
})();
} } }
return logInfo((String("pages: ") + String(len(_pageRoutes)) + String(" routes loaded")));
})(); };
// sx-hydrate-islands
var sxHydrateIslands = function(root) { return (function() {
var els = domQueryAll(sxOr(root, domBody()), "[data-sx-island]");
return forEach(function(el) { return (isSxTruthy(!isSxTruthy(isProcessed(el, "island-hydrated"))) ? (markProcessed(el, "island-hydrated"), hydrateIsland(el)) : NIL); }, els);
})(); };
// hydrate-island
var hydrateIsland = function(el) { return (function() {
var name = domGetAttr(el, "data-sx-island");
var stateJson = sxOr(domGetAttr(el, "data-sx-state"), "{}");
return (function() {
var compName = (String("~") + String(name));
var env = getRenderEnv(NIL);
return (function() {
var comp = envGet(env, compName);
return (isSxTruthy(!isSxTruthy(sxOr(isComponent(comp), isIsland(comp)))) ? logWarn((String("hydrate-island: unknown island ") + String(compName))) : (function() {
var kwargs = jsonParse(stateJson);
var disposers = [];
var local = envMerge(componentClosure(comp), env);
{ var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; local[p] = (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL); } }
return (function() {
var bodyDom = withIslandScope(function(disposable) { return append_b(disposers, disposable); }, function() { return renderToDom(componentBody(comp), local, NIL); });
domSetTextContent(el, "");
domAppend(el, bodyDom);
domSetData(el, "sx-disposers", disposers);
processElements(el);
return logInfo((String("hydrated island: ") + String(compName) + String(" (") + String(len(disposers)) + String(" disposers)")));
})();
})());
})();
})();
})(); };
// dispose-island
var disposeIsland = function(el) { return (function() {
var disposers = domGetData(el, "sx-disposers");
return (isSxTruthy(disposers) ? (forEach(function(d) { return (isSxTruthy(isCallable(d)) ? d() : NIL); }, disposers), domSetData(el, "sx-disposers", NIL)) : NIL);
})(); };
// dispose-islands-in
var disposeIslandsIn = function(root) { return (isSxTruthy(root) ? (function() {
var islands = domQueryAll(root, "[data-sx-island]");
return (isSxTruthy((isSxTruthy(islands) && !isSxTruthy(isEmpty(islands)))) ? (logInfo((String("disposing ") + String(len(islands)) + String(" island(s)"))), forEach(disposeIsland, islands)) : NIL);
})() : NIL); };
// boot-init
var bootInit = function() { return (logInfo((String("sx-browser ") + String(SX_VERSION))), initCssTracking(), processPageScripts(), processSxScripts(NIL), sxHydrateElements(NIL), sxHydrateIslands(NIL), processElements(NIL)); };
// === Transpiled from router (client-side route matching) ===
// split-path-segments
var splitPathSegments = function(path) { return (function() {
var trimmed = (isSxTruthy(startsWith(path, "/")) ? slice(path, 1) : path);
return (function() {
var trimmed2 = (isSxTruthy((isSxTruthy(!isSxTruthy(isEmpty(trimmed))) && endsWith(trimmed, "/"))) ? slice(trimmed, 0, (len(trimmed) - 1)) : trimmed);
return (isSxTruthy(isEmpty(trimmed2)) ? [] : split(trimmed2, "/"));
})();
})(); };
// make-route-segment
var makeRouteSegment = function(seg) { return (isSxTruthy((isSxTruthy(startsWith(seg, "<")) && endsWith(seg, ">"))) ? (function() {
var paramName = slice(seg, 1, (len(seg) - 1));
return (function() {
var d = {};
d["type"] = "param";
d["value"] = paramName;
return d;
})();
})() : (function() {
var d = {};
d["type"] = "literal";
d["value"] = seg;
return d;
})()); };
// parse-route-pattern
var parseRoutePattern = function(pattern) { return (function() {
var segments = splitPathSegments(pattern);
return map(makeRouteSegment, segments);
})(); };
// match-route-segments
var matchRouteSegments = function(pathSegs, parsedSegs) { return (isSxTruthy(!isSxTruthy((len(pathSegs) == len(parsedSegs)))) ? NIL : (function() {
var params = {};
var matched = true;
forEachIndexed(function(i, parsedSeg) { return (isSxTruthy(matched) ? (function() {
var pathSeg = nth(pathSegs, i);
var segType = get(parsedSeg, "type");
return (isSxTruthy((segType == "literal")) ? (isSxTruthy(!isSxTruthy((pathSeg == get(parsedSeg, "value")))) ? (matched = false) : NIL) : (isSxTruthy((segType == "param")) ? dictSet(params, get(parsedSeg, "value"), pathSeg) : (matched = false)));
})() : NIL); }, parsedSegs);
return (isSxTruthy(matched) ? params : NIL);
})()); };
// match-route
var matchRoute = function(path, pattern) { return (function() {
var pathSegs = splitPathSegments(path);
var parsedSegs = parseRoutePattern(pattern);
return matchRouteSegments(pathSegs, parsedSegs);
})(); };
// find-matching-route
var findMatchingRoute = function(path, routes) { return (function() {
var pathSegs = splitPathSegments(path);
var result = NIL;
{ var _c = routes; for (var _i = 0; _i < _c.length; _i++) { var route = _c[_i]; if (isSxTruthy(isNil(result))) {
(function() {
var params = matchRouteSegments(pathSegs, get(route, "parsed"));
return (isSxTruthy(!isSxTruthy(isNil(params))) ? (function() {
var matched = merge(route, {});
matched["params"] = params;
return (result = matched);
})() : NIL);
})();
} } }
return result;
})(); };
// === Transpiled from signals (reactive signal runtime) ===
// signal
var signal = function(initialValue) { return makeSignal(initialValue); };
// deref
var deref = function(s) { return (isSxTruthy(!isSxTruthy(isSignal(s))) ? s : (function() {
var ctx = getTrackingContext();
if (isSxTruthy(ctx)) {
trackingContextAddDep(ctx, s);
signalAddSub(s, trackingContextNotifyFn(ctx));
}
return signalValue(s);
})()); };
// reset!
var reset_b = function(s, value) { return (isSxTruthy(isSignal(s)) ? (function() {
var old = signalValue(s);
return (isSxTruthy(!isSxTruthy(isIdentical(old, value))) ? (signalSetValue(s, value), notifySubscribers(s)) : NIL);
})() : NIL); };
// swap!
var swap_b = function(s, f) { var args = Array.prototype.slice.call(arguments, 2); return (isSxTruthy(isSignal(s)) ? (function() {
var old = signalValue(s);
var newVal = apply(f, cons(old, args));
return (isSxTruthy(!isSxTruthy(isIdentical(old, newVal))) ? (signalSetValue(s, newVal), notifySubscribers(s)) : NIL);
})() : NIL); };
// computed
var computed = function(computeFn) { return (function() {
var s = makeSignal(NIL);
var deps = [];
var computeCtx = NIL;
return (function() {
var recompute = function() { { var _c = signalDeps(s); for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, recompute); } }
signalSetDeps(s, []);
return (function() {
var ctx = makeTrackingContext(recompute);
return (function() {
var prev = getTrackingContext();
setTrackingContext(ctx);
return (function() {
var newVal = invoke(computeFn);
setTrackingContext(prev);
signalSetDeps(s, trackingContextDeps(ctx));
return (function() {
var old = signalValue(s);
signalSetValue(s, newVal);
return (isSxTruthy(!isSxTruthy(isIdentical(old, newVal))) ? notifySubscribers(s) : NIL);
})();
})();
})();
})(); };
recompute();
registerInScope(function() { return disposeComputed(s); });
return s;
})();
})(); };
// effect
var effect = function(effectFn) { return (function() {
var deps = [];
var disposed = false;
var cleanupFn = NIL;
return (function() {
var runEffect = function() { return (isSxTruthy(!isSxTruthy(disposed)) ? ((isSxTruthy(cleanupFn) ? invoke(cleanupFn) : NIL), forEach(function(dep) { return signalRemoveSub(dep, runEffect); }, deps), (deps = []), (function() {
var ctx = makeTrackingContext(runEffect);
return (function() {
var prev = getTrackingContext();
setTrackingContext(ctx);
return (function() {
var result = invoke(effectFn);
setTrackingContext(prev);
deps = trackingContextDeps(ctx);
return (isSxTruthy(isCallable(result)) ? (cleanupFn = result) : NIL);
})();
})();
})()) : NIL); };
runEffect();
return (function() {
var disposeFn = function() { disposed = true;
if (isSxTruthy(cleanupFn)) {
invoke(cleanupFn);
}
{ var _c = deps; for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, runEffect); } }
return (deps = []); };
registerInScope(disposeFn);
return disposeFn;
})();
})();
})(); };
// *batch-depth*
var _batchDepth = NIL;
// *batch-queue*
var _batchQueue = [];
// batch
var batch = function(thunk) { _batchDepth = (_batchDepth + 1);
invoke(thunk);
_batchDepth = (_batchDepth - 1);
return (isSxTruthy((_batchDepth == 0)) ? (function() {
var queue = _batchQueue;
_batchQueue = [];
return (function() {
var seen = [];
var pending = [];
{ var _c = queue; for (var _i = 0; _i < _c.length; _i++) { var s = _c[_i]; { var _c = signalSubscribers(s); for (var _i = 0; _i < _c.length; _i++) { var sub = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(seen, sub)))) {
seen.push(sub);
pending.push(sub);
} } } } }
return forEach(function(sub) { return sub(); }, pending);
})();
})() : NIL); };
// notify-subscribers
var notifySubscribers = function(s) { return (isSxTruthy((_batchDepth > 0)) ? (isSxTruthy(!isSxTruthy(contains(_batchQueue, s))) ? append_b(_batchQueue, s) : NIL) : flushSubscribers(s)); };
// flush-subscribers
var flushSubscribers = function(s) { return forEach(function(sub) { return sub(); }, signalSubscribers(s)); };
// dispose-computed
var disposeComputed = function(s) { return (isSxTruthy(isSignal(s)) ? (forEach(function(dep) { return signalRemoveSub(dep, NIL); }, signalDeps(s)), signalSetDeps(s, [])) : NIL); };
// *island-scope*
var _islandScope = NIL;
// with-island-scope
var withIslandScope = function(scopeFn, bodyFn) { return (function() {
var prev = _islandScope;
_islandScope = scopeFn;
return (function() {
var result = bodyFn();
_islandScope = prev;
return result;
})();
})(); };
// register-in-scope
var registerInScope = function(disposable) { return (isSxTruthy(_islandScope) ? _islandScope(disposable) : NIL); };
// *store-registry*
var _storeRegistry = {};
// def-store
var defStore = function(name, initFn) { return (function() {
var registry = _storeRegistry;
if (isSxTruthy(!isSxTruthy(dictHas(registry, name)))) {
_storeRegistry = assoc(registry, name, invoke(initFn));
}
return get(_storeRegistry, name);
})(); };
// use-store
var useStore = function(name) { return (isSxTruthy(dictHas(_storeRegistry, name)) ? get(_storeRegistry, name) : error((String("Store not found: ") + String(name) + String(". Call (def-store ...) before (use-store ...).")))); };
// clear-stores
var clearStores = function() { return (_storeRegistry = {}); };
// emit-event
var emitEvent = function(el, eventName, detail) { return domDispatch(el, eventName, detail); };
// on-event
var onEvent = function(el, eventName, handler) { return domListen(el, eventName, handler); };
// bridge-event
var bridgeEvent = function(el, eventName, targetSignal, transformFn) { return effect(function() { return (function() {
var remove = domListen(el, eventName, function(e) { return (function() {
var detail = eventDetail(e);
var newVal = (isSxTruthy(transformFn) ? invoke(transformFn, detail) : detail);
return reset_b(targetSignal, newVal);
})(); });
return remove;
})(); }); };
// resource
var resource = function(fetchFn) { return (function() {
var state = signal({["loading"]: true, ["data"]: NIL, ["error"]: NIL});
promiseThen(invoke(fetchFn), function(data) { return reset_b(state, {["loading"]: false, ["data"]: data, ["error"]: NIL}); }, function(err) { return reset_b(state, {["loading"]: false, ["data"]: NIL, ["error"]: err}); });
return state;
})(); };
// =========================================================================
// Platform interface — DOM adapter (browser-only)
// =========================================================================
var _hasDom = typeof document !== "undefined";
// Register DOM adapter as the render dispatch target for the evaluator.
_renderExprFn = function(expr, env) { return renderToDom(expr, env, null); };
var SVG_NS = "http://www.w3.org/2000/svg";
var MATH_NS = "http://www.w3.org/1998/Math/MathML";
function domCreateElement(tag, ns) {
if (!_hasDom) return null;
if (ns && ns !== NIL) return document.createElementNS(ns, tag);
return document.createElement(tag);
}
function createTextNode(s) {
return _hasDom ? document.createTextNode(s) : null;
}
function createComment(s) {
return _hasDom ? document.createComment(s || "") : null;
}
function createFragment() {
return _hasDom ? document.createDocumentFragment() : null;
}
function domAppend(parent, child) {
if (parent && child) parent.appendChild(child);
}
function domPrepend(parent, child) {
if (parent && child) parent.insertBefore(child, parent.firstChild);
}
function domSetAttr(el, name, val) {
if (el && el.setAttribute) el.setAttribute(name, val);
}
function domGetAttr(el, name) {
if (!el || !el.getAttribute) return NIL;
var v = el.getAttribute(name);
return v === null ? NIL : v;
}
function domRemoveAttr(el, name) {
if (el && el.removeAttribute) el.removeAttribute(name);
}
function domHasAttr(el, name) {
return !!(el && el.hasAttribute && el.hasAttribute(name));
}
function domParseHtml(html) {
if (!_hasDom) return null;
var tpl = document.createElement("template");
tpl.innerHTML = html;
return tpl.content;
}
function domClone(node) {
return node && node.cloneNode ? node.cloneNode(true) : node;
}
function domParent(el) { return el ? el.parentNode : null; }
function domId(el) { return el && el.id ? el.id : NIL; }
function domNodeType(el) { return el ? el.nodeType : 0; }
function domNodeName(el) { return el ? el.nodeName : ""; }
function domTextContent(el) { return el ? el.textContent || el.nodeValue || "" : ""; }
function domSetTextContent(el, s) { if (el) { if (el.nodeType === 3 || el.nodeType === 8) el.nodeValue = s; else el.textContent = s; } }
function domIsFragment(el) { return el ? el.nodeType === 11 : false; }
function domIsChildOf(child, parent) { return !!(parent && child && child.parentNode === parent); }
function domIsActiveElement(el) { return _hasDom && el === document.activeElement; }
function domIsInputElement(el) {
if (!el || !el.tagName) return false;
var t = el.tagName;
return t === "INPUT" || t === "TEXTAREA" || t === "SELECT";
}
function domFirstChild(el) { return el ? el.firstChild : null; }
function domNextSibling(el) { return el ? el.nextSibling : null; }
function domChildList(el) {
if (!el || !el.childNodes) return [];
return Array.prototype.slice.call(el.childNodes);
}
function domAttrList(el) {
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;
}
function domInsertBefore(parent, node, ref) {
if (parent && node) parent.insertBefore(node, ref || null);
}
function domInsertAfter(ref, node) {
if (ref && ref.parentNode && node) {
ref.parentNode.insertBefore(node, ref.nextSibling);
}
}
function domRemoveChild(parent, child) {
if (parent && child && child.parentNode === parent) parent.removeChild(child);
}
function domReplaceChild(parent, newChild, oldChild) {
if (parent && newChild && oldChild) parent.replaceChild(newChild, oldChild);
}
function domSetInnerHtml(el, html) {
if (el) el.innerHTML = html;
}
function domInsertAdjacentHtml(el, pos, html) {
if (el && el.insertAdjacentHTML) el.insertAdjacentHTML(pos, html);
}
function domGetStyle(el, prop) {
return el && el.style ? el.style[prop] || "" : "";
}
function domSetStyle(el, prop, val) {
if (el && el.style) el.style[prop] = val;
}
function domGetProp(el, name) { return el ? el[name] : NIL; }
function domSetProp(el, name, val) { if (el) el[name] = val; }
function domAddClass(el, cls) {
if (el && el.classList) el.classList.add(cls);
}
function domRemoveClass(el, cls) {
if (el && el.classList) el.classList.remove(cls);
}
function domDispatch(el, name, detail) {
if (!_hasDom || !el) return false;
var evt = new CustomEvent(name, { bubbles: true, cancelable: true, detail: detail || {} });
return el.dispatchEvent(evt);
}
function domListen(el, name, handler) {
if (!_hasDom || !el) return function() {};
// Wrap SX lambdas from runtime-evaluated island code into native fns
var wrapped = isLambda(handler)
? function(e) { invoke(handler, e); }
: handler;
el.addEventListener(name, wrapped);
return function() { el.removeEventListener(name, wrapped); };
}
function eventDetail(e) {
return (e && e.detail != null) ? e.detail : nil;
}
function domQuery(sel) {
return _hasDom ? document.querySelector(sel) : null;
}
function domEnsureElement(sel) {
if (!_hasDom) return null;
var el = document.querySelector(sel);
if (el) return el;
// Parse #id selector → create div with that id, append to body
if (sel.charAt(0) === '#') {
el = document.createElement('div');
el.id = sel.slice(1);
document.body.appendChild(el);
return el;
}
return null;
}
function domQueryAll(root, sel) {
if (!root || !root.querySelectorAll) return [];
return Array.prototype.slice.call(root.querySelectorAll(sel));
}
function domTagName(el) { return el && el.tagName ? el.tagName : ""; }
// Island DOM helpers
function domRemove(node) {
if (node && node.parentNode) node.parentNode.removeChild(node);
}
function domChildNodes(el) {
if (!el || !el.childNodes) return [];
return Array.prototype.slice.call(el.childNodes);
}
function domRemoveChildrenAfter(marker) {
if (!marker || !marker.parentNode) return;
var parent = marker.parentNode;
while (marker.nextSibling) parent.removeChild(marker.nextSibling);
}
function domSetData(el, key, val) {
if (el) { if (!el._sxData) el._sxData = {}; el._sxData[key] = val; }
}
function domGetData(el, key) {
return (el && el._sxData) ? (el._sxData[key] != null ? el._sxData[key] : nil) : nil;
}
function jsonParse(s) {
try { return JSON.parse(s); } catch(e) { return {}; }
}
// renderDomComponent and renderDomElement are transpiled from
// adapter-dom.sx — no imperative overrides needed.
// =========================================================================
// Platform interface — Engine pure logic (browser + node compatible)
// =========================================================================
function browserLocationHref() {
return typeof location !== "undefined" ? location.href : "";
}
function browserSameOrigin(url) {
try { return new URL(url, location.href).origin === location.origin; }
catch (e) { return true; }
}
function browserPushState(url) {
if (typeof history !== "undefined") {
try { history.pushState({ sxUrl: url, scrollY: typeof window !== "undefined" ? window.scrollY : 0 }, "", url); }
catch (e) {}
}
}
function browserReplaceState(url) {
if (typeof history !== "undefined") {
try { history.replaceState({ sxUrl: url, scrollY: typeof window !== "undefined" ? window.scrollY : 0 }, "", url); }
catch (e) {}
}
}
function nowMs() { return Date.now(); }
function parseHeaderValue(s) {
if (!s) return null;
try {
if (s.charAt(0) === "{" && s.charAt(1) === ":") return parse(s);
return JSON.parse(s);
} catch (e) { return null; }
}
// =========================================================================
// Platform interface — Orchestration (browser-only)
// =========================================================================
// --- Browser/Network ---
function browserNavigate(url) {
if (typeof location !== "undefined") location.assign(url);
}
function browserReload() {
if (typeof location !== "undefined") location.reload();
}
function browserScrollTo(x, y) {
if (typeof window !== "undefined") window.scrollTo(x, y);
}
function browserMediaMatches(query) {
if (typeof window === "undefined") return false;
return window.matchMedia(query).matches;
}
function browserConfirm(msg) {
if (typeof window === "undefined") return false;
return window.confirm(msg);
}
function browserPrompt(msg) {
if (typeof window === "undefined") return NIL;
var r = window.prompt(msg);
return r === null ? NIL : r;
}
function csrfToken() {
if (!_hasDom) return NIL;
var m = document.querySelector('meta[name="csrf-token"]');
return m ? m.getAttribute("content") : NIL;
}
function isCrossOrigin(url) {
try {
var h = new URL(url, location.href).hostname;
return h !== location.hostname &&
(h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0);
} catch (e) { return false; }
}
// --- Promises ---
function promiseResolve(val) { return Promise.resolve(val); }
function promiseThen(p, onResolve, onReject) {
if (!p || !p.then) return p;
return onReject ? p.then(onResolve, onReject) : p.then(onResolve);
}
function promiseCatch(p, fn) { return p && p.catch ? p.catch(fn) : p; }
function promiseDelayed(ms, value) {
return new Promise(function(resolve) {
setTimeout(function() { resolve(value); }, ms);
});
}
// --- Abort controllers ---
var _controllers = typeof WeakMap !== "undefined" ? new WeakMap() : null;
function abortPrevious(el) {
if (_controllers) {
var prev = _controllers.get(el);
if (prev) prev.abort();
}
}
function trackController(el, ctrl) {
if (_controllers) _controllers.set(el, ctrl);
}
function newAbortController() {
return typeof AbortController !== "undefined" ? new AbortController() : { signal: null, abort: function() {} };
}
function controllerSignal(ctrl) { return ctrl ? ctrl.signal : null; }
function isAbortError(err) { return err && err.name === "AbortError"; }
// --- Timers ---
function _wrapSxFn(fn) {
if (fn && fn._lambda) {
return function() { return trampoline(callLambda(fn, [], lambdaClosure(fn))); };
}
return fn;
}
function setTimeout_(fn, ms) { return setTimeout(_wrapSxFn(fn), ms || 0); }
function setInterval_(fn, ms) { return setInterval(_wrapSxFn(fn), ms || 1000); }
function clearTimeout_(id) { clearTimeout(id); }
function clearInterval_(id) { clearInterval(id); }
function requestAnimationFrame_(fn) {
var cb = _wrapSxFn(fn);
if (typeof requestAnimationFrame !== "undefined") requestAnimationFrame(cb);
else setTimeout(cb, 16);
}
// --- Fetch ---
function fetchRequest(config, successFn, errorFn) {
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";
var p = (config.preloaded && config.preloaded !== NIL)
? Promise.resolve({
ok: true, status: 200,
headers: new Headers({ "Content-Type": config.preloaded["content-type"] || "" }),
text: function() { return Promise.resolve(config.preloaded.text); }
})
: fetch(config.url, opts);
return p.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 successFn(resp.ok, resp.status, getHeader, text);
});
}).catch(function(err) {
return errorFn(err);
});
}
function fetchLocation(headerVal) {
if (!_hasDom) return;
var locUrl = headerVal;
try { var obj = JSON.parse(headerVal); locUrl = obj.path || obj; } catch (e) {}
fetch(locUrl, { headers: { "SX-Request": "true" } }).then(function(r) {
return r.text().then(function(t) {
var main = document.getElementById("main-panel");
if (main) {
main.innerHTML = t;
postSwap(main);
try { history.pushState({ sxUrl: locUrl }, "", locUrl); } catch (e) {}
}
});
});
}
function fetchAndRestore(main, url, headers, scrollY) {
var opts = { headers: headers };
try {
var h = new URL(url, location.href).hostname;
if (h !== location.hostname &&
(h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0)) {
opts.credentials = "include";
}
} catch (e) {}
fetch(url, opts).then(function(resp) {
return resp.text().then(function(text) {
text = stripComponentScripts(text);
text = extractResponseCss(text);
text = text.trim();
if (text.charAt(0) === "(") {
try {
var dom = sxRender(text);
var container = document.createElement("div");
container.appendChild(dom);
processOobSwaps(container, function(t, oob, s) {
swapDomNodes(t, oob, s);
sxHydrate(t);
processElements(t);
});
var newMain = container.querySelector("#main-panel");
morphChildren(main, newMain || container);
postSwap(main);
if (typeof window !== "undefined") window.scrollTo(0, scrollY || 0);
} catch (err) {
console.error("sx-ref popstate error:", err);
location.reload();
}
} else {
var parser = new DOMParser();
var doc = parser.parseFromString(text, "text/html");
var newMain = doc.getElementById("main-panel");
if (newMain) {
morphChildren(main, newMain);
postSwap(main);
if (typeof window !== "undefined") window.scrollTo(0, scrollY || 0);
} else {
location.reload();
}
}
});
}).catch(function() { location.reload(); });
}
function fetchStreaming(target, url, headers) {
// Streaming fetch for multi-stream pages.
// First chunk = OOB SX swap (shell with skeletons).
// Subsequent chunks = __sxResolve script tags filling suspense slots.
var opts = { headers: headers };
try {
var h = new URL(url, location.href).hostname;
if (h !== location.hostname &&
(h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0)) {
opts.credentials = "include";
}
} catch (e) {}
fetch(url, opts).then(function(resp) {
if (!resp.ok || !resp.body) {
// Fallback: non-streaming
return resp.text().then(function(text) {
text = stripComponentScripts(text);
text = extractResponseCss(text);
text = text.trim();
if (text.charAt(0) === "(") {
var dom = sxRender(text);
var container = document.createElement("div");
container.appendChild(dom);
processOobSwaps(container, function(t, oob, s) {
swapDomNodes(t, oob, s);
sxHydrate(t);
processElements(t);
});
var newMain = container.querySelector("#main-panel");
morphChildren(target, newMain || container);
postSwap(target);
}
});
}
var reader = resp.body.getReader();
var decoder = new TextDecoder();
var buffer = "";
var initialSwapDone = false;
// Regex to match __sxResolve script tags
var RESOLVE_START = "<script>window.__sxResolve&&window.__sxResolve(";
var RESOLVE_END = ")</script>";
function processResolveScripts() {
// Strip and load any extra component defs before resolve scripts
buffer = stripSxScripts(buffer);
var idx;
while ((idx = buffer.indexOf(RESOLVE_START)) >= 0) {
var endIdx = buffer.indexOf(RESOLVE_END, idx);
if (endIdx < 0) break; // incomplete, wait for more data
var argsStr = buffer.substring(idx + RESOLVE_START.length, endIdx);
buffer = buffer.substring(endIdx + RESOLVE_END.length);
// argsStr is: "stream-id","sx source"
var commaIdx = argsStr.indexOf(",");
if (commaIdx >= 0) {
try {
var id = JSON.parse(argsStr.substring(0, commaIdx));
var sx = JSON.parse(argsStr.substring(commaIdx + 1));
if (typeof Sx !== "undefined" && Sx.resolveSuspense) {
Sx.resolveSuspense(id, sx);
}
} catch (e) {
console.error("[sx-ref] resolve parse error:", e);
}
}
}
}
function pump() {
return reader.read().then(function(result) {
buffer += decoder.decode(result.value || new Uint8Array(), { stream: !result.done });
if (!initialSwapDone) {
// Look for the first resolve script — everything before it is OOB content
var scriptIdx = buffer.indexOf("<script>window.__sxResolve");
// If we found a script tag, or the stream is done, process OOB
var oobEnd = scriptIdx >= 0 ? scriptIdx : (result.done ? buffer.length : -1);
if (oobEnd >= 0) {
var oobContent = buffer.substring(0, oobEnd);
buffer = buffer.substring(oobEnd);
initialSwapDone = true;
// Process OOB SX content (same as fetchAndRestore)
oobContent = stripComponentScripts(oobContent);
// Also strip bare <script type="text/sx"> (extra defs from resolve chunks)
oobContent = stripSxScripts(oobContent);
oobContent = extractResponseCss(oobContent);
oobContent = oobContent.trim();
if (oobContent.charAt(0) === "(") {
try {
var dom = sxRender(oobContent);
var container = document.createElement("div");
container.appendChild(dom);
processOobSwaps(container, function(t, oob, s) {
swapDomNodes(t, oob, s);
sxHydrate(t);
processElements(t);
});
var newMain = container.querySelector("#main-panel");
morphChildren(target, newMain || container);
postSwap(target);
// Dispatch clientRoute so nav links update active state
domDispatch(target, "sx:clientRoute",
{ pathname: new URL(url, location.href).pathname });
} catch (err) {
console.error("[sx-ref] streaming OOB swap error:", err);
}
}
// Process any resolve scripts already in buffer
processResolveScripts();
}
} else {
// Process resolve scripts as they arrive
processResolveScripts();
}
if (!result.done) return pump();
});
}
return pump();
}).catch(function(err) {
console.error("[sx-ref] streaming fetch error:", err);
location.reload();
});
}
function fetchPreload(url, headers, cache) {
fetch(url, { headers: headers }).then(function(resp) {
if (!resp.ok) return;
var ct = resp.headers.get("Content-Type") || "";
return resp.text().then(function(text) {
preloadCacheSet(cache, url, text, ct);
});
}).catch(function() { /* ignore */ });
}
// --- Request body building ---
function buildRequestBody(el, method, url) {
if (!_hasDom) return { body: null, url: url, "content-type": NIL };
var body = null;
var ct = NIL;
var finalUrl = url;
var isJson = el.getAttribute("sx-encoding") === "json";
if (method !== "GET") {
var form = el.closest("form") || (el.tagName === "FORM" ? el : null);
if (form) {
if (isJson) {
var fd = new FormData(form);
var obj = {};
fd.forEach(function(v, k) {
if (obj[k] !== undefined) {
if (!Array.isArray(obj[k])) obj[k] = [obj[k]];
obj[k].push(v);
} else { obj[k] = v; }
});
body = JSON.stringify(obj);
ct = "application/json";
} else {
body = new URLSearchParams(new FormData(form));
ct = "application/x-www-form-urlencoded";
}
}
}
// sx-params
var paramsSpec = el.getAttribute("sx-params");
if (paramsSpec && body instanceof URLSearchParams) {
if (paramsSpec === "none") {
body = new URLSearchParams();
} else if (paramsSpec.indexOf("not ") === 0) {
paramsSpec.substring(4).split(",").forEach(function(k) { body.delete(k.trim()); });
} else if (paramsSpec !== "*") {
var allowed = paramsSpec.split(",").map(function(s) { return s.trim(); });
var filtered = new URLSearchParams();
allowed.forEach(function(k) {
body.getAll(k).forEach(function(v) { filtered.append(k, v); });
});
body = filtered;
}
}
// sx-include
var includeSel = el.getAttribute("sx-include");
if (includeSel && method !== "GET") {
if (!body) body = new URLSearchParams();
document.querySelectorAll(includeSel).forEach(function(inp) {
if (inp.name) body.append(inp.name, inp.value);
});
}
// sx-vals
var valsAttr = el.getAttribute("sx-vals");
if (valsAttr) {
try {
var vals = valsAttr.charAt(0) === "{" && valsAttr.charAt(1) === ":" ? parse(valsAttr) : JSON.parse(valsAttr);
if (method === "GET") {
for (var vk in vals) finalUrl += (finalUrl.indexOf("?") >= 0 ? "&" : "?") + encodeURIComponent(vk) + "=" + encodeURIComponent(vals[vk]);
} else if (body instanceof URLSearchParams) {
for (var vk2 in vals) body.append(vk2, vals[vk2]);
} else if (!body) {
body = new URLSearchParams();
for (var vk3 in vals) body.append(vk3, vals[vk3]);
ct = "application/x-www-form-urlencoded";
}
} catch (e) {}
}
// GET form data → URL
if (method === "GET") {
var form2 = el.closest("form") || (el.tagName === "FORM" ? el : null);
if (form2) {
var qs = new URLSearchParams(new FormData(form2)).toString();
if (qs) finalUrl += (finalUrl.indexOf("?") >= 0 ? "&" : "?") + qs;
}
if ((el.tagName === "INPUT" || el.tagName === "SELECT" || el.tagName === "TEXTAREA") && el.name) {
finalUrl += (finalUrl.indexOf("?") >= 0 ? "&" : "?") + encodeURIComponent(el.name) + "=" + encodeURIComponent(el.value);
}
}
return { body: body, url: finalUrl, "content-type": ct };
}
// --- Loading state ---
function showIndicator(el) {
if (!_hasDom) return NIL;
var sel = el.getAttribute("sx-indicator");
var ind = sel ? (document.querySelector(sel) || el.closest(sel)) : null;
if (ind) { ind.classList.add("sx-request"); ind.style.display = ""; }
return ind || NIL;
}
function disableElements(el) {
if (!_hasDom) return [];
var sel = el.getAttribute("sx-disabled-elt");
if (!sel) return [];
var elts = Array.prototype.slice.call(document.querySelectorAll(sel));
elts.forEach(function(e) { e.disabled = true; });
return elts;
}
function clearLoadingState(el, indicator, disabledElts) {
el.classList.remove("sx-request");
el.removeAttribute("aria-busy");
if (indicator && !isNil(indicator)) {
indicator.classList.remove("sx-request");
indicator.style.display = "none";
}
if (disabledElts) {
for (var i = 0; i < disabledElts.length; i++) disabledElts[i].disabled = false;
}
}
// --- DOM extras ---
function domQueryById(id) {
return _hasDom ? document.getElementById(id) : null;
}
function domMatches(el, sel) {
return el && el.matches ? el.matches(sel) : false;
}
function domClosest(el, sel) {
return el && el.closest ? el.closest(sel) : null;
}
function domBody() {
return _hasDom ? document.body : null;
}
function domHasClass(el, cls) {
return el && el.classList ? el.classList.contains(cls) : false;
}
function domAppendToHead(el) {
if (_hasDom && document.head) document.head.appendChild(el);
}
function domParseHtmlDocument(text) {
if (!_hasDom) return null;
return new DOMParser().parseFromString(text, "text/html");
}
function domOuterHtml(el) {
return el ? el.outerHTML : "";
}
function domBodyInnerHtml(doc) {
return doc && doc.body ? doc.body.innerHTML : "";
}
// --- Events ---
function preventDefault_(e) { if (e && e.preventDefault) e.preventDefault(); }
function stopPropagation_(e) { if (e && e.stopPropagation) e.stopPropagation(); }
function domFocus(el) { if (el && el.focus) el.focus(); }
function tryCatch(tryFn, catchFn) {
var t = _wrapSxFn(tryFn);
var c = catchFn && catchFn._lambda
? function(e) { return trampoline(callLambda(catchFn, [e], lambdaClosure(catchFn))); }
: catchFn;
try { return t(); } catch (e) { return c(e); }
}
function errorMessage(e) {
return e && e.message ? e.message : String(e);
}
function scheduleIdle(fn) {
var cb = _wrapSxFn(fn);
if (typeof requestIdleCallback !== "undefined") requestIdleCallback(cb);
else setTimeout(cb, 0);
}
function elementValue(el) { return el && el.value !== undefined ? el.value : NIL; }
function domAddListener(el, event, fn, opts) {
if (!el || !el.addEventListener) return;
var o = {};
if (opts && !isNil(opts)) {
if (opts.once || opts["once"]) o.once = true;
}
el.addEventListener(event, function(e) {
try { fn(e); } catch (err) { logInfo("EVENT ERROR: " + event + " " + (err && err.message ? err.message : err)); console.error("[sx-ref] event handler error:", event, err); }
}, o);
}
// --- Validation ---
function validateForRequest(el) {
if (!_hasDom) return true;
var attr = el.getAttribute("sx-validate");
if (attr === null) {
var vForm = el.closest("[sx-validate]");
if (vForm) attr = vForm.getAttribute("sx-validate");
}
if (attr === null) return true; // no validation configured
var form = el.tagName === "FORM" ? el : el.closest("form");
if (form && !form.reportValidity()) return false;
if (attr && attr !== "true" && attr !== "") {
var fn = window[attr];
if (typeof fn === "function" && !fn(el)) return false;
}
return true;
}
// --- View Transitions ---
function withTransition(enabled, fn) {
if (enabled && _hasDom && document.startViewTransition) {
document.startViewTransition(fn);
} else {
fn();
}
}
// --- IntersectionObserver ---
function observeIntersection(el, fn, once, delay) {
if (!_hasDom || !("IntersectionObserver" in window)) { fn(); return; }
var fired = false;
var d = isNil(delay) ? 0 : delay;
var obs = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (!entry.isIntersecting) return;
if (once && fired) return;
fired = true;
if (once) obs.unobserve(el);
if (d) setTimeout(fn, d); else fn();
});
});
obs.observe(el);
}
// --- EventSource ---
function eventSourceConnect(url, el) {
var source = new EventSource(url);
source.addEventListener("error", function() { domDispatch(el, "sx:sseError", {}); });
source.addEventListener("open", function() { domDispatch(el, "sx:sseOpen", {}); });
if (typeof MutationObserver !== "undefined") {
var obs = new MutationObserver(function() {
if (!document.body.contains(el)) { source.close(); obs.disconnect(); }
});
obs.observe(document.body, { childList: true, subtree: true });
}
return source;
}
function eventSourceListen(source, event, fn) {
source.addEventListener(event, function(e) { fn(e.data); });
}
// --- Boost bindings ---
function bindBoostLink(el, _href) {
el.addEventListener("click", function(e) {
e.preventDefault();
// Re-read href from element at click time (not closed-over value)
var liveHref = el.getAttribute("href") || _href;
executeRequest(el, { method: "GET", url: liveHref }).then(function() {
try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {}
});
});
}
function bindBoostForm(form, _method, _action) {
form.addEventListener("submit", function(e) {
e.preventDefault();
// Re-read from element at submit time
var liveMethod = (form.getAttribute("method") || _method || "GET").toUpperCase();
var liveAction = form.getAttribute("action") || _action || location.href;
executeRequest(form, { method: liveMethod, url: liveAction }).then(function() {
try { history.pushState({ sxUrl: liveAction, scrollY: window.scrollY }, "", liveAction); } catch (err) {}
});
});
}
// --- Client-side route bindings ---
function bindClientRouteClick(link, _href, fallbackFn) {
link.addEventListener("click", function(e) {
e.preventDefault();
// Re-read href from element at click time (not closed-over value)
var liveHref = link.getAttribute("href") || _href;
var pathname = urlPathname(liveHref);
// Find target selector: sx-boost ancestor, explicit sx-target, or #main-panel
var boostEl = link.closest("[sx-boost]");
var targetSel = boostEl ? boostEl.getAttribute("sx-boost") : null;
if (!targetSel || targetSel === "true") {
targetSel = link.getAttribute("sx-target") || "#main-panel";
}
if (tryClientRoute(pathname, targetSel)) {
try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {}
if (typeof window !== "undefined") window.scrollTo(0, 0);
} else {
logInfo("sx:route server " + pathname);
executeRequest(link, { method: "GET", url: liveHref }).then(function() {
try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {}
}).catch(function(err) {
logWarn("sx:route server fetch error: " + (err && err.message ? err.message : err));
});
}
});
}
function tryEvalContent(source, env) {
try {
var merged = merge(componentEnv);
if (env && !isNil(env)) {
var ks = Object.keys(env);
for (var i = 0; i < ks.length; i++) merged[ks[i]] = env[ks[i]];
}
return sxRenderWithEnv(source, merged);
} catch (e) {
logInfo("sx:route eval miss: " + (e && e.message ? e.message : e));
return NIL;
}
}
// Async eval with callback — used for pages with IO deps.
// Calls callback(rendered) when done, callback(null) on failure.
function tryAsyncEvalContent(source, env, callback) {
var merged = merge(componentEnv);
if (env && !isNil(env)) {
var ks = Object.keys(env);
for (var i = 0; i < ks.length; i++) merged[ks[i]] = env[ks[i]];
}
try {
var result = asyncSxRenderWithEnv(source, merged);
if (isPromise(result)) {
result.then(function(rendered) {
callback(rendered);
}).catch(function(e) {
logWarn("sx:async eval miss: " + (e && e.message ? e.message : e));
callback(null);
});
} else {
callback(result);
}
} catch (e) {
logInfo("sx:async eval miss: " + (e && e.message ? e.message : e));
callback(null);
}
}
function resolvePageData(pageName, params, callback) {
// Platform implementation: fetch page data via HTTP from /sx/data/ endpoint.
// The spec only knows about resolve-page-data(name, params, callback) —
// this function provides the concrete transport.
var url = "/sx/data/" + encodeURIComponent(pageName);
if (params && !isNil(params)) {
var qs = [];
var ks = Object.keys(params);
for (var i = 0; i < ks.length; i++) {
var v = params[ks[i]];
if (v !== null && v !== undefined && v !== NIL) {
qs.push(encodeURIComponent(ks[i]) + "=" + encodeURIComponent(v));
}
}
if (qs.length) url += "?" + qs.join("&");
}
var headers = { "SX-Request": "true" };
fetch(url, { headers: headers }).then(function(resp) {
if (!resp.ok) {
logWarn("sx:data resolve failed " + resp.status + " for " + pageName);
return;
}
return resp.text().then(function(text) {
try {
var exprs = parse(text);
var data = exprs.length === 1 ? exprs[0] : {};
callback(data || {});
} catch (e) {
logWarn("sx:data parse error for " + pageName + ": " + (e && e.message ? e.message : e));
}
});
}).catch(function(err) {
logWarn("sx:data resolve error for " + pageName + ": " + (err && err.message ? err.message : err));
});
}
function parseSxData(text) {
// Parse SX text into a data value. Returns the first parsed expression,
// or NIL on error. Used by cache update directives.
try {
var exprs = parse(text);
return exprs.length >= 1 ? exprs[0] : NIL;
} catch (e) {
logWarn("sx:cache parse error: " + (e && e.message ? e.message : e));
return NIL;
}
}
function swPostMessage(msg) {
// Send a message to the active service worker (if registered).
// Used to notify SW of cache invalidation.
if (typeof navigator !== "undefined" && navigator.serviceWorker &&
navigator.serviceWorker.controller) {
navigator.serviceWorker.controller.postMessage(msg);
}
}
function urlPathname(href) {
try {
return new URL(href, location.href).pathname;
} catch (e) {
// Fallback: strip query/hash
var idx = href.indexOf("?");
if (idx >= 0) href = href.substring(0, idx);
idx = href.indexOf("#");
if (idx >= 0) href = href.substring(0, idx);
return href;
}
}
// --- Inline handlers ---
function bindInlineHandler(el, eventName, body) {
el.addEventListener(eventName, new Function("event", body));
}
// --- Preload binding ---
function bindPreload(el, events, debounceMs, fn) {
var timer = null;
events.forEach(function(evt) {
el.addEventListener(evt, function() {
if (debounceMs) {
clearTimeout(timer);
timer = setTimeout(fn, debounceMs);
} else {
fn();
}
});
});
}
// --- Processing markers ---
var PROCESSED = "_sxBound";
function markProcessed(el, key) { el[PROCESSED + key] = true; }
function isProcessed(el, key) { return !!el[PROCESSED + key]; }
// --- Script cloning ---
function createScriptClone(dead) {
var live = document.createElement("script");
for (var i = 0; i < dead.attributes.length; i++)
live.setAttribute(dead.attributes[i].name, dead.attributes[i].value);
live.textContent = dead.textContent;
return live;
}
// --- SX API references ---
function sxRender(source) {
var SxObj = typeof Sx !== "undefined" ? Sx : null;
if (SxObj && SxObj.render) return SxObj.render(source);
throw new Error("No SX renderer available");
}
function sxProcessScripts(root) {
var SxObj = typeof Sx !== "undefined" ? Sx : null;
var r = (root && root !== NIL) ? root : undefined;
if (SxObj && SxObj.processScripts) SxObj.processScripts(r);
}
function sxHydrate(root) {
var SxObj = typeof Sx !== "undefined" ? Sx : null;
var r = (root && root !== NIL) ? root : undefined;
if (SxObj && SxObj.hydrate) SxObj.hydrate(r);
}
function loadedComponentNames() {
var SxObj = typeof Sx !== "undefined" ? Sx : null;
if (!SxObj) return [];
var env = SxObj.componentEnv || (SxObj.getEnv ? SxObj.getEnv() : {});
return Object.keys(env).filter(function(k) { return k.charAt(0) === "~"; });
}
// --- Response processing ---
function stripComponentScripts(text) {
var SxObj = typeof Sx !== "undefined" ? Sx : null;
return text.replace(/<script[^>]*type="text\/sx"[^>]*data-components[^>]*>([\s\S]*?)<\/script>/gi,
function(_, defs) { if (SxObj && SxObj.loadComponents) SxObj.loadComponents(defs); return ""; });
}
function stripSxScripts(text) {
// Strip <script type="text/sx">...</script> (without data-components).
// These contain extra component defs from streaming resolve chunks.
var SxObj = typeof Sx !== "undefined" ? Sx : null;
return text.replace(/<script[^>]*type="text\/sx"[^>]*>([\s\S]*?)<\/script>/gi,
function(_, defs) { if (SxObj && SxObj.loadComponents) SxObj.loadComponents(defs); return ""; });
}
function extractResponseCss(text) {
if (!_hasDom) return text;
var target = document.getElementById("sx-css");
if (!target) return text;
return text.replace(/<style[^>]*data-sx-css[^>]*>([\s\S]*?)<\/style>/gi,
function(_, css) { target.textContent += css; return ""; });
}
function selectFromContainer(container, sel) {
var frag = document.createDocumentFragment();
sel.split(",").forEach(function(s) {
container.querySelectorAll(s.trim()).forEach(function(m) { frag.appendChild(m); });
});
return frag;
}
function childrenToFragment(container) {
var frag = document.createDocumentFragment();
while (container.firstChild) frag.appendChild(container.firstChild);
return frag;
}
function selectHtmlFromDoc(doc, sel) {
var parts = sel.split(",").map(function(s) { return s.trim(); });
var frags = [];
parts.forEach(function(s) {
doc.querySelectorAll(s).forEach(function(m) { frags.push(m.outerHTML); });
});
return frags.join("");
}
// --- Parsing ---
function tryParseJson(s) {
if (!s) return NIL;
try { return JSON.parse(s); } catch (e) { return NIL; }
}
// =========================================================================
// Platform interface — Boot (mount, hydrate, scripts, cookies)
// =========================================================================
function resolveMountTarget(target) {
if (typeof target === "string") return _hasDom ? document.querySelector(target) : null;
return target;
}
function sxRenderWithEnv(source, extraEnv) {
var env = extraEnv ? merge(componentEnv, extraEnv) : componentEnv;
var exprs = parse(source);
if (!_hasDom) return null;
var frag = document.createDocumentFragment();
for (var i = 0; i < exprs.length; i++) {
var node = renderToDom(exprs[i], env, null);
if (node) frag.appendChild(node);
}
return frag;
}
function getRenderEnv(extraEnv) {
return extraEnv ? merge(componentEnv, extraEnv) : componentEnv;
}
function mergeEnvs(base, newEnv) {
return newEnv ? merge(componentEnv, base, newEnv) : merge(componentEnv, base);
}
function sxLoadComponents(text) {
try {
var exprs = parse(text);
for (var i = 0; i < exprs.length; i++) trampoline(evalExpr(exprs[i], componentEnv));
} catch (err) {
logParseError("loadComponents", text, err);
throw err;
}
}
function setDocumentTitle(s) {
if (_hasDom) document.title = s || "";
}
function removeHeadElement(sel) {
if (!_hasDom) return;
var old = document.head.querySelector(sel);
if (old) old.parentNode.removeChild(old);
}
function querySxScripts(root) {
if (!_hasDom) return [];
var r = (root && root !== NIL) ? root : document;
return Array.prototype.slice.call(
r.querySelectorAll('script[type="text/sx"]'));
}
function queryPageScripts() {
if (!_hasDom) return [];
return Array.prototype.slice.call(
document.querySelectorAll('script[type="text/sx-pages"]'));
}
// --- localStorage ---
function localStorageGet(key) {
try { var v = localStorage.getItem(key); return v === null ? NIL : v; }
catch (e) { return NIL; }
}
function localStorageSet(key, val) {
try { localStorage.setItem(key, val); } catch (e) {}
}
function localStorageRemove(key) {
try { localStorage.removeItem(key); } catch (e) {}
}
// --- Cookies ---
function setSxCompCookie(hash) {
if (_hasDom) document.cookie = "sx-comp-hash=" + hash + ";path=/;max-age=31536000;SameSite=Lax";
}
function clearSxCompCookie() {
if (_hasDom) document.cookie = "sx-comp-hash=;path=/;max-age=0;SameSite=Lax";
}
// --- Env helpers ---
function parseEnvAttr(el) {
var attr = el && el.getAttribute ? el.getAttribute("data-sx-env") : null;
if (!attr) return {};
try { return JSON.parse(attr); } catch (e) { return {}; }
}
function storeEnvAttr(el, base, newEnv) {
var merged = merge(base, newEnv);
if (el && el.setAttribute) el.setAttribute("data-sx-env", JSON.stringify(merged));
}
function toKebab(s) { return s.replace(/_/g, "-"); }
// --- Logging ---
function logInfo(msg) {
if (typeof console !== "undefined") console.log("[sx-ref] " + msg);
}
function logWarn(msg) {
if (typeof console !== "undefined") console.warn("[sx-ref] " + msg);
}
function logParseError(label, text, err) {
if (typeof console === "undefined") return;
var msg = err && err.message ? err.message : String(err);
var colMatch = msg.match(/col (\d+)/);
var lineMatch = msg.match(/line (\d+)/);
if (colMatch && text) {
var errLine = lineMatch ? parseInt(lineMatch[1]) : 1;
var errCol = parseInt(colMatch[1]);
var lines = text.split("\n");
var pos = 0;
for (var i = 0; i < errLine - 1 && i < lines.length; i++) pos += lines[i].length + 1;
pos += errCol;
var ws = 80;
var start = Math.max(0, pos - ws);
var end = Math.min(text.length, pos + ws);
console.error("[sx-ref] " + label + ":", msg,
"\n around error (pos ~" + pos + "):",
"\n \u00ab" + text.substring(start, pos) + "\u26d4" + text.substring(pos, end) + "\u00bb");
} else {
console.error("[sx-ref] " + label + ":", msg);
}
}
// =========================================================================
// Post-transpilation fixups
// =========================================================================
// The reference spec's call-lambda only handles Lambda objects, but HO forms
// (map, reduce, etc.) may receive native primitives. Wrap to handle both.
var _rawCallLambda = callLambda;
callLambda = function(f, args, callerEnv) {
if (typeof f === "function") return f.apply(null, args);
return _rawCallLambda(f, args, callerEnv);
};
// Expose render functions as primitives so SX code can call them
if (typeof renderToHtml === "function") PRIMITIVES["render-to-html"] = renderToHtml;
if (typeof renderToSx === "function") PRIMITIVES["render-to-sx"] = renderToSx;
if (typeof aser === "function") PRIMITIVES["aser"] = aser;
if (typeof renderToDom === "function") PRIMITIVES["render-to-dom"] = renderToDom;
// Expose signal functions as primitives so runtime-evaluated SX code
// (e.g. island bodies from .sx files) can call them
PRIMITIVES["signal"] = signal;
PRIMITIVES["signal?"] = isSignal;
PRIMITIVES["deref"] = deref;
PRIMITIVES["reset!"] = reset_b;
PRIMITIVES["swap!"] = swap_b;
PRIMITIVES["computed"] = computed;
PRIMITIVES["effect"] = effect;
PRIMITIVES["batch"] = batch;
// Timer primitives for island code
PRIMITIVES["set-interval"] = setInterval_;
PRIMITIVES["clear-interval"] = clearInterval_;
// Reactive DOM helpers for island code
PRIMITIVES["reactive-text"] = reactiveText;
PRIMITIVES["create-text-node"] = createTextNode;
PRIMITIVES["dom-set-text-content"] = domSetTextContent;
PRIMITIVES["dom-listen"] = domListen;
PRIMITIVES["dom-dispatch"] = domDispatch;
PRIMITIVES["event-detail"] = eventDetail;
PRIMITIVES["resource"] = resource;
PRIMITIVES["promise-delayed"] = promiseDelayed;
PRIMITIVES["promise-then"] = promiseThen;
PRIMITIVES["def-store"] = defStore;
PRIMITIVES["use-store"] = useStore;
PRIMITIVES["emit-event"] = emitEvent;
PRIMITIVES["on-event"] = onEvent;
PRIMITIVES["bridge-event"] = bridgeEvent;
// DOM primitives for island code
PRIMITIVES["dom-focus"] = domFocus;
PRIMITIVES["dom-tag-name"] = domTagName;
PRIMITIVES["dom-get-prop"] = domGetProp;
PRIMITIVES["stop-propagation"] = stopPropagation_;
PRIMITIVES["error-message"] = errorMessage;
PRIMITIVES["schedule-idle"] = scheduleIdle;
PRIMITIVES["invoke"] = invoke;
PRIMITIVES["error"] = function(msg) { throw new Error(msg); };
PRIMITIVES["filter"] = filter;
// =========================================================================
// Async IO: Promise-aware rendering for client-side IO primitives
// =========================================================================
//
// IO primitives (query, current-user, etc.) return Promises on the client.
// asyncRenderToDom walks the component tree; when it encounters an IO
// primitive, it awaits the Promise and continues rendering.
//
// The sync evaluator/renderer is untouched. This is a separate async path
// used only when a page's component tree contains IO references.
var IO_PRIMITIVES = {};
function registerIoPrimitive(name, fn) {
IO_PRIMITIVES[name] = fn;
}
function isPromise(x) {
return x != null && typeof x === "object" && typeof x.then === "function";
}
// Async trampoline: resolves thunks, awaits Promises
function asyncTrampoline(val) {
if (isPromise(val)) return val.then(asyncTrampoline);
if (isThunk(val)) return asyncTrampoline(evalExpr(thunkExpr(val), thunkEnv(val)));
return val;
}
// Async eval: like trampoline(evalExpr(...)) but handles IO primitives
function asyncEval(expr, env) {
// Intercept IO primitive calls at the AST level
if (Array.isArray(expr) && expr.length > 0) {
var head = expr[0];
if (head && head._sym) {
var name = head.name;
if (IO_PRIMITIVES[name]) {
// Evaluate args, then call the IO primitive
return asyncEvalIoCall(name, expr.slice(1), env);
}
}
}
// Non-IO: use sync eval, but result might be a thunk
var result = evalExpr(expr, env);
return asyncTrampoline(result);
}
function asyncEvalIoCall(name, rawArgs, env) {
// Parse keyword args and positional args, evaluating each (may be async)
var kwargs = {};
var args = [];
var promises = [];
var i = 0;
while (i < rawArgs.length) {
var arg = rawArgs[i];
if (arg && arg._kw && (i + 1) < rawArgs.length) {
var kName = arg.name;
var kVal = asyncEval(rawArgs[i + 1], env);
if (isPromise(kVal)) {
(function(k) { promises.push(kVal.then(function(v) { kwargs[k] = v; })); })(kName);
} else {
kwargs[kName] = kVal;
}
i += 2;
} else {
var aVal = asyncEval(arg, env);
if (isPromise(aVal)) {
(function(idx) { promises.push(aVal.then(function(v) { args[idx] = v; })); })(args.length);
args.push(null); // placeholder
} else {
args.push(aVal);
}
i++;
}
}
var ioFn = IO_PRIMITIVES[name];
if (promises.length > 0) {
return Promise.all(promises).then(function() { return ioFn(args, kwargs); });
}
return ioFn(args, kwargs);
}
// Async render-to-dom: returns Promise<Node> or Node
function asyncRenderToDom(expr, env, ns) {
// Literals
if (expr === NIL || expr === null || expr === undefined) return null;
if (expr === true || expr === false) return null;
if (typeof expr === "string") return document.createTextNode(expr);
if (typeof expr === "number") return document.createTextNode(String(expr));
// Symbol -> async eval then render
if (expr && expr._sym) {
var val = asyncEval(expr, env);
if (isPromise(val)) return val.then(function(v) { return asyncRenderToDom(v, env, ns); });
return asyncRenderToDom(val, env, ns);
}
// Keyword
if (expr && expr._kw) return document.createTextNode(expr.name);
// DocumentFragment / DOM nodes pass through
if (expr instanceof DocumentFragment || (expr && expr.nodeType)) return expr;
// Dict -> skip
if (expr && typeof expr === "object" && !Array.isArray(expr)) return null;
// List
if (!Array.isArray(expr) || expr.length === 0) return null;
var head = expr[0];
if (!head) return null;
// Symbol head
if (head._sym) {
var hname = head.name;
// IO primitive
if (IO_PRIMITIVES[hname]) {
var ioResult = asyncEval(expr, env);
if (isPromise(ioResult)) return ioResult.then(function(v) { return asyncRenderToDom(v, env, ns); });
return asyncRenderToDom(ioResult, env, ns);
}
// Fragment
if (hname === "<>") return asyncRenderChildren(expr.slice(1), env, ns);
// raw!
if (hname === "raw!") {
return asyncEvalRaw(expr.slice(1), env);
}
// Special forms that need async handling
if (hname === "if") return asyncRenderIf(expr, env, ns);
if (hname === "when") return asyncRenderWhen(expr, env, ns);
if (hname === "cond") return asyncRenderCond(expr, env, ns);
if (hname === "case") return asyncRenderCase(expr, env, ns);
if (hname === "let" || hname === "let*") return asyncRenderLet(expr, env, ns);
if (hname === "begin" || hname === "do") return asyncRenderChildren(expr.slice(1), env, ns);
if (hname === "map") return asyncRenderMap(expr, env, ns);
if (hname === "map-indexed") return asyncRenderMapIndexed(expr, env, ns);
if (hname === "for-each") return asyncRenderMap(expr, env, ns);
// define/defcomp/defmacro — eval for side effects
if (hname === "define" || hname === "defcomp" || hname === "defmacro" ||
hname === "defstyle" || hname === "defhandler") {
trampoline(evalExpr(expr, env));
return null;
}
// quote
if (hname === "quote") return null;
// lambda/fn
if (hname === "lambda" || hname === "fn") {
trampoline(evalExpr(expr, env));
return null;
}
// and/or — eval and render result
if (hname === "and" || hname === "or" || hname === "->") {
var aoResult = asyncEval(expr, env);
if (isPromise(aoResult)) return aoResult.then(function(v) { return asyncRenderToDom(v, env, ns); });
return asyncRenderToDom(aoResult, env, ns);
}
// set!
if (hname === "set!") {
asyncEval(expr, env);
return null;
}
// Component or Island
if (hname.charAt(0) === "~") {
var comp = env[hname];
if (comp && comp._island) return renderDomIsland(comp, expr.slice(1), env, ns);
if (comp && comp._component) return asyncRenderComponent(comp, expr.slice(1), env, ns);
if (comp && comp._macro) {
var expanded = trampoline(expandMacro(comp, expr.slice(1), env));
return asyncRenderToDom(expanded, env, ns);
}
}
// Macro
if (env[hname] && env[hname]._macro) {
var mac = env[hname];
var expanded = trampoline(expandMacro(mac, expr.slice(1), env));
return asyncRenderToDom(expanded, env, ns);
}
// HTML tag
if (typeof renderDomElement === "function" && contains(HTML_TAGS, hname)) {
return asyncRenderElement(hname, expr.slice(1), env, ns);
}
// html: prefix
if (hname.indexOf("html:") === 0) {
return asyncRenderElement(hname.slice(5), expr.slice(1), env, ns);
}
// Custom element
if (hname.indexOf("-") >= 0 && expr.length > 1 && expr[1] && expr[1]._kw) {
return asyncRenderElement(hname, expr.slice(1), env, ns);
}
// SVG context
if (ns) return asyncRenderElement(hname, expr.slice(1), env, ns);
// Fallback: eval and render
var fResult = asyncEval(expr, env);
if (isPromise(fResult)) return fResult.then(function(v) { return asyncRenderToDom(v, env, ns); });
return asyncRenderToDom(fResult, env, ns);
}
// Non-symbol head: eval call
var cResult = asyncEval(expr, env);
if (isPromise(cResult)) return cResult.then(function(v) { return asyncRenderToDom(v, env, ns); });
return asyncRenderToDom(cResult, env, ns);
}
function asyncRenderChildren(exprs, env, ns) {
var frag = document.createDocumentFragment();
var pending = [];
for (var i = 0; i < exprs.length; i++) {
var result = asyncRenderToDom(exprs[i], env, ns);
if (isPromise(result)) {
// Insert placeholder, replace when resolved
var placeholder = document.createComment("async");
frag.appendChild(placeholder);
(function(ph) {
pending.push(result.then(function(node) {
if (node) ph.parentNode.replaceChild(node, ph);
else ph.parentNode.removeChild(ph);
}));
})(placeholder);
} else if (result) {
frag.appendChild(result);
}
}
if (pending.length > 0) {
return Promise.all(pending).then(function() { return frag; });
}
return frag;
}
function asyncRenderElement(tag, args, env, ns) {
var newNs = tag === "svg" ? SVG_NS : tag === "math" ? MATH_NS : ns;
var el = domCreateElement(tag, newNs);
var pending = [];
var isVoid = contains(VOID_ELEMENTS, tag);
for (var i = 0; i < args.length; i++) {
var arg = args[i];
if (arg && arg._kw && (i + 1) < args.length) {
var attrName = arg.name;
var attrVal = asyncEval(args[i + 1], env);
i++;
if (isPromise(attrVal)) {
(function(an, av) {
pending.push(av.then(function(v) {
if (!isNil(v) && v !== false) {
if (contains(BOOLEAN_ATTRS, an)) { if (isSxTruthy(v)) el.setAttribute(an, ""); }
else if (v === true) el.setAttribute(an, "");
else el.setAttribute(an, String(v));
}
}));
})(attrName, attrVal);
} else {
if (!isNil(attrVal) && attrVal !== false) {
if (contains(BOOLEAN_ATTRS, attrName)) {
if (isSxTruthy(attrVal)) el.setAttribute(attrName, "");
} else if (attrVal === true) {
el.setAttribute(attrName, "");
} else {
el.setAttribute(attrName, String(attrVal));
}
}
}
} else if (!isVoid) {
var child = asyncRenderToDom(arg, env, newNs);
if (isPromise(child)) {
var placeholder = document.createComment("async");
el.appendChild(placeholder);
(function(ph) {
pending.push(child.then(function(node) {
if (node) ph.parentNode.replaceChild(node, ph);
else ph.parentNode.removeChild(ph);
}));
})(placeholder);
} else if (child) {
el.appendChild(child);
}
}
}
if (pending.length > 0) return Promise.all(pending).then(function() { return el; });
return el;
}
function asyncRenderComponent(comp, args, env, ns) {
var kwargs = {};
var children = [];
var pending = [];
for (var i = 0; i < args.length; i++) {
var arg = args[i];
if (arg && arg._kw && (i + 1) < args.length) {
var kName = arg.name;
var kVal = asyncEval(args[i + 1], env);
if (isPromise(kVal)) {
(function(k) { pending.push(kVal.then(function(v) { kwargs[k] = v; })); })(kName);
} else {
kwargs[kName] = kVal;
}
i++;
} else {
children.push(arg);
}
}
function doRender() {
var local = Object.create(componentClosure(comp));
for (var k in env) if (env.hasOwnProperty(k)) local[k] = env[k];
var params = componentParams(comp);
for (var j = 0; j < params.length; j++) {
local[params[j]] = params[j] in kwargs ? kwargs[params[j]] : NIL;
}
if (componentHasChildren(comp)) {
var childResult = asyncRenderChildren(children, env, ns);
if (isPromise(childResult)) {
return childResult.then(function(childFrag) {
local["children"] = childFrag;
return asyncRenderToDom(componentBody(comp), local, ns);
});
}
local["children"] = childResult;
}
return asyncRenderToDom(componentBody(comp), local, ns);
}
if (pending.length > 0) return Promise.all(pending).then(doRender);
return doRender();
}
function asyncRenderIf(expr, env, ns) {
var cond = asyncEval(expr[1], env);
if (isPromise(cond)) {
return cond.then(function(v) {
return isSxTruthy(v)
? asyncRenderToDom(expr[2], env, ns)
: (expr.length > 3 ? asyncRenderToDom(expr[3], env, ns) : null);
});
}
return isSxTruthy(cond)
? asyncRenderToDom(expr[2], env, ns)
: (expr.length > 3 ? asyncRenderToDom(expr[3], env, ns) : null);
}
function asyncRenderWhen(expr, env, ns) {
var cond = asyncEval(expr[1], env);
if (isPromise(cond)) {
return cond.then(function(v) {
return isSxTruthy(v) ? asyncRenderChildren(expr.slice(2), env, ns) : null;
});
}
return isSxTruthy(cond) ? asyncRenderChildren(expr.slice(2), env, ns) : null;
}
function asyncRenderCond(expr, env, ns) {
var clauses = expr.slice(1);
function step(idx) {
if (idx >= clauses.length) return null;
var clause = clauses[idx];
if (!Array.isArray(clause) || clause.length < 2) return step(idx + 1);
var test = clause[0];
if ((test && test._sym && (test.name === "else" || test.name === ":else")) ||
(test && test._kw && test.name === "else")) {
return asyncRenderToDom(clause[1], env, ns);
}
var v = asyncEval(test, env);
if (isPromise(v)) return v.then(function(r) { return isSxTruthy(r) ? asyncRenderToDom(clause[1], env, ns) : step(idx + 1); });
return isSxTruthy(v) ? asyncRenderToDom(clause[1], env, ns) : step(idx + 1);
}
return step(0);
}
function asyncRenderCase(expr, env, ns) {
var matchVal = asyncEval(expr[1], env);
function doCase(mv) {
var clauses = expr.slice(2);
for (var i = 0; i < clauses.length - 1; i += 2) {
var test = clauses[i];
if ((test && test._kw && test.name === "else") ||
(test && test._sym && (test.name === "else" || test.name === ":else"))) {
return asyncRenderToDom(clauses[i + 1], env, ns);
}
var tv = trampoline(evalExpr(test, env));
if (mv === tv || (typeof mv === "string" && typeof tv === "string" && mv === tv)) {
return asyncRenderToDom(clauses[i + 1], env, ns);
}
}
return null;
}
if (isPromise(matchVal)) return matchVal.then(doCase);
return doCase(matchVal);
}
function asyncRenderLet(expr, env, ns) {
var bindings = expr[1];
var local = Object.create(env);
for (var k in env) if (env.hasOwnProperty(k)) local[k] = env[k];
function bindStep(idx) {
if (!Array.isArray(bindings)) return asyncRenderChildren(expr.slice(2), local, ns);
// Nested pairs: ((a 1) (b 2))
if (bindings.length > 0 && Array.isArray(bindings[0])) {
if (idx >= bindings.length) return asyncRenderChildren(expr.slice(2), local, ns);
var b = bindings[idx];
var vname = b[0]._sym ? b[0].name : String(b[0]);
var val = asyncEval(b[1], local);
if (isPromise(val)) return val.then(function(v) { local[vname] = v; return bindStep(idx + 1); });
local[vname] = val;
return bindStep(idx + 1);
}
// Flat pairs: (a 1 b 2)
if (idx >= bindings.length) return asyncRenderChildren(expr.slice(2), local, ns);
var vn = bindings[idx]._sym ? bindings[idx].name : String(bindings[idx]);
var vv = asyncEval(bindings[idx + 1], local);
if (isPromise(vv)) return vv.then(function(v) { local[vn] = v; return bindStep(idx + 2); });
local[vn] = vv;
return bindStep(idx + 2);
}
return bindStep(0);
}
function asyncRenderMap(expr, env, ns) {
var fn = asyncEval(expr[1], env);
var coll = asyncEval(expr[2], env);
function doMap(f, c) {
if (!Array.isArray(c)) return null;
var frag = document.createDocumentFragment();
var pending = [];
for (var i = 0; i < c.length; i++) {
var item = c[i];
var result;
if (f && f._lambda) {
var lenv = Object.create(f.closure || env);
for (var k in env) if (env.hasOwnProperty(k)) lenv[k] = env[k];
lenv[f.params[0]] = item;
result = asyncRenderToDom(f.body, lenv, null);
} else if (typeof f === "function") {
var r = f(item);
result = isPromise(r) ? r.then(function(v) { return asyncRenderToDom(v, env, null); }) : asyncRenderToDom(r, env, null);
} else {
result = asyncRenderToDom(item, env, null);
}
if (isPromise(result)) {
var ph = document.createComment("async");
frag.appendChild(ph);
(function(p) { pending.push(result.then(function(n) { if (n) p.parentNode.replaceChild(n, p); else p.parentNode.removeChild(p); })); })(ph);
} else if (result) {
frag.appendChild(result);
}
}
if (pending.length) return Promise.all(pending).then(function() { return frag; });
return frag;
}
if (isPromise(fn) || isPromise(coll)) {
return Promise.all([isPromise(fn) ? fn : Promise.resolve(fn), isPromise(coll) ? coll : Promise.resolve(coll)])
.then(function(r) { return doMap(r[0], r[1]); });
}
return doMap(fn, coll);
}
function asyncRenderMapIndexed(expr, env, ns) {
var fn = asyncEval(expr[1], env);
var coll = asyncEval(expr[2], env);
function doMap(f, c) {
if (!Array.isArray(c)) return null;
var frag = document.createDocumentFragment();
var pending = [];
for (var i = 0; i < c.length; i++) {
var item = c[i];
var result;
if (f && f._lambda) {
var lenv = Object.create(f.closure || env);
for (var k in env) if (env.hasOwnProperty(k)) lenv[k] = env[k];
lenv[f.params[0]] = i;
lenv[f.params[1]] = item;
result = asyncRenderToDom(f.body, lenv, null);
} else if (typeof f === "function") {
var r = f(i, item);
result = isPromise(r) ? r.then(function(v) { return asyncRenderToDom(v, env, null); }) : asyncRenderToDom(r, env, null);
} else {
result = asyncRenderToDom(item, env, null);
}
if (isPromise(result)) {
var ph = document.createComment("async");
frag.appendChild(ph);
(function(p) { pending.push(result.then(function(n) { if (n) p.parentNode.replaceChild(n, p); else p.parentNode.removeChild(p); })); })(ph);
} else if (result) {
frag.appendChild(result);
}
}
if (pending.length) return Promise.all(pending).then(function() { return frag; });
return frag;
}
if (isPromise(fn) || isPromise(coll)) {
return Promise.all([isPromise(fn) ? fn : Promise.resolve(fn), isPromise(coll) ? coll : Promise.resolve(coll)])
.then(function(r) { return doMap(r[0], r[1]); });
}
return doMap(fn, coll);
}
function asyncEvalRaw(args, env) {
var parts = [];
var pending = [];
for (var i = 0; i < args.length; i++) {
var val = asyncEval(args[i], env);
if (isPromise(val)) {
(function(idx) {
pending.push(val.then(function(v) { parts[idx] = v; }));
})(parts.length);
parts.push(null);
} else {
parts.push(val);
}
}
function assemble() {
var html = "";
for (var j = 0; j < parts.length; j++) {
var p = parts[j];
if (p && p._rawHtml) html += p.html;
else if (typeof p === "string") html += p;
else if (p != null && !isNil(p)) html += String(p);
}
var el = document.createElement("span");
el.innerHTML = html;
var frag = document.createDocumentFragment();
while (el.firstChild) frag.appendChild(el.firstChild);
return frag;
}
if (pending.length) return Promise.all(pending).then(assemble);
return assemble();
}
// Async version of sxRenderWithEnv — returns Promise<DocumentFragment>
function asyncSxRenderWithEnv(source, extraEnv) {
var env = extraEnv ? merge(componentEnv, extraEnv) : componentEnv;
var exprs = parse(source);
if (!_hasDom) return Promise.resolve(null);
return asyncRenderChildren(exprs, env, null);
}
// IO proxy cache: key → { value, expires }
var _ioCache = {};
var IO_CACHE_TTL = 300000; // 5 minutes
// Register a server-proxied IO primitive: fetches from /sx/io/<name>
// Uses GET for short args, POST for long payloads (URL length safety).
// Results are cached client-side by (name + args) with a TTL.
function registerProxiedIo(name) {
registerIoPrimitive(name, function(args, kwargs) {
// Cache key: name + serialized args
var cacheKey = name;
for (var ci = 0; ci < args.length; ci++) cacheKey += "" + String(args[ci]);
for (var ck in kwargs) {
if (kwargs.hasOwnProperty(ck)) cacheKey += "" + ck + "=" + String(kwargs[ck]);
}
var cached = _ioCache[cacheKey];
if (cached && cached.expires > Date.now()) return cached.value;
var url = "/sx/io/" + encodeURIComponent(name);
var qs = [];
for (var i = 0; i < args.length; i++) {
qs.push("_arg" + i + "=" + encodeURIComponent(String(args[i])));
}
for (var k in kwargs) {
if (kwargs.hasOwnProperty(k)) {
qs.push(encodeURIComponent(k) + "=" + encodeURIComponent(String(kwargs[k])));
}
}
var queryStr = qs.join("&");
var fetchOpts;
if (queryStr.length > 1500) {
// POST with JSON body for long payloads
var sArgs = [];
for (var j = 0; j < args.length; j++) sArgs.push(String(args[j]));
var sKwargs = {};
for (var kk in kwargs) {
if (kwargs.hasOwnProperty(kk)) sKwargs[kk] = String(kwargs[kk]);
}
var postHeaders = { "SX-Request": "true", "Content-Type": "application/json" };
var csrf = csrfToken();
if (csrf && csrf !== NIL) postHeaders["X-CSRFToken"] = csrf;
fetchOpts = {
method: "POST",
headers: postHeaders,
body: JSON.stringify({ args: sArgs, kwargs: sKwargs })
};
} else {
if (queryStr) url += "?" + queryStr;
fetchOpts = { headers: { "SX-Request": "true" } };
}
var result = fetch(url, fetchOpts)
.then(function(resp) {
if (!resp.ok) {
logWarn("sx:io " + name + " failed " + resp.status);
return NIL;
}
return resp.text();
})
.then(function(text) {
if (!text || text === "nil") return NIL;
try {
var exprs = parse(text);
var val = exprs.length === 1 ? exprs[0] : exprs;
_ioCache[cacheKey] = { value: val, expires: Date.now() + IO_CACHE_TTL };
return val;
} catch (e) {
logWarn("sx:io " + name + " parse error: " + (e && e.message ? e.message : e));
return NIL;
}
})
.catch(function(e) {
logWarn("sx:io " + name + " network error: " + (e && e.message ? e.message : e));
return NIL;
});
// Cache the in-flight promise too (dedup concurrent calls for same args)
_ioCache[cacheKey] = { value: result, expires: Date.now() + IO_CACHE_TTL };
return result;
});
}
// Register IO deps as proxied primitives (idempotent, called per-page)
function registerIoDeps(names) {
if (!names || !names.length) return;
var registered = 0;
for (var i = 0; i < names.length; i++) {
var name = names[i];
if (!IO_PRIMITIVES[name]) {
registerProxiedIo(name);
registered++;
}
}
if (registered > 0) {
logInfo("sx:io registered " + registered + " proxied primitives: " + names.join(", "));
}
}
// Parser — compiled from parser.sx (see PLATFORM_PARSER_JS for ident char classes)
var parse = sxParse;
// =========================================================================
// Public API
// =========================================================================
var componentEnv = {};
function loadComponents(source) {
var exprs = parse(source);
for (var i = 0; i < exprs.length; i++) {
trampoline(evalExpr(exprs[i], componentEnv));
}
}
function render(source) {
if (!_hasDom) {
var exprs = parse(source);
var parts = [];
for (var i = 0; i < exprs.length; i++) parts.push(renderToHtml(exprs[i], merge(componentEnv)));
return parts.join("");
}
var exprs = parse(source);
var frag = document.createDocumentFragment();
for (var i = 0; i < exprs.length; i++) frag.appendChild(renderToDom(exprs[i], merge(componentEnv), null));
return frag;
}
function renderToString(source) {
var exprs = parse(source);
var parts = [];
for (var i = 0; i < exprs.length; i++) parts.push(renderToHtml(exprs[i], merge(componentEnv)));
return parts.join("");
}
var Sx = {
VERSION: "ref-2.0",
parse: parse,
parseAll: parse,
eval: function(expr, env) { return trampoline(evalExpr(expr, env || merge(componentEnv))); },
loadComponents: loadComponents,
render: render,
renderToString: renderToString,
serialize: serialize,
NIL: NIL,
Symbol: Symbol,
Keyword: Keyword,
isTruthy: isSxTruthy,
isNil: isNil,
componentEnv: componentEnv,
renderToHtml: function(expr, env) { return renderToHtml(expr, env || merge(componentEnv)); },
renderToSx: function(expr, env) { return renderToSx(expr, env || merge(componentEnv)); },
renderToDom: _hasDom ? function(expr, env, ns) { return renderToDom(expr, env || merge(componentEnv), ns || null); } : null,
parseTriggerSpec: typeof parseTriggerSpec === "function" ? parseTriggerSpec : null,
parseTime: typeof parseTime === "function" ? parseTime : null,
defaultTrigger: typeof defaultTrigger === "function" ? defaultTrigger : null,
parseSwapSpec: typeof parseSwapSpec === "function" ? parseSwapSpec : null,
parseRetrySpec: typeof parseRetrySpec === "function" ? parseRetrySpec : null,
nextRetryMs: typeof nextRetryMs === "function" ? nextRetryMs : null,
filterParams: typeof filterParams === "function" ? filterParams : null,
morphNode: typeof morphNode === "function" ? morphNode : null,
morphChildren: typeof morphChildren === "function" ? morphChildren : null,
swapDomNodes: typeof swapDomNodes === "function" ? swapDomNodes : null,
process: typeof processElements === "function" ? processElements : null,
executeRequest: typeof executeRequest === "function" ? executeRequest : null,
postSwap: typeof postSwap === "function" ? postSwap : null,
processScripts: typeof processSxScripts === "function" ? processSxScripts : null,
mount: typeof sxMount === "function" ? sxMount : null,
hydrate: typeof sxHydrateElements === "function" ? sxHydrateElements : null,
update: typeof sxUpdateElement === "function" ? sxUpdateElement : null,
renderComponent: typeof sxRenderComponent === "function" ? sxRenderComponent : null,
getEnv: function() { return componentEnv; },
resolveSuspense: typeof resolveSuspense === "function" ? resolveSuspense : null,
hydrateIslands: typeof sxHydrateIslands === "function" ? sxHydrateIslands : null,
disposeIsland: typeof disposeIsland === "function" ? disposeIsland : null,
init: typeof bootInit === "function" ? bootInit : null,
splitPathSegments: splitPathSegments,
parseRoutePattern: parseRoutePattern,
matchRoute: matchRoute,
findMatchingRoute: findMatchingRoute,
registerIo: typeof registerIoPrimitive === "function" ? registerIoPrimitive : null,
registerIoDeps: typeof registerIoDeps === "function" ? registerIoDeps : null,
asyncRender: typeof asyncSxRenderWithEnv === "function" ? asyncSxRenderWithEnv : null,
asyncRenderToDom: typeof asyncRenderToDom === "function" ? asyncRenderToDom : null,
signal: signal,
deref: deref,
reset: reset_b,
swap: swap_b,
computed: computed,
effect: effect,
batch: batch,
isSignal: isSignal,
makeSignal: makeSignal,
defStore: defStore,
useStore: useStore,
clearStores: clearStores,
emitEvent: emitEvent,
onEvent: onEvent,
bridgeEvent: bridgeEvent,
_version: "ref-2.0 (boot+dom+engine+html+orchestration+parser+sx, bootstrap-compiled)"
};
// --- Popstate listener ---
if (typeof window !== "undefined") {
window.addEventListener("popstate", function(e) {
handlePopstate(e && e.state ? e.state.scrollY || 0 : 0);
});
}
// --- Auto-init ---
if (typeof document !== "undefined") {
var _sxInit = function() {
bootInit();
// Process any suspense resolutions that arrived before init
if (global.__sxPending) {
for (var pi = 0; pi < global.__sxPending.length; pi++) {
resolveSuspense(global.__sxPending[pi].id, global.__sxPending[pi].sx);
}
global.__sxPending = null;
}
// Set up direct resolution for future chunks
global.__sxResolve = function(id, sx) { resolveSuspense(id, sx); };
// Register service worker for offline data caching
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/sx-sw.js", { scope: "/" }).then(function(reg) {
logInfo("sx:sw registered (scope: " + reg.scope + ")");
}).catch(function(err) {
logWarn("sx:sw registration failed: " + (err && err.message ? err.message : err));
});
}
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", _sxInit);
} else {
_sxInit();
}
}
if (typeof module !== "undefined" && module.exports) module.exports = Sx;
else global.Sx = Sx;
})(typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : this);
/**
* sx.js — S-expression parser, evaluator, and DOM renderer. [v2-debug]
*
* Client-side counterpart to shared/sx/ Python modules.
* Parses s-expression text, evaluates it, and renders to DOM nodes.
*
* Usage:
* Sx.loadComponents('(defcomp ~card (&key title) (div :class "c" title))');
* const node = Sx.render('(~card :title "Hello")');
* document.body.appendChild(node);
*/
;(function (global) {
"use strict";
// --- Types ---
/** Singleton nil — falsy placeholder. */
var NIL = Object.freeze({ _nil: true, toString: function () { return "nil"; } });
function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); }
function Symbol(name) { this.name = name; }
Symbol.prototype.toString = function () { return this.name; };
Symbol.prototype._sym = true;
function Keyword(name) { this.name = name; }
Keyword.prototype.toString = function () { return ":" + this.name; };
Keyword.prototype._kw = true;
function Lambda(params, body, closure, name) {
this.params = params;
this.body = body;
this.closure = closure || {};
this.name = name || null;
}
Lambda.prototype._lambda = true;
function Component(name, params, hasChildren, body, closure) {
this.name = name;
this.params = params;
this.hasChildren = hasChildren;
this.body = body;
this.closure = closure || {};
}
Component.prototype._component = true;
function Macro(params, restParam, body, closure, name) {
this.params = params;
this.restParam = restParam;
this.body = body;
this.closure = closure || {};
this.name = name || null;
}
Macro.prototype._macro = true;
/** Thunk — deferred evaluation for tail-call optimization. */
function _Thunk(expr, env) { this.expr = expr; this.env = env; }
_Thunk.prototype._thunk = true;
function isThunk(x) { return x && x._thunk; }
/** Marker for pre-rendered HTML that bypasses escaping. */
function RawHTML(html) { this.html = html; }
RawHTML.prototype._raw = true;
function isSym(x) { return x && x._sym === true; }
function isKw(x) { return x && x._kw === true; }
function isLambda(x) { return x && x._lambda === true; }
function isComponent(x) { return x && x._component === true; }
function isMacro(x) { return x && x._macro === true; }
function isRaw(x) { return x && x._raw === true; }
// --- Reader macro registry ---
var _readerMacros = {};
// --- Parser ---
var RE_WS = /\s+/y;
var RE_COMMENT = /;[^\n]*/y;
var RE_STRING = /"(?:[^"\\]|\\[\s\S])*"/y;
var RE_NUMBER = /-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/y;
var RE_KEYWORD = /:[a-zA-Z_~*+\-><=/!?&\[][a-zA-Z0-9_~*+\-><=/!?.:&/\[\]#,]*/y;
var RE_SYMBOL = /[a-zA-Z_~*+\-><=/!?&][a-zA-Z0-9_~*+\-><=/!?.:&]*/y;
function Tokenizer(text) {
this.text = text;
this.pos = 0;
this.line = 1;
this.col = 1;
}
Tokenizer.prototype._advance = function (count) {
for (var i = 0; i < count; i++) {
if (this.pos < this.text.length) {
if (this.text[this.pos] === "\n") { this.line++; this.col = 1; }
else { this.col++; }
this.pos++;
}
}
};
Tokenizer.prototype._skip = function () {
while (this.pos < this.text.length) {
RE_WS.lastIndex = this.pos;
var m = RE_WS.exec(this.text);
if (m && m.index === this.pos) { this._advance(m[0].length); continue; }
RE_COMMENT.lastIndex = this.pos;
m = RE_COMMENT.exec(this.text);
if (m && m.index === this.pos) { this._advance(m[0].length); continue; }
break;
}
};
Tokenizer.prototype.peek = function () {
this._skip();
return this.pos < this.text.length ? this.text[this.pos] : null;
};
Tokenizer.prototype.next = function () {
this._skip();
if (this.pos >= this.text.length) return null;
var ch = this.text[this.pos];
// Delimiters
if ("()[]{}".indexOf(ch) !== -1) { this._advance(1); return ch; }
// String
if (ch === '"') {
RE_STRING.lastIndex = this.pos;
var m = RE_STRING.exec(this.text);
if (!m || m.index !== this.pos) throw parseErr("Unterminated string", this);
this._advance(m[0].length);
var raw = m[0].slice(1, -1);
return raw.replace(/\\n/g, "\n").replace(/\\t/g, "\t")
.replace(/\\"/g, '"').replace(/\\[/]/g, "/").replace(/\\\\/g, "\\");
}
// Keyword
if (ch === ":") {
RE_KEYWORD.lastIndex = this.pos;
m = RE_KEYWORD.exec(this.text);
if (!m || m.index !== this.pos) throw parseErr("Invalid keyword", this);
this._advance(m[0].length);
return new Keyword(m[0].slice(1));
}
// Number (before symbol due to leading -)
if (isDigit(ch) || (ch === "-" && this.pos + 1 < this.text.length &&
(isDigit(this.text[this.pos + 1]) || this.text[this.pos + 1] === "."))) {
RE_NUMBER.lastIndex = this.pos;
m = RE_NUMBER.exec(this.text);
if (m && m.index === this.pos) {
this._advance(m[0].length);
var s = m[0];
return (s.indexOf(".") !== -1 || s.indexOf("e") !== -1 || s.indexOf("E") !== -1)
? parseFloat(s) : parseInt(s, 10);
}
}
// Reader macro dispatch: #
if (ch === "#") { this._advance(1); return "#"; }
// Symbol
RE_SYMBOL.lastIndex = this.pos;
m = RE_SYMBOL.exec(this.text);
if (m && m.index === this.pos) {
this._advance(m[0].length);
var name = m[0];
if (name === "true") return true;
if (name === "false") return false;
if (name === "nil") return NIL;
return new Symbol(name);
}
var ctx = this.text.substring(Math.max(0, this.pos - 40), this.pos + 40);
throw parseErr("Unexpected character: " + ch + " | context: «" + ctx.replace(/\n/g, "\\n") + "»", this);
};
Tokenizer.prototype._readRawString = function () {
var buf = [];
while (this.pos < this.text.length) {
var ch = this.text[this.pos];
if (ch === "|") { this._advance(1); return buf.join(""); }
buf.push(ch);
this._advance(1);
}
throw parseErr("Unterminated raw string", this);
};
Tokenizer.prototype._readIdent = function () {
RE_SYMBOL.lastIndex = this.pos;
var m = RE_SYMBOL.exec(this.text);
if (m && m.index === this.pos) {
this._advance(m[0].length);
return m[0];
}
throw parseErr("Expected identifier after #", this);
};
function isDigit(c) { return c >= "0" && c <= "9"; }
function parseErr(msg, tok) {
return new Error(msg + " at line " + tok.line + ", col " + tok.col);
}
function parseExpr(tok) {
// Use peek() (raw character) for structural decisions so that string
// values like ")" or "(" don't get confused with actual delimiters.
var raw = tok.peek();
if (raw === null) throw parseErr("Unexpected end of input", tok);
if (raw === ")" || raw === "]" || raw === "}") {
tok.next(); // consume the delimiter
throw parseErr("Unexpected " + raw, tok);
}
if (raw === "(") { tok.next(); return parseList(tok, ")"); }
if (raw === "[") { tok.next(); return parseList(tok, "]"); }
if (raw === "{") { tok.next(); return parseMap(tok); }
// Quasiquote syntax
if (raw === "`") { tok._advance(1); return [new Symbol("quasiquote"), parseExpr(tok)]; }
if (raw === ",") {
tok._advance(1);
if (tok.pos < tok.text.length && tok.text[tok.pos] === "@") {
tok._advance(1);
return [new Symbol("splice-unquote"), parseExpr(tok)];
}
return [new Symbol("unquote"), parseExpr(tok)];
}
// Reader macro dispatch: #
if (raw === "#") {
tok._advance(1); // consume #
if (tok.pos >= tok.text.length) throw parseErr("Unexpected end of input after #", tok);
var dispatch = tok.text[tok.pos];
if (dispatch === ";") {
tok._advance(1);
parseExpr(tok); // read and discard
return parseExpr(tok); // return next
}
if (dispatch === "|") {
tok._advance(1);
return tok._readRawString();
}
if (dispatch === "'") {
tok._advance(1);
return [new Symbol("quote"), parseExpr(tok)];
}
// Extensible dispatch: #name expr
if (/[a-zA-Z_~]/.test(dispatch)) {
var macroName = tok._readIdent();
var handler = _readerMacros[macroName];
if (!handler) throw parseErr("Unknown reader macro: #" + macroName, tok);
return handler(parseExpr(tok));
}
throw parseErr("Unknown reader macro: #" + dispatch, tok);
}
return tok.next();
}
function parseList(tok, closer) {
var items = [];
while (true) {
var c = tok.peek();
if (c === null) throw parseErr("Unterminated list, expected " + closer, tok);
if (c === closer) { tok.next(); return items; }
items.push(parseExpr(tok));
}
}
function parseMap(tok) {
var result = {};
while (true) {
var c = tok.peek();
if (c === null) throw parseErr("Unterminated map", tok);
if (c === "}") { tok.next(); return result; }
var key = parseExpr(tok);
var keyStr = isKw(key) ? key.name : String(key);
result[keyStr] = parseExpr(tok);
}
}
/** Parse a single s-expression. */
function parse(text) {
var tok = new Tokenizer(text);
var result = parseExpr(tok);
if (tok.peek() !== null) throw parseErr("Unexpected content after expression", tok);
return result;
}
/** Parse zero or more s-expressions. */
function parseAll(text) {
var tok = new Tokenizer(text);
var results = [];
while (tok.peek() !== null) results.push(parseExpr(tok));
return results;
}
/** Serialize a JS object as SX dict {:key "val" ...} for attribute values. */
function _serializeDict(obj) {
var parts = [];
for (var k in obj) {
if (!obj.hasOwnProperty(k)) continue;
var v = obj[k];
var vs = typeof v === "string" ? '"' + v.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"' : String(v);
parts.push(":" + k + " " + vs);
}
return "{" + parts.join(" ") + "}";
}
// --- Primitives ---
var PRIMITIVES = {};
// Arithmetic
PRIMITIVES["+"] = function () { var s = 0; for (var i = 0; i < arguments.length; i++) s += arguments[i]; return s; };
PRIMITIVES["-"] = function (a, b) { return arguments.length === 1 ? -a : a - b; };
PRIMITIVES["*"] = function () { var s = 1; for (var i = 0; i < arguments.length; i++) s *= arguments[i]; return s; };
PRIMITIVES["/"] = function (a, b) { return a / b; };
PRIMITIVES["mod"] = function (a, b) { return a % b; };
PRIMITIVES["inc"] = function (n) { return n + 1; };
PRIMITIVES["dec"] = function (n) { return n - 1; };
PRIMITIVES["abs"] = Math.abs;
PRIMITIVES["floor"] = Math.floor;
PRIMITIVES["ceil"] = Math.ceil;
PRIMITIVES["round"] = Math.round;
PRIMITIVES["min"] = Math.min;
PRIMITIVES["max"] = Math.max;
PRIMITIVES["sqrt"] = Math.sqrt;
PRIMITIVES["pow"] = Math.pow;
// Comparison
PRIMITIVES["="] = function (a, b) { return a == b; }; // loose, matches Python sx
PRIMITIVES["!="] = function (a, b) { return a != b; };
PRIMITIVES["<"] = function (a, b) { return a < b; };
PRIMITIVES[">"] = function (a, b) { return a > b; };
PRIMITIVES["<="] = function (a, b) { return a <= b; };
PRIMITIVES[">="] = function (a, b) { return a >= b; };
// Logic
PRIMITIVES["not"] = function (x) { return !isSxTruthy(x); };
// String
PRIMITIVES["str"] = function () {
var parts = [];
for (var i = 0; i < arguments.length; i++) {
var v = arguments[i];
if (isNil(v)) continue;
parts.push(String(v));
}
return parts.join("");
};
PRIMITIVES["upper"] = function (s) { return String(s).toUpperCase(); };
PRIMITIVES["lower"] = function (s) { return String(s).toLowerCase(); };
PRIMITIVES["trim"] = function (s) { return String(s).trim(); };
PRIMITIVES["split"] = function (s, sep) { return String(s).split(sep); };
PRIMITIVES["join"] = function (sep, coll) { return coll.join(sep); };
PRIMITIVES["starts-with?"] = function (s, p) { return String(s).indexOf(p) === 0; };
PRIMITIVES["ends-with?"] = function (s, p) { var str = String(s); return str.indexOf(p, str.length - p.length) !== -1; };
PRIMITIVES["concat"] = function () {
var out = [];
for (var i = 0; i < arguments.length; i++) out = out.concat(arguments[i]);
return out;
};
// Predicates
PRIMITIVES["nil?"] = function (x) { return isNil(x); };
PRIMITIVES["number?"] = function (x) { return typeof x === "number"; };
PRIMITIVES["string?"] = function (x) { return typeof x === "string"; };
PRIMITIVES["list?"] = function (x) { return Array.isArray(x); };
PRIMITIVES["dict?"] = function (x) { return x !== null && typeof x === "object" && !Array.isArray(x) && !x._sym && !x._kw; };
PRIMITIVES["empty?"] = function (c) { return !c || (Array.isArray(c) ? c.length === 0 : Object.keys(c).length === 0); };
PRIMITIVES["contains?"] = function (c, k) { return typeof c === "string" ? c.indexOf(k) !== -1 : Array.isArray(c) ? c.indexOf(k) !== -1 : k in c; };
PRIMITIVES["odd?"] = function (n) { return n % 2 !== 0; };
PRIMITIVES["even?"] = function (n) { return n % 2 === 0; };
PRIMITIVES["zero?"] = function (n) { return n === 0; };
// Collections
PRIMITIVES["list"] = function () { return Array.prototype.slice.call(arguments); };
PRIMITIVES["dict"] = function () {
var d = {};
for (var i = 0; i < arguments.length - 1; i += 2) d[arguments[i]] = arguments[i + 1];
return d;
};
PRIMITIVES["get"] = function (c, k, def) { var v = (c && c[k]); return v !== undefined ? v : (def !== undefined ? def : NIL); };
PRIMITIVES["len"] = function (c) { return Array.isArray(c) ? c.length : Object.keys(c).length; };
PRIMITIVES["first"] = function (c) { return c && c.length > 0 ? c[0] : NIL; };
PRIMITIVES["last"] = function (c) { return c && c.length > 0 ? c[c.length - 1] : NIL; };
PRIMITIVES["rest"] = function (c) { return c ? c.slice(1) : []; };
PRIMITIVES["nth"] = function (c, n) { return c && n < c.length ? c[n] : NIL; };
PRIMITIVES["slice"] = function (c, start, end) { return c ? (end !== undefined && end !== NIL ? c.slice(start, end) : c.slice(start)) : c; };
PRIMITIVES["cons"] = function (x, c) { return [x].concat(c || []); };
PRIMITIVES["append"] = function (c, x) { return (c || []).concat([x]); };
PRIMITIVES["keys"] = function (d) { return Object.keys(d || {}); };
PRIMITIVES["vals"] = function (d) { var r = []; for (var k in d) r.push(d[k]); return r; };
PRIMITIVES["merge"] = function () {
var out = {};
for (var i = 0; i < arguments.length; i++) { var d = arguments[i]; for (var k in d) out[k] = d[k]; }
return out;
};
PRIMITIVES["assoc"] = function (d) {
var out = {}; for (var k in d) out[k] = d[k];
for (var i = 1; i < arguments.length - 1; i += 2) out[arguments[i]] = arguments[i + 1];
return out;
};
PRIMITIVES["range"] = function (a, b, step) {
var r = []; step = step || 1;
if (b === undefined) { b = a; a = 0; }
for (var i = a; step > 0 ? i < b : i > b; i += step) r.push(i);
return r;
};
PRIMITIVES["dissoc"] = function (d) {
var out = {}; for (var k in d) out[k] = d[k];
for (var i = 1; i < arguments.length; i++) delete out[arguments[i]];
return out;
};
PRIMITIVES["into"] = function (target, src) {
if (Array.isArray(target)) return target.concat(src || []);
var out = {}; for (var k in target) out[k] = target[k]; for (var k2 in src) out[k2] = src[k2]; return out;
};
// String operations
PRIMITIVES["replace"] = function (s, from, to) { return s ? String(s).split(from).join(to) : ""; };
PRIMITIVES["upper"] = function (s) { return s ? String(s).toUpperCase() : ""; };
PRIMITIVES["lower"] = function (s) { return s ? String(s).toLowerCase() : ""; };
PRIMITIVES["trim"] = function (s) { return s ? String(s).trim() : ""; };
PRIMITIVES["starts-with?"] = function (s, pfx) { return s ? String(s).indexOf(pfx) === 0 : false; };
PRIMITIVES["ends-with?"] = function (s, sfx) { var str = String(s || ""); return str.indexOf(sfx, str.length - sfx.length) !== -1; };
PRIMITIVES["escape"] = function (s) {
if (!s) return "";
return String(s).replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
};
PRIMITIVES["strip-tags"] = function (s) { return s ? String(s).replace(/<[^>]*>/g, "") : ""; };
PRIMITIVES["split"] = function (s, sep) { return s ? String(s).split(sep) : []; };
PRIMITIVES["join"] = function (lst, sep) { return (lst || []).join(sep !== undefined ? sep : ""); };
PRIMITIVES["pluralize"] = function (n, singular, plural) { return n === 1 ? singular : (plural || singular + "s"); };
// Numeric
PRIMITIVES["clamp"] = function (val, lo, hi) { return Math.max(lo, Math.min(hi, val)); };
PRIMITIVES["parse-int"] = function (s, def) { var n = parseInt(s, 10); return isNaN(n) ? (def !== undefined ? def : 0) : n; };
PRIMITIVES["format-decimal"] = function (n, places) { return Number(n || 0).toFixed(places !== undefined ? places : 2); };
// Date formatting (basic)
PRIMITIVES["format-date"] = function (s, fmt) {
if (!s) return "";
try {
var d = new Date(s);
if (isNaN(d.getTime())) return String(s);
// Basic strftime-like: %Y %m %d %H %M %B %b %-d
var months = ["January","February","March","April","May","June","July","August","September","October","November","December"];
var short_months = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"];
return fmt.replace(/%-d/g, d.getDate()).replace(/%d/g, ("0"+d.getDate()).slice(-2))
.replace(/%B/g, months[d.getMonth()]).replace(/%b/g, short_months[d.getMonth()])
.replace(/%Y/g, d.getFullYear()).replace(/%m/g, ("0"+(d.getMonth()+1)).slice(-2))
.replace(/%H/g, ("0"+d.getHours()).slice(-2)).replace(/%M/g, ("0"+d.getMinutes()).slice(-2));
} catch (e) { return String(s); }
};
PRIMITIVES["parse-datetime"] = function (s) { return s ? String(s) : NIL; };
PRIMITIVES["split-ids"] = function (s) {
if (!s) return [];
return String(s).split(",").map(function(x) { return x.trim(); }).filter(function(x) { return x; });
};
// --- Evaluator ---
/** Unwrap thunks by re-entering the evaluator until we get an actual value. */
function trampoline(val) {
while (isThunk(val)) val = _sxEval(val.expr, val.env);
return val;
}
/** Public evaluator — trampolines thunks from tail positions. */
function sxEval(expr, env) { return trampoline(_sxEval(expr, env)); }
/** Internal evaluator — may return _Thunk for tail positions. */
function _sxEval(expr, env) {
// Literals
if (typeof expr === "number" || typeof expr === "string" || typeof expr === "boolean") return expr;
if (isNil(expr)) return NIL;
// Symbol lookup
if (isSym(expr)) {
var name = expr.name;
if (name in env) return env[name];
if (name in PRIMITIVES) return PRIMITIVES[name];
if (name === "true") return true;
if (name === "false") return false;
if (name === "nil") return NIL;
throw new Error("Undefined symbol: " + name);
}
// Keyword → its name
if (isKw(expr)) return expr.name;
// Dict literal
if (expr && typeof expr === "object" && !Array.isArray(expr) && !expr._sym && !expr._kw && !expr._raw) {
var d = {};
for (var dk in expr) d[dk] = sxEval(expr[dk], env);
return d;
}
// List
if (!Array.isArray(expr)) return expr;
if (expr.length === 0) return [];
var head = expr[0];
// Non-callable head → data list
if (!isSym(head) && !isLambda(head) && !Array.isArray(head)) {
return expr.map(function (x) { return sxEval(x, env); });
}
// Special forms
if (isSym(head)) {
var sf = SPECIAL_FORMS[head.name];
if (sf) return sf(expr, env);
var ho = HO_FORMS[head.name];
if (ho) {
// If name is also an HTML tag and first arg is Keyword → not a HO call
if (!(HTML_TAGS[head.name] && expr.length > 1 && isKw(expr[1]))) return ho(expr, env);
}
// Macro expansion
if (head.name in env) {
var macroVal = env[head.name];
if (isMacro(macroVal)) {
var expanded = expandMacro(macroVal, expr.slice(1), env);
return new _Thunk(expanded, env);
}
}
// HTML tag or component in data position — delegate to renderDOM
if (_isRenderExpr(expr)) return renderDOM(expr, env);
}
// Function call
var fn = sxEval(head, env);
var args = [];
for (var ai = 1; ai < expr.length; ai++) args.push(sxEval(expr[ai], env));
if (typeof fn === "function") return fn.apply(null, args);
if (isLambda(fn)) return callLambda(fn, args, env);
if (isComponent(fn)) return callComponent(fn, expr.slice(1), env);
throw new Error("Not callable: " + fn);
}
function callLambda(fn, args, callerEnv) {
if (args.length !== fn.params.length) {
throw new Error((fn.name || "lambda") + " expects " + fn.params.length + " args, got " + args.length);
}
var local = merge({}, fn.closure, callerEnv);
for (var i = 0; i < fn.params.length; i++) local[fn.params[i]] = args[i];
return new _Thunk(fn.body, local);
}
function callComponent(comp, rawArgs, env) {
var kwargs = {}, children = [];
var i = 0;
while (i < rawArgs.length) {
if (isKw(rawArgs[i]) && i + 1 < rawArgs.length) {
kwargs[rawArgs[i].name] = sxEval(rawArgs[i + 1], env);
i += 2;
} else {
children.push(sxEval(rawArgs[i], env));
i++;
}
}
var local = merge({}, comp.closure, env);
for (var pi = 0; pi < comp.params.length; pi++) {
var p = comp.params[pi];
local[p] = (p in kwargs) ? kwargs[p] : NIL;
}
if (comp.hasChildren) local["children"] = children;
return new _Thunk(comp.body, local);
}
// --- Shared helpers for special/render forms ---
function _processBindings(bindings, env) {
var local = merge({}, env);
if (Array.isArray(bindings)) {
if (bindings.length && Array.isArray(bindings[0])) {
for (var i = 0; i < bindings.length; i++) {
var vname = isSym(bindings[i][0]) ? bindings[i][0].name : bindings[i][0];
local[vname] = sxEval(bindings[i][1], local);
}
} else {
for (var j = 0; j < bindings.length; j += 2) {
var vn = isSym(bindings[j]) ? bindings[j].name : bindings[j];
local[vn] = sxEval(bindings[j + 1], local);
}
}
}
return local;
}
function _evalCond(clauses, env) {
if (!clauses.length) return null;
if (Array.isArray(clauses[0]) && clauses[0].length === 2) {
for (var i = 0; i < clauses.length; i++) {
var test = clauses[i][0];
if ((isSym(test) && (test.name === "else" || test.name === ":else")) ||
(isKw(test) && test.name === "else")) return clauses[i][1];
if (isSxTruthy(sxEval(test, env))) return clauses[i][1];
}
} else {
for (var j = 0; j < clauses.length - 1; j += 2) {
var t = clauses[j];
if ((isKw(t) && t.name === "else") || (isSym(t) && (t.name === ":else" || t.name === "else")))
return clauses[j + 1];
if (isSxTruthy(sxEval(t, env))) return clauses[j + 1];
}
}
return null;
}
function _logParseError(label, text, err, windowSize) {
var colMatch = err.message && err.message.match(/col (\d+)/);
var lineMatch = err.message && err.message.match(/line (\d+)/);
if (colMatch && text) {
var errLine = lineMatch ? parseInt(lineMatch[1]) : 1;
var errCol = parseInt(colMatch[1]);
var lines = text.split("\n");
var pos = 0;
for (var i = 0; i < errLine - 1 && i < lines.length; i++) pos += lines[i].length + 1;
pos += errCol;
var start = Math.max(0, pos - windowSize);
var end = Math.min(text.length, pos + windowSize);
console.error("sx.js " + label + ":", err.message,
"\n total length:", text.length, "lines:", lines.length,
"\n error line " + errLine + ":", lines[errLine - 1] ? lines[errLine - 1].substring(0, 200) : "(no such line)",
"\n around error (pos ~" + pos + "):",
"\n «" + text.substring(start, pos) + "⛔" + text.substring(pos, end) + "»");
} else {
console.error("sx.js " + label + ":", err.message || err);
}
}
// --- Special forms -------------------------------------------------------
var SPECIAL_FORMS = {};
SPECIAL_FORMS["if"] = function (expr, env) {
var cond = sxEval(expr[1], env);
if (isSxTruthy(cond)) return new _Thunk(expr[2], env);
return expr.length > 3 ? new _Thunk(expr[3], env) : NIL;
};
SPECIAL_FORMS["when"] = function (expr, env) {
if (!isSxTruthy(sxEval(expr[1], env))) return NIL;
for (var i = 2; i < expr.length - 1; i++) sxEval(expr[i], env);
return new _Thunk(expr[expr.length - 1], env);
};
SPECIAL_FORMS["cond"] = function (expr, env) {
var branch = _evalCond(expr.slice(1), env);
return branch ? new _Thunk(branch, env) : NIL;
};
SPECIAL_FORMS["case"] = function (expr, env) {
var val = sxEval(expr[1], env);
for (var i = 2; i < expr.length - 1; i += 2) {
var t = expr[i];
if ((isKw(t) && t.name === "else") || (isSym(t) && (t.name === ":else" || t.name === "else")))
return new _Thunk(expr[i + 1], env);
if (val == sxEval(t, env)) return new _Thunk(expr[i + 1], env);
}
return NIL;
};
SPECIAL_FORMS["and"] = function (expr, env) {
var result = true;
for (var i = 1; i < expr.length; i++) {
result = sxEval(expr[i], env);
if (!isSxTruthy(result)) return result;
}
return result;
};
SPECIAL_FORMS["or"] = function (expr, env) {
var result = false;
for (var i = 1; i < expr.length; i++) {
result = sxEval(expr[i], env);
if (isSxTruthy(result)) return result;
}
return result;
};
SPECIAL_FORMS["let"] = SPECIAL_FORMS["let*"] = function (expr, env) {
var local = _processBindings(expr[1], env);
for (var k = 2; k < expr.length - 1; k++) sxEval(expr[k], local);
return expr.length > 2 ? new _Thunk(expr[expr.length - 1], local) : NIL;
};
SPECIAL_FORMS["lambda"] = SPECIAL_FORMS["fn"] = function (expr, env) {
var paramsExpr = expr[1], paramNames = [];
for (var i = 0; i < paramsExpr.length; i++) {
var p = paramsExpr[i];
paramNames.push(isSym(p) ? p.name : String(p));
}
return new Lambda(paramNames, expr[2], merge({}, env));
};
SPECIAL_FORMS["define"] = function (expr, env) {
var name = expr[1].name;
var value = sxEval(expr[2], env);
if (isLambda(value) && !value.name) value.name = name;
env[name] = value;
return value;
};
SPECIAL_FORMS["defstyle"] = function (expr, env) {
var name = expr[1].name;
var value = sxEval(expr[2], env);
env[name] = value;
return value;
};
SPECIAL_FORMS["defcomp"] = function (expr, env) {
var nameSym = expr[1];
var compName = nameSym.name.replace(/^~/, "");
var paramsExpr = expr[2];
var params = [], hasChildren = false, inKey = false;
for (var i = 0; i < paramsExpr.length; i++) {
var p = paramsExpr[i];
if (isSym(p)) {
if (p.name === "&key") { inKey = true; continue; }
if (p.name === "&rest") { hasChildren = true; continue; }
if (inKey || hasChildren) { if (!hasChildren) params.push(p.name); }
else params.push(p.name);
}
}
var comp = new Component(compName, params, hasChildren, expr[3], merge({}, env));
env[nameSym.name] = comp;
return comp;
};
SPECIAL_FORMS["begin"] = SPECIAL_FORMS["do"] = function (expr, env) {
for (var i = 1; i < expr.length - 1; i++) sxEval(expr[i], env);
return expr.length > 1 ? new _Thunk(expr[expr.length - 1], env) : NIL;
};
SPECIAL_FORMS["quote"] = function (expr) { return expr[1]; };
SPECIAL_FORMS["set!"] = function (expr, env) {
var v = sxEval(expr[2], env);
env[expr[1].name] = v;
return v;
};
SPECIAL_FORMS["->"] = function (expr, env) {
var result = sxEval(expr[1], env);
for (var i = 2; i < expr.length; i++) {
var form = expr[i];
var fn, args;
if (Array.isArray(form)) {
fn = sxEval(form[0], env);
args = [result];
for (var j = 1; j < form.length; j++) args.push(sxEval(form[j], env));
} else {
fn = sxEval(form, env);
args = [result];
}
if (typeof fn === "function") result = fn.apply(null, args);
else if (isLambda(fn)) result = trampoline(callLambda(fn, args, env));
else throw new Error("-> form not callable: " + fn);
}
return result;
};
SPECIAL_FORMS["defmacro"] = function (expr, env) {
var nameSym = expr[1];
var paramsExpr = expr[2];
var params = [], restParam = null;
for (var i = 0; i < paramsExpr.length; i++) {
var p = paramsExpr[i];
if (isSym(p) && p.name === "&rest") {
if (i + 1 < paramsExpr.length) {
var rp = paramsExpr[i + 1];
restParam = isSym(rp) ? rp.name : String(rp);
}
break;
}
if (isSym(p)) params.push(p.name);
else if (typeof p === "string") params.push(p);
}
var macro = new Macro(params, restParam, expr[3], merge({}, env), nameSym.name);
env[nameSym.name] = macro;
return macro;
};
SPECIAL_FORMS["quasiquote"] = function (expr, env) {
return qqExpand(expr[1], env);
};
function qqExpand(template, env) {
if (!Array.isArray(template)) return template;
if (!template.length) return [];
var head = template[0];
if (isSym(head)) {
if (head.name === "unquote") return sxEval(template[1], env);
if (head.name === "splice-unquote") throw new Error("splice-unquote not inside a list");
}
var result = [];
for (var i = 0; i < template.length; i++) {
var item = template[i];
if (Array.isArray(item) && item.length === 2 && isSym(item[0]) && item[0].name === "splice-unquote") {
var spliced = sxEval(item[1], env);
if (Array.isArray(spliced)) { for (var j = 0; j < spliced.length; j++) result.push(spliced[j]); }
else if (!isNil(spliced)) result.push(spliced);
} else {
result.push(qqExpand(item, env));
}
}
return result;
}
function expandMacro(macro, rawArgs, env) {
var local = merge({}, macro.closure, env);
for (var i = 0; i < macro.params.length; i++) {
local[macro.params[i]] = i < rawArgs.length ? rawArgs[i] : NIL;
}
if (macro.restParam !== null) {
local[macro.restParam] = rawArgs.slice(macro.params.length);
}
return sxEval(macro.body, local);
}
// --- Higher-order forms --------------------------------------------------
var HO_FORMS = {};
HO_FORMS["map"] = function (expr, env) {
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
return coll.map(function (item) { return isLambda(fn) ? trampoline(callLambda(fn, [item], env)) : fn(item); });
};
HO_FORMS["map-indexed"] = function (expr, env) {
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
return coll.map(function (item, i) { return isLambda(fn) ? trampoline(callLambda(fn, [i, item], env)) : fn(i, item); });
};
HO_FORMS["filter"] = function (expr, env) {
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
return coll.filter(function (item) {
var r = isLambda(fn) ? trampoline(callLambda(fn, [item], env)) : fn(item);
return isSxTruthy(r);
});
};
HO_FORMS["reduce"] = function (expr, env) {
var fn = sxEval(expr[1], env), acc = sxEval(expr[2], env), coll = sxEval(expr[3], env);
for (var i = 0; i < coll.length; i++) acc = isLambda(fn) ? trampoline(callLambda(fn, [acc, coll[i]], env)) : fn(acc, coll[i]);
return acc;
};
HO_FORMS["some"] = function (expr, env) {
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
for (var i = 0; i < coll.length; i++) {
var r = isLambda(fn) ? trampoline(callLambda(fn, [coll[i]], env)) : fn(coll[i]);
if (isSxTruthy(r)) return r;
}
return NIL;
};
HO_FORMS["every?"] = function (expr, env) {
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
for (var i = 0; i < coll.length; i++) {
if (!isSxTruthy(isLambda(fn) ? trampoline(callLambda(fn, [coll[i]], env)) : fn(coll[i]))) return false;
}
return true;
};
HO_FORMS["for-each"] = function (expr, env) {
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
for (var i = 0; i < coll.length; i++) isLambda(fn) ? trampoline(callLambda(fn, [coll[i]], env)) : fn(coll[i]);
return NIL;
};
// --- DOM Renderer ---
var HTML_TAGS = makeSet(
"html head body title meta link style script base noscript " +
"header footer main nav aside section article address hgroup " +
"h1 h2 h3 h4 h5 h6 " +
"div p blockquote pre figure figcaption ul ol li dl dt dd hr " +
"a span em strong small s cite q abbr code var samp kbd sub sup " +
"i b u mark ruby rt rp bdi bdo br wbr time data " +
"ins del " +
"img picture source iframe embed object param video audio track canvas map area " +
"svg math path circle ellipse line polygon polyline rect g defs use text tspan " +
"clipPath mask linearGradient radialGradient stop filter " +
"feGaussianBlur feOffset feMerge feMergeNode " +
"feTurbulence feColorMatrix feBlend " +
"feComponentTransfer feFuncR feFuncG feFuncB feFuncA " +
"feDisplacementMap feComposite feFlood feImage " +
"feMorphology feSpecularLighting feDiffuseLighting " +
"fePointLight feSpotLight feDistantLight " +
"animate animateTransform " +
"table thead tbody tfoot tr th td caption colgroup col " +
"form fieldset legend label input button select option optgroup textarea output " +
"datalist progress meter details summary dialog template slot"
);
var VOID_ELEMENTS = makeSet(
"area base br col embed hr img input link meta param source track wbr"
);
var BOOLEAN_ATTRS = makeSet(
"async autofocus autoplay checked controls default defer disabled " +
"formnovalidate hidden inert ismap loop multiple muted nomodule " +
"novalidate open playsinline readonly required reversed selected"
);
// SVG elements that need createElementNS
var SVG_TAGS = makeSet(
"svg path circle ellipse line polygon polyline rect g defs use text tspan " +
"clipPath mask linearGradient radialGradient stop filter " +
"feGaussianBlur feOffset feMerge feMergeNode " +
"feTurbulence feColorMatrix feBlend " +
"feComponentTransfer feFuncR feFuncG feFuncB feFuncA " +
"feDisplacementMap feComposite feFlood feImage " +
"feMorphology feSpecularLighting feDiffuseLighting " +
"fePointLight feSpotLight feDistantLight " +
"animate animateTransform"
);
var SVG_NS = "http://www.w3.org/2000/svg";
var MATH_NS = "http://www.w3.org/1998/Math/MathML";
/**
* Render an s-expression to DOM node(s).
* Returns a DocumentFragment, Element, or Text node.
* @param {*} expr - s-expression
* @param {Object} env - variable bindings
* @param {string|null} ns - namespace URI (SVG_NS or MATH_NS) when inside svg/math
*/
function renderDOM(expr, env, ns) {
// nil / false → empty
if (isNil(expr) || expr === false || expr === true) return document.createDocumentFragment();
// Pre-rendered HTML
if (isRaw(expr)) {
var tpl = document.createElement("template");
tpl.innerHTML = expr.html;
return tpl.content;
}
// String → text node
if (typeof expr === "string") return document.createTextNode(expr);
// Number → text node
if (typeof expr === "number") return document.createTextNode(String(expr));
// Symbol → evaluate then render
if (isSym(expr)) return renderDOM(sxEval(expr, env), env, ns);
// Keyword → text
if (isKw(expr)) return document.createTextNode(expr.name);
// Pre-rendered DOM node → return as-is
if (expr && expr.nodeType) return expr;
// Dict → empty
if (expr && typeof expr === "object" && !Array.isArray(expr)) return document.createDocumentFragment();
// List → dispatch
if (Array.isArray(expr)) {
if (!expr.length) return document.createDocumentFragment();
return renderList(expr, env, ns);
}
return document.createTextNode(String(expr));
}
/** Render-aware special forms for DOM output. */
var RENDER_FORMS = {};
RENDER_FORMS["if"] = function (expr, env, ns) {
var cond = sxEval(expr[1], env);
if (isSxTruthy(cond)) return renderDOM(expr[2], env, ns);
return expr.length > 3 ? renderDOM(expr[3], env, ns) : document.createDocumentFragment();
};
RENDER_FORMS["when"] = function (expr, env, ns) {
if (!isSxTruthy(sxEval(expr[1], env))) return document.createDocumentFragment();
var frag = document.createDocumentFragment();
for (var i = 2; i < expr.length; i++) frag.appendChild(renderDOM(expr[i], env, ns));
return frag;
};
RENDER_FORMS["cond"] = function (expr, env, ns) {
var branch = _evalCond(expr.slice(1), env);
return branch ? renderDOM(branch, env, ns) : document.createDocumentFragment();
};
RENDER_FORMS["let"] = RENDER_FORMS["let*"] = function (expr, env, ns) {
var local = _processBindings(expr[1], env);
var frag = document.createDocumentFragment();
for (var k = 2; k < expr.length; k++) frag.appendChild(renderDOM(expr[k], local, ns));
return frag;
};
RENDER_FORMS["begin"] = RENDER_FORMS["do"] = function (expr, env, ns) {
var frag = document.createDocumentFragment();
for (var i = 1; i < expr.length; i++) frag.appendChild(renderDOM(expr[i], env, ns));
return frag;
};
RENDER_FORMS["define"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
RENDER_FORMS["defstyle"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
RENDER_FORMS["defcomp"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
RENDER_FORMS["defmacro"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
RENDER_FORMS["defhandler"] = function (expr, env) { sxEval(expr, env); return document.createDocumentFragment(); };
RENDER_FORMS["map"] = function (expr, env, ns) {
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
var frag = document.createDocumentFragment();
for (var i = 0; i < coll.length; i++) {
var val = isLambda(fn) ? renderLambdaDOM(fn, [coll[i]], env, ns) : renderDOM(fn(coll[i]), env, ns);
frag.appendChild(val);
}
return frag;
};
RENDER_FORMS["map-indexed"] = function (expr, env, ns) {
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
var frag = document.createDocumentFragment();
for (var i = 0; i < coll.length; i++) {
var val = isLambda(fn) ? renderLambdaDOM(fn, [i, coll[i]], env, ns) : renderDOM(fn(i, coll[i]), env, ns);
frag.appendChild(val);
}
return frag;
};
RENDER_FORMS["filter"] = function (expr, env, ns) {
var result = sxEval(expr, env);
return renderDOM(result, env, ns);
};
RENDER_FORMS["for-each"] = function (expr, env, ns) {
var fn = sxEval(expr[1], env), coll = sxEval(expr[2], env);
var frag = document.createDocumentFragment();
for (var i = 0; i < coll.length; i++) {
var val = isLambda(fn) ? renderLambdaDOM(fn, [coll[i]], env, ns) : renderDOM(fn(coll[i]), env, ns);
frag.appendChild(val);
}
return frag;
};
function renderLambdaDOM(fn, args, env, ns) {
var local = merge({}, fn.closure, env);
for (var i = 0; i < fn.params.length; i++) local[fn.params[i]] = args[i];
return renderDOM(fn.body, local, ns);
}
/** True when the array expr is a render-only form (HTML tag, <>, raw!, ~comp). */
function _isRenderExpr(v) {
if (!Array.isArray(v) || !v.length) return false;
var h = v[0];
if (!isSym(h)) return false;
var n = h.name;
return !!(HTML_TAGS[n] || SVG_TAGS[n] || n === "<>" || n === "raw!" || n.charAt(0) === "~" || n.indexOf("html:") === 0 || (n.indexOf("-") > 0 && v.length > 1 && isKw(v[1])));
}
function renderComponentDOM(comp, args, env) {
var kwargs = {}, children = [];
var i = 0;
while (i < args.length) {
if (isKw(args[i]) && i + 1 < args.length) {
// Evaluate kwarg values eagerly in the caller's env so expressions
// like (get t "src") resolve while lambda params are still bound.
var v = args[i + 1];
if (typeof v === "string" || typeof v === "number" ||
typeof v === "boolean" || isNil(v) || isKw(v)) {
kwargs[args[i].name] = v;
} else if (isSym(v)) {
kwargs[args[i].name] = sxEval(v, env);
} else if (Array.isArray(v) && v.length && isSym(v[0])) {
// Expression with Symbol head — evaluate in caller's env.
// Render-only forms go through renderDOM; data exprs through sxEval.
if (_isRenderExpr(v)) {
kwargs[args[i].name] = renderDOM(v, env);
} else {
kwargs[args[i].name] = sxEval(v, env);
}
} else {
// Data arrays, dicts, etc — evaluate in caller's env
kwargs[args[i].name] = sxEval(v, env);
}
i += 2;
} else {
children.push(args[i]);
i++;
}
}
var local = merge({}, comp.closure, env);
for (var pi = 0; pi < comp.params.length; pi++) {
var p = comp.params[pi];
local[p] = (p in kwargs) ? kwargs[p] : NIL;
}
if (comp.hasChildren) {
// Pre-render children to a fragment, wrap as RawHTML for raw! compatibility
var childFrag = document.createDocumentFragment();
for (var ci = 0; ci < children.length; ci++) childFrag.appendChild(renderDOM(children[ci], env));
local["children"] = childFrag;
}
return renderDOM(comp.body, local);
}
function renderList(expr, env, ns) {
var head = expr[0];
if (isSym(head)) {
var name = head.name;
// raw! → insert unescaped
if (name === "raw!") {
var frag = document.createDocumentFragment();
for (var ri = 1; ri < expr.length; ri++) {
var val = sxEval(expr[ri], env);
if (typeof val === "string") {
var tpl = document.createElement("template");
tpl.innerHTML = val;
// Scripts in innerHTML don't execute — recreate them as live elements
var deadScripts = tpl.content.querySelectorAll("script");
for (var si = 0; si < deadScripts.length; si++) {
var dead = deadScripts[si];
var live = document.createElement("script");
for (var ai = 0; ai < dead.attributes.length; ai++)
live.setAttribute(dead.attributes[ai].name, dead.attributes[ai].value);
live.textContent = dead.textContent;
dead.parentNode.replaceChild(live, dead);
}
frag.appendChild(tpl.content);
} else if (val && val.nodeType) {
// Already a DOM node (e.g. from children fragment)
frag.appendChild(val.cloneNode ? val.cloneNode(true) : val);
} else if (!isNil(val)) {
frag.appendChild(document.createTextNode(String(val)));
}
}
return frag;
}
// <> → fragment
if (name === "<>") {
var f = document.createDocumentFragment();
for (var fi = 1; fi < expr.length; fi++) f.appendChild(renderDOM(expr[fi], env, ns));
return f;
}
// html: prefix → force element rendering
if (name.indexOf("html:") === 0) return renderElement(name.substring(5), expr.slice(1), env, ns);
// Render-aware special forms
// If name is also an HTML tag and (keyword arg or SVG/MathML ns) → tag call
if (RENDER_FORMS[name]) {
if (HTML_TAGS[name] && ((expr.length > 1 && isKw(expr[1])) || ns)) return renderElement(name, expr.slice(1), env, ns);
return RENDER_FORMS[name](expr, env, ns);
}
// Macro expansion
if (name in env && isMacro(env[name])) {
var mExpanded = expandMacro(env[name], expr.slice(1), env);
return renderDOM(mExpanded, env, ns);
}
// HTML tag
if (HTML_TAGS[name]) return renderElement(name, expr.slice(1), env, ns);
// Component
if (name.charAt(0) === "~") {
var comp = env[name];
if (isComponent(comp)) return renderComponentDOM(comp, expr.slice(1), env);
// Unknown component — render a visible warning, don't crash
console.warn("sx.js: unknown component " + name);
var warn = document.createElement("div");
warn.setAttribute("style",
"background:#fef2f2;border:1px solid #fca5a5;color:#991b1b;" +
"padding:4px 8px;margin:2px;border-radius:4px;font-size:12px;font-family:monospace");
warn.textContent = "Unknown component: " + name;
return warn;
}
// Custom element (hyphenated name with keyword attrs) → render as element
if (name.indexOf("-") > 0 && expr.length > 1 && isKw(expr[1])) return renderElement(name, expr.slice(1), env, ns);
// SVG/MathML namespace auto-detection: inside (svg ...) or (math ...),
// unknown tags are created with the inherited namespace
if (ns) return renderElement(name, expr.slice(1), env, ns);
// Fallback: evaluate then render
return renderDOM(sxEval(expr, env), env, ns);
}
// Lambda/list head → evaluate
if (isLambda(head) || Array.isArray(head)) return renderDOM(sxEval(expr, env), env, ns);
// Data list
var dl = document.createDocumentFragment();
for (var di = 0; di < expr.length; di++) dl.appendChild(renderDOM(expr[di], env, ns));
return dl;
}
function renderElement(tag, args, env, ns) {
// Detect namespace from tag: svg → SVG_NS, math → MATH_NS
if (tag === "svg") ns = SVG_NS;
else if (tag === "math") ns = MATH_NS;
var el = ns
? document.createElementNS(ns, tag)
: (SVG_TAGS[tag] ? document.createElementNS(SVG_NS, tag) : document.createElement(tag));
var i = 0;
while (i < args.length) {
var arg = args[i];
if (isKw(arg) && i + 1 < args.length) {
var attrName = arg.name;
var attrVal = sxEval(args[i + 1], env);
i += 2;
if (isNil(attrVal) || attrVal === false) continue;
if (BOOLEAN_ATTRS[attrName]) {
if (attrVal) el.setAttribute(attrName, "");
} else if (attrVal === true) {
el.setAttribute(attrName, "");
} else {
el.setAttribute(attrName, typeof attrVal === "object" && attrVal !== null && !Array.isArray(attrVal) ? _serializeDict(attrVal) : String(attrVal));
}
} else {
// Child
if (!(tag in VOID_ELEMENTS)) {
el.appendChild(renderDOM(arg, env, ns));
}
i++;
}
}
return el;
}
// --- Helpers ---
function merge(target) {
for (var i = 1; i < arguments.length; i++) {
var src = arguments[i];
if (src) for (var k in src) target[k] = src[k];
}
return target;
}
function makeSet(str) {
var s = {}, parts = str.split(/\s+/);
for (var i = 0; i < parts.length; i++) if (parts[i]) s[parts[i]] = true;
return s;
}
/** Convert snake_case kwargs to kebab-case for sx conventions. */
function toKebab(s) { return s.replace(/_/g, "-"); }
// --- Public API ---
var _componentEnv = {};
// --- Head auto-hoist ---
var HEAD_HOIST_SELECTOR =
"meta, title, link[rel='canonical'], script[type='application/ld+json']";
function _hoistHeadElements(root) {
var els = root.querySelectorAll(HEAD_HOIST_SELECTOR);
if (!els.length) return;
var head = document.head;
for (var i = 0; i < els.length; i++) {
var el = els[i];
var tag = el.tagName.toLowerCase();
// For <title>, replace existing
if (tag === "title") {
document.title = el.textContent || "";
el.parentNode.removeChild(el);
continue;
}
// For <meta>, remove existing with same name/property to avoid duplicates
if (tag === "meta") {
var name = el.getAttribute("name");
var prop = el.getAttribute("property");
if (name) {
var old = head.querySelector('meta[name="' + name + '"]');
if (old) old.parentNode.removeChild(old);
}
if (prop) {
var old2 = head.querySelector('meta[property="' + prop + '"]');
if (old2) old2.parentNode.removeChild(old2);
}
}
// For <link rel=canonical>, remove existing
if (tag === "link" && el.getAttribute("rel") === "canonical") {
var oldLink = head.querySelector('link[rel="canonical"]');
if (oldLink) oldLink.parentNode.removeChild(oldLink);
}
// Move from body to head
el.parentNode.removeChild(el);
head.appendChild(el);
}
}
var Sx = {
// Types
NIL: NIL,
Symbol: Symbol,
Keyword: Keyword,
// Parser
parse: parse,
parseAll: parseAll,
// Evaluator
eval: function (expr, env) { return sxEval(expr, env || _componentEnv); },
// DOM Renderer
render: function (exprOrText, extraEnv) {
var env = extraEnv ? merge({}, _componentEnv, extraEnv) : _componentEnv;
if (typeof exprOrText === "string") {
// Try single expression first; fall back to multi-expression fragment
try {
return renderDOM(parse(exprOrText), env);
} catch (e) {
var exprs = parseAll(exprOrText);
if (exprs.length === 0) throw e;
var frag = document.createDocumentFragment();
for (var i = 0; i < exprs.length; i++) {
var node = renderDOM(exprs[i], env);
if (node) frag.appendChild(node);
}
return frag;
}
}
return renderDOM(exprOrText, env);
},
/**
* Render a named component with keyword args (Python-style API).
* Sx.renderComponent("card", {title: "Hi"})
*/
renderComponent: function (name, kwargs, extraEnv) {
var fullName = name.charAt(0) === "~" ? name : "~" + name;
var env = extraEnv ? merge({}, _componentEnv, extraEnv) : _componentEnv;
var comp = env[fullName];
if (!isComponent(comp)) throw new Error("Unknown component: " + fullName);
// Build a synthetic call expression
var callExpr = [new Symbol(fullName)];
if (kwargs) {
for (var k in kwargs) {
callExpr.push(new Keyword(toKebab(k)));
callExpr.push(kwargs[k]);
}
}
return renderDOM(callExpr, env);
},
// Component management
loadComponents: function (text) {
try {
var exprs = parseAll(text);
for (var i = 0; i < exprs.length; i++) sxEval(exprs[i], _componentEnv);
} catch (err) {
_logParseError("loadComponents PARSE ERROR", text, err, 120);
throw err;
}
},
getEnv: function () { return _componentEnv; },
// Utility
isTruthy: isSxTruthy,
isNil: isNil,
/**
* Resolve a streaming suspense placeholder.
* Called by inline <script> tags that arrive during chunked transfer:
* __sxResolve("content", "(~article :title \"Hello\")")
*
* Finds the suspense wrapper by data-suspense attribute, renders the
* new SX content, and replaces the wrapper's children.
*/
resolveSuspense: function (id, sx) {
// Process any new <script type="text/sx"> tags (streaming extras)
Sx.processScripts();
var el = document.querySelector('[data-suspense="' + id + '"]');
if (!el) {
console.warn("[sx] resolveSuspense: no element for id=" + id);
return;
}
try {
var node = Sx.render(sx);
el.textContent = "";
el.appendChild(node);
if (typeof SxEngine !== "undefined") SxEngine.process(el);
Sx.hydrate(el);
el.dispatchEvent(new CustomEvent("sx:resolved", { bubbles: true, detail: { id: id } }));
} catch (e) {
console.error("[sx] resolveSuspense error for id=" + id, e);
}
},
/**
* Mount a sx expression into a DOM element, replacing its contents.
* Sx.mount(el, '(~card :title "Hi")')
* Sx.mount("#target", '(~card :title "Hi")')
* Sx.mount(el, '(~card :title name)', {name: "Jo"})
*/
mount: function (target, exprOrText, extraEnv) {
var el = typeof target === "string" ? document.querySelector(target) : target;
if (!el) return;
var node;
try {
node = Sx.render(exprOrText, extraEnv);
} catch (e) {
if (typeof exprOrText === "string") _logParseError("MOUNT PARSE ERROR", exprOrText, e, 80);
throw e;
}
el.textContent = "";
el.appendChild(node);
// Auto-hoist head elements (meta, title, link, script[ld+json]) to <head>
_hoistHeadElements(el);
// Process sx- attributes and hydrate the newly mounted content
if (typeof SxEngine !== "undefined") SxEngine.process(el);
Sx.hydrate(el);
},
/**
* Process all <script type="text/sx"> tags in the document.
* Tags with data-components load component definitions.
* Tags with data-mount="<selector>" render into that element.
*/
processScripts: function (root) {
var scripts = (root || document).querySelectorAll('script[type="text/sx"]');
for (var i = 0; i < scripts.length; i++) {
var s = scripts[i];
if (s._sxProcessed) continue;
s._sxProcessed = true;
var text = s.textContent;
// data-components: check before empty-text guard (may load from localStorage)
if (s.hasAttribute("data-components")) {
var hash = s.getAttribute("data-hash");
if (hash) {
var hasInline = text && text.trim();
try {
var cachedHash = localStorage.getItem("sx-components-hash");
if (cachedHash === hash) {
// Cache hit
if (hasInline) {
// Server sent full source (cookie was missing/stale) — update cache
localStorage.setItem("sx-components-src", text);
Sx.loadComponents(text);
console.log("[sx.js] components: downloaded (cookie stale)");
} else {
// Server omitted source — load from cache
var cached = localStorage.getItem("sx-components-src");
if (cached) {
Sx.loadComponents(cached);
console.log("[sx.js] components: cached (" + hash + ")");
} else {
// Cache entry missing — clear cookie and reload to get full source
_clearSxCompCookie();
location.reload();
return;
}
}
} else {
// Cache miss — hash mismatch
if (hasInline) {
// Server sent full source — parse and cache
localStorage.setItem("sx-components-hash", hash);
localStorage.setItem("sx-components-src", text);
Sx.loadComponents(text);
console.log("[sx.js] components: downloaded (" + hash + ")");
} else {
// Server omitted source but our cache is stale — clear and reload
localStorage.removeItem("sx-components-hash");
localStorage.removeItem("sx-components-src");
_clearSxCompCookie();
location.reload();
return;
}
}
} catch (e) {
// localStorage unavailable — fall back to inline
if (hasInline) Sx.loadComponents(text);
}
_setSxCompCookie(hash);
} else {
// Legacy: no hash attribute — just load inline
if (text && text.trim()) Sx.loadComponents(text);
}
continue;
}
if (!text || !text.trim()) continue;
// data-mount="<selector>": render into target
var mountSel = s.getAttribute("data-mount");
if (mountSel) {
var target = document.querySelector(mountSel);
if (target) Sx.mount(target, text);
continue;
}
// Default: load as components
Sx.loadComponents(text);
}
},
/**
* Bind client-side sx rendering to elements with data-sx-* attrs.
*
* Pattern:
* <div data-sx="(~card :title title)" data-sx-env='{"title":"Hi"}'>
* <!-- server-rendered HTML (hydration target) -->
* </div>
*
* Call Sx.update(el, {title: "New"}) to re-render with new data.
*/
update: function (target, newEnv) {
var el = typeof target === "string" ? document.querySelector(target) : target;
if (!el) return;
var source = el.getAttribute("data-sx");
if (!source) return;
var baseEnv = {};
var envAttr = el.getAttribute("data-sx-env");
if (envAttr) {
try { baseEnv = JSON.parse(envAttr); } catch (e) { /* ignore */ }
}
var env = merge({}, _componentEnv, baseEnv, newEnv || {});
var node = renderDOM(parse(source), env);
el.textContent = "";
el.appendChild(node);
if (newEnv) {
merge(baseEnv, newEnv);
el.setAttribute("data-sx-env", JSON.stringify(baseEnv));
}
},
/**
* Find all [data-sx] elements within root and render them.
* Useful after HTMX swaps bring in new sx-enabled elements.
*/
hydrate: function (root) {
var els = (root || document).querySelectorAll("[data-sx]");
for (var i = 0; i < els.length; i++) {
if (els[i]._sxHydrated) continue;
els[i]._sxHydrated = true;
Sx.update(els[i]);
}
},
/** Register a reader macro: Sx.registerReaderMacro("z3", fn) */
registerReaderMacro: function (name, handler) { _readerMacros[name] = handler; },
// For testing / sx-test.js
_types: { NIL: NIL, Symbol: Symbol, Keyword: Keyword, Lambda: Lambda, Component: Component, RawHTML: RawHTML },
_eval: sxEval,
_expandMacro: expandMacro,
_callLambda: function (fn, args, env) { return trampoline(callLambda(fn, args, env)); },
_renderDOM: renderDOM,
};
global.Sx = Sx;
// --- SxEngine — native fetch/swap/history engine ---
var SxEngine = (function () {
if (typeof document === "undefined") return {};
// ---- helpers ----------------------------------------------------------
var PROCESSED = "_sxBound";
var VERBS = ["get", "post", "put", "delete", "patch"];
var DEFAULT_SWAP = "outerHTML";
var _config = { globalViewTransitions: false };
/** Wrap a function in View Transition API if supported and enabled. */
function _withTransition(enabled, fn) {
if (enabled && document.startViewTransition) {
document.startViewTransition(fn);
} else {
fn();
}
}
function dispatch(el, name, detail) {
var evt = new CustomEvent(name, { bubbles: true, cancelable: true, detail: detail || {} });
return el.dispatchEvent(evt);
}
/** Parse and dispatch SX-Trigger header events.
* Value can be: "myEvent" (plain string), '{"myEvent": {"key": "val"}}' (JSON). */
function _dispatchTriggerEvents(el, headerVal) {
if (!headerVal) return;
try {
var parsed = JSON.parse(headerVal);
if (typeof parsed === "object" && parsed !== null) {
for (var evtName in parsed) dispatch(el, evtName, parsed[evtName]);
} else {
dispatch(el, String(parsed), {});
}
} catch (e) {
// Plain string — may be comma-separated event names
headerVal.split(",").forEach(function (name) {
var n = name.trim();
if (n) dispatch(el, n, {});
});
}
}
function csrfToken() {
var m = document.querySelector('meta[name="csrf-token"]');
return m ? m.getAttribute("content") : null;
}
function sameOrigin(url) {
try { return new URL(url, location.href).origin === location.origin; } catch (e) { return true; }
}
function resolveTarget(el, attr) {
var sel = el.getAttribute("sx-target") || attr;
if (!sel || sel === "this") return el;
if (sel === "closest") return el.parentElement;
return document.querySelector(sel);
}
function getVerb(el) {
for (var i = 0; i < VERBS.length; i++) {
var v = VERBS[i];
if (el.hasAttribute("sx-" + v)) return { method: v.toUpperCase(), url: el.getAttribute("sx-" + v) };
}
return null;
}
// ---- Sync manager -----------------------------------------------------
var _controllers = new WeakMap();
function abortPrevious(el) {
var prev = _controllers.get(el);
if (prev) prev.abort();
}
function trackController(el, ctrl) {
_controllers.set(el, ctrl);
}
// ---- Request executor -------------------------------------------------
function executeRequest(el, verbInfo, extraParams) {
// Re-read verb from element in case attributes were morphed since binding
var currentVerb = getVerb(el);
if (currentVerb) verbInfo = currentVerb;
var method = verbInfo.method;
var url = verbInfo.url;
// Reset retry backoff on fresh (non-retry) requests
if (!el.classList.contains("sx-error")) {
el.removeAttribute("data-sx-retry-ms");
}
// sx-media: skip if media query doesn't match
var media = el.getAttribute("sx-media");
if (media && !window.matchMedia(media).matches) return Promise.resolve();
// sx-confirm: show dialog first
var confirmMsg = el.getAttribute("sx-confirm");
if (confirmMsg) {
if (typeof Swal !== "undefined") {
return Swal.fire({
title: confirmMsg,
icon: "warning",
showCancelButton: true,
confirmButtonText: "Yes",
cancelButtonText: "Cancel"
}).then(function (result) {
if (!result.isConfirmed) return;
return _doFetch(el, verbInfo, method, url, extraParams);
});
}
if (!window.confirm(confirmMsg)) return Promise.resolve();
}
// sx-prompt: show prompt dialog, send result as SX-Prompt header
var promptMsg = el.getAttribute("sx-prompt");
if (promptMsg) {
var promptVal = window.prompt(promptMsg);
if (promptVal === null) return Promise.resolve(); // cancelled
extraParams = extraParams || {};
extraParams.promptValue = promptVal;
}
return _doFetch(el, verbInfo, method, url, extraParams);
}
function _doFetch(el, verbInfo, method, url, extraParams) {
// sx-sync: abort previous
var sync = el.getAttribute("sx-sync");
if (sync && sync.indexOf("replace") >= 0) abortPrevious(el);
var ctrl = new AbortController();
trackController(el, ctrl);
// Build headers
var headers = {
"SX-Request": "true",
"SX-Current-URL": location.href
};
var targetSel = el.getAttribute("sx-target");
if (targetSel) headers["SX-Target"] = targetSel;
// Send loaded component names so cross-domain responses can prepend missing defs
var loadedNames = Object.keys(_componentEnv).filter(function (k) {
return k.charAt(0) === "~";
});
if (loadedNames.length) headers["SX-Components"] = loadedNames.join(",");
// Send known CSS classes so server only sends new rules
var cssHeader = _getSxCssHeader();
if (cssHeader) headers["SX-Css"] = cssHeader;
// Extra headers from sx-headers (SX dict {:key "val"} or JSON)
var extraH = el.getAttribute("sx-headers");
if (extraH) {
try {
var parsed = extraH.charAt(0) === "{" && extraH.charAt(1) === ":" ? parse(extraH) : JSON.parse(extraH);
for (var k in parsed) headers[k] = String(parsed[k]);
} catch (e) { /* ignore */ }
}
// SX-Prompt header from sx-prompt dialog result
if (extraParams && extraParams.promptValue !== undefined) {
headers["SX-Prompt"] = extraParams.promptValue;
}
// CSRF for same-origin mutating requests
if (method !== "GET" && sameOrigin(url)) {
var csrf = csrfToken();
if (csrf) headers["X-CSRFToken"] = csrf;
}
// Build body
var body = null;
var isJson = el.getAttribute("sx-encoding") === "json";
if (method !== "GET") {
var form = el.closest("form") || (el.tagName === "FORM" ? el : null);
if (form) {
if (isJson) {
var fd = new FormData(form);
var obj = {};
fd.forEach(function (v, k) {
if (obj[k] !== undefined) {
if (!Array.isArray(obj[k])) obj[k] = [obj[k]];
obj[k].push(v);
} else {
obj[k] = v;
}
});
body = JSON.stringify(obj);
headers["Content-Type"] = "application/json";
} else {
body = new URLSearchParams(new FormData(form));
headers["Content-Type"] = "application/x-www-form-urlencoded";
}
}
}
// sx-params: filter form parameters
var paramsSpec = el.getAttribute("sx-params");
if (paramsSpec && body instanceof URLSearchParams) {
if (paramsSpec === "none") {
body = new URLSearchParams();
} else if (paramsSpec.indexOf("not ") === 0) {
var excluded = paramsSpec.substring(4).split(",").map(function (s) { return s.trim(); });
excluded.forEach(function (k) { body.delete(k); });
} else if (paramsSpec !== "*") {
var allowed = paramsSpec.split(",").map(function (s) { return s.trim(); });
var filtered = new URLSearchParams();
allowed.forEach(function (k) {
body.getAll(k).forEach(function (v) { filtered.append(k, v); });
});
body = filtered;
}
}
// Include extra inputs
var includeSel = el.getAttribute("sx-include");
if (includeSel && method !== "GET") {
var extras = document.querySelectorAll(includeSel);
if (!body) body = new URLSearchParams();
extras.forEach(function (inp) {
if (inp.name) body.append(inp.name, inp.value);
});
}
// sx-vals: merge extra key-value pairs
var valsAttr = el.getAttribute("sx-vals");
if (valsAttr) {
try {
var vals = valsAttr.charAt(0) === "{" && valsAttr.charAt(1) === ":" ? parse(valsAttr) : JSON.parse(valsAttr);
if (method === "GET") {
for (var vk in vals) {
url += (url.indexOf("?") >= 0 ? "&" : "?") + encodeURIComponent(vk) + "=" + encodeURIComponent(vals[vk]);
}
} else if (body instanceof URLSearchParams) {
for (var vk2 in vals) body.append(vk2, vals[vk2]);
} else if (!body) {
body = new URLSearchParams();
for (var vk3 in vals) body.append(vk3, vals[vk3]);
headers["Content-Type"] = "application/x-www-form-urlencoded";
}
} catch (e) { /* ignore */ }
}
// For GET with form data, append to URL
if (method === "GET") {
var form2 = el.closest("form") || (el.tagName === "FORM" ? el : null);
if (form2) {
var qs = new URLSearchParams(new FormData(form2)).toString();
if (qs) url += (url.indexOf("?") >= 0 ? "&" : "?") + qs;
}
// Also handle inputs/selects/textareas with name attr
if ((el.tagName === "INPUT" || el.tagName === "SELECT" || el.tagName === "TEXTAREA") && el.name) {
var param = encodeURIComponent(el.name) + "=" + encodeURIComponent(el.value);
url += (url.indexOf("?") >= 0 ? "&" : "?") + param;
}
}
// Lifecycle: beforeRequest
if (!dispatch(el, "sx:beforeRequest", { method: method, url: url })) return Promise.resolve();
// Loading state
el.classList.add("sx-request");
el.setAttribute("aria-busy", "true");
// sx-indicator: show indicator element
var indicatorSel = el.getAttribute("sx-indicator");
var indicatorEl = indicatorSel ? (document.querySelector(indicatorSel) || el.closest(indicatorSel)) : null;
if (indicatorEl) {
indicatorEl.classList.add("sx-request");
indicatorEl.style.display = "";
}
// sx-disabled-elt: disable elements during request
var disabledEltSel = el.getAttribute("sx-disabled-elt");
var disabledElts = disabledEltSel ? Array.prototype.slice.call(document.querySelectorAll(disabledEltSel)) : [];
disabledElts.forEach(function (e) { e.disabled = true; });
var fetchOpts = { method: method, headers: headers, signal: ctrl.signal };
// Cross-origin credentials for known subdomains
try {
var urlHost = new URL(url, location.href).hostname;
if (urlHost !== location.hostname &&
(urlHost.endsWith(".rose-ash.com") || urlHost.endsWith(".localhost"))) {
fetchOpts.credentials = "include";
}
} catch (e) {}
if (body && method !== "GET") fetchOpts.body = body;
// sx-preload: use cached response if available
var preloaded = method === "GET" ? _getPreloaded(url) : null;
var fetchPromise = preloaded
? Promise.resolve({ ok: true, status: 200, headers: new Headers({ "Content-Type": preloaded.contentType }), text: function () { return Promise.resolve(preloaded.text); }, _preloaded: true })
: fetch(url, fetchOpts);
return fetchPromise.then(function (resp) {
el.classList.remove("sx-request");
el.removeAttribute("aria-busy");
if (indicatorEl) { indicatorEl.classList.remove("sx-request"); indicatorEl.style.display = "none"; }
disabledElts.forEach(function (e) { e.disabled = false; });
if (!resp.ok) {
dispatch(el, "sx:responseError", { response: resp, status: resp.status });
return _handleRetry(el, verbInfo, extraParams);
}
return resp.text().then(function (text) {
dispatch(el, "sx:afterRequest", { response: resp });
// --- Response header processing ---
// SX-Redirect: navigate away (skip swap entirely)
var hdrRedirect = resp.headers.get("SX-Redirect");
if (hdrRedirect) { location.assign(hdrRedirect); return; }
// SX-Refresh: reload page (skip swap entirely)
var hdrRefresh = resp.headers.get("SX-Refresh");
if (hdrRefresh === "true") { location.reload(); return; }
// SX-Trigger: dispatch custom events on target
var hdrTrigger = resp.headers.get("SX-Trigger");
if (hdrTrigger) _dispatchTriggerEvents(el, hdrTrigger);
// Process the response
var rawSwap = el.getAttribute("sx-swap") || DEFAULT_SWAP;
var target = resolveTarget(el, null);
var selectSel = el.getAttribute("sx-select");
// SX-Retarget: server overrides target
var hdrRetarget = resp.headers.get("SX-Retarget");
if (hdrRetarget) target = document.querySelector(hdrRetarget) || target;
// SX-Reswap: server overrides swap strategy
var hdrReswap = resp.headers.get("SX-Reswap");
if (hdrReswap) rawSwap = hdrReswap;
// Parse swap style and modifiers (e.g. "innerHTML transition:true")
var swapParts = rawSwap.split(/\s+/);
var swapStyle = swapParts[0];
var useTransition = _config.globalViewTransitions;
for (var sp = 1; sp < swapParts.length; sp++) {
if (swapParts[sp] === "transition:true") useTransition = true;
else if (swapParts[sp] === "transition:false") useTransition = false;
}
// Check for text/sx content type — use direct DOM rendering path
var ct = resp.headers.get("Content-Type") || "";
if (ct.indexOf("text/sx") >= 0) {
try {
// Strip and load any <script type="text/sx" data-components> blocks
text = text.replace(/<script[^>]*type="text\/sx"[^>]*data-components[^>]*>([\s\S]*?)<\/script>/gi,
function (_, defs) { Sx.loadComponents(defs); return ""; });
// Process on-demand CSS: extract <style data-sx-css> and inject into head
text = _processCssResponse(text, resp);
var sxSource = text.trim();
// Parse and render to live DOM nodes (skip renderToString + DOMParser)
if (sxSource && sxSource.charAt(0) !== "(") {
console.error("sx.js: sxSource does not start with '(' — first 200 chars:", sxSource.substring(0, 200));
}
var sxDom = Sx.render(sxSource);
// Wrap in container for querySelectorAll (DocumentFragment doesn't support it)
var container = document.createElement("div");
container.appendChild(sxDom);
// OOB processing on live DOM nodes
_processOOBSwaps(container, _swapDOM);
// sx-select filtering
var selectedDOM;
if (selectSel) {
selectedDOM = document.createDocumentFragment();
selectSel.split(",").forEach(function (sel) {
container.querySelectorAll(sel.trim()).forEach(function (m) {
selectedDOM.appendChild(m);
});
});
} else {
// Use all remaining children
selectedDOM = document.createDocumentFragment();
while (container.firstChild) selectedDOM.appendChild(container.firstChild);
}
// Main swap using DOM morph
if (swapStyle !== "none" && target) {
_withTransition(useTransition, function () {
_swapDOM(target, selectedDOM, swapStyle);
_hoistHeadElements(target);
});
}
} catch (err) {
console.error("sx.js render error [v2]:", err, "\nsxSource first 500:", sxSource ? sxSource.substring(0, 500) : "(empty)");
return;
}
} else {
// HTML string path — existing DOMParser pipeline
var parser = new DOMParser();
var doc = parser.parseFromString(text, "text/html");
// Process any sx script blocks in the response
Sx.processScripts(doc);
// OOB processing
_processOOBSwaps(doc, function (t, o, s) { _swapContent(t, o.outerHTML, s); });
// Build final content
var content;
if (selectSel) {
var parts = selectSel.split(",").map(function (s) { return s.trim(); });
var frags = [];
parts.forEach(function (sel) {
var matches = doc.querySelectorAll(sel);
matches.forEach(function (m) { frags.push(m.outerHTML); });
});
content = frags.join("");
} else {
content = doc.body ? doc.body.innerHTML : text;
}
// Main swap
if (swapStyle !== "none" && target) {
_withTransition(useTransition, function () {
_swapContent(target, content, swapStyle);
_hoistHeadElements(target);
});
}
}
// SX-Location: server-driven client-side navigation
var hdrLocation = resp.headers.get("SX-Location");
if (hdrLocation) {
var locUrl = hdrLocation;
try { var locObj = JSON.parse(hdrLocation); locUrl = locObj.path || locObj; } catch (e) {}
fetch(locUrl, { headers: { "SX-Request": "true" } }).then(function (r) {
return r.text().then(function (t) {
var main = document.getElementById("main-panel");
if (main) { _swapContent(main, t, "innerHTML"); _postSwap(main); }
try { history.pushState({ sxUrl: locUrl }, "", locUrl); } catch (e) {}
});
});
return;
}
// History: sx-push-url (pushState) and sx-replace-url (replaceState)
var pushUrl = el.getAttribute("sx-push-url");
var replaceUrl = el.getAttribute("sx-replace-url");
// SX-Replace-Url response header overrides client-side attribute
var hdrReplaceUrl = resp.headers.get("SX-Replace-Url");
if (hdrReplaceUrl) {
try { history.replaceState({ sxUrl: hdrReplaceUrl, scrollY: window.scrollY }, "", hdrReplaceUrl); } catch (e) {}
} else if (pushUrl === "true" || (pushUrl && pushUrl !== "false")) {
var pushTarget = pushUrl === "true" ? url : pushUrl;
try {
history.pushState({ sxUrl: pushTarget, scrollY: window.scrollY }, "", pushTarget);
} catch (e) {
location.assign(pushTarget);
return;
}
} else if (replaceUrl === "true" || (replaceUrl && replaceUrl !== "false")) {
var replTarget = replaceUrl === "true" ? url : replaceUrl;
try {
history.replaceState({ sxUrl: replTarget, scrollY: window.scrollY }, "", replTarget);
} catch (e) { /* ignore */ }
}
dispatch(el, "sx:afterSwap", { target: target });
// SX-Trigger-After-Swap
var hdrTriggerSwap = resp.headers.get("SX-Trigger-After-Swap");
if (hdrTriggerSwap) _dispatchTriggerEvents(el, hdrTriggerSwap);
// Settle tick
requestAnimationFrame(function () {
dispatch(el, "sx:afterSettle", { target: target });
// SX-Trigger-After-Settle
var hdrTriggerSettle = resp.headers.get("SX-Trigger-After-Settle");
if (hdrTriggerSettle) _dispatchTriggerEvents(el, hdrTriggerSettle);
});
});
}).catch(function (err) {
el.classList.remove("sx-request");
el.removeAttribute("aria-busy");
if (indicatorEl) { indicatorEl.classList.remove("sx-request"); indicatorEl.style.display = "none"; }
disabledElts.forEach(function (e) { e.disabled = false; });
if (err.name === "AbortError") return;
dispatch(el, "sx:sendError", { error: err });
return _handleRetry(el, verbInfo, extraParams);
});
}
// ---- DOM morphing ------------------------------------------------------
/**
* Lightweight DOM reconciler — patches oldNode to match newNode in-place,
* preserving event listeners, focus, scroll position, and form state on
* keyed (id) elements.
*/
function _morphDOM(oldNode, newNode) {
// sx-preserve / sx-ignore: skip morphing entirely
if (oldNode.hasAttribute && (oldNode.hasAttribute("sx-preserve") || oldNode.hasAttribute("sx-ignore"))) return;
// Different node types or tag names → replace wholesale
if (oldNode.nodeType !== newNode.nodeType ||
oldNode.nodeName !== newNode.nodeName) {
oldNode.parentNode.replaceChild(newNode.cloneNode(true), oldNode);
return;
}
// Text/comment nodes → update content
if (oldNode.nodeType === 3 || oldNode.nodeType === 8) {
if (oldNode.nodeValue !== newNode.nodeValue)
oldNode.nodeValue = newNode.nodeValue;
return;
}
// Element nodes → sync attributes, then recurse children
if (oldNode.nodeType === 1) {
// Skip morphing focused input to preserve user's in-progress edits
if (oldNode === document.activeElement &&
(oldNode.tagName === "INPUT" || oldNode.tagName === "TEXTAREA" || oldNode.tagName === "SELECT")) {
_syncAttrs(oldNode, newNode); // sync non-value attrs (class, style, etc.)
return; // don't touch value or children
}
_syncAttrs(oldNode, newNode);
_morphChildren(oldNode, newNode);
}
}
function _syncAttrs(old, neu) {
// Add/update attributes from new
var newAttrs = neu.attributes;
for (var i = 0; i < newAttrs.length; i++) {
var a = newAttrs[i];
if (old.getAttribute(a.name) !== a.value)
old.setAttribute(a.name, a.value);
}
// Remove attributes not in new
var oldAttrs = old.attributes;
for (var j = oldAttrs.length - 1; j >= 0; j--) {
if (!neu.hasAttribute(oldAttrs[j].name))
old.removeAttribute(oldAttrs[j].name);
}
}
function _morphChildren(oldParent, newParent) {
var oldChildren = Array.prototype.slice.call(oldParent.childNodes);
var newChildren = Array.prototype.slice.call(newParent.childNodes);
// Build ID map of old children for keyed matching
var oldById = {};
for (var k = 0; k < oldChildren.length; k++) {
var kid = oldChildren[k];
if (kid.id) oldById[kid.id] = kid;
}
var oi = 0;
for (var ni = 0; ni < newChildren.length; ni++) {
var newChild = newChildren[ni];
var matchById = newChild.id ? oldById[newChild.id] : null;
if (matchById) {
// Keyed match — move into position if needed, then morph
if (matchById !== oldChildren[oi]) {
oldParent.insertBefore(matchById, oldChildren[oi] || null);
}
_morphDOM(matchById, newChild);
oi++;
} else if (oi < oldChildren.length) {
// Positional match — morph in place
var oldChild = oldChildren[oi];
if (oldChild.id && !newChild.id) {
// Old has ID, new doesn't — insert new before old (don't clobber keyed)
oldParent.insertBefore(newChild.cloneNode(true), oldChild);
} else {
_morphDOM(oldChild, newChild);
oi++;
}
} else {
// Extra new children — append
oldParent.appendChild(newChild.cloneNode(true));
}
}
// Remove leftover old children (skip sx-preserve / sx-ignore)
while (oi < oldChildren.length) {
var leftover = oldChildren[oi];
if (leftover.parentNode === oldParent &&
!(leftover.hasAttribute && (leftover.hasAttribute("sx-preserve") || leftover.hasAttribute("sx-ignore")))) {
oldParent.removeChild(leftover);
}
oi++;
}
}
// ---- DOM-native swap engine --------------------------------------------
/**
* Swap using live DOM nodes (from Sx.render) instead of HTML strings.
* Uses _morphDOM for innerHTML/outerHTML to preserve state.
*/
function _swapDOM(target, newNodes, strategy) {
// newNodes is a DocumentFragment, Element, or Text node
var wrapper;
switch (strategy) {
case "innerHTML":
// Morph children of target to match newNodes
if (newNodes.nodeType === 11) {
// DocumentFragment — morph its children into target
_morphChildren(target, newNodes);
} else {
wrapper = document.createElement("div");
wrapper.appendChild(newNodes);
_morphChildren(target, wrapper);
}
break;
case "outerHTML":
var parent = target.parentNode;
if (newNodes.nodeType === 11) {
// Fragment — morph first child, insert rest
var first = newNodes.firstChild;
if (first) {
_morphDOM(target, first);
var sib = first.nextSibling; // skip first (used as morph template, not consumed)
while (sib) {
var next = sib.nextSibling;
parent.insertBefore(sib, target.nextSibling);
sib = next;
}
} else {
parent.removeChild(target);
}
} else {
_morphDOM(target, newNodes);
}
_postSwap(parent);
return; // early return like existing outerHTML
case "afterend":
target.parentNode.insertBefore(newNodes, target.nextSibling);
break;
case "beforeend":
target.appendChild(newNodes);
break;
case "afterbegin":
target.insertBefore(newNodes, target.firstChild);
break;
case "beforebegin":
target.parentNode.insertBefore(newNodes, target);
break;
case "delete":
target.parentNode.removeChild(target);
return;
default: // fallback = innerHTML
if (newNodes.nodeType === 11) {
_morphChildren(target, newNodes);
} else {
wrapper = document.createElement("div");
wrapper.appendChild(newNodes);
_morphChildren(target, wrapper);
}
}
_postSwap(target);
}
// ---- Swap engine (string-based, kept as fallback) ----------------------
function _processOOBSwaps(container, swapFn, postSwapFn) {
["sx-swap-oob", "hx-swap-oob"].forEach(function (attr) {
container.querySelectorAll("[" + attr + "]").forEach(function (oob) {
var swapType = oob.getAttribute(attr) || "outerHTML";
var target = document.getElementById(oob.id);
oob.removeAttribute(attr);
if (oob.parentNode) oob.parentNode.removeChild(oob);
if (target) {
swapFn(target, oob, swapType);
if (postSwapFn) postSwapFn(target);
}
});
});
}
/** Scripts inserted via innerHTML/insertAdjacentHTML don't execute.
* Recreate them as live elements so the browser fetches & runs them. */
function _activateScripts(root) {
var dead = root.querySelectorAll("script:not([type]), script[type='text/javascript']");
for (var i = 0; i < dead.length; i++) {
var d = dead[i];
var live = document.createElement("script");
for (var a = 0; a < d.attributes.length; a++)
live.setAttribute(d.attributes[a].name, d.attributes[a].value);
live.textContent = d.textContent;
d.parentNode.replaceChild(live, d);
}
}
function _postSwap(root) {
_activateScripts(root);
Sx.processScripts(root);
Sx.hydrate(root);
SxEngine.process(root);
}
function _swapContent(target, html, strategy) {
switch (strategy) {
case "innerHTML":
// Detach sx-preserve elements, swap, then re-attach
var preserved = [];
target.querySelectorAll("[sx-preserve][id]").forEach(function (el) {
preserved.push({ id: el.id, node: el });
el.parentNode.removeChild(el);
});
target.innerHTML = html;
preserved.forEach(function (p) {
var placeholder = target.querySelector("#" + CSS.escape(p.id));
if (placeholder) placeholder.parentNode.replaceChild(p.node, placeholder);
else target.appendChild(p.node);
});
break;
case "outerHTML":
var tgt = target;
var parent = tgt.parentNode;
tgt.insertAdjacentHTML("afterend", html);
parent.removeChild(tgt);
_postSwap(parent);
return; // early return — afterSwap handling done inline
case "afterend":
target.insertAdjacentHTML("afterend", html);
break;
case "beforeend":
target.insertAdjacentHTML("beforeend", html);
break;
case "afterbegin":
target.insertAdjacentHTML("afterbegin", html);
break;
case "beforebegin":
target.insertAdjacentHTML("beforebegin", html);
break;
case "delete":
target.parentNode.removeChild(target);
return;
default:
target.innerHTML = html;
}
_postSwap(target);
}
// ---- Retry system -----------------------------------------------------
function _handleRetry(el, verbInfo, extraParams) {
var retry = el.getAttribute("sx-retry");
if (!retry) return;
var parts = retry.split(":");
var strategy = parts[0]; // "exponential"
var startMs = parseInt(parts[1], 10) || 1000;
var capMs = parseInt(parts[2], 10) || 30000;
var currentMs = parseInt(el.getAttribute("data-sx-retry-ms"), 10) || startMs;
el.classList.add("sx-error");
el.classList.remove("sx-loading");
setTimeout(function () {
el.classList.remove("sx-error");
el.classList.add("sx-loading");
el.setAttribute("data-sx-retry-ms", Math.min(currentMs * 2, capMs));
executeRequest(el, verbInfo, extraParams);
}, currentMs);
}
// ---- Trigger system ---------------------------------------------------
function _parseTime(s) {
// Parse time string: "2s" → 2000, "500ms" → 500, "1.5s" → 1500
if (!s) return 0;
if (s.indexOf("ms") >= 0) return parseInt(s, 10);
if (s.indexOf("s") >= 0) return parseFloat(s) * 1000;
return parseInt(s, 10);
}
function parseTrigger(spec) {
if (!spec) return null;
var triggers = [];
var parts = spec.split(",");
for (var i = 0; i < parts.length; i++) {
var p = parts[i].trim();
if (!p) continue;
var tokens = p.split(/\s+/);
// Handle "every <time>" as a special trigger
if (tokens[0] === "every" && tokens.length >= 2) {
triggers.push({ event: "every", modifiers: { interval: _parseTime(tokens[1]) } });
continue;
}
var trigger = { event: tokens[0], modifiers: {} };
for (var j = 1; j < tokens.length; j++) {
var tok = tokens[j];
if (tok === "once") trigger.modifiers.once = true;
else if (tok === "changed") trigger.modifiers.changed = true;
else if (tok.indexOf("delay:") === 0) trigger.modifiers.delay = _parseTime(tok.substring(6));
else if (tok.indexOf("from:") === 0) trigger.modifiers.from = tok.substring(5);
}
triggers.push(trigger);
}
return triggers;
}
function bindTriggers(el, verbInfo) {
var triggerSpec = el.getAttribute("sx-trigger");
var triggers;
if (triggerSpec) {
triggers = parseTrigger(triggerSpec);
} else {
// Defaults
if (el.tagName === "FORM") {
triggers = [{ event: "submit", modifiers: {} }];
} else if (el.tagName === "INPUT" || el.tagName === "SELECT" || el.tagName === "TEXTAREA") {
triggers = [{ event: "change", modifiers: {} }];
} else {
triggers = [{ event: "click", modifiers: {} }];
}
}
triggers.forEach(function (trig) {
if (trig.event === "every") {
var ms = trig.modifiers.interval || 1000;
setInterval(function () { executeRequest(el, verbInfo); }, ms);
} else if (trig.event === "intersect") {
_bindIntersect(el, verbInfo, trig.modifiers);
} else if (trig.event === "load") {
setTimeout(function () { executeRequest(el, verbInfo); }, 0);
} else if (trig.event === "revealed") {
_bindIntersect(el, verbInfo, { once: true });
} else {
_bindEvent(el, verbInfo, trig);
}
});
}
function _bindEvent(el, verbInfo, trig) {
var eventName = trig.event;
var mods = trig.modifiers;
var listenTarget = mods.from ? document.querySelector(mods.from) || el : el;
var timer = null;
var lastVal = undefined;
var handler = function (e) {
// For form submissions, prevent default
if (eventName === "submit") e.preventDefault();
// For links, prevent navigation
if (eventName === "click" && el.tagName === "A") e.preventDefault();
// sx-validate: run validation before request
var validateAttr = el.getAttribute("sx-validate");
if (validateAttr === null) {
var vForm = el.closest("[sx-validate]");
if (vForm) validateAttr = vForm.getAttribute("sx-validate");
}
if (validateAttr !== null) {
var formToValidate = el.tagName === "FORM" ? el : el.closest("form");
if (formToValidate && !formToValidate.reportValidity()) {
dispatch(el, "sx:validationFailed", {});
return;
}
// Custom validator function
if (validateAttr && validateAttr !== "true" && validateAttr !== "") {
var validatorFn = window[validateAttr];
if (typeof validatorFn === "function" && !validatorFn(el)) {
dispatch(el, "sx:validationFailed", {});
return;
}
}
}
// changed modifier: only fire if value changed
if (mods.changed && el.value !== undefined) {
if (el.value === lastVal) return;
lastVal = el.value;
}
// sx-optimistic: apply preview before request
var optimisticState = _applyOptimistic(el);
var _execAndReconcile = function () {
var p = executeRequest(el, verbInfo);
if (optimisticState && p && p.catch) {
p.catch(function () { _revertOptimistic(optimisticState); });
}
};
if (mods.delay) {
clearTimeout(timer);
timer = setTimeout(_execAndReconcile, mods.delay);
} else {
_execAndReconcile();
}
};
listenTarget.addEventListener(eventName, handler, { once: !!mods.once });
}
function _bindIntersect(el, verbInfo, mods) {
if (!("IntersectionObserver" in window)) {
executeRequest(el, verbInfo);
return;
}
var fired = false;
var delay = mods.delay || 0;
var obs = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (!entry.isIntersecting) return;
if (mods.once && fired) return;
fired = true;
if (mods.once) obs.unobserve(el);
if (delay) {
setTimeout(function () { executeRequest(el, verbInfo); }, delay);
} else {
executeRequest(el, verbInfo);
}
});
});
obs.observe(el);
}
// ---- History manager --------------------------------------------------
if (typeof window !== "undefined") {
window.addEventListener("popstate", function (e) {
var url = location.href;
var main = document.getElementById("main-panel");
if (!main) { location.reload(); return; }
var histHeaders = { "SX-Request": "true", "SX-History-Restore": "true" };
var cssH = _getSxCssHeader();
if (cssH) histHeaders["SX-Css"] = cssH;
var loadedN = Object.keys(_componentEnv).filter(function (k) { return k.charAt(0) === "~"; });
if (loadedN.length) histHeaders["SX-Components"] = loadedN.join(",");
var histOpts = { headers: histHeaders };
try {
var hHost = new URL(url, location.href).hostname;
if (hHost !== location.hostname &&
(hHost.endsWith(".rose-ash.com") || hHost.endsWith(".localhost"))) {
histOpts.credentials = "include";
}
} catch (e) {}
fetch(url, histOpts).then(function (resp) {
return resp.text().then(function (t) { return { text: t, resp: resp }; });
}).then(function (r) {
var text = r.text;
var resp = r.resp;
// Strip and load any <script type="text/sx" data-components> blocks
text = text.replace(/<script[^>]*type="text\/sx"[^>]*data-components[^>]*>([\s\S]*?)<\/script>/gi,
function (_, defs) { Sx.loadComponents(defs); return ""; });
// Process on-demand CSS
text = _processCssResponse(text, resp);
text = text.trim();
if (text.charAt(0) === "(") {
// sx response — render to live DOM, morph into main
try {
var popDom = Sx.render(text);
var popContainer = document.createElement("div");
popContainer.appendChild(popDom);
// Process OOB swaps (sidebar, filter, menu, headers)
_processOOBSwaps(popContainer, _swapDOM, function (t) { Sx.hydrate(t); SxEngine.process(t); });
var newMain = popContainer.querySelector("#main-panel");
_morphChildren(main, newMain || popContainer);
_postSwap(main);
dispatch(document.body, "sx:afterSettle", { target: main });
window.scrollTo(0, e.state && e.state.scrollY || 0);
} catch (err) {
console.error("sx.js popstate render error [v2]:", err, "\ntext first 500:", text ? text.substring(0, 500) : "(empty)");
location.reload();
}
} else {
// HTML response — parse and morph
var parser = new DOMParser();
var doc = parser.parseFromString(text, "text/html");
// Process OOB swaps from HTML response
_processOOBSwaps(doc, function (t, o, s) { _swapContent(t, o.outerHTML, s); });
var newMain = doc.getElementById("main-panel");
if (newMain) {
_morphChildren(main, newMain);
_postSwap(main);
dispatch(document.body, "sx:afterSettle", { target: main });
window.scrollTo(0, e.state && e.state.scrollY || 0);
} else {
location.reload();
}
}
}).catch(function () {
location.reload();
});
});
}
// ---- sx-on:* inline event handlers ------------------------------------
function _bindInlineHandlers(el) {
var attrs = el.attributes;
for (var i = 0; i < attrs.length; i++) {
var name = attrs[i].name;
if (name.indexOf("sx-on:") === 0) {
var evtName = name.substring(6);
el.addEventListener(evtName, new Function("event", attrs[i].value));
}
}
}
// ---- sx-optimistic ----------------------------------------------------
function _applyOptimistic(el) {
var directive = el.getAttribute("sx-optimistic");
if (!directive) return null;
var target = resolveTarget(el, null) || el;
var state = { target: target, directive: directive };
if (directive === "remove") {
state.display = target.style.display;
state.opacity = target.style.opacity;
target.style.opacity = "0";
target.style.pointerEvents = "none";
} else if (directive === "disable") {
state.disabled = target.disabled;
target.disabled = true;
} else if (directive.indexOf("add-class:") === 0) {
var cls = directive.substring(10);
state.addClass = cls;
target.classList.add(cls);
}
return state;
}
function _revertOptimistic(state) {
if (!state) return;
var target = state.target;
if (state.directive === "remove") {
target.style.opacity = state.opacity || "";
target.style.pointerEvents = "";
} else if (state.directive === "disable") {
target.disabled = state.disabled || false;
} else if (state.addClass) {
target.classList.remove(state.addClass);
}
}
// ---- sx-preload -------------------------------------------------------
var _preloadCache = {};
var _PRELOAD_TTL = 30000; // 30 seconds
function _bindPreload(el) {
if (!el.hasAttribute("sx-preload")) return;
var mode = el.getAttribute("sx-preload") || "mousedown";
var events = mode === "mouseover" ? ["mouseenter", "focusin"] : ["mousedown", "focusin"];
var debounceTimer = null;
var debounceMs = mode === "mouseover" ? 100 : 0;
events.forEach(function (evt) {
el.addEventListener(evt, function () {
var verb = getVerb(el);
if (!verb) return;
var url = verb.url;
var cached = _preloadCache[url];
if (cached && (Date.now() - cached.timestamp < _PRELOAD_TTL)) return; // already cached
if (debounceMs) {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(function () { _doPreload(url); }, debounceMs);
} else {
_doPreload(url);
}
});
});
}
function _doPreload(url) {
var headers = { "SX-Request": "true", "SX-Current-URL": location.href };
var cssH = _getSxCssHeader();
if (cssH) headers["SX-Css"] = cssH;
var loadedN = Object.keys(_componentEnv).filter(function (k) { return k.charAt(0) === "~"; });
if (loadedN.length) headers["SX-Components"] = loadedN.join(",");
fetch(url, { headers: headers }).then(function (resp) {
if (!resp.ok) return;
var ct = resp.headers.get("Content-Type") || "";
return resp.text().then(function (text) {
_preloadCache[url] = { text: text, contentType: ct, timestamp: Date.now() };
});
}).catch(function () { /* ignore preload errors */ });
}
function _getPreloaded(url) {
var cached = _preloadCache[url];
if (!cached) return null;
if (Date.now() - cached.timestamp > _PRELOAD_TTL) {
delete _preloadCache[url];
return null;
}
delete _preloadCache[url]; // consume once
return cached;
}
// ---- sx-boost ---------------------------------------------------------
function _processBoosted(root) {
var boostContainers = root.querySelectorAll("[sx-boost]");
if (root.matches && root.matches("[sx-boost]")) {
_boostDescendants(root);
}
for (var i = 0; i < boostContainers.length; i++) {
_boostDescendants(boostContainers[i]);
}
}
function _boostDescendants(container) {
// Boost links
var links = container.querySelectorAll("a[href]");
for (var i = 0; i < links.length; i++) {
var link = links[i];
if (link[PROCESSED] || link[PROCESSED + "boost"]) continue;
var href = link.getAttribute("href");
// Skip anchors, external, javascript:, mailto:, already sx-processed
if (!href || href.charAt(0) === "#" || href.indexOf("javascript:") === 0 ||
href.indexOf("mailto:") === 0 || !sameOrigin(href) ||
link.hasAttribute("sx-get") || link.hasAttribute("sx-post") ||
link.hasAttribute("sx-disable")) continue;
link[PROCESSED + "boost"] = true;
(function (el, url) {
el.addEventListener("click", function (e) {
e.preventDefault();
executeRequest(el, { method: "GET", url: url }).then(function () {
try { history.pushState({ sxUrl: url, scrollY: window.scrollY }, "", url); } catch (err) {}
});
});
})(link, href);
// Default target for boosted links
if (!link.hasAttribute("sx-target")) link.setAttribute("sx-target", "#main-panel");
if (!link.hasAttribute("sx-swap")) link.setAttribute("sx-swap", "innerHTML");
if (!link.hasAttribute("sx-select")) link.setAttribute("sx-select", "#main-panel");
}
// Boost forms
var forms = container.querySelectorAll("form");
for (var j = 0; j < forms.length; j++) {
var form = forms[j];
if (form[PROCESSED] || form[PROCESSED + "boost"]) continue;
if (form.hasAttribute("sx-get") || form.hasAttribute("sx-post") ||
form.hasAttribute("sx-disable")) continue;
form[PROCESSED + "boost"] = true;
(function (el) {
var method = (el.getAttribute("method") || "GET").toUpperCase();
var action = el.getAttribute("action") || location.href;
el.addEventListener("submit", function (e) {
e.preventDefault();
executeRequest(el, { method: method, url: action }).then(function () {
try { history.pushState({ sxUrl: action, scrollY: window.scrollY }, "", action); } catch (err) {}
});
});
})(form);
if (!form.hasAttribute("sx-target")) form.setAttribute("sx-target", "#main-panel");
if (!form.hasAttribute("sx-swap")) form.setAttribute("sx-swap", "innerHTML");
}
}
// ---- SSE (Server-Sent Events) ----------------------------------------
function _processSSE(root) {
var sseEls = root.querySelectorAll("[sx-sse]");
if (root.matches && root.matches("[sx-sse]")) _bindSSE(root);
for (var i = 0; i < sseEls.length; i++) _bindSSE(sseEls[i]);
}
function _bindSSE(el) {
if (el._sxSSE) return; // already connected
var url = el.getAttribute("sx-sse");
if (!url) return;
var source = new EventSource(url);
el._sxSSE = source;
// Bind swap handlers for sx-sse-swap="eventName" attributes on el and descendants
var swapEls = el.querySelectorAll("[sx-sse-swap]");
if (el.hasAttribute("sx-sse-swap")) _bindSSESwap(el, source);
for (var i = 0; i < swapEls.length; i++) _bindSSESwap(swapEls[i], source);
source.addEventListener("error", function () { dispatch(el, "sx:sseError", {}); });
source.addEventListener("open", function () { dispatch(el, "sx:sseOpen", {}); });
// Cleanup: close EventSource when element is removed from DOM
if (typeof MutationObserver !== "undefined") {
var obs = new MutationObserver(function () {
if (!document.body.contains(el)) {
source.close();
el._sxSSE = null;
obs.disconnect();
}
});
obs.observe(document.body, { childList: true, subtree: true });
}
}
function _bindSSESwap(el, source) {
var eventName = el.getAttribute("sx-sse-swap") || "message";
source.addEventListener(eventName, function (e) {
var target = resolveTarget(el, null) || el;
var swapStyle = el.getAttribute("sx-swap") || "innerHTML";
var data = e.data;
if (data.trim().charAt(0) === "(") {
try {
var dom = Sx.render(data);
_swapDOM(target, dom, swapStyle);
} catch (err) {
_swapContent(target, data, swapStyle);
}
} else {
_swapContent(target, data, swapStyle);
}
_postSwap(target);
dispatch(el, "sx:sseMessage", { data: data, event: eventName });
});
}
// ---- Process function -------------------------------------------------
function process(root) {
root = root || document.body;
if (!root || !root.querySelectorAll) return;
var selector = "[sx-get],[sx-post],[sx-put],[sx-delete],[sx-patch]";
var elements = root.querySelectorAll(selector);
// Also check root itself
if (root.matches && root.matches(selector)) {
_processOne(root);
}
for (var i = 0; i < elements.length; i++) {
_processOne(elements[i]);
}
// Process sx-boost containers
_processBoosted(root);
// Process SSE connections
_processSSE(root);
// Bind sx-on:* handlers on all elements
var allOnEls = root.querySelectorAll("[sx-on\\:beforeRequest],[sx-on\\:afterRequest],[sx-on\\:afterSwap],[sx-on\\:afterSettle],[sx-on\\:responseError]");
allOnEls.forEach(function (el) {
if (el[PROCESSED + "on"]) return;
el[PROCESSED + "on"] = true;
_bindInlineHandlers(el);
});
}
function _processOne(el) {
if (el[PROCESSED]) return;
// sx-disable: skip processing
if (el.hasAttribute("sx-disable") || el.closest("[sx-disable]")) return;
el[PROCESSED] = true;
var verbInfo = getVerb(el);
if (!verbInfo) return;
bindTriggers(el, verbInfo);
_bindPreload(el);
}
// ---- Public API -------------------------------------------------------
var engine = {
process: process,
executeRequest: executeRequest,
config: _config,
version: "1.0.0"
};
return engine;
})();
global.SxEngine = SxEngine;
// --- Auto-init in browser ---
Sx.VERSION = "2026-03-04-cssx2";
// CSS class tracking for on-demand CSS delivery
var _sxCssHash = ""; // 8-char hex hash from server
function _initCssTracking() {
var meta = document.querySelector('meta[name="sx-css-classes"]');
if (meta) {
var content = meta.getAttribute("content");
if (content) _sxCssHash = content;
}
}
function _getSxCssHeader() {
return _sxCssHash;
}
function _processCssResponse(text, resp) {
var hashHeader = resp.headers.get("SX-Css-Hash");
if (hashHeader) _sxCssHash = hashHeader;
// Extract <style data-sx-css>...</style> blocks and inject into <style id="sx-css">
var cssTarget = document.getElementById("sx-css");
if (cssTarget) {
text = text.replace(/<style[^>]*data-sx-css[^>]*>([\s\S]*?)<\/style>/gi,
function (_, css) {
cssTarget.textContent += css;
return "";
});
}
return text;
}
// --- sx-comp-hash cookie helpers ---
function _setSxCompCookie(hash) {
document.cookie = "sx-comp-hash=" + hash + ";path=/;max-age=31536000;SameSite=Lax";
}
function _clearSxCompCookie() {
document.cookie = "sx-comp-hash=;path=/;max-age=0;SameSite=Lax";
}
if (typeof document !== "undefined") {
var init = function () {
console.log("[sx.js] v" + Sx.VERSION + " init");
_initCssTracking();
Sx.processScripts();
Sx.hydrate();
SxEngine.process();
// Process any streaming suspense resolutions that arrived before init
if (global.__sxPending) {
for (var pi = 0; pi < global.__sxPending.length; pi++) {
Sx.resolveSuspense(global.__sxPending[pi].id, global.__sxPending[pi].sx);
}
global.__sxPending = null;
}
// Replace bootstrap resolver with direct calls
global.__sxResolve = function (id, sx) { Sx.resolveSuspense(id, sx); };
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
}
})(typeof window !== "undefined" ? window : this);