Fix reactive islands client-side navigation and hydration
Three bugs prevented islands from working during SX wire navigation: 1. components_for_request() only bundled Component and Macro defs, not Island defs — client never received defisland definitions during navigation (components_for_page for initial HTML shell was correct). 2. hydrate-island used morph-children which can't transfer addEventListener event handlers from freshly rendered DOM to existing nodes. Changed to clear+append so reactive DOM with live signal subscriptions is inserted directly. 3. asyncRenderToDom (client-side async page eval) checked _component but not _island on ~-prefixed names — islands fell through to generic eval which failed. Now delegates to renderDomIsland. 4. setInterval_/setTimeout_ passed SX Lambda objects directly to native timers. JS coerced them to "[object Object]" and tried to eval as code, causing "missing ] after element list". Added _wrapSxFn to convert SX lambdas to JS functions before passing to timers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@
|
|||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
|
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
|
||||||
var SX_VERSION = "2026-03-08T11:56:02Z";
|
var SX_VERSION = "2026-03-08T15:15:32Z";
|
||||||
|
|
||||||
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
||||||
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
||||||
@@ -191,6 +191,17 @@
|
|||||||
function trackingContextAddDep(ctx, s) { if (ctx && ctx.deps.indexOf(s) < 0) ctx.deps.push(s); }
|
function trackingContextAddDep(ctx, s) { if (ctx && ctx.deps.indexOf(s) < 0) ctx.deps.push(s); }
|
||||||
function trackingContextNotifyFn(ctx) { return ctx ? ctx.notifyFn : NIL; }
|
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
|
// JSON / dict helpers for island state serialization
|
||||||
function jsonSerialize(obj) {
|
function jsonSerialize(obj) {
|
||||||
try { return JSON.stringify(obj); } catch(e) { return "{}"; }
|
try { return JSON.stringify(obj); } catch(e) { return "{}"; }
|
||||||
@@ -216,17 +227,8 @@
|
|||||||
|
|
||||||
// Render-expression detection — lets the evaluator delegate to the active adapter.
|
// Render-expression detection — lets the evaluator delegate to the active adapter.
|
||||||
// Matches HTML tags, SVG tags, <>, raw!, ~components, html: prefix, custom elements.
|
// Matches HTML tags, SVG tags, <>, raw!, ~components, html: prefix, custom elements.
|
||||||
function isRenderExpr(expr) {
|
// Placeholder — overridden by transpiled version from render.sx
|
||||||
if (!Array.isArray(expr) || !expr.length) return false;
|
function isRenderExpr(expr) { return false; }
|
||||||
var h = expr[0];
|
|
||||||
if (!h || !h._sym) return false;
|
|
||||||
var n = h.name;
|
|
||||||
return !!(n === "<>" || n === "raw!" ||
|
|
||||||
n.charAt(0) === "~" || n.indexOf("html:") === 0 ||
|
|
||||||
(typeof HTML_TAGS !== "undefined" && HTML_TAGS.indexOf(n) >= 0) ||
|
|
||||||
(typeof SVG_TAGS !== "undefined" && SVG_TAGS.indexOf(n) >= 0) ||
|
|
||||||
(n.indexOf("-") > 0 && expr.length > 1 && expr[1] && expr[1]._kw));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render dispatch — call the active adapter's render function.
|
// Render dispatch — call the active adapter's render function.
|
||||||
// Set by each adapter when loaded; defaults to identity (no rendering).
|
// Set by each adapter when loaded; defaults to identity (no rendering).
|
||||||
@@ -322,6 +324,8 @@
|
|||||||
PRIMITIVES["starts-with?"] = function(s, p) { return String(s).indexOf(p) === 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["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["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["concat"] = function() {
|
PRIMITIVES["concat"] = function() {
|
||||||
var out = [];
|
var out = [];
|
||||||
for (var i = 0; i < arguments.length; i++) if (!isNil(arguments[i])) out = out.concat(arguments[i]);
|
for (var i = 0; i < arguments.length; i++) if (!isNil(arguments[i])) out = out.concat(arguments[i]);
|
||||||
@@ -465,7 +469,10 @@
|
|||||||
var range = PRIMITIVES["range"];
|
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 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; }
|
function append_b(arr, x) { arr.push(x); return arr; }
|
||||||
var apply = function(f, args) { return f.apply(null, args); };
|
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
|
// Additional primitive aliases used by adapter/engine transpiled code
|
||||||
var split = PRIMITIVES["split"];
|
var split = PRIMITIVES["split"];
|
||||||
@@ -484,28 +491,12 @@
|
|||||||
function escapeAttr(s) { return escapeHtml(s); }
|
function escapeAttr(s) { return escapeHtml(s); }
|
||||||
function rawHtmlContent(r) { return r.html; }
|
function rawHtmlContent(r) { return r.html; }
|
||||||
function makeRawHtml(s) { return { _raw: true, html: s }; }
|
function makeRawHtml(s) { return { _raw: true, html: s }; }
|
||||||
|
function sxExprSource(x) { return x && x.source ? x.source : String(x); }
|
||||||
|
|
||||||
// Serializer
|
// Placeholders — overridden by transpiled spec from parser.sx / adapter-sx.sx
|
||||||
function serialize(val) {
|
function serialize(val) { return String(val); }
|
||||||
if (isNil(val)) return "nil";
|
function isSpecialForm(n) { return false; }
|
||||||
if (typeof val === "boolean") return val ? "true" : "false";
|
function isHoForm(n) { return false; }
|
||||||
if (typeof val === "number") return String(val);
|
|
||||||
if (typeof val === "string") return '"' + val.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
|
|
||||||
if (isSym(val)) return val.name;
|
|
||||||
if (isKw(val)) return ":" + val.name;
|
|
||||||
if (Array.isArray(val)) return "(" + val.map(serialize).join(" ") + ")";
|
|
||||||
return String(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isSpecialForm(n) { return n in {
|
|
||||||
"if":1,"when":1,"cond":1,"case":1,"and":1,"or":1,"let":1,"let*":1,
|
|
||||||
"lambda":1,"fn":1,"define":1,"defcomp":1,"defmacro":1,"defstyle":1,
|
|
||||||
"defhandler":1,"begin":1,"do":1,
|
|
||||||
"quote":1,"quasiquote":1,"->":1,"set!":1
|
|
||||||
}; }
|
|
||||||
function isHoForm(n) { return n in {
|
|
||||||
"map":1,"map-indexed":1,"filter":1,"reduce":1,"some":1,"every?":1,"for-each":1
|
|
||||||
}; }
|
|
||||||
|
|
||||||
// processBindings and evalCond — now specced in render.sx, bootstrapped above
|
// processBindings and evalCond — now specced in render.sx, bootstrapped above
|
||||||
|
|
||||||
@@ -1077,6 +1068,15 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai
|
|||||||
return 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 ===
|
// === Transpiled from parser ===
|
||||||
|
|
||||||
@@ -1188,6 +1188,269 @@ continue; } else { return NIL; } } };
|
|||||||
// sx-serialize-dict
|
// 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("}")); };
|
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 ===
|
// === Transpiled from adapter-dom ===
|
||||||
|
|
||||||
@@ -1229,10 +1492,7 @@ continue; } else { return NIL; } } };
|
|||||||
return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() {
|
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 attrName = keywordName(arg);
|
||||||
var attrVal = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env));
|
var attrVal = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env));
|
||||||
(isSxTruthy(sxOr(isNil(attrVal), (attrVal == false))) ? NIL : (isSxTruthy((isSxTruthy(startsWith(attrName, "on-")) && isCallable(attrVal))) ? (function() {
|
(isSxTruthy(sxOr(isNil(attrVal), (attrVal == false))) ? NIL : (isSxTruthy((isSxTruthy(startsWith(attrName, "on-")) && isCallable(attrVal))) ? domListen(el, slice(attrName, 3), attrVal) : (isSxTruthy(contains(BOOLEAN_ATTRS, attrName)) ? (isSxTruthy(attrVal) ? domSetAttr(el, attrName, "") : NIL) : (isSxTruthy((attrVal == true)) ? domSetAttr(el, attrName, "") : domSetAttr(el, attrName, (String(attrVal)))))));
|
||||||
var eventName = substring(attrName, 3, stringLength(attrName));
|
|
||||||
return domListen(el, eventName, attrVal);
|
|
||||||
})() : (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));
|
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)))));
|
})() : ((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);
|
})(); }, {["i"]: 0, ["skip"]: false}, args);
|
||||||
@@ -2445,7 +2705,8 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
{ 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); } }
|
{ 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() {
|
return (function() {
|
||||||
var bodyDom = withIslandScope(function(disposable) { return append_b(disposers, disposable); }, function() { return renderToDom(componentBody(comp), local, NIL); });
|
var bodyDom = withIslandScope(function(disposable) { return append_b(disposers, disposable); }, function() { return renderToDom(componentBody(comp), local, NIL); });
|
||||||
morphChildren(el, bodyDom);
|
domSetTextContent(el, "");
|
||||||
|
domAppend(el, bodyDom);
|
||||||
domSetData(el, "sx-disposers", disposers);
|
domSetData(el, "sx-disposers", disposers);
|
||||||
processElements(el);
|
processElements(el);
|
||||||
return logInfo((String("hydrated island: ") + String(compName) + String(" (") + String(len(disposers)) + String(" disposers)")));
|
return logInfo((String("hydrated island: ") + String(compName) + String(" (") + String(len(disposers)) + String(" disposers)")));
|
||||||
@@ -2577,7 +2838,7 @@ return (function() {
|
|||||||
var prev = getTrackingContext();
|
var prev = getTrackingContext();
|
||||||
setTrackingContext(ctx);
|
setTrackingContext(ctx);
|
||||||
return (function() {
|
return (function() {
|
||||||
var newVal = computeFn();
|
var newVal = invoke(computeFn);
|
||||||
setTrackingContext(prev);
|
setTrackingContext(prev);
|
||||||
signalSetDeps(s, trackingContextDeps(ctx));
|
signalSetDeps(s, trackingContextDeps(ctx));
|
||||||
return (function() {
|
return (function() {
|
||||||
@@ -2599,13 +2860,13 @@ return (function() {
|
|||||||
var disposed = false;
|
var disposed = false;
|
||||||
var cleanupFn = NIL;
|
var cleanupFn = NIL;
|
||||||
return (function() {
|
return (function() {
|
||||||
var runEffect = function() { return (isSxTruthy(!isSxTruthy(disposed)) ? ((isSxTruthy(cleanupFn) ? cleanupFn() : NIL), forEach(function(dep) { return signalRemoveSub(dep, runEffect); }, deps), (deps = []), (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);
|
var ctx = makeTrackingContext(runEffect);
|
||||||
return (function() {
|
return (function() {
|
||||||
var prev = getTrackingContext();
|
var prev = getTrackingContext();
|
||||||
setTrackingContext(ctx);
|
setTrackingContext(ctx);
|
||||||
return (function() {
|
return (function() {
|
||||||
var result = effectFn();
|
var result = invoke(effectFn);
|
||||||
setTrackingContext(prev);
|
setTrackingContext(prev);
|
||||||
deps = trackingContextDeps(ctx);
|
deps = trackingContextDeps(ctx);
|
||||||
return (isSxTruthy(isCallable(result)) ? (cleanupFn = result) : NIL);
|
return (isSxTruthy(isCallable(result)) ? (cleanupFn = result) : NIL);
|
||||||
@@ -2615,7 +2876,7 @@ return (function() {
|
|||||||
runEffect();
|
runEffect();
|
||||||
return function() { disposed = true;
|
return function() { disposed = true;
|
||||||
if (isSxTruthy(cleanupFn)) {
|
if (isSxTruthy(cleanupFn)) {
|
||||||
cleanupFn();
|
invoke(cleanupFn);
|
||||||
}
|
}
|
||||||
{ var _c = deps; for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, runEffect); } }
|
{ var _c = deps; for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, runEffect); } }
|
||||||
return (deps = []); };
|
return (deps = []); };
|
||||||
@@ -2630,7 +2891,7 @@ return (deps = []); };
|
|||||||
|
|
||||||
// batch
|
// batch
|
||||||
var batch = function(thunk) { _batchDepth = (_batchDepth + 1);
|
var batch = function(thunk) { _batchDepth = (_batchDepth + 1);
|
||||||
thunk();
|
invoke(thunk);
|
||||||
_batchDepth = (_batchDepth - 1);
|
_batchDepth = (_batchDepth - 1);
|
||||||
return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
||||||
var queue = _batchQueue;
|
var queue = _batchQueue;
|
||||||
@@ -2679,7 +2940,7 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
var defStore = function(name, initFn) { return (function() {
|
var defStore = function(name, initFn) { return (function() {
|
||||||
var registry = _storeRegistry;
|
var registry = _storeRegistry;
|
||||||
if (isSxTruthy(!isSxTruthy(hasKey_p(registry, name)))) {
|
if (isSxTruthy(!isSxTruthy(hasKey_p(registry, name)))) {
|
||||||
_storeRegistry = assoc(registry, name, initFn());
|
_storeRegistry = assoc(registry, name, invoke(initFn));
|
||||||
}
|
}
|
||||||
return get(_storeRegistry, name);
|
return get(_storeRegistry, name);
|
||||||
})(); };
|
})(); };
|
||||||
@@ -2700,7 +2961,7 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
var bridgeEvent = function(el, eventName, targetSignal, transformFn) { return effect(function() { return (function() {
|
var bridgeEvent = function(el, eventName, targetSignal, transformFn) { return effect(function() { return (function() {
|
||||||
var remove = domListen(el, eventName, function(e) { return (function() {
|
var remove = domListen(el, eventName, function(e) { return (function() {
|
||||||
var detail = eventDetail(e);
|
var detail = eventDetail(e);
|
||||||
var newVal = (isSxTruthy(transformFn) ? transformFn(detail) : detail);
|
var newVal = (isSxTruthy(transformFn) ? invoke(transformFn, detail) : detail);
|
||||||
return reset_b(targetSignal, newVal);
|
return reset_b(targetSignal, newVal);
|
||||||
})(); });
|
})(); });
|
||||||
return remove;
|
return remove;
|
||||||
@@ -2858,8 +3119,12 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
|
|
||||||
function domListen(el, name, handler) {
|
function domListen(el, name, handler) {
|
||||||
if (!_hasDom || !el) return function() {};
|
if (!_hasDom || !el) return function() {};
|
||||||
el.addEventListener(name, handler);
|
// Wrap SX lambdas from runtime-evaluated island code into native fns
|
||||||
return function() { el.removeEventListener(name, handler); };
|
var wrapped = isLambda(handler)
|
||||||
|
? function(e) { invoke(handler, e); }
|
||||||
|
: handler;
|
||||||
|
el.addEventListener(name, wrapped);
|
||||||
|
return function() { el.removeEventListener(name, wrapped); };
|
||||||
}
|
}
|
||||||
|
|
||||||
function eventDetail(e) {
|
function eventDetail(e) {
|
||||||
@@ -2900,79 +3165,8 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
try { return JSON.parse(s); } catch(e) { return {}; }
|
try { return JSON.parse(s); } catch(e) { return {}; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// renderDomComponent and renderDomElement are transpiled from
|
||||||
// Performance overrides — replace transpiled spec with imperative JS
|
// adapter-dom.sx — no imperative overrides needed.
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
// Override renderDomComponent: imperative kwarg parsing, no reduce/assoc
|
|
||||||
renderDomComponent = function(comp, args, env, ns) {
|
|
||||||
// Parse keyword args imperatively
|
|
||||||
var kwargs = {};
|
|
||||||
var children = [];
|
|
||||||
for (var i = 0; i < args.length; i++) {
|
|
||||||
var arg = args[i];
|
|
||||||
if (arg && arg._kw && (i + 1) < args.length) {
|
|
||||||
kwargs[arg.name] = trampoline(evalExpr(args[i + 1], env));
|
|
||||||
i++; // skip value
|
|
||||||
} else {
|
|
||||||
children.push(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Build local env via prototype chain
|
|
||||||
var local = Object.create(componentClosure(comp));
|
|
||||||
// Copy caller env own properties
|
|
||||||
for (var k in env) if (env.hasOwnProperty(k)) local[k] = env[k];
|
|
||||||
// Bind params
|
|
||||||
var params = componentParams(comp);
|
|
||||||
for (var j = 0; j < params.length; j++) {
|
|
||||||
var p = params[j];
|
|
||||||
local[p] = p in kwargs ? kwargs[p] : NIL;
|
|
||||||
}
|
|
||||||
// Bind children
|
|
||||||
if (componentHasChildren(comp)) {
|
|
||||||
var childFrag = document.createDocumentFragment();
|
|
||||||
for (var c = 0; c < children.length; c++) {
|
|
||||||
var rendered = renderToDom(children[c], env, ns);
|
|
||||||
if (rendered) childFrag.appendChild(rendered);
|
|
||||||
}
|
|
||||||
local["children"] = childFrag;
|
|
||||||
}
|
|
||||||
return renderToDom(componentBody(comp), local, ns);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Override renderDomElement: imperative attr parsing, no reduce/assoc
|
|
||||||
renderDomElement = function(tag, args, env, ns) {
|
|
||||||
var newNs = tag === "svg" ? SVG_NS : tag === "math" ? MATH_NS : ns;
|
|
||||||
var el = domCreateElement(tag, newNs);
|
|
||||||
var extraClasses = [];
|
|
||||||
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 = trampoline(evalExpr(args[i + 1], env));
|
|
||||||
i++; // skip value
|
|
||||||
if (isNil(attrVal) || attrVal === false) continue;
|
|
||||||
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 = renderToDom(arg, env, newNs);
|
|
||||||
if (child) el.appendChild(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (extraClasses.length) {
|
|
||||||
var existing = el.getAttribute("class") || "";
|
|
||||||
el.setAttribute("class", (existing ? existing + " " : "") + extraClasses.join(" "));
|
|
||||||
}
|
|
||||||
return el;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -3092,8 +3286,14 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
|
|
||||||
// --- Timers ---
|
// --- Timers ---
|
||||||
|
|
||||||
function setTimeout_(fn, ms) { return setTimeout(fn, ms || 0); }
|
function _wrapSxFn(fn) {
|
||||||
function setInterval_(fn, ms) { return setInterval(fn, ms || 1000); }
|
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 clearTimeout_(id) { clearTimeout(id); }
|
||||||
function clearInterval_(id) { clearInterval(id); }
|
function clearInterval_(id) { clearInterval(id); }
|
||||||
function requestAnimationFrame_(fn) {
|
function requestAnimationFrame_(fn) {
|
||||||
@@ -3619,6 +3819,8 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
logInfo("sx:route server " + pathname);
|
logInfo("sx:route server " + pathname);
|
||||||
executeRequest(link, { method: "GET", url: liveHref }).then(function() {
|
executeRequest(link, { method: "GET", url: liveHref }).then(function() {
|
||||||
try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {}
|
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));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -4004,11 +4206,14 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Expose render functions as primitives so SX code can call them
|
// 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;
|
if (typeof renderToDom === "function") PRIMITIVES["render-to-dom"] = renderToDom;
|
||||||
|
|
||||||
// Expose signal functions as primitives so runtime-evaluated SX code
|
// Expose signal functions as primitives so runtime-evaluated SX code
|
||||||
// (e.g. island bodies from .sx files) can call them
|
// (e.g. island bodies from .sx files) can call them
|
||||||
PRIMITIVES["signal"] = createSignal;
|
PRIMITIVES["signal"] = signal;
|
||||||
PRIMITIVES["signal?"] = isSignal;
|
PRIMITIVES["signal?"] = isSignal;
|
||||||
PRIMITIVES["deref"] = deref;
|
PRIMITIVES["deref"] = deref;
|
||||||
PRIMITIVES["reset!"] = reset_b;
|
PRIMITIVES["reset!"] = reset_b;
|
||||||
@@ -4016,7 +4221,9 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
PRIMITIVES["computed"] = computed;
|
PRIMITIVES["computed"] = computed;
|
||||||
PRIMITIVES["effect"] = effect;
|
PRIMITIVES["effect"] = effect;
|
||||||
PRIMITIVES["batch"] = batch;
|
PRIMITIVES["batch"] = batch;
|
||||||
PRIMITIVES["dispose"] = dispose;
|
// Timer primitives for island code
|
||||||
|
PRIMITIVES["set-interval"] = setInterval_;
|
||||||
|
PRIMITIVES["clear-interval"] = clearInterval_;
|
||||||
// Reactive DOM helpers for island code
|
// Reactive DOM helpers for island code
|
||||||
PRIMITIVES["reactive-text"] = reactiveText;
|
PRIMITIVES["reactive-text"] = reactiveText;
|
||||||
PRIMITIVES["create-text-node"] = createTextNode;
|
PRIMITIVES["create-text-node"] = createTextNode;
|
||||||
@@ -4200,9 +4407,10 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Component
|
// Component or Island
|
||||||
if (hname.charAt(0) === "~") {
|
if (hname.charAt(0) === "~") {
|
||||||
var comp = env[hname];
|
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._component) return asyncRenderComponent(comp, expr.slice(1), env, ns);
|
||||||
if (comp && comp._macro) {
|
if (comp && comp._macro) {
|
||||||
var expanded = trampoline(expandMacro(comp, expr.slice(1), env));
|
var expanded = trampoline(expandMacro(comp, expr.slice(1), env));
|
||||||
@@ -4691,12 +4899,25 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function render(source) {
|
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 exprs = parse(source);
|
||||||
var frag = document.createDocumentFragment();
|
var frag = document.createDocumentFragment();
|
||||||
for (var i = 0; i < exprs.length; i++) frag.appendChild(renderToDom(exprs[i], merge(componentEnv), null));
|
for (var i = 0; i < exprs.length; i++) frag.appendChild(renderToDom(exprs[i], merge(componentEnv), null));
|
||||||
return frag;
|
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 = {
|
var Sx = {
|
||||||
VERSION: "ref-2.0",
|
VERSION: "ref-2.0",
|
||||||
parse: parse,
|
parse: parse,
|
||||||
@@ -4704,7 +4925,7 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
eval: function(expr, env) { return trampoline(evalExpr(expr, env || merge(componentEnv))); },
|
eval: function(expr, env) { return trampoline(evalExpr(expr, env || merge(componentEnv))); },
|
||||||
loadComponents: loadComponents,
|
loadComponents: loadComponents,
|
||||||
render: render,
|
render: render,
|
||||||
|
renderToString: renderToString,
|
||||||
serialize: serialize,
|
serialize: serialize,
|
||||||
NIL: NIL,
|
NIL: NIL,
|
||||||
Symbol: Symbol,
|
Symbol: Symbol,
|
||||||
@@ -4712,6 +4933,8 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
isTruthy: isSxTruthy,
|
isTruthy: isSxTruthy,
|
||||||
isNil: isNil,
|
isNil: isNil,
|
||||||
componentEnv: componentEnv,
|
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,
|
renderToDom: _hasDom ? function(expr, env, ns) { return renderToDom(expr, env || merge(componentEnv), ns || null); } : null,
|
||||||
parseTriggerSpec: typeof parseTriggerSpec === "function" ? parseTriggerSpec : null,
|
parseTriggerSpec: typeof parseTriggerSpec === "function" ? parseTriggerSpec : null,
|
||||||
parseTime: typeof parseTime === "function" ? parseTime : null,
|
parseTime: typeof parseTime === "function" ? parseTime : null,
|
||||||
@@ -4759,7 +4982,7 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
emitEvent: emitEvent,
|
emitEvent: emitEvent,
|
||||||
onEvent: onEvent,
|
onEvent: onEvent,
|
||||||
bridgeEvent: bridgeEvent,
|
bridgeEvent: bridgeEvent,
|
||||||
_version: "ref-2.0 (boot+dom+engine+orchestration+parser, bootstrap-compiled)"
|
_version: "ref-2.0 (boot+dom+engine+html+orchestration+parser+sx, bootstrap-compiled)"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
|
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
|
||||||
var SX_VERSION = "2026-03-08T11:49:09Z";
|
var SX_VERSION = "2026-03-08T15:15:32Z";
|
||||||
|
|
||||||
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
||||||
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
||||||
@@ -191,6 +191,17 @@
|
|||||||
function trackingContextAddDep(ctx, s) { if (ctx && ctx.deps.indexOf(s) < 0) ctx.deps.push(s); }
|
function trackingContextAddDep(ctx, s) { if (ctx && ctx.deps.indexOf(s) < 0) ctx.deps.push(s); }
|
||||||
function trackingContextNotifyFn(ctx) { return ctx ? ctx.notifyFn : NIL; }
|
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
|
// JSON / dict helpers for island state serialization
|
||||||
function jsonSerialize(obj) {
|
function jsonSerialize(obj) {
|
||||||
try { return JSON.stringify(obj); } catch(e) { return "{}"; }
|
try { return JSON.stringify(obj); } catch(e) { return "{}"; }
|
||||||
@@ -216,17 +227,8 @@
|
|||||||
|
|
||||||
// Render-expression detection — lets the evaluator delegate to the active adapter.
|
// Render-expression detection — lets the evaluator delegate to the active adapter.
|
||||||
// Matches HTML tags, SVG tags, <>, raw!, ~components, html: prefix, custom elements.
|
// Matches HTML tags, SVG tags, <>, raw!, ~components, html: prefix, custom elements.
|
||||||
function isRenderExpr(expr) {
|
// Placeholder — overridden by transpiled version from render.sx
|
||||||
if (!Array.isArray(expr) || !expr.length) return false;
|
function isRenderExpr(expr) { return false; }
|
||||||
var h = expr[0];
|
|
||||||
if (!h || !h._sym) return false;
|
|
||||||
var n = h.name;
|
|
||||||
return !!(n === "<>" || n === "raw!" ||
|
|
||||||
n.charAt(0) === "~" || n.indexOf("html:") === 0 ||
|
|
||||||
(typeof HTML_TAGS !== "undefined" && HTML_TAGS.indexOf(n) >= 0) ||
|
|
||||||
(typeof SVG_TAGS !== "undefined" && SVG_TAGS.indexOf(n) >= 0) ||
|
|
||||||
(n.indexOf("-") > 0 && expr.length > 1 && expr[1] && expr[1]._kw));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render dispatch — call the active adapter's render function.
|
// Render dispatch — call the active adapter's render function.
|
||||||
// Set by each adapter when loaded; defaults to identity (no rendering).
|
// Set by each adapter when loaded; defaults to identity (no rendering).
|
||||||
@@ -322,6 +324,8 @@
|
|||||||
PRIMITIVES["starts-with?"] = function(s, p) { return String(s).indexOf(p) === 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["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["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["concat"] = function() {
|
PRIMITIVES["concat"] = function() {
|
||||||
var out = [];
|
var out = [];
|
||||||
for (var i = 0; i < arguments.length; i++) if (!isNil(arguments[i])) out = out.concat(arguments[i]);
|
for (var i = 0; i < arguments.length; i++) if (!isNil(arguments[i])) out = out.concat(arguments[i]);
|
||||||
@@ -465,7 +469,10 @@
|
|||||||
var range = PRIMITIVES["range"];
|
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 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; }
|
function append_b(arr, x) { arr.push(x); return arr; }
|
||||||
var apply = function(f, args) { return f.apply(null, args); };
|
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
|
// Additional primitive aliases used by adapter/engine transpiled code
|
||||||
var split = PRIMITIVES["split"];
|
var split = PRIMITIVES["split"];
|
||||||
@@ -484,28 +491,12 @@
|
|||||||
function escapeAttr(s) { return escapeHtml(s); }
|
function escapeAttr(s) { return escapeHtml(s); }
|
||||||
function rawHtmlContent(r) { return r.html; }
|
function rawHtmlContent(r) { return r.html; }
|
||||||
function makeRawHtml(s) { return { _raw: true, html: s }; }
|
function makeRawHtml(s) { return { _raw: true, html: s }; }
|
||||||
|
function sxExprSource(x) { return x && x.source ? x.source : String(x); }
|
||||||
|
|
||||||
// Serializer
|
// Placeholders — overridden by transpiled spec from parser.sx / adapter-sx.sx
|
||||||
function serialize(val) {
|
function serialize(val) { return String(val); }
|
||||||
if (isNil(val)) return "nil";
|
function isSpecialForm(n) { return false; }
|
||||||
if (typeof val === "boolean") return val ? "true" : "false";
|
function isHoForm(n) { return false; }
|
||||||
if (typeof val === "number") return String(val);
|
|
||||||
if (typeof val === "string") return '"' + val.replace(/\\/g, "\\\\").replace(/"/g, '\\"') + '"';
|
|
||||||
if (isSym(val)) return val.name;
|
|
||||||
if (isKw(val)) return ":" + val.name;
|
|
||||||
if (Array.isArray(val)) return "(" + val.map(serialize).join(" ") + ")";
|
|
||||||
return String(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isSpecialForm(n) { return n in {
|
|
||||||
"if":1,"when":1,"cond":1,"case":1,"and":1,"or":1,"let":1,"let*":1,
|
|
||||||
"lambda":1,"fn":1,"define":1,"defcomp":1,"defmacro":1,"defstyle":1,
|
|
||||||
"defhandler":1,"begin":1,"do":1,
|
|
||||||
"quote":1,"quasiquote":1,"->":1,"set!":1
|
|
||||||
}; }
|
|
||||||
function isHoForm(n) { return n in {
|
|
||||||
"map":1,"map-indexed":1,"filter":1,"reduce":1,"some":1,"every?":1,"for-each":1
|
|
||||||
}; }
|
|
||||||
|
|
||||||
// processBindings and evalCond — now specced in render.sx, bootstrapped above
|
// processBindings and evalCond — now specced in render.sx, bootstrapped above
|
||||||
|
|
||||||
@@ -572,92 +563,6 @@
|
|||||||
return makeThunk(componentBody(comp), local);
|
return makeThunk(componentBody(comp), local);
|
||||||
};
|
};
|
||||||
|
|
||||||
// =========================================================================
|
|
||||||
// Platform: deps module — component dependency analysis
|
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
function componentDeps(c) {
|
|
||||||
return c.deps ? c.deps.slice() : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function componentSetDeps(c, deps) {
|
|
||||||
c.deps = deps;
|
|
||||||
}
|
|
||||||
|
|
||||||
function componentCssClasses(c) {
|
|
||||||
return c.cssClasses ? c.cssClasses.slice() : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function envComponents(env) {
|
|
||||||
var names = [];
|
|
||||||
for (var k in env) {
|
|
||||||
var v = env[k];
|
|
||||||
if (v && (v._component || v._macro)) names.push(k);
|
|
||||||
}
|
|
||||||
return names;
|
|
||||||
}
|
|
||||||
|
|
||||||
function regexFindAll(pattern, source) {
|
|
||||||
var re = new RegExp(pattern, "g");
|
|
||||||
var results = [];
|
|
||||||
var m;
|
|
||||||
while ((m = re.exec(source)) !== null) {
|
|
||||||
if (m[1] !== undefined) results.push(m[1]);
|
|
||||||
else results.push(m[0]);
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
function scanCssClasses(source) {
|
|
||||||
var classes = {};
|
|
||||||
var result = [];
|
|
||||||
var m;
|
|
||||||
var re1 = /:class\s+"([^"]*)"/g;
|
|
||||||
while ((m = re1.exec(source)) !== null) {
|
|
||||||
var parts = m[1].split(/\s+/);
|
|
||||||
for (var i = 0; i < parts.length; i++) {
|
|
||||||
if (parts[i] && !classes[parts[i]]) {
|
|
||||||
classes[parts[i]] = true;
|
|
||||||
result.push(parts[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var re2 = /:class\s+\(str\s+((?:"[^"]*"\s*)+)\)/g;
|
|
||||||
while ((m = re2.exec(source)) !== null) {
|
|
||||||
var re3 = /"([^"]*)"/g;
|
|
||||||
var m2;
|
|
||||||
while ((m2 = re3.exec(m[1])) !== null) {
|
|
||||||
var parts2 = m2[1].split(/\s+/);
|
|
||||||
for (var j = 0; j < parts2.length; j++) {
|
|
||||||
if (parts2[j] && !classes[parts2[j]]) {
|
|
||||||
classes[parts2[j]] = true;
|
|
||||||
result.push(parts2[j]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var re4 = /;;\s*@css\s+(.+)/g;
|
|
||||||
while ((m = re4.exec(source)) !== null) {
|
|
||||||
var parts3 = m[1].split(/\s+/);
|
|
||||||
for (var k = 0; k < parts3.length; k++) {
|
|
||||||
if (parts3[k] && !classes[parts3[k]]) {
|
|
||||||
classes[parts3[k]] = true;
|
|
||||||
result.push(parts3[k]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function componentIoRefs(c) {
|
|
||||||
return c.ioRefs ? c.ioRefs.slice() : [];
|
|
||||||
}
|
|
||||||
|
|
||||||
function componentSetIoRefs(c, refs) {
|
|
||||||
c.ioRefs = refs;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Platform interface — Parser
|
// Platform interface — Parser
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -1163,6 +1068,15 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai
|
|||||||
return 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 ===
|
// === Transpiled from parser ===
|
||||||
|
|
||||||
@@ -1274,6 +1188,9 @@ continue; } else { return NIL; } } };
|
|||||||
// sx-serialize-dict
|
// 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("}")); };
|
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 ===
|
// === Transpiled from adapter-html ===
|
||||||
|
|
||||||
@@ -1452,6 +1369,88 @@ continue; } else { return NIL; } } };
|
|||||||
return (String("(") + String(join(" ", parts)) + String(")"));
|
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 ===
|
// === Transpiled from adapter-dom ===
|
||||||
|
|
||||||
@@ -1493,10 +1492,7 @@ continue; } else { return NIL; } } };
|
|||||||
return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() {
|
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 attrName = keywordName(arg);
|
||||||
var attrVal = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env));
|
var attrVal = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env));
|
||||||
(isSxTruthy(sxOr(isNil(attrVal), (attrVal == false))) ? NIL : (isSxTruthy((isSxTruthy(startsWith(attrName, "on-")) && isCallable(attrVal))) ? (function() {
|
(isSxTruthy(sxOr(isNil(attrVal), (attrVal == false))) ? NIL : (isSxTruthy((isSxTruthy(startsWith(attrName, "on-")) && isCallable(attrVal))) ? domListen(el, slice(attrName, 3), attrVal) : (isSxTruthy(contains(BOOLEAN_ATTRS, attrName)) ? (isSxTruthy(attrVal) ? domSetAttr(el, attrName, "") : NIL) : (isSxTruthy((attrVal == true)) ? domSetAttr(el, attrName, "") : domSetAttr(el, attrName, (String(attrVal)))))));
|
||||||
var eventName = substring(attrName, 3, stringLength(attrName));
|
|
||||||
return domListen(el, eventName, attrVal);
|
|
||||||
})() : (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));
|
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)))));
|
})() : ((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);
|
})(); }, {["i"]: 0, ["skip"]: false}, args);
|
||||||
@@ -2709,7 +2705,8 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
{ 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); } }
|
{ 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() {
|
return (function() {
|
||||||
var bodyDom = withIslandScope(function(disposable) { return append_b(disposers, disposable); }, function() { return renderToDom(componentBody(comp), local, NIL); });
|
var bodyDom = withIslandScope(function(disposable) { return append_b(disposers, disposable); }, function() { return renderToDom(componentBody(comp), local, NIL); });
|
||||||
morphChildren(el, bodyDom);
|
domSetTextContent(el, "");
|
||||||
|
domAppend(el, bodyDom);
|
||||||
domSetData(el, "sx-disposers", disposers);
|
domSetData(el, "sx-disposers", disposers);
|
||||||
processElements(el);
|
processElements(el);
|
||||||
return logInfo((String("hydrated island: ") + String(compName) + String(" (") + String(len(disposers)) + String(" disposers)")));
|
return logInfo((String("hydrated island: ") + String(compName) + String(" (") + String(len(disposers)) + String(" disposers)")));
|
||||||
@@ -2729,146 +2726,6 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
var bootInit = function() { return (logInfo((String("sx-browser ") + String(SX_VERSION))), initCssTracking(), processPageScripts(), processSxScripts(NIL), sxHydrateElements(NIL), sxHydrateIslands(NIL), processElements(NIL)); };
|
var bootInit = function() { return (logInfo((String("sx-browser ") + String(SX_VERSION))), initCssTracking(), processPageScripts(), processSxScripts(NIL), sxHydrateElements(NIL), sxHydrateIslands(NIL), processElements(NIL)); };
|
||||||
|
|
||||||
|
|
||||||
// === Transpiled from deps (component dependency analysis) ===
|
|
||||||
|
|
||||||
// scan-refs
|
|
||||||
var scanRefs = function(node) { return (function() {
|
|
||||||
var refs = [];
|
|
||||||
scanRefsWalk(node, refs);
|
|
||||||
return refs;
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// scan-refs-walk
|
|
||||||
var scanRefsWalk = function(node, refs) { return (isSxTruthy((typeOf(node) == "symbol")) ? (function() {
|
|
||||||
var name = symbolName(node);
|
|
||||||
return (isSxTruthy(startsWith(name, "~")) ? (isSxTruthy(!isSxTruthy(contains(refs, name))) ? append_b(refs, name) : NIL) : NIL);
|
|
||||||
})() : (isSxTruthy((typeOf(node) == "list")) ? forEach(function(item) { return scanRefsWalk(item, refs); }, node) : (isSxTruthy((typeOf(node) == "dict")) ? forEach(function(key) { return scanRefsWalk(dictGet(node, key), refs); }, keys(node)) : NIL))); };
|
|
||||||
|
|
||||||
// transitive-deps-walk
|
|
||||||
var transitiveDepsWalk = function(n, seen, env) { return (isSxTruthy(!isSxTruthy(contains(seen, n))) ? (append_b(seen, n), (function() {
|
|
||||||
var val = envGet(env, n);
|
|
||||||
return (isSxTruthy((typeOf(val) == "component")) ? forEach(function(ref) { return transitiveDepsWalk(ref, seen, env); }, scanRefs(componentBody(val))) : (isSxTruthy((typeOf(val) == "macro")) ? forEach(function(ref) { return transitiveDepsWalk(ref, seen, env); }, scanRefs(macroBody(val))) : NIL));
|
|
||||||
})()) : NIL); };
|
|
||||||
|
|
||||||
// transitive-deps
|
|
||||||
var transitiveDeps = function(name, env) { return (function() {
|
|
||||||
var seen = [];
|
|
||||||
var key = (isSxTruthy(startsWith(name, "~")) ? name : (String("~") + String(name)));
|
|
||||||
transitiveDepsWalk(key, seen, env);
|
|
||||||
return filter(function(x) { return !isSxTruthy((x == key)); }, seen);
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// compute-all-deps
|
|
||||||
var computeAllDeps = function(env) { return forEach(function(name) { return (function() {
|
|
||||||
var val = envGet(env, name);
|
|
||||||
return (isSxTruthy((typeOf(val) == "component")) ? componentSetDeps(val, transitiveDeps(name, env)) : NIL);
|
|
||||||
})(); }, envComponents(env)); };
|
|
||||||
|
|
||||||
// scan-components-from-source
|
|
||||||
var scanComponentsFromSource = function(source) { return (function() {
|
|
||||||
var matches = regexFindAll("\\(~([a-zA-Z_][a-zA-Z0-9_\\-]*)", source);
|
|
||||||
return map(function(m) { return (String("~") + String(m)); }, matches);
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// components-needed
|
|
||||||
var componentsNeeded = function(pageSource, env) { return (function() {
|
|
||||||
var direct = scanComponentsFromSource(pageSource);
|
|
||||||
var allNeeded = [];
|
|
||||||
{ var _c = direct; for (var _i = 0; _i < _c.length; _i++) { var name = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(allNeeded, name)))) {
|
|
||||||
allNeeded.push(name);
|
|
||||||
}
|
|
||||||
(function() {
|
|
||||||
var val = envGet(env, name);
|
|
||||||
return (function() {
|
|
||||||
var deps = (isSxTruthy((isSxTruthy((typeOf(val) == "component")) && !isSxTruthy(isEmpty(componentDeps(val))))) ? componentDeps(val) : transitiveDeps(name, env));
|
|
||||||
return forEach(function(dep) { return (isSxTruthy(!isSxTruthy(contains(allNeeded, dep))) ? append_b(allNeeded, dep) : NIL); }, deps);
|
|
||||||
})();
|
|
||||||
})(); } }
|
|
||||||
return allNeeded;
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// page-component-bundle
|
|
||||||
var pageComponentBundle = function(pageSource, env) { return componentsNeeded(pageSource, env); };
|
|
||||||
|
|
||||||
// page-css-classes
|
|
||||||
var pageCssClasses = function(pageSource, env) { return (function() {
|
|
||||||
var needed = componentsNeeded(pageSource, env);
|
|
||||||
var classes = [];
|
|
||||||
{ var _c = needed; for (var _i = 0; _i < _c.length; _i++) { var name = _c[_i]; (function() {
|
|
||||||
var val = envGet(env, name);
|
|
||||||
return (isSxTruthy((typeOf(val) == "component")) ? forEach(function(cls) { return (isSxTruthy(!isSxTruthy(contains(classes, cls))) ? append_b(classes, cls) : NIL); }, componentCssClasses(val)) : NIL);
|
|
||||||
})(); } }
|
|
||||||
{ var _c = scanCssClasses(pageSource); for (var _i = 0; _i < _c.length; _i++) { var cls = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(classes, cls)))) {
|
|
||||||
classes.push(cls);
|
|
||||||
} } }
|
|
||||||
return classes;
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// scan-io-refs-walk
|
|
||||||
var scanIoRefsWalk = function(node, ioNames, refs) { return (isSxTruthy((typeOf(node) == "symbol")) ? (function() {
|
|
||||||
var name = symbolName(node);
|
|
||||||
return (isSxTruthy(contains(ioNames, name)) ? (isSxTruthy(!isSxTruthy(contains(refs, name))) ? append_b(refs, name) : NIL) : NIL);
|
|
||||||
})() : (isSxTruthy((typeOf(node) == "list")) ? forEach(function(item) { return scanIoRefsWalk(item, ioNames, refs); }, node) : (isSxTruthy((typeOf(node) == "dict")) ? forEach(function(key) { return scanIoRefsWalk(dictGet(node, key), ioNames, refs); }, keys(node)) : NIL))); };
|
|
||||||
|
|
||||||
// scan-io-refs
|
|
||||||
var scanIoRefs = function(node, ioNames) { return (function() {
|
|
||||||
var refs = [];
|
|
||||||
scanIoRefsWalk(node, ioNames, refs);
|
|
||||||
return refs;
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// transitive-io-refs-walk
|
|
||||||
var transitiveIoRefsWalk = function(n, seen, allRefs, env, ioNames) { return (isSxTruthy(!isSxTruthy(contains(seen, n))) ? (append_b(seen, n), (function() {
|
|
||||||
var val = envGet(env, n);
|
|
||||||
return (isSxTruthy((typeOf(val) == "component")) ? (forEach(function(ref) { return (isSxTruthy(!isSxTruthy(contains(allRefs, ref))) ? append_b(allRefs, ref) : NIL); }, scanIoRefs(componentBody(val), ioNames)), forEach(function(dep) { return transitiveIoRefsWalk(dep, seen, allRefs, env, ioNames); }, scanRefs(componentBody(val)))) : (isSxTruthy((typeOf(val) == "macro")) ? (forEach(function(ref) { return (isSxTruthy(!isSxTruthy(contains(allRefs, ref))) ? append_b(allRefs, ref) : NIL); }, scanIoRefs(macroBody(val), ioNames)), forEach(function(dep) { return transitiveIoRefsWalk(dep, seen, allRefs, env, ioNames); }, scanRefs(macroBody(val)))) : NIL));
|
|
||||||
})()) : NIL); };
|
|
||||||
|
|
||||||
// transitive-io-refs
|
|
||||||
var transitiveIoRefs = function(name, env, ioNames) { return (function() {
|
|
||||||
var allRefs = [];
|
|
||||||
var seen = [];
|
|
||||||
var key = (isSxTruthy(startsWith(name, "~")) ? name : (String("~") + String(name)));
|
|
||||||
transitiveIoRefsWalk(key, seen, allRefs, env, ioNames);
|
|
||||||
return allRefs;
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// compute-all-io-refs
|
|
||||||
var computeAllIoRefs = function(env, ioNames) { return forEach(function(name) { return (function() {
|
|
||||||
var val = envGet(env, name);
|
|
||||||
return (isSxTruthy((typeOf(val) == "component")) ? componentSetIoRefs(val, transitiveIoRefs(name, env, ioNames)) : NIL);
|
|
||||||
})(); }, envComponents(env)); };
|
|
||||||
|
|
||||||
// component-pure?
|
|
||||||
var componentPure_p = function(name, env, ioNames) { return isEmpty(transitiveIoRefs(name, env, ioNames)); };
|
|
||||||
|
|
||||||
// render-target
|
|
||||||
var renderTarget = function(name, env, ioNames) { return (function() {
|
|
||||||
var key = (isSxTruthy(startsWith(name, "~")) ? name : (String("~") + String(name)));
|
|
||||||
return (function() {
|
|
||||||
var val = envGet(env, key);
|
|
||||||
return (isSxTruthy(!isSxTruthy((typeOf(val) == "component"))) ? "server" : (function() {
|
|
||||||
var affinity = componentAffinity(val);
|
|
||||||
return (isSxTruthy((affinity == "server")) ? "server" : (isSxTruthy((affinity == "client")) ? "client" : (isSxTruthy(!isSxTruthy(componentPure_p(name, env, ioNames))) ? "server" : "client")));
|
|
||||||
})());
|
|
||||||
})();
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
// page-render-plan
|
|
||||||
var pageRenderPlan = function(pageSource, env, ioNames) { return (function() {
|
|
||||||
var needed = componentsNeeded(pageSource, env);
|
|
||||||
var compTargets = {};
|
|
||||||
var serverList = [];
|
|
||||||
var clientList = [];
|
|
||||||
var ioDeps = [];
|
|
||||||
{ var _c = needed; for (var _i = 0; _i < _c.length; _i++) { var name = _c[_i]; (function() {
|
|
||||||
var target = renderTarget(name, env, ioNames);
|
|
||||||
compTargets[name] = target;
|
|
||||||
return (isSxTruthy((target == "server")) ? (append_b(serverList, name), forEach(function(ioRef) { return (isSxTruthy(!isSxTruthy(contains(ioDeps, ioRef))) ? append_b(ioDeps, ioRef) : NIL); }, transitiveIoRefs(name, env, ioNames))) : append_b(clientList, name));
|
|
||||||
})(); } }
|
|
||||||
return {"components": compTargets, "server": serverList, "client": clientList, "io-deps": ioDeps};
|
|
||||||
})(); };
|
|
||||||
|
|
||||||
|
|
||||||
// === Transpiled from router (client-side route matching) ===
|
// === Transpiled from router (client-side route matching) ===
|
||||||
|
|
||||||
// split-path-segments
|
// split-path-segments
|
||||||
@@ -2981,7 +2838,7 @@ return (function() {
|
|||||||
var prev = getTrackingContext();
|
var prev = getTrackingContext();
|
||||||
setTrackingContext(ctx);
|
setTrackingContext(ctx);
|
||||||
return (function() {
|
return (function() {
|
||||||
var newVal = computeFn();
|
var newVal = invoke(computeFn);
|
||||||
setTrackingContext(prev);
|
setTrackingContext(prev);
|
||||||
signalSetDeps(s, trackingContextDeps(ctx));
|
signalSetDeps(s, trackingContextDeps(ctx));
|
||||||
return (function() {
|
return (function() {
|
||||||
@@ -3003,13 +2860,13 @@ return (function() {
|
|||||||
var disposed = false;
|
var disposed = false;
|
||||||
var cleanupFn = NIL;
|
var cleanupFn = NIL;
|
||||||
return (function() {
|
return (function() {
|
||||||
var runEffect = function() { return (isSxTruthy(!isSxTruthy(disposed)) ? ((isSxTruthy(cleanupFn) ? cleanupFn() : NIL), forEach(function(dep) { return signalRemoveSub(dep, runEffect); }, deps), (deps = []), (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);
|
var ctx = makeTrackingContext(runEffect);
|
||||||
return (function() {
|
return (function() {
|
||||||
var prev = getTrackingContext();
|
var prev = getTrackingContext();
|
||||||
setTrackingContext(ctx);
|
setTrackingContext(ctx);
|
||||||
return (function() {
|
return (function() {
|
||||||
var result = effectFn();
|
var result = invoke(effectFn);
|
||||||
setTrackingContext(prev);
|
setTrackingContext(prev);
|
||||||
deps = trackingContextDeps(ctx);
|
deps = trackingContextDeps(ctx);
|
||||||
return (isSxTruthy(isCallable(result)) ? (cleanupFn = result) : NIL);
|
return (isSxTruthy(isCallable(result)) ? (cleanupFn = result) : NIL);
|
||||||
@@ -3019,7 +2876,7 @@ return (function() {
|
|||||||
runEffect();
|
runEffect();
|
||||||
return function() { disposed = true;
|
return function() { disposed = true;
|
||||||
if (isSxTruthy(cleanupFn)) {
|
if (isSxTruthy(cleanupFn)) {
|
||||||
cleanupFn();
|
invoke(cleanupFn);
|
||||||
}
|
}
|
||||||
{ var _c = deps; for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, runEffect); } }
|
{ var _c = deps; for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, runEffect); } }
|
||||||
return (deps = []); };
|
return (deps = []); };
|
||||||
@@ -3034,7 +2891,7 @@ return (deps = []); };
|
|||||||
|
|
||||||
// batch
|
// batch
|
||||||
var batch = function(thunk) { _batchDepth = (_batchDepth + 1);
|
var batch = function(thunk) { _batchDepth = (_batchDepth + 1);
|
||||||
thunk();
|
invoke(thunk);
|
||||||
_batchDepth = (_batchDepth - 1);
|
_batchDepth = (_batchDepth - 1);
|
||||||
return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
||||||
var queue = _batchQueue;
|
var queue = _batchQueue;
|
||||||
@@ -3083,7 +2940,7 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
var defStore = function(name, initFn) { return (function() {
|
var defStore = function(name, initFn) { return (function() {
|
||||||
var registry = _storeRegistry;
|
var registry = _storeRegistry;
|
||||||
if (isSxTruthy(!isSxTruthy(hasKey_p(registry, name)))) {
|
if (isSxTruthy(!isSxTruthy(hasKey_p(registry, name)))) {
|
||||||
_storeRegistry = assoc(registry, name, initFn());
|
_storeRegistry = assoc(registry, name, invoke(initFn));
|
||||||
}
|
}
|
||||||
return get(_storeRegistry, name);
|
return get(_storeRegistry, name);
|
||||||
})(); };
|
})(); };
|
||||||
@@ -3104,7 +2961,7 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
var bridgeEvent = function(el, eventName, targetSignal, transformFn) { return effect(function() { return (function() {
|
var bridgeEvent = function(el, eventName, targetSignal, transformFn) { return effect(function() { return (function() {
|
||||||
var remove = domListen(el, eventName, function(e) { return (function() {
|
var remove = domListen(el, eventName, function(e) { return (function() {
|
||||||
var detail = eventDetail(e);
|
var detail = eventDetail(e);
|
||||||
var newVal = (isSxTruthy(transformFn) ? transformFn(detail) : detail);
|
var newVal = (isSxTruthy(transformFn) ? invoke(transformFn, detail) : detail);
|
||||||
return reset_b(targetSignal, newVal);
|
return reset_b(targetSignal, newVal);
|
||||||
})(); });
|
})(); });
|
||||||
return remove;
|
return remove;
|
||||||
@@ -3262,8 +3119,12 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
|
|
||||||
function domListen(el, name, handler) {
|
function domListen(el, name, handler) {
|
||||||
if (!_hasDom || !el) return function() {};
|
if (!_hasDom || !el) return function() {};
|
||||||
el.addEventListener(name, handler);
|
// Wrap SX lambdas from runtime-evaluated island code into native fns
|
||||||
return function() { el.removeEventListener(name, handler); };
|
var wrapped = isLambda(handler)
|
||||||
|
? function(e) { invoke(handler, e); }
|
||||||
|
: handler;
|
||||||
|
el.addEventListener(name, wrapped);
|
||||||
|
return function() { el.removeEventListener(name, wrapped); };
|
||||||
}
|
}
|
||||||
|
|
||||||
function eventDetail(e) {
|
function eventDetail(e) {
|
||||||
@@ -3304,79 +3165,8 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
try { return JSON.parse(s); } catch(e) { return {}; }
|
try { return JSON.parse(s); } catch(e) { return {}; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// renderDomComponent and renderDomElement are transpiled from
|
||||||
// Performance overrides — replace transpiled spec with imperative JS
|
// adapter-dom.sx — no imperative overrides needed.
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
// Override renderDomComponent: imperative kwarg parsing, no reduce/assoc
|
|
||||||
renderDomComponent = function(comp, args, env, ns) {
|
|
||||||
// Parse keyword args imperatively
|
|
||||||
var kwargs = {};
|
|
||||||
var children = [];
|
|
||||||
for (var i = 0; i < args.length; i++) {
|
|
||||||
var arg = args[i];
|
|
||||||
if (arg && arg._kw && (i + 1) < args.length) {
|
|
||||||
kwargs[arg.name] = trampoline(evalExpr(args[i + 1], env));
|
|
||||||
i++; // skip value
|
|
||||||
} else {
|
|
||||||
children.push(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Build local env via prototype chain
|
|
||||||
var local = Object.create(componentClosure(comp));
|
|
||||||
// Copy caller env own properties
|
|
||||||
for (var k in env) if (env.hasOwnProperty(k)) local[k] = env[k];
|
|
||||||
// Bind params
|
|
||||||
var params = componentParams(comp);
|
|
||||||
for (var j = 0; j < params.length; j++) {
|
|
||||||
var p = params[j];
|
|
||||||
local[p] = p in kwargs ? kwargs[p] : NIL;
|
|
||||||
}
|
|
||||||
// Bind children
|
|
||||||
if (componentHasChildren(comp)) {
|
|
||||||
var childFrag = document.createDocumentFragment();
|
|
||||||
for (var c = 0; c < children.length; c++) {
|
|
||||||
var rendered = renderToDom(children[c], env, ns);
|
|
||||||
if (rendered) childFrag.appendChild(rendered);
|
|
||||||
}
|
|
||||||
local["children"] = childFrag;
|
|
||||||
}
|
|
||||||
return renderToDom(componentBody(comp), local, ns);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Override renderDomElement: imperative attr parsing, no reduce/assoc
|
|
||||||
renderDomElement = function(tag, args, env, ns) {
|
|
||||||
var newNs = tag === "svg" ? SVG_NS : tag === "math" ? MATH_NS : ns;
|
|
||||||
var el = domCreateElement(tag, newNs);
|
|
||||||
var extraClasses = [];
|
|
||||||
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 = trampoline(evalExpr(args[i + 1], env));
|
|
||||||
i++; // skip value
|
|
||||||
if (isNil(attrVal) || attrVal === false) continue;
|
|
||||||
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 = renderToDom(arg, env, newNs);
|
|
||||||
if (child) el.appendChild(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (extraClasses.length) {
|
|
||||||
var existing = el.getAttribute("class") || "";
|
|
||||||
el.setAttribute("class", (existing ? existing + " " : "") + extraClasses.join(" "));
|
|
||||||
}
|
|
||||||
return el;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
@@ -3496,8 +3286,14 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
|
|
||||||
// --- Timers ---
|
// --- Timers ---
|
||||||
|
|
||||||
function setTimeout_(fn, ms) { return setTimeout(fn, ms || 0); }
|
function _wrapSxFn(fn) {
|
||||||
function setInterval_(fn, ms) { return setInterval(fn, ms || 1000); }
|
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 clearTimeout_(id) { clearTimeout(id); }
|
||||||
function clearInterval_(id) { clearInterval(id); }
|
function clearInterval_(id) { clearInterval(id); }
|
||||||
function requestAnimationFrame_(fn) {
|
function requestAnimationFrame_(fn) {
|
||||||
@@ -4023,6 +3819,8 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
logInfo("sx:route server " + pathname);
|
logInfo("sx:route server " + pathname);
|
||||||
executeRequest(link, { method: "GET", url: liveHref }).then(function() {
|
executeRequest(link, { method: "GET", url: liveHref }).then(function() {
|
||||||
try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {}
|
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));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -4415,7 +4213,7 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
|
|
||||||
// Expose signal functions as primitives so runtime-evaluated SX code
|
// Expose signal functions as primitives so runtime-evaluated SX code
|
||||||
// (e.g. island bodies from .sx files) can call them
|
// (e.g. island bodies from .sx files) can call them
|
||||||
PRIMITIVES["signal"] = createSignal;
|
PRIMITIVES["signal"] = signal;
|
||||||
PRIMITIVES["signal?"] = isSignal;
|
PRIMITIVES["signal?"] = isSignal;
|
||||||
PRIMITIVES["deref"] = deref;
|
PRIMITIVES["deref"] = deref;
|
||||||
PRIMITIVES["reset!"] = reset_b;
|
PRIMITIVES["reset!"] = reset_b;
|
||||||
@@ -4423,7 +4221,9 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
PRIMITIVES["computed"] = computed;
|
PRIMITIVES["computed"] = computed;
|
||||||
PRIMITIVES["effect"] = effect;
|
PRIMITIVES["effect"] = effect;
|
||||||
PRIMITIVES["batch"] = batch;
|
PRIMITIVES["batch"] = batch;
|
||||||
PRIMITIVES["dispose"] = dispose;
|
// Timer primitives for island code
|
||||||
|
PRIMITIVES["set-interval"] = setInterval_;
|
||||||
|
PRIMITIVES["clear-interval"] = clearInterval_;
|
||||||
// Reactive DOM helpers for island code
|
// Reactive DOM helpers for island code
|
||||||
PRIMITIVES["reactive-text"] = reactiveText;
|
PRIMITIVES["reactive-text"] = reactiveText;
|
||||||
PRIMITIVES["create-text-node"] = createTextNode;
|
PRIMITIVES["create-text-node"] = createTextNode;
|
||||||
@@ -4607,9 +4407,10 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Component
|
// Component or Island
|
||||||
if (hname.charAt(0) === "~") {
|
if (hname.charAt(0) === "~") {
|
||||||
var comp = env[hname];
|
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._component) return asyncRenderComponent(comp, expr.slice(1), env, ns);
|
||||||
if (comp && comp._macro) {
|
if (comp && comp._macro) {
|
||||||
var expanded = trampoline(expandMacro(comp, expr.slice(1), env));
|
var expanded = trampoline(expandMacro(comp, expr.slice(1), env));
|
||||||
@@ -5158,17 +4959,6 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
hydrateIslands: typeof sxHydrateIslands === "function" ? sxHydrateIslands : null,
|
hydrateIslands: typeof sxHydrateIslands === "function" ? sxHydrateIslands : null,
|
||||||
disposeIsland: typeof disposeIsland === "function" ? disposeIsland : null,
|
disposeIsland: typeof disposeIsland === "function" ? disposeIsland : null,
|
||||||
init: typeof bootInit === "function" ? bootInit : null,
|
init: typeof bootInit === "function" ? bootInit : null,
|
||||||
scanRefs: scanRefs,
|
|
||||||
scanComponentsFromSource: scanComponentsFromSource,
|
|
||||||
transitiveDeps: transitiveDeps,
|
|
||||||
computeAllDeps: computeAllDeps,
|
|
||||||
componentsNeeded: componentsNeeded,
|
|
||||||
pageComponentBundle: pageComponentBundle,
|
|
||||||
pageCssClasses: pageCssClasses,
|
|
||||||
scanIoRefs: scanIoRefs,
|
|
||||||
transitiveIoRefs: transitiveIoRefs,
|
|
||||||
computeAllIoRefs: computeAllIoRefs,
|
|
||||||
componentPure_p: componentPure_p,
|
|
||||||
splitPathSegments: splitPathSegments,
|
splitPathSegments: splitPathSegments,
|
||||||
parseRoutePattern: parseRoutePattern,
|
parseRoutePattern: parseRoutePattern,
|
||||||
matchRoute: matchRoute,
|
matchRoute: matchRoute,
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ import contextvars
|
|||||||
import inspect
|
import inspect
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from .types import Component, Keyword, Lambda, Macro, NIL, Symbol
|
from .types import Component, Island, Keyword, Lambda, Macro, NIL, Symbol
|
||||||
|
|
||||||
# When True, _aser expands known components server-side instead of serializing
|
# When True, _aser expands known components server-side instead of serializing
|
||||||
# them for client rendering. Set during page slot evaluation so Python-only
|
# them for client rendering. Set during page slot evaluation so Python-only
|
||||||
@@ -1219,6 +1219,8 @@ async def _eval_slot_inner(
|
|||||||
if isinstance(result, str):
|
if isinstance(result, str):
|
||||||
return SxExpr(result)
|
return SxExpr(result)
|
||||||
return SxExpr(serialize(result))
|
return SxExpr(serialize(result))
|
||||||
|
elif isinstance(comp, Island):
|
||||||
|
pass # Islands serialize as SX for client-side rendering
|
||||||
else:
|
else:
|
||||||
import logging
|
import logging
|
||||||
logging.getLogger("sx.eval").error(
|
logging.getLogger("sx.eval").error(
|
||||||
@@ -1596,6 +1598,14 @@ async def _assf_define(expr, env, ctx):
|
|||||||
return NIL
|
return NIL
|
||||||
|
|
||||||
|
|
||||||
|
async def _assf_defisland(expr, env, ctx):
|
||||||
|
"""Evaluate defisland AND serialize it — client needs the definition."""
|
||||||
|
import logging
|
||||||
|
logging.getLogger("sx.eval").info("_assf_defisland called for: %s", expr[1] if len(expr) > 1 else expr)
|
||||||
|
await async_eval(expr, env, ctx)
|
||||||
|
return serialize(expr)
|
||||||
|
|
||||||
|
|
||||||
async def _assf_lambda(expr, env, ctx):
|
async def _assf_lambda(expr, env, ctx):
|
||||||
return await _asf_lambda(expr, env, ctx)
|
return await _asf_lambda(expr, env, ctx)
|
||||||
|
|
||||||
@@ -1703,7 +1713,7 @@ _ASER_FORMS: dict[str, Any] = {
|
|||||||
"defcomp": _assf_define,
|
"defcomp": _assf_define,
|
||||||
"defmacro": _assf_define,
|
"defmacro": _assf_define,
|
||||||
"defhandler": _assf_define,
|
"defhandler": _assf_define,
|
||||||
"defisland": _assf_define,
|
"defisland": _assf_defisland,
|
||||||
"begin": _assf_begin,
|
"begin": _assf_begin,
|
||||||
"do": _assf_begin,
|
"do": _assf_begin,
|
||||||
"quote": _assf_quote,
|
"quote": _assf_quote,
|
||||||
|
|||||||
@@ -473,7 +473,7 @@ def components_for_request(source: str = "",
|
|||||||
from quart import request
|
from quart import request
|
||||||
from .jinja_bridge import _COMPONENT_ENV
|
from .jinja_bridge import _COMPONENT_ENV
|
||||||
from .deps import components_needed
|
from .deps import components_needed
|
||||||
from .types import Component, Macro
|
from .types import Component, Island, Macro
|
||||||
from .parser import serialize
|
from .parser import serialize
|
||||||
|
|
||||||
# Determine which components the page needs
|
# Determine which components the page needs
|
||||||
@@ -493,7 +493,19 @@ def components_for_request(source: str = "",
|
|||||||
|
|
||||||
parts = []
|
parts = []
|
||||||
for key, val in _COMPONENT_ENV.items():
|
for key, val in _COMPONENT_ENV.items():
|
||||||
if isinstance(val, Component):
|
if isinstance(val, Island):
|
||||||
|
comp_name = f"~{val.name}"
|
||||||
|
if needed is not None and comp_name not in needed and key not in needed:
|
||||||
|
continue
|
||||||
|
if comp_name in loaded or val.name in loaded:
|
||||||
|
continue
|
||||||
|
param_strs = ["&key"] + list(val.params)
|
||||||
|
if val.has_children:
|
||||||
|
param_strs.extend(["&rest", "children"])
|
||||||
|
params_sx = "(" + " ".join(param_strs) + ")"
|
||||||
|
body_sx = serialize(val.body, pretty=True)
|
||||||
|
parts.append(f"(defisland ~{val.name} {params_sx} {body_sx})")
|
||||||
|
elif isinstance(val, Component):
|
||||||
comp_name = f"~{val.name}"
|
comp_name = f"~{val.name}"
|
||||||
# Skip if not needed for this page
|
# Skip if not needed for this page
|
||||||
if needed is not None and comp_name not in needed and key not in needed:
|
if needed is not None and comp_name not in needed and key not in needed:
|
||||||
|
|||||||
@@ -181,8 +181,7 @@
|
|||||||
;; Value must be callable (lambda/function)
|
;; Value must be callable (lambda/function)
|
||||||
(and (starts-with? attr-name "on-")
|
(and (starts-with? attr-name "on-")
|
||||||
(callable? attr-val))
|
(callable? attr-val))
|
||||||
(let ((event-name (substring attr-name 3 (string-length attr-name))))
|
(dom-listen el (slice attr-name 3) attr-val)
|
||||||
(dom-listen el event-name attr-val))
|
|
||||||
;; Boolean attr
|
;; Boolean attr
|
||||||
(contains? BOOLEAN_ATTRS attr-name)
|
(contains? BOOLEAN_ATTRS attr-name)
|
||||||
(when attr-val (dom-set-attr el attr-name ""))
|
(when attr-val (dom-set-attr el attr-name ""))
|
||||||
|
|||||||
@@ -130,20 +130,191 @@
|
|||||||
(str "(" (join " " parts) ")"))))
|
(str "(" (join " " parts) ")"))))
|
||||||
|
|
||||||
|
|
||||||
|
;; --------------------------------------------------------------------------
|
||||||
|
;; Form classification
|
||||||
|
;; --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(define SPECIAL_FORM_NAMES
|
||||||
|
(list "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"))
|
||||||
|
|
||||||
|
(define HO_FORM_NAMES
|
||||||
|
(list "map" "map-indexed" "filter" "reduce"
|
||||||
|
"some" "every?" "for-each"))
|
||||||
|
|
||||||
|
(define special-form?
|
||||||
|
(fn (name)
|
||||||
|
(contains? SPECIAL_FORM_NAMES name)))
|
||||||
|
|
||||||
|
(define ho-form?
|
||||||
|
(fn (name)
|
||||||
|
(contains? HO_FORM_NAMES name)))
|
||||||
|
|
||||||
|
|
||||||
|
;; --------------------------------------------------------------------------
|
||||||
|
;; aser-special — evaluate special/HO forms in aser mode
|
||||||
|
;; --------------------------------------------------------------------------
|
||||||
|
;;
|
||||||
|
;; Control flow forms evaluate conditions normally but render branches
|
||||||
|
;; through aser (serializing tags/components instead of rendering HTML).
|
||||||
|
;; Definition forms evaluate for side effects and return nil.
|
||||||
|
|
||||||
|
(define aser-special
|
||||||
|
(fn (name expr env)
|
||||||
|
(let ((args (rest expr)))
|
||||||
|
(cond
|
||||||
|
;; if — evaluate condition, aser chosen branch
|
||||||
|
(= name "if")
|
||||||
|
(if (trampoline (eval-expr (first args) env))
|
||||||
|
(aser (nth args 1) env)
|
||||||
|
(if (> (len args) 2)
|
||||||
|
(aser (nth args 2) env)
|
||||||
|
nil))
|
||||||
|
|
||||||
|
;; when — evaluate condition, aser body if true
|
||||||
|
(= name "when")
|
||||||
|
(if (not (trampoline (eval-expr (first args) env)))
|
||||||
|
nil
|
||||||
|
(let ((result nil))
|
||||||
|
(for-each (fn (body) (set! result (aser body env)))
|
||||||
|
(rest args))
|
||||||
|
result))
|
||||||
|
|
||||||
|
;; cond — evaluate conditions, aser matching branch
|
||||||
|
(= name "cond")
|
||||||
|
(let ((branch (eval-cond args env)))
|
||||||
|
(if branch (aser branch env) nil))
|
||||||
|
|
||||||
|
;; case — evaluate match value, check each pair
|
||||||
|
(= name "case")
|
||||||
|
(let ((match-val (trampoline (eval-expr (first args) env)))
|
||||||
|
(clauses (rest args)))
|
||||||
|
(eval-case-aser match-val clauses env))
|
||||||
|
|
||||||
|
;; let / let*
|
||||||
|
(or (= name "let") (= name "let*"))
|
||||||
|
(let ((local (process-bindings (first args) env))
|
||||||
|
(result nil))
|
||||||
|
(for-each (fn (body) (set! result (aser body local)))
|
||||||
|
(rest args))
|
||||||
|
result)
|
||||||
|
|
||||||
|
;; begin / do
|
||||||
|
(or (= name "begin") (= name "do"))
|
||||||
|
(let ((result nil))
|
||||||
|
(for-each (fn (body) (set! result (aser body env))) args)
|
||||||
|
result)
|
||||||
|
|
||||||
|
;; and — short-circuit
|
||||||
|
(= name "and")
|
||||||
|
(let ((result true))
|
||||||
|
(some (fn (arg)
|
||||||
|
(set! result (trampoline (eval-expr arg env)))
|
||||||
|
(not result))
|
||||||
|
args)
|
||||||
|
result)
|
||||||
|
|
||||||
|
;; or — short-circuit
|
||||||
|
(= name "or")
|
||||||
|
(let ((result false))
|
||||||
|
(some (fn (arg)
|
||||||
|
(set! result (trampoline (eval-expr arg env)))
|
||||||
|
result)
|
||||||
|
args)
|
||||||
|
result)
|
||||||
|
|
||||||
|
;; map — evaluate function and collection, map through aser
|
||||||
|
(= name "map")
|
||||||
|
(let ((f (trampoline (eval-expr (first args) env)))
|
||||||
|
(coll (trampoline (eval-expr (nth args 1) env))))
|
||||||
|
(map (fn (item)
|
||||||
|
(if (lambda? f)
|
||||||
|
(let ((local (env-merge (lambda-closure f) env)))
|
||||||
|
(env-set! local (first (lambda-params f)) item)
|
||||||
|
(aser (lambda-body f) local))
|
||||||
|
(invoke f item)))
|
||||||
|
coll))
|
||||||
|
|
||||||
|
;; map-indexed
|
||||||
|
(= name "map-indexed")
|
||||||
|
(let ((f (trampoline (eval-expr (first args) env)))
|
||||||
|
(coll (trampoline (eval-expr (nth args 1) env))))
|
||||||
|
(map-indexed (fn (i item)
|
||||||
|
(if (lambda? f)
|
||||||
|
(let ((local (env-merge (lambda-closure f) env)))
|
||||||
|
(env-set! local (first (lambda-params f)) i)
|
||||||
|
(env-set! local (nth (lambda-params f) 1) item)
|
||||||
|
(aser (lambda-body f) local))
|
||||||
|
(invoke f i item)))
|
||||||
|
coll))
|
||||||
|
|
||||||
|
;; for-each — evaluate for side effects, aser each body
|
||||||
|
(= name "for-each")
|
||||||
|
(let ((f (trampoline (eval-expr (first args) env)))
|
||||||
|
(coll (trampoline (eval-expr (nth args 1) env)))
|
||||||
|
(results (list)))
|
||||||
|
(for-each (fn (item)
|
||||||
|
(if (lambda? f)
|
||||||
|
(let ((local (env-merge (lambda-closure f) env)))
|
||||||
|
(env-set! local (first (lambda-params f)) item)
|
||||||
|
(append! results (aser (lambda-body f) local)))
|
||||||
|
(invoke f item)))
|
||||||
|
coll)
|
||||||
|
(if (empty? results) nil results))
|
||||||
|
|
||||||
|
;; defisland — evaluate AND serialize (client needs the definition)
|
||||||
|
(= name "defisland")
|
||||||
|
(do (trampoline (eval-expr expr env))
|
||||||
|
(serialize expr))
|
||||||
|
|
||||||
|
;; Definition forms — evaluate for side effects
|
||||||
|
(or (= name "define") (= name "defcomp") (= name "defmacro")
|
||||||
|
(= name "defstyle") (= name "defhandler") (= name "defpage")
|
||||||
|
(= name "defquery") (= name "defaction") (= name "defrelation"))
|
||||||
|
(do (trampoline (eval-expr expr env)) nil)
|
||||||
|
|
||||||
|
;; Everything else — evaluate normally
|
||||||
|
:else
|
||||||
|
(trampoline (eval-expr expr env))))))
|
||||||
|
|
||||||
|
|
||||||
|
;; Helper: case dispatch for aser mode
|
||||||
|
(define eval-case-aser
|
||||||
|
(fn (match-val clauses env)
|
||||||
|
(if (< (len clauses) 2)
|
||||||
|
nil
|
||||||
|
(let ((test (first clauses))
|
||||||
|
(body (nth clauses 1)))
|
||||||
|
(if (or (and (= (type-of test) "keyword") (= (keyword-name test) "else"))
|
||||||
|
(and (= (type-of test) "symbol")
|
||||||
|
(or (= (symbol-name test) ":else")
|
||||||
|
(= (symbol-name test) "else"))))
|
||||||
|
(aser body env)
|
||||||
|
(if (= match-val (trampoline (eval-expr test env)))
|
||||||
|
(aser body env)
|
||||||
|
(eval-case-aser match-val (slice clauses 2) env)))))))
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
;; Platform interface — SX wire adapter
|
;; Platform interface — SX wire adapter
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
;;
|
;;
|
||||||
;; Serialization:
|
|
||||||
;; (serialize val) → SX source string representation of val
|
|
||||||
;;
|
|
||||||
;; Form classification:
|
|
||||||
;; (special-form? name) → boolean
|
|
||||||
;; (ho-form? name) → boolean
|
|
||||||
;; (aser-special name expr env) → evaluate special/HO form through aser
|
|
||||||
;;
|
|
||||||
;; From eval.sx:
|
;; From eval.sx:
|
||||||
;; eval-expr, trampoline, call-lambda, expand-macro
|
;; eval-expr, trampoline, call-lambda, expand-macro
|
||||||
;; env-has?, env-get, callable?, lambda?, component?, macro?
|
;; env-has?, env-get, env-set!, env-merge, callable?, lambda?, component?,
|
||||||
;; primitive?, get-primitive, component-name
|
;; macro?, island?, primitive?, get-primitive, component-name
|
||||||
|
;; lambda-closure, lambda-params, lambda-body
|
||||||
|
;;
|
||||||
|
;; From render.sx:
|
||||||
|
;; HTML_TAGS, eval-cond, process-bindings
|
||||||
|
;;
|
||||||
|
;; From parser.sx:
|
||||||
|
;; serialize (= sx-serialize)
|
||||||
|
;;
|
||||||
|
;; From signals.sx (optional):
|
||||||
|
;; invoke
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -361,8 +361,11 @@
|
|||||||
(fn (disposable) (append! disposers disposable))
|
(fn (disposable) (append! disposers disposable))
|
||||||
(fn () (render-to-dom (component-body comp) local nil)))))
|
(fn () (render-to-dom (component-body comp) local nil)))))
|
||||||
|
|
||||||
;; Morph existing DOM against reactive output
|
;; Clear existing content and append reactive DOM directly.
|
||||||
(morph-children el body-dom)
|
;; Unlike morph-children, this preserves addEventListener-based
|
||||||
|
;; event handlers on the freshly rendered nodes.
|
||||||
|
(dom-set-text-content el "")
|
||||||
|
(dom-append el body-dom)
|
||||||
|
|
||||||
;; Store disposers for cleanup
|
;; Store disposers for cleanup
|
||||||
(dom-set-data el "sx-disposers" disposers)
|
(dom-set-data el "sx-disposers" disposers)
|
||||||
|
|||||||
@@ -209,6 +209,10 @@ class JSEmitter:
|
|||||||
"aser-fragment": "aserFragment",
|
"aser-fragment": "aserFragment",
|
||||||
"aser-call": "aserCall",
|
"aser-call": "aserCall",
|
||||||
"aser-special": "aserSpecial",
|
"aser-special": "aserSpecial",
|
||||||
|
"eval-case-aser": "evalCaseAser",
|
||||||
|
"sx-serialize": "sxSerialize",
|
||||||
|
"sx-serialize-dict": "sxSerializeDict",
|
||||||
|
"sx-expr-source": "sxExprSource",
|
||||||
"sf-if": "sfIf",
|
"sf-if": "sfIf",
|
||||||
"sf-when": "sfWhen",
|
"sf-when": "sfWhen",
|
||||||
"sf-cond": "sfCond",
|
"sf-cond": "sfCond",
|
||||||
@@ -1384,9 +1388,10 @@ ASYNC_IO_JS = '''
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Component
|
// Component or Island
|
||||||
if (hname.charAt(0) === "~") {
|
if (hname.charAt(0) === "~") {
|
||||||
var comp = env[hname];
|
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._component) return asyncRenderComponent(comp, expr.slice(1), env, ns);
|
||||||
if (comp && comp._macro) {
|
if (comp && comp._macro) {
|
||||||
var expanded = trampoline(expandMacro(comp, expr.slice(1), env));
|
var expanded = trampoline(expandMacro(comp, expr.slice(1), env));
|
||||||
@@ -2206,6 +2211,8 @@ PRIMITIVES_JS_MODULES: dict[str, str] = {
|
|||||||
PRIMITIVES["starts-with?"] = function(s, p) { return String(s).indexOf(p) === 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["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["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["concat"] = function() {
|
PRIMITIVES["concat"] = function() {
|
||||||
var out = [];
|
var out = [];
|
||||||
for (var i = 0; i < arguments.length; i++) if (!isNil(arguments[i])) out = out.concat(arguments[i]);
|
for (var i = 0; i < arguments.length; i++) if (!isNil(arguments[i])) out = out.concat(arguments[i]);
|
||||||
@@ -2422,6 +2429,17 @@ PLATFORM_JS_PRE = '''
|
|||||||
function trackingContextAddDep(ctx, s) { if (ctx && ctx.deps.indexOf(s) < 0) ctx.deps.push(s); }
|
function trackingContextAddDep(ctx, s) { if (ctx && ctx.deps.indexOf(s) < 0) ctx.deps.push(s); }
|
||||||
function trackingContextNotifyFn(ctx) { return ctx ? ctx.notifyFn : NIL; }
|
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
|
// JSON / dict helpers for island state serialization
|
||||||
function jsonSerialize(obj) {
|
function jsonSerialize(obj) {
|
||||||
try { return JSON.stringify(obj); } catch(e) { return "{}"; }
|
try { return JSON.stringify(obj); } catch(e) { return "{}"; }
|
||||||
@@ -2447,17 +2465,8 @@ PLATFORM_JS_PRE = '''
|
|||||||
|
|
||||||
// Render-expression detection — lets the evaluator delegate to the active adapter.
|
// Render-expression detection — lets the evaluator delegate to the active adapter.
|
||||||
// Matches HTML tags, SVG tags, <>, raw!, ~components, html: prefix, custom elements.
|
// Matches HTML tags, SVG tags, <>, raw!, ~components, html: prefix, custom elements.
|
||||||
function isRenderExpr(expr) {
|
// Placeholder — overridden by transpiled version from render.sx
|
||||||
if (!Array.isArray(expr) || !expr.length) return false;
|
function isRenderExpr(expr) { return false; }
|
||||||
var h = expr[0];
|
|
||||||
if (!h || !h._sym) return false;
|
|
||||||
var n = h.name;
|
|
||||||
return !!(n === "<>" || n === "raw!" ||
|
|
||||||
n.charAt(0) === "~" || n.indexOf("html:") === 0 ||
|
|
||||||
(typeof HTML_TAGS !== "undefined" && HTML_TAGS.indexOf(n) >= 0) ||
|
|
||||||
(typeof SVG_TAGS !== "undefined" && SVG_TAGS.indexOf(n) >= 0) ||
|
|
||||||
(n.indexOf("-") > 0 && expr.length > 1 && expr[1] && expr[1]._kw));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render dispatch — call the active adapter's render function.
|
// Render dispatch — call the active adapter's render function.
|
||||||
// Set by each adapter when loaded; defaults to identity (no rendering).
|
// Set by each adapter when loaded; defaults to identity (no rendering).
|
||||||
@@ -2522,7 +2531,10 @@ PLATFORM_JS_POST = '''
|
|||||||
var range = PRIMITIVES["range"];
|
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 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; }
|
function append_b(arr, x) { arr.push(x); return arr; }
|
||||||
var apply = function(f, args) { return f.apply(null, args); };
|
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
|
// Additional primitive aliases used by adapter/engine transpiled code
|
||||||
var split = PRIMITIVES["split"];
|
var split = PRIMITIVES["split"];
|
||||||
@@ -2541,28 +2553,12 @@ PLATFORM_JS_POST = '''
|
|||||||
function escapeAttr(s) { return escapeHtml(s); }
|
function escapeAttr(s) { return escapeHtml(s); }
|
||||||
function rawHtmlContent(r) { return r.html; }
|
function rawHtmlContent(r) { return r.html; }
|
||||||
function makeRawHtml(s) { return { _raw: true, html: s }; }
|
function makeRawHtml(s) { return { _raw: true, html: s }; }
|
||||||
|
function sxExprSource(x) { return x && x.source ? x.source : String(x); }
|
||||||
|
|
||||||
// Serializer
|
// Placeholders — overridden by transpiled spec from parser.sx / adapter-sx.sx
|
||||||
function serialize(val) {
|
function serialize(val) { return String(val); }
|
||||||
if (isNil(val)) return "nil";
|
function isSpecialForm(n) { return false; }
|
||||||
if (typeof val === "boolean") return val ? "true" : "false";
|
function isHoForm(n) { return false; }
|
||||||
if (typeof val === "number") return String(val);
|
|
||||||
if (typeof val === "string") return \'"\' + val.replace(/\\\\/g, "\\\\\\\\").replace(/"/g, \'\\\\"\') + \'"\';
|
|
||||||
if (isSym(val)) return val.name;
|
|
||||||
if (isKw(val)) return ":" + val.name;
|
|
||||||
if (Array.isArray(val)) return "(" + val.map(serialize).join(" ") + ")";
|
|
||||||
return String(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isSpecialForm(n) { return n in {
|
|
||||||
"if":1,"when":1,"cond":1,"case":1,"and":1,"or":1,"let":1,"let*":1,
|
|
||||||
"lambda":1,"fn":1,"define":1,"defcomp":1,"defmacro":1,"defstyle":1,
|
|
||||||
"defhandler":1,"begin":1,"do":1,
|
|
||||||
"quote":1,"quasiquote":1,"->":1,"set!":1
|
|
||||||
}; }
|
|
||||||
function isHoForm(n) { return n in {
|
|
||||||
"map":1,"map-indexed":1,"filter":1,"reduce":1,"some":1,"every?":1,"for-each":1
|
|
||||||
}; }
|
|
||||||
|
|
||||||
// processBindings and evalCond — now specced in render.sx, bootstrapped above
|
// processBindings and evalCond — now specced in render.sx, bootstrapped above
|
||||||
|
|
||||||
@@ -2888,8 +2884,12 @@ PLATFORM_DOM_JS = """
|
|||||||
|
|
||||||
function domListen(el, name, handler) {
|
function domListen(el, name, handler) {
|
||||||
if (!_hasDom || !el) return function() {};
|
if (!_hasDom || !el) return function() {};
|
||||||
el.addEventListener(name, handler);
|
// Wrap SX lambdas from runtime-evaluated island code into native fns
|
||||||
return function() { el.removeEventListener(name, handler); };
|
var wrapped = isLambda(handler)
|
||||||
|
? function(e) { invoke(handler, e); }
|
||||||
|
: handler;
|
||||||
|
el.addEventListener(name, wrapped);
|
||||||
|
return function() { el.removeEventListener(name, wrapped); };
|
||||||
}
|
}
|
||||||
|
|
||||||
function eventDetail(e) {
|
function eventDetail(e) {
|
||||||
@@ -2930,79 +2930,8 @@ PLATFORM_DOM_JS = """
|
|||||||
try { return JSON.parse(s); } catch(e) { return {}; }
|
try { return JSON.parse(s); } catch(e) { return {}; }
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================================================================
|
// renderDomComponent and renderDomElement are transpiled from
|
||||||
// Performance overrides — replace transpiled spec with imperative JS
|
// adapter-dom.sx — no imperative overrides needed.
|
||||||
// =========================================================================
|
|
||||||
|
|
||||||
// Override renderDomComponent: imperative kwarg parsing, no reduce/assoc
|
|
||||||
renderDomComponent = function(comp, args, env, ns) {
|
|
||||||
// Parse keyword args imperatively
|
|
||||||
var kwargs = {};
|
|
||||||
var children = [];
|
|
||||||
for (var i = 0; i < args.length; i++) {
|
|
||||||
var arg = args[i];
|
|
||||||
if (arg && arg._kw && (i + 1) < args.length) {
|
|
||||||
kwargs[arg.name] = trampoline(evalExpr(args[i + 1], env));
|
|
||||||
i++; // skip value
|
|
||||||
} else {
|
|
||||||
children.push(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Build local env via prototype chain
|
|
||||||
var local = Object.create(componentClosure(comp));
|
|
||||||
// Copy caller env own properties
|
|
||||||
for (var k in env) if (env.hasOwnProperty(k)) local[k] = env[k];
|
|
||||||
// Bind params
|
|
||||||
var params = componentParams(comp);
|
|
||||||
for (var j = 0; j < params.length; j++) {
|
|
||||||
var p = params[j];
|
|
||||||
local[p] = p in kwargs ? kwargs[p] : NIL;
|
|
||||||
}
|
|
||||||
// Bind children
|
|
||||||
if (componentHasChildren(comp)) {
|
|
||||||
var childFrag = document.createDocumentFragment();
|
|
||||||
for (var c = 0; c < children.length; c++) {
|
|
||||||
var rendered = renderToDom(children[c], env, ns);
|
|
||||||
if (rendered) childFrag.appendChild(rendered);
|
|
||||||
}
|
|
||||||
local["children"] = childFrag;
|
|
||||||
}
|
|
||||||
return renderToDom(componentBody(comp), local, ns);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Override renderDomElement: imperative attr parsing, no reduce/assoc
|
|
||||||
renderDomElement = function(tag, args, env, ns) {
|
|
||||||
var newNs = tag === "svg" ? SVG_NS : tag === "math" ? MATH_NS : ns;
|
|
||||||
var el = domCreateElement(tag, newNs);
|
|
||||||
var extraClasses = [];
|
|
||||||
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 = trampoline(evalExpr(args[i + 1], env));
|
|
||||||
i++; // skip value
|
|
||||||
if (isNil(attrVal) || attrVal === false) continue;
|
|
||||||
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 = renderToDom(arg, env, newNs);
|
|
||||||
if (child) el.appendChild(child);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (extraClasses.length) {
|
|
||||||
var existing = el.getAttribute("class") || "";
|
|
||||||
el.setAttribute("class", (existing ? existing + " " : "") + extraClasses.join(" "));
|
|
||||||
}
|
|
||||||
return el;
|
|
||||||
};
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
PLATFORM_ENGINE_PURE_JS = """
|
PLATFORM_ENGINE_PURE_JS = """
|
||||||
@@ -3124,8 +3053,14 @@ PLATFORM_ORCHESTRATION_JS = """
|
|||||||
|
|
||||||
// --- Timers ---
|
// --- Timers ---
|
||||||
|
|
||||||
function setTimeout_(fn, ms) { return setTimeout(fn, ms || 0); }
|
function _wrapSxFn(fn) {
|
||||||
function setInterval_(fn, ms) { return setInterval(fn, ms || 1000); }
|
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 clearTimeout_(id) { clearTimeout(id); }
|
||||||
function clearInterval_(id) { clearInterval(id); }
|
function clearInterval_(id) { clearInterval(id); }
|
||||||
function requestAnimationFrame_(fn) {
|
function requestAnimationFrame_(fn) {
|
||||||
@@ -3651,6 +3586,8 @@ PLATFORM_ORCHESTRATION_JS = """
|
|||||||
logInfo("sx:route server " + pathname);
|
logInfo("sx:route server " + pathname);
|
||||||
executeRequest(link, { method: "GET", url: liveHref }).then(function() {
|
executeRequest(link, { method: "GET", url: liveHref }).then(function() {
|
||||||
try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {}
|
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));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -4050,7 +3987,7 @@ def fixups_js(has_html, has_sx, has_dom, has_signals=False):
|
|||||||
lines.append('''
|
lines.append('''
|
||||||
// Expose signal functions as primitives so runtime-evaluated SX code
|
// Expose signal functions as primitives so runtime-evaluated SX code
|
||||||
// (e.g. island bodies from .sx files) can call them
|
// (e.g. island bodies from .sx files) can call them
|
||||||
PRIMITIVES["signal"] = createSignal;
|
PRIMITIVES["signal"] = signal;
|
||||||
PRIMITIVES["signal?"] = isSignal;
|
PRIMITIVES["signal?"] = isSignal;
|
||||||
PRIMITIVES["deref"] = deref;
|
PRIMITIVES["deref"] = deref;
|
||||||
PRIMITIVES["reset!"] = reset_b;
|
PRIMITIVES["reset!"] = reset_b;
|
||||||
@@ -4058,7 +3995,9 @@ def fixups_js(has_html, has_sx, has_dom, has_signals=False):
|
|||||||
PRIMITIVES["computed"] = computed;
|
PRIMITIVES["computed"] = computed;
|
||||||
PRIMITIVES["effect"] = effect;
|
PRIMITIVES["effect"] = effect;
|
||||||
PRIMITIVES["batch"] = batch;
|
PRIMITIVES["batch"] = batch;
|
||||||
PRIMITIVES["dispose"] = dispose;
|
// Timer primitives for island code
|
||||||
|
PRIMITIVES["set-interval"] = setInterval_;
|
||||||
|
PRIMITIVES["clear-interval"] = clearInterval_;
|
||||||
// Reactive DOM helpers for island code
|
// Reactive DOM helpers for island code
|
||||||
PRIMITIVES["reactive-text"] = reactiveText;
|
PRIMITIVES["reactive-text"] = reactiveText;
|
||||||
PRIMITIVES["create-text-node"] = createTextNode;
|
PRIMITIVES["create-text-node"] = createTextNode;
|
||||||
|
|||||||
@@ -276,6 +276,10 @@ class PyEmitter:
|
|||||||
# adapter-sx.sx
|
# adapter-sx.sx
|
||||||
"render-to-sx": "render_to_sx",
|
"render-to-sx": "render_to_sx",
|
||||||
"aser": "aser",
|
"aser": "aser",
|
||||||
|
"eval-case-aser": "eval_case_aser",
|
||||||
|
"sx-serialize": "sx_serialize",
|
||||||
|
"sx-serialize-dict": "sx_serialize_dict",
|
||||||
|
"sx-expr-source": "sx_expr_source",
|
||||||
# Primitives that need exact aliases
|
# Primitives that need exact aliases
|
||||||
"contains?": "contains_p",
|
"contains?": "contains_p",
|
||||||
"starts-with?": "starts_with_p",
|
"starts-with?": "starts_with_p",
|
||||||
@@ -1540,6 +1544,16 @@ def tracking_context_notify_fn(ctx):
|
|||||||
return ctx.notify_fn if isinstance(ctx, _TrackingContext) else NIL
|
return ctx.notify_fn if isinstance(ctx, _TrackingContext) else NIL
|
||||||
|
|
||||||
|
|
||||||
|
def invoke(f, *args):
|
||||||
|
"""Call f with args — handles both native callables and SX lambdas.
|
||||||
|
|
||||||
|
In Python, all transpiled lambdas are natively callable, so this is
|
||||||
|
just a direct call. The JS host needs dispatch logic here because
|
||||||
|
SX lambdas from runtime-evaluated code are objects, not functions.
|
||||||
|
"""
|
||||||
|
return f(*args)
|
||||||
|
|
||||||
|
|
||||||
def json_serialize(obj):
|
def json_serialize(obj):
|
||||||
import json
|
import json
|
||||||
try:
|
try:
|
||||||
@@ -1605,17 +1619,8 @@ def dict_delete(d, k):
|
|||||||
|
|
||||||
|
|
||||||
def is_render_expr(expr):
|
def is_render_expr(expr):
|
||||||
"""Check if expression is an HTML element, component, or fragment."""
|
"""Placeholder — overridden by transpiled version from render.sx."""
|
||||||
if not isinstance(expr, list) or not expr:
|
return False
|
||||||
return False
|
|
||||||
h = expr[0]
|
|
||||||
if not isinstance(h, Symbol):
|
|
||||||
return False
|
|
||||||
n = h.name
|
|
||||||
return (n == "<>" or n == "raw!" or
|
|
||||||
n.startswith("~") or n.startswith("html:") or
|
|
||||||
n in HTML_TAGS or
|
|
||||||
("-" in n and len(expr) > 1 and isinstance(expr[1], Keyword)))
|
|
||||||
|
|
||||||
|
|
||||||
# Render dispatch -- set by adapter
|
# Render dispatch -- set by adapter
|
||||||
@@ -1657,6 +1662,10 @@ def make_raw_html(s):
|
|||||||
return _RawHTML(s)
|
return _RawHTML(s)
|
||||||
|
|
||||||
|
|
||||||
|
def sx_expr_source(x):
|
||||||
|
return x.source if isinstance(x, SxExpr) else str(x)
|
||||||
|
|
||||||
|
|
||||||
class EvalError(Exception):
|
class EvalError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -1696,7 +1705,12 @@ def escape_string(s):
|
|||||||
|
|
||||||
|
|
||||||
def serialize(val):
|
def serialize(val):
|
||||||
"""Serialize an SX value to SX source text."""
|
"""Serialize an SX value to SX source text.
|
||||||
|
|
||||||
|
Note: parser.sx defines sx-serialize with a serialize alias, but parser.sx
|
||||||
|
is only included in JS builds (for client-side parsing). Python builds
|
||||||
|
provide this as a platform function.
|
||||||
|
"""
|
||||||
t = type_of(val)
|
t = type_of(val)
|
||||||
if t == "sx-expr":
|
if t == "sx-expr":
|
||||||
return val.source
|
return val.source
|
||||||
@@ -1730,179 +1744,26 @@ def serialize(val):
|
|||||||
return "nil"
|
return "nil"
|
||||||
return str(val)
|
return str(val)
|
||||||
|
|
||||||
|
# Aliases for transpiled code — parser.sx defines sx-serialize/sx-serialize-dict
|
||||||
|
# but parser.sx is JS-only. Provide aliases so transpiled render.sx works.
|
||||||
|
sx_serialize = serialize
|
||||||
|
sx_serialize_dict = lambda d: serialize(d)
|
||||||
|
|
||||||
_SPECIAL_FORM_NAMES = frozenset([
|
_SPECIAL_FORM_NAMES = frozenset() # Placeholder — overridden by transpiled adapter-sx.sx
|
||||||
"if", "when", "cond", "case", "and", "or",
|
_HO_FORM_NAMES = frozenset()
|
||||||
"let", "let*", "lambda", "fn",
|
|
||||||
"define", "defcomp", "defmacro", "defstyle",
|
|
||||||
"defhandler", "defpage", "defquery", "defaction", "defrelation",
|
|
||||||
"begin", "do", "quote", "quasiquote",
|
|
||||||
"->", "set!",
|
|
||||||
])
|
|
||||||
|
|
||||||
_HO_FORM_NAMES = frozenset([
|
|
||||||
"map", "map-indexed", "filter", "reduce",
|
|
||||||
"some", "every?", "for-each",
|
|
||||||
])
|
|
||||||
|
|
||||||
def is_special_form(name):
|
def is_special_form(name):
|
||||||
return name in _SPECIAL_FORM_NAMES
|
"""Placeholder — overridden by transpiled version from adapter-sx.sx."""
|
||||||
|
return False
|
||||||
|
|
||||||
def is_ho_form(name):
|
def is_ho_form(name):
|
||||||
return name in _HO_FORM_NAMES
|
"""Placeholder — overridden by transpiled version from adapter-sx.sx."""
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def aser_special(name, expr, env):
|
def aser_special(name, expr, env):
|
||||||
"""Evaluate a special/HO form in aser mode.
|
"""Placeholder — overridden by transpiled version from adapter-sx.sx."""
|
||||||
|
return trampoline(eval_expr(expr, env))
|
||||||
Control flow forms evaluate conditions normally but render branches
|
|
||||||
through aser (serializing tags/components instead of rendering HTML).
|
|
||||||
Definition forms evaluate for side effects and return nil.
|
|
||||||
"""
|
|
||||||
# Control flow — evaluate conditions, aser branches
|
|
||||||
args = expr[1:]
|
|
||||||
if name == "if":
|
|
||||||
cond_val = trampoline(eval_expr(args[0], env))
|
|
||||||
if sx_truthy(cond_val):
|
|
||||||
return aser(args[1], env)
|
|
||||||
return aser(args[2], env) if _b_len(args) > 2 else NIL
|
|
||||||
if name == "when":
|
|
||||||
cond_val = trampoline(eval_expr(args[0], env))
|
|
||||||
if sx_truthy(cond_val):
|
|
||||||
result = NIL
|
|
||||||
for body in args[1:]:
|
|
||||||
result = aser(body, env)
|
|
||||||
return result
|
|
||||||
return NIL
|
|
||||||
if name == "cond":
|
|
||||||
clauses = args
|
|
||||||
if clauses and isinstance(clauses[0], _b_list) and _b_len(clauses[0]) == 2:
|
|
||||||
for clause in clauses:
|
|
||||||
test = clause[0]
|
|
||||||
if isinstance(test, Symbol) and test.name in ("else", ":else"):
|
|
||||||
return aser(clause[1], env)
|
|
||||||
if isinstance(test, Keyword) and test.name == "else":
|
|
||||||
return aser(clause[1], env)
|
|
||||||
if sx_truthy(trampoline(eval_expr(test, env))):
|
|
||||||
return aser(clause[1], env)
|
|
||||||
else:
|
|
||||||
i = 0
|
|
||||||
while i < _b_len(clauses) - 1:
|
|
||||||
test = clauses[i]
|
|
||||||
result = clauses[i + 1]
|
|
||||||
if isinstance(test, Keyword) and test.name == "else":
|
|
||||||
return aser(result, env)
|
|
||||||
if isinstance(test, Symbol) and test.name in (":else", "else"):
|
|
||||||
return aser(result, env)
|
|
||||||
if sx_truthy(trampoline(eval_expr(test, env))):
|
|
||||||
return aser(result, env)
|
|
||||||
i += 2
|
|
||||||
return NIL
|
|
||||||
if name == "case":
|
|
||||||
match_val = trampoline(eval_expr(args[0], env))
|
|
||||||
clauses = args[1:]
|
|
||||||
i = 0
|
|
||||||
while i < _b_len(clauses) - 1:
|
|
||||||
test = clauses[i]
|
|
||||||
result = clauses[i + 1]
|
|
||||||
if isinstance(test, Keyword) and test.name == "else":
|
|
||||||
return aser(result, env)
|
|
||||||
if isinstance(test, Symbol) and test.name in (":else", "else"):
|
|
||||||
return aser(result, env)
|
|
||||||
if match_val == trampoline(eval_expr(test, env)):
|
|
||||||
return aser(result, env)
|
|
||||||
i += 2
|
|
||||||
return NIL
|
|
||||||
if name in ("let", "let*"):
|
|
||||||
bindings = args[0]
|
|
||||||
local = _b_dict(env)
|
|
||||||
if isinstance(bindings, _b_list):
|
|
||||||
if bindings and isinstance(bindings[0], _b_list):
|
|
||||||
for b in bindings:
|
|
||||||
var = b[0]
|
|
||||||
vname = var.name if isinstance(var, Symbol) else var
|
|
||||||
local[vname] = trampoline(eval_expr(b[1], local))
|
|
||||||
else:
|
|
||||||
for i in _b_range(0, _b_len(bindings), 2):
|
|
||||||
var = bindings[i]
|
|
||||||
vname = var.name if isinstance(var, Symbol) else var
|
|
||||||
local[vname] = trampoline(eval_expr(bindings[i + 1], local))
|
|
||||||
result = NIL
|
|
||||||
for body in args[1:]:
|
|
||||||
result = aser(body, local)
|
|
||||||
return result
|
|
||||||
if name in ("begin", "do"):
|
|
||||||
result = NIL
|
|
||||||
for body in args:
|
|
||||||
result = aser(body, env)
|
|
||||||
return result
|
|
||||||
if name == "and":
|
|
||||||
result = True
|
|
||||||
for arg in args:
|
|
||||||
result = trampoline(eval_expr(arg, env))
|
|
||||||
if not sx_truthy(result):
|
|
||||||
return result
|
|
||||||
return result
|
|
||||||
if name == "or":
|
|
||||||
result = False
|
|
||||||
for arg in args:
|
|
||||||
result = trampoline(eval_expr(arg, env))
|
|
||||||
if sx_truthy(result):
|
|
||||||
return result
|
|
||||||
return result
|
|
||||||
# HO forms in aser mode — map/for-each render through aser
|
|
||||||
if name == "map":
|
|
||||||
fn = trampoline(eval_expr(args[0], env))
|
|
||||||
coll = trampoline(eval_expr(args[1], env))
|
|
||||||
results = []
|
|
||||||
for item in coll:
|
|
||||||
if isinstance(fn, Lambda):
|
|
||||||
local = _b_dict(fn.closure)
|
|
||||||
local.update(env)
|
|
||||||
local[fn.params[0]] = item
|
|
||||||
results.append(aser(fn.body, local))
|
|
||||||
elif callable(fn):
|
|
||||||
results.append(fn(item))
|
|
||||||
else:
|
|
||||||
raise EvalError("map requires callable")
|
|
||||||
return results
|
|
||||||
if name == "map-indexed":
|
|
||||||
fn = trampoline(eval_expr(args[0], env))
|
|
||||||
coll = trampoline(eval_expr(args[1], env))
|
|
||||||
results = []
|
|
||||||
for i, item in enumerate(coll):
|
|
||||||
if isinstance(fn, Lambda):
|
|
||||||
local = _b_dict(fn.closure)
|
|
||||||
local.update(env)
|
|
||||||
local[fn.params[0]] = i
|
|
||||||
local[fn.params[1]] = item
|
|
||||||
results.append(aser(fn.body, local))
|
|
||||||
elif callable(fn):
|
|
||||||
results.append(fn(i, item))
|
|
||||||
else:
|
|
||||||
raise EvalError("map-indexed requires callable")
|
|
||||||
return results
|
|
||||||
if name == "for-each":
|
|
||||||
fn = trampoline(eval_expr(args[0], env))
|
|
||||||
coll = trampoline(eval_expr(args[1], env))
|
|
||||||
results = []
|
|
||||||
for item in coll:
|
|
||||||
if isinstance(fn, Lambda):
|
|
||||||
local = _b_dict(fn.closure)
|
|
||||||
local.update(env)
|
|
||||||
local[fn.params[0]] = item
|
|
||||||
results.append(aser(fn.body, local))
|
|
||||||
elif callable(fn):
|
|
||||||
fn(item)
|
|
||||||
return results if results else NIL
|
|
||||||
# Definition forms — evaluate for side effects
|
|
||||||
if name in ("define", "defcomp", "defmacro", "defstyle",
|
|
||||||
"defhandler", "defpage", "defquery", "defaction", "defrelation"):
|
|
||||||
trampoline(eval_expr(expr, env))
|
|
||||||
return NIL
|
|
||||||
# Lambda/fn, quote, quasiquote, set!, -> : evaluate normally
|
|
||||||
result = eval_expr(expr, env)
|
|
||||||
return trampoline(result)
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -2193,6 +2054,7 @@ parse_int = PRIMITIVES["parse-int"]
|
|||||||
upper = PRIMITIVES["upper"]
|
upper = PRIMITIVES["upper"]
|
||||||
has_key_p = PRIMITIVES["has-key?"]
|
has_key_p = PRIMITIVES["has-key?"]
|
||||||
dissoc = PRIMITIVES["dissoc"]
|
dissoc = PRIMITIVES["dissoc"]
|
||||||
|
index_of = PRIMITIVES["index-of"]
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
@@ -2217,7 +2079,7 @@ PLATFORM_DEPS_PY = (
|
|||||||
' return list(c.css_classes) if hasattr(c, "css_classes") and c.css_classes else []\n'
|
' return list(c.css_classes) if hasattr(c, "css_classes") and c.css_classes else []\n'
|
||||||
'\n'
|
'\n'
|
||||||
'def env_components(env):\n'
|
'def env_components(env):\n'
|
||||||
' """Return list of component/macro names in an environment."""\n'
|
' """Placeholder — overridden by transpiled version from deps.sx."""\n'
|
||||||
' return [k for k, v in env.items()\n'
|
' return [k for k, v in env.items()\n'
|
||||||
' if isinstance(v, (Component, Macro))]\n'
|
' if isinstance(v, (Component, Macro))]\n'
|
||||||
'\n'
|
'\n'
|
||||||
@@ -2287,7 +2149,11 @@ CONTINUATIONS_PY = '''
|
|||||||
|
|
||||||
_RESET_RESUME = [] # stack of resume values; empty = not resuming
|
_RESET_RESUME = [] # stack of resume values; empty = not resuming
|
||||||
|
|
||||||
_SPECIAL_FORM_NAMES = _SPECIAL_FORM_NAMES | frozenset(["reset", "shift"])
|
# Extend the transpiled form name lists with continuation forms
|
||||||
|
if isinstance(SPECIAL_FORM_NAMES, list):
|
||||||
|
SPECIAL_FORM_NAMES.extend(["reset", "shift"])
|
||||||
|
else:
|
||||||
|
_SPECIAL_FORM_NAMES = _SPECIAL_FORM_NAMES | frozenset(["reset", "shift"])
|
||||||
|
|
||||||
def sf_reset(args, env):
|
def sf_reset(args, env):
|
||||||
"""(reset body) -- establish a continuation delimiter."""
|
"""(reset body) -- establish a continuation delimiter."""
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
;; (component-deps c) → cached deps list (may be empty)
|
;; (component-deps c) → cached deps list (may be empty)
|
||||||
;; (component-set-deps! c d)→ cache deps on component
|
;; (component-set-deps! c d)→ cache deps on component
|
||||||
;; (component-css-classes c)→ pre-scanned CSS class list
|
;; (component-css-classes c)→ pre-scanned CSS class list
|
||||||
;; (env-components env) → list of component/macro names in env
|
|
||||||
;; (regex-find-all pat src) → list of capture group 1 matches
|
;; (regex-find-all pat src) → list of capture group 1 matches
|
||||||
;; (scan-css-classes src) → list of CSS class strings from source
|
;; (scan-css-classes src) → list of CSS class strings from source
|
||||||
;; ==========================================================================
|
;; ==========================================================================
|
||||||
@@ -423,7 +422,20 @@
|
|||||||
;; (component-set-io-refs! c r)→ cache IO refs on component
|
;; (component-set-io-refs! c r)→ cache IO refs on component
|
||||||
;; (component-affinity c) → "auto" | "client" | "server"
|
;; (component-affinity c) → "auto" | "client" | "server"
|
||||||
;; (macro-body m) → AST body of macro
|
;; (macro-body m) → AST body of macro
|
||||||
;; (env-components env) → list of component names in env
|
|
||||||
;; (regex-find-all pat src) → list of capture group matches
|
;; (regex-find-all pat src) → list of capture group matches
|
||||||
;; (scan-css-classes src) → list of CSS class strings from source
|
;; (scan-css-classes src) → list of CSS class strings from source
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
;; --------------------------------------------------------------------------
|
||||||
|
;; env-components — list component/macro names in an environment
|
||||||
|
;; --------------------------------------------------------------------------
|
||||||
|
;; Moved from platform to spec: pure logic using type predicates.
|
||||||
|
|
||||||
|
(define env-components
|
||||||
|
(fn (env)
|
||||||
|
(filter
|
||||||
|
(fn (k)
|
||||||
|
(let ((v (env-get env k)))
|
||||||
|
(or (component? v) (macro? v))))
|
||||||
|
(keys env))))
|
||||||
|
|||||||
@@ -305,6 +305,10 @@
|
|||||||
"}")))
|
"}")))
|
||||||
|
|
||||||
|
|
||||||
|
;; Alias: adapters use (serialize val) — canonicalize to sx-serialize
|
||||||
|
(define serialize sx-serialize)
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
;; Platform parser interface
|
;; Platform parser interface
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -190,6 +190,30 @@
|
|||||||
local)))
|
local)))
|
||||||
|
|
||||||
|
|
||||||
|
;; --------------------------------------------------------------------------
|
||||||
|
;; is-render-expr? — check if expression is a rendering form
|
||||||
|
;; --------------------------------------------------------------------------
|
||||||
|
;; Used by eval-list to dispatch rendering forms to the active adapter
|
||||||
|
;; (HTML, SX wire, or DOM) rather than evaluating them as function calls.
|
||||||
|
|
||||||
|
(define is-render-expr?
|
||||||
|
(fn (expr)
|
||||||
|
(if (or (not (= (type-of expr) "list")) (empty? expr))
|
||||||
|
false
|
||||||
|
(let ((h (first expr)))
|
||||||
|
(if (not (= (type-of h) "symbol"))
|
||||||
|
false
|
||||||
|
(let ((n (symbol-name h)))
|
||||||
|
(or (= n "<>")
|
||||||
|
(= n "raw!")
|
||||||
|
(starts-with? n "~")
|
||||||
|
(starts-with? n "html:")
|
||||||
|
(contains? HTML_TAGS n)
|
||||||
|
(and (> (index-of n "-") 0)
|
||||||
|
(> (len expr) 1)
|
||||||
|
(= (type-of (nth expr 1)) "keyword")))))))))
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
;; Platform interface (shared across adapters)
|
;; Platform interface (shared across adapters)
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
@@ -199,11 +223,6 @@
|
|||||||
;; (escape-attr s) → attribute-value-escaped string
|
;; (escape-attr s) → attribute-value-escaped string
|
||||||
;; (raw-html-content r) → unwrap RawHTML marker to string
|
;; (raw-html-content r) → unwrap RawHTML marker to string
|
||||||
;;
|
;;
|
||||||
;; Serialization:
|
;; From parser.sx:
|
||||||
;; (serialize val) → SX source string representation of val
|
;; (sx-serialize val) → SX source string (aliased as serialize above)
|
||||||
;;
|
|
||||||
;; Form classification (used by SX wire adapter):
|
|
||||||
;; (special-form? name) → boolean
|
|
||||||
;; (ho-form? name) → boolean
|
|
||||||
;; (aser-special name expr env) → evaluate special/HO form through aser
|
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -25,6 +25,15 @@
|
|||||||
;; (set-tracking-context! c) → void
|
;; (set-tracking-context! c) → void
|
||||||
;; (get-tracking-context) → context or nil
|
;; (get-tracking-context) → context or nil
|
||||||
;;
|
;;
|
||||||
|
;; Runtime callable dispatch:
|
||||||
|
;; (invoke f &rest args) → any — call f with args; handles both
|
||||||
|
;; native host functions AND SX lambdas
|
||||||
|
;; from runtime-evaluated code (islands).
|
||||||
|
;; Transpiled code emits direct calls
|
||||||
|
;; f(args) which fail on SX lambdas.
|
||||||
|
;; invoke goes through the evaluator's
|
||||||
|
;; dispatch (call-fn) so either works.
|
||||||
|
;;
|
||||||
;; ==========================================================================
|
;; ==========================================================================
|
||||||
|
|
||||||
|
|
||||||
@@ -112,7 +121,7 @@
|
|||||||
(let ((ctx (make-tracking-context recompute)))
|
(let ((ctx (make-tracking-context recompute)))
|
||||||
(let ((prev (get-tracking-context)))
|
(let ((prev (get-tracking-context)))
|
||||||
(set-tracking-context! ctx)
|
(set-tracking-context! ctx)
|
||||||
(let ((new-val (compute-fn)))
|
(let ((new-val (invoke compute-fn)))
|
||||||
(set-tracking-context! prev)
|
(set-tracking-context! prev)
|
||||||
;; Save discovered deps
|
;; Save discovered deps
|
||||||
(signal-set-deps! s (tracking-context-deps ctx))
|
(signal-set-deps! s (tracking-context-deps ctx))
|
||||||
@@ -144,7 +153,7 @@
|
|||||||
(fn ()
|
(fn ()
|
||||||
(when (not disposed)
|
(when (not disposed)
|
||||||
;; Run previous cleanup if any
|
;; Run previous cleanup if any
|
||||||
(when cleanup-fn (cleanup-fn))
|
(when cleanup-fn (invoke cleanup-fn))
|
||||||
|
|
||||||
;; Unsubscribe from old deps
|
;; Unsubscribe from old deps
|
||||||
(for-each
|
(for-each
|
||||||
@@ -156,7 +165,7 @@
|
|||||||
(let ((ctx (make-tracking-context run-effect)))
|
(let ((ctx (make-tracking-context run-effect)))
|
||||||
(let ((prev (get-tracking-context)))
|
(let ((prev (get-tracking-context)))
|
||||||
(set-tracking-context! ctx)
|
(set-tracking-context! ctx)
|
||||||
(let ((result (effect-fn)))
|
(let ((result (invoke effect-fn)))
|
||||||
(set-tracking-context! prev)
|
(set-tracking-context! prev)
|
||||||
(set! deps (tracking-context-deps ctx))
|
(set! deps (tracking-context-deps ctx))
|
||||||
;; If effect returns a function, it's the cleanup
|
;; If effect returns a function, it's the cleanup
|
||||||
@@ -169,7 +178,7 @@
|
|||||||
;; Return dispose function
|
;; Return dispose function
|
||||||
(fn ()
|
(fn ()
|
||||||
(set! disposed true)
|
(set! disposed true)
|
||||||
(when cleanup-fn (cleanup-fn))
|
(when cleanup-fn (invoke cleanup-fn))
|
||||||
(for-each
|
(for-each
|
||||||
(fn (dep) (signal-remove-sub! dep run-effect))
|
(fn (dep) (signal-remove-sub! dep run-effect))
|
||||||
deps)
|
deps)
|
||||||
@@ -189,7 +198,7 @@
|
|||||||
(define batch
|
(define batch
|
||||||
(fn (thunk)
|
(fn (thunk)
|
||||||
(set! *batch-depth* (+ *batch-depth* 1))
|
(set! *batch-depth* (+ *batch-depth* 1))
|
||||||
(thunk)
|
(invoke thunk)
|
||||||
(set! *batch-depth* (- *batch-depth* 1))
|
(set! *batch-depth* (- *batch-depth* 1))
|
||||||
(when (= *batch-depth* 0)
|
(when (= *batch-depth* 0)
|
||||||
(let ((queue *batch-queue*))
|
(let ((queue *batch-queue*))
|
||||||
@@ -308,7 +317,7 @@
|
|||||||
(let ((registry *store-registry*))
|
(let ((registry *store-registry*))
|
||||||
;; Only create the store once — subsequent calls return existing
|
;; Only create the store once — subsequent calls return existing
|
||||||
(when (not (has-key? registry name))
|
(when (not (has-key? registry name))
|
||||||
(set! *store-registry* (assoc registry name (init-fn))))
|
(set! *store-registry* (assoc registry name (invoke init-fn))))
|
||||||
(get *store-registry* name))))
|
(get *store-registry* name))))
|
||||||
|
|
||||||
(define use-store
|
(define use-store
|
||||||
@@ -367,7 +376,7 @@
|
|||||||
(fn (e)
|
(fn (e)
|
||||||
(let ((detail (event-detail e))
|
(let ((detail (event-detail e))
|
||||||
(new-val (if transform-fn
|
(new-val (if transform-fn
|
||||||
(transform-fn detail)
|
(invoke transform-fn detail)
|
||||||
detail)))
|
detail)))
|
||||||
(reset! target-signal new-val))))))
|
(reset! target-signal new-val))))))
|
||||||
;; Return cleanup — removes listener on dispose/re-run
|
;; Return cleanup — removes listener on dispose/re-run
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# WARNING: special-forms.sx declares forms not in eval.sx: reset, shift
|
# WARNING: special-forms.sx declares forms not in eval.sx: reset, shift
|
||||||
|
# WARNING: eval.sx dispatches forms not in special-forms.sx: form?
|
||||||
"""
|
"""
|
||||||
sx_ref.py -- Generated from reference SX evaluator specification.
|
sx_ref.py -- Generated from reference SX evaluator specification.
|
||||||
|
|
||||||
@@ -402,6 +403,16 @@ def tracking_context_notify_fn(ctx):
|
|||||||
return ctx.notify_fn if isinstance(ctx, _TrackingContext) else NIL
|
return ctx.notify_fn if isinstance(ctx, _TrackingContext) else NIL
|
||||||
|
|
||||||
|
|
||||||
|
def invoke(f, *args):
|
||||||
|
"""Call f with args — handles both native callables and SX lambdas.
|
||||||
|
|
||||||
|
In Python, all transpiled lambdas are natively callable, so this is
|
||||||
|
just a direct call. The JS host needs dispatch logic here because
|
||||||
|
SX lambdas from runtime-evaluated code are objects, not functions.
|
||||||
|
"""
|
||||||
|
return f(*args)
|
||||||
|
|
||||||
|
|
||||||
def json_serialize(obj):
|
def json_serialize(obj):
|
||||||
import json
|
import json
|
||||||
try:
|
try:
|
||||||
@@ -467,17 +478,8 @@ def dict_delete(d, k):
|
|||||||
|
|
||||||
|
|
||||||
def is_render_expr(expr):
|
def is_render_expr(expr):
|
||||||
"""Check if expression is an HTML element, component, or fragment."""
|
"""Placeholder — overridden by transpiled version from render.sx."""
|
||||||
if not isinstance(expr, list) or not expr:
|
return False
|
||||||
return False
|
|
||||||
h = expr[0]
|
|
||||||
if not isinstance(h, Symbol):
|
|
||||||
return False
|
|
||||||
n = h.name
|
|
||||||
return (n == "<>" or n == "raw!" or
|
|
||||||
n.startswith("~") or n.startswith("html:") or
|
|
||||||
n in HTML_TAGS or
|
|
||||||
("-" in n and len(expr) > 1 and isinstance(expr[1], Keyword)))
|
|
||||||
|
|
||||||
|
|
||||||
# Render dispatch -- set by adapter
|
# Render dispatch -- set by adapter
|
||||||
@@ -519,6 +521,10 @@ def make_raw_html(s):
|
|||||||
return _RawHTML(s)
|
return _RawHTML(s)
|
||||||
|
|
||||||
|
|
||||||
|
def sx_expr_source(x):
|
||||||
|
return x.source if isinstance(x, SxExpr) else str(x)
|
||||||
|
|
||||||
|
|
||||||
class EvalError(Exception):
|
class EvalError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -558,7 +564,12 @@ def escape_string(s):
|
|||||||
|
|
||||||
|
|
||||||
def serialize(val):
|
def serialize(val):
|
||||||
"""Serialize an SX value to SX source text."""
|
"""Serialize an SX value to SX source text.
|
||||||
|
|
||||||
|
Note: parser.sx defines sx-serialize with a serialize alias, but parser.sx
|
||||||
|
is only included in JS builds (for client-side parsing). Python builds
|
||||||
|
provide this as a platform function.
|
||||||
|
"""
|
||||||
t = type_of(val)
|
t = type_of(val)
|
||||||
if t == "sx-expr":
|
if t == "sx-expr":
|
||||||
return val.source
|
return val.source
|
||||||
@@ -592,179 +603,26 @@ def serialize(val):
|
|||||||
return "nil"
|
return "nil"
|
||||||
return str(val)
|
return str(val)
|
||||||
|
|
||||||
|
# Aliases for transpiled code — parser.sx defines sx-serialize/sx-serialize-dict
|
||||||
|
# but parser.sx is JS-only. Provide aliases so transpiled render.sx works.
|
||||||
|
sx_serialize = serialize
|
||||||
|
sx_serialize_dict = lambda d: serialize(d)
|
||||||
|
|
||||||
_SPECIAL_FORM_NAMES = frozenset([
|
_SPECIAL_FORM_NAMES = frozenset() # Placeholder — overridden by transpiled adapter-sx.sx
|
||||||
"if", "when", "cond", "case", "and", "or",
|
_HO_FORM_NAMES = frozenset()
|
||||||
"let", "let*", "lambda", "fn",
|
|
||||||
"define", "defcomp", "defmacro", "defstyle",
|
|
||||||
"defhandler", "defpage", "defquery", "defaction", "defrelation",
|
|
||||||
"begin", "do", "quote", "quasiquote",
|
|
||||||
"->", "set!",
|
|
||||||
])
|
|
||||||
|
|
||||||
_HO_FORM_NAMES = frozenset([
|
|
||||||
"map", "map-indexed", "filter", "reduce",
|
|
||||||
"some", "every?", "for-each",
|
|
||||||
])
|
|
||||||
|
|
||||||
def is_special_form(name):
|
def is_special_form(name):
|
||||||
return name in _SPECIAL_FORM_NAMES
|
"""Placeholder — overridden by transpiled version from adapter-sx.sx."""
|
||||||
|
return False
|
||||||
|
|
||||||
def is_ho_form(name):
|
def is_ho_form(name):
|
||||||
return name in _HO_FORM_NAMES
|
"""Placeholder — overridden by transpiled version from adapter-sx.sx."""
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def aser_special(name, expr, env):
|
def aser_special(name, expr, env):
|
||||||
"""Evaluate a special/HO form in aser mode.
|
"""Placeholder — overridden by transpiled version from adapter-sx.sx."""
|
||||||
|
return trampoline(eval_expr(expr, env))
|
||||||
Control flow forms evaluate conditions normally but render branches
|
|
||||||
through aser (serializing tags/components instead of rendering HTML).
|
|
||||||
Definition forms evaluate for side effects and return nil.
|
|
||||||
"""
|
|
||||||
# Control flow — evaluate conditions, aser branches
|
|
||||||
args = expr[1:]
|
|
||||||
if name == "if":
|
|
||||||
cond_val = trampoline(eval_expr(args[0], env))
|
|
||||||
if sx_truthy(cond_val):
|
|
||||||
return aser(args[1], env)
|
|
||||||
return aser(args[2], env) if _b_len(args) > 2 else NIL
|
|
||||||
if name == "when":
|
|
||||||
cond_val = trampoline(eval_expr(args[0], env))
|
|
||||||
if sx_truthy(cond_val):
|
|
||||||
result = NIL
|
|
||||||
for body in args[1:]:
|
|
||||||
result = aser(body, env)
|
|
||||||
return result
|
|
||||||
return NIL
|
|
||||||
if name == "cond":
|
|
||||||
clauses = args
|
|
||||||
if clauses and isinstance(clauses[0], _b_list) and _b_len(clauses[0]) == 2:
|
|
||||||
for clause in clauses:
|
|
||||||
test = clause[0]
|
|
||||||
if isinstance(test, Symbol) and test.name in ("else", ":else"):
|
|
||||||
return aser(clause[1], env)
|
|
||||||
if isinstance(test, Keyword) and test.name == "else":
|
|
||||||
return aser(clause[1], env)
|
|
||||||
if sx_truthy(trampoline(eval_expr(test, env))):
|
|
||||||
return aser(clause[1], env)
|
|
||||||
else:
|
|
||||||
i = 0
|
|
||||||
while i < _b_len(clauses) - 1:
|
|
||||||
test = clauses[i]
|
|
||||||
result = clauses[i + 1]
|
|
||||||
if isinstance(test, Keyword) and test.name == "else":
|
|
||||||
return aser(result, env)
|
|
||||||
if isinstance(test, Symbol) and test.name in (":else", "else"):
|
|
||||||
return aser(result, env)
|
|
||||||
if sx_truthy(trampoline(eval_expr(test, env))):
|
|
||||||
return aser(result, env)
|
|
||||||
i += 2
|
|
||||||
return NIL
|
|
||||||
if name == "case":
|
|
||||||
match_val = trampoline(eval_expr(args[0], env))
|
|
||||||
clauses = args[1:]
|
|
||||||
i = 0
|
|
||||||
while i < _b_len(clauses) - 1:
|
|
||||||
test = clauses[i]
|
|
||||||
result = clauses[i + 1]
|
|
||||||
if isinstance(test, Keyword) and test.name == "else":
|
|
||||||
return aser(result, env)
|
|
||||||
if isinstance(test, Symbol) and test.name in (":else", "else"):
|
|
||||||
return aser(result, env)
|
|
||||||
if match_val == trampoline(eval_expr(test, env)):
|
|
||||||
return aser(result, env)
|
|
||||||
i += 2
|
|
||||||
return NIL
|
|
||||||
if name in ("let", "let*"):
|
|
||||||
bindings = args[0]
|
|
||||||
local = _b_dict(env)
|
|
||||||
if isinstance(bindings, _b_list):
|
|
||||||
if bindings and isinstance(bindings[0], _b_list):
|
|
||||||
for b in bindings:
|
|
||||||
var = b[0]
|
|
||||||
vname = var.name if isinstance(var, Symbol) else var
|
|
||||||
local[vname] = trampoline(eval_expr(b[1], local))
|
|
||||||
else:
|
|
||||||
for i in _b_range(0, _b_len(bindings), 2):
|
|
||||||
var = bindings[i]
|
|
||||||
vname = var.name if isinstance(var, Symbol) else var
|
|
||||||
local[vname] = trampoline(eval_expr(bindings[i + 1], local))
|
|
||||||
result = NIL
|
|
||||||
for body in args[1:]:
|
|
||||||
result = aser(body, local)
|
|
||||||
return result
|
|
||||||
if name in ("begin", "do"):
|
|
||||||
result = NIL
|
|
||||||
for body in args:
|
|
||||||
result = aser(body, env)
|
|
||||||
return result
|
|
||||||
if name == "and":
|
|
||||||
result = True
|
|
||||||
for arg in args:
|
|
||||||
result = trampoline(eval_expr(arg, env))
|
|
||||||
if not sx_truthy(result):
|
|
||||||
return result
|
|
||||||
return result
|
|
||||||
if name == "or":
|
|
||||||
result = False
|
|
||||||
for arg in args:
|
|
||||||
result = trampoline(eval_expr(arg, env))
|
|
||||||
if sx_truthy(result):
|
|
||||||
return result
|
|
||||||
return result
|
|
||||||
# HO forms in aser mode — map/for-each render through aser
|
|
||||||
if name == "map":
|
|
||||||
fn = trampoline(eval_expr(args[0], env))
|
|
||||||
coll = trampoline(eval_expr(args[1], env))
|
|
||||||
results = []
|
|
||||||
for item in coll:
|
|
||||||
if isinstance(fn, Lambda):
|
|
||||||
local = _b_dict(fn.closure)
|
|
||||||
local.update(env)
|
|
||||||
local[fn.params[0]] = item
|
|
||||||
results.append(aser(fn.body, local))
|
|
||||||
elif callable(fn):
|
|
||||||
results.append(fn(item))
|
|
||||||
else:
|
|
||||||
raise EvalError("map requires callable")
|
|
||||||
return results
|
|
||||||
if name == "map-indexed":
|
|
||||||
fn = trampoline(eval_expr(args[0], env))
|
|
||||||
coll = trampoline(eval_expr(args[1], env))
|
|
||||||
results = []
|
|
||||||
for i, item in enumerate(coll):
|
|
||||||
if isinstance(fn, Lambda):
|
|
||||||
local = _b_dict(fn.closure)
|
|
||||||
local.update(env)
|
|
||||||
local[fn.params[0]] = i
|
|
||||||
local[fn.params[1]] = item
|
|
||||||
results.append(aser(fn.body, local))
|
|
||||||
elif callable(fn):
|
|
||||||
results.append(fn(i, item))
|
|
||||||
else:
|
|
||||||
raise EvalError("map-indexed requires callable")
|
|
||||||
return results
|
|
||||||
if name == "for-each":
|
|
||||||
fn = trampoline(eval_expr(args[0], env))
|
|
||||||
coll = trampoline(eval_expr(args[1], env))
|
|
||||||
results = []
|
|
||||||
for item in coll:
|
|
||||||
if isinstance(fn, Lambda):
|
|
||||||
local = _b_dict(fn.closure)
|
|
||||||
local.update(env)
|
|
||||||
local[fn.params[0]] = item
|
|
||||||
results.append(aser(fn.body, local))
|
|
||||||
elif callable(fn):
|
|
||||||
fn(item)
|
|
||||||
return results if results else NIL
|
|
||||||
# Definition forms — evaluate for side effects
|
|
||||||
if name in ("define", "defcomp", "defmacro", "defstyle",
|
|
||||||
"defhandler", "defpage", "defquery", "defaction", "defrelation"):
|
|
||||||
trampoline(eval_expr(expr, env))
|
|
||||||
return NIL
|
|
||||||
# Lambda/fn, quote, quasiquote, set!, -> : evaluate normally
|
|
||||||
result = eval_expr(expr, env)
|
|
||||||
return trampoline(result)
|
|
||||||
|
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
@@ -1021,6 +879,7 @@ parse_int = PRIMITIVES["parse-int"]
|
|||||||
upper = PRIMITIVES["upper"]
|
upper = PRIMITIVES["upper"]
|
||||||
has_key_p = PRIMITIVES["has-key?"]
|
has_key_p = PRIMITIVES["has-key?"]
|
||||||
dissoc = PRIMITIVES["dissoc"]
|
dissoc = PRIMITIVES["dissoc"]
|
||||||
|
index_of = PRIMITIVES["index-of"]
|
||||||
|
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
@@ -1042,7 +901,7 @@ def component_css_classes(c):
|
|||||||
return list(c.css_classes) if hasattr(c, "css_classes") and c.css_classes else []
|
return list(c.css_classes) if hasattr(c, "css_classes") and c.css_classes else []
|
||||||
|
|
||||||
def env_components(env):
|
def env_components(env):
|
||||||
"""Return list of component/macro names in an environment."""
|
"""Placeholder — overridden by transpiled version from deps.sx."""
|
||||||
return [k for k, v in env.items()
|
return [k for k, v in env.items()
|
||||||
if isinstance(v, (Component, Macro))]
|
if isinstance(v, (Component, Macro))]
|
||||||
|
|
||||||
@@ -1320,6 +1179,9 @@ eval_cond_clojure = lambda clauses, env: (NIL if sx_truthy((len(clauses) < 2)) e
|
|||||||
# process-bindings
|
# process-bindings
|
||||||
process_bindings = lambda bindings, env: (lambda local: _sx_begin(for_each(lambda pair: ((lambda name: _sx_dict_set(local, name, trampoline(eval_expr(nth(pair, 1), local))))((symbol_name(first(pair)) if sx_truthy((type_of(first(pair)) == 'symbol')) else sx_str(first(pair)))) if sx_truthy(((type_of(pair) == 'list') if not sx_truthy((type_of(pair) == 'list')) else (len(pair) >= 2))) else NIL), bindings), local))(merge(env))
|
process_bindings = lambda bindings, env: (lambda local: _sx_begin(for_each(lambda pair: ((lambda name: _sx_dict_set(local, name, trampoline(eval_expr(nth(pair, 1), local))))((symbol_name(first(pair)) if sx_truthy((type_of(first(pair)) == 'symbol')) else sx_str(first(pair)))) if sx_truthy(((type_of(pair) == 'list') if not sx_truthy((type_of(pair) == 'list')) else (len(pair) >= 2))) else NIL), bindings), local))(merge(env))
|
||||||
|
|
||||||
|
# is-render-expr?
|
||||||
|
is_render_expr = lambda expr: (False if sx_truthy(((not sx_truthy((type_of(expr) == 'list'))) if sx_truthy((not sx_truthy((type_of(expr) == 'list')))) else empty_p(expr))) else (lambda h: (False if sx_truthy((not sx_truthy((type_of(h) == 'symbol')))) else (lambda n: ((n == '<>') if sx_truthy((n == '<>')) else ((n == 'raw!') if sx_truthy((n == 'raw!')) else (starts_with_p(n, '~') if sx_truthy(starts_with_p(n, '~')) else (starts_with_p(n, 'html:') if sx_truthy(starts_with_p(n, 'html:')) else (contains_p(HTML_TAGS, n) if sx_truthy(contains_p(HTML_TAGS, n)) else ((index_of(n, '-') > 0) if not sx_truthy((index_of(n, '-') > 0)) else ((len(expr) > 1) if not sx_truthy((len(expr) > 1)) else (type_of(nth(expr, 1)) == 'keyword')))))))))(symbol_name(h))))(first(expr)))
|
||||||
|
|
||||||
|
|
||||||
# === Transpiled from adapter-html ===
|
# === Transpiled from adapter-html ===
|
||||||
|
|
||||||
@@ -1357,6 +1219,51 @@ render_html_island = lambda island, args, env: (lambda kwargs: (lambda children:
|
|||||||
serialize_island_state = lambda kwargs: (NIL if sx_truthy(is_empty_dict(kwargs)) else json_serialize(kwargs))
|
serialize_island_state = lambda kwargs: (NIL if sx_truthy(is_empty_dict(kwargs)) else json_serialize(kwargs))
|
||||||
|
|
||||||
|
|
||||||
|
# === Transpiled from adapter-sx ===
|
||||||
|
|
||||||
|
# render-to-sx
|
||||||
|
render_to_sx = lambda expr, env: (lambda result: (result if sx_truthy((type_of(result) == 'string')) else serialize(result)))(aser(expr, env))
|
||||||
|
|
||||||
|
# aser
|
||||||
|
aser = lambda expr, env: _sx_case(type_of(expr), [('number', lambda: expr), ('string', lambda: expr), ('boolean', lambda: expr), ('nil', lambda: NIL), ('symbol', lambda: (lambda name: (env_get(env, name) if sx_truthy(env_has(env, name)) else (get_primitive(name) if sx_truthy(is_primitive(name)) else (True if sx_truthy((name == 'true')) else (False if sx_truthy((name == 'false')) else (NIL if sx_truthy((name == 'nil')) else error(sx_str('Undefined symbol: ', name))))))))(symbol_name(expr))), ('keyword', lambda: keyword_name(expr)), ('list', lambda: ([] if sx_truthy(empty_p(expr)) else aser_list(expr, env))), (None, lambda: expr)])
|
||||||
|
|
||||||
|
# aser-list
|
||||||
|
aser_list = lambda expr, env: (lambda head: (lambda args: (map(lambda x: aser(x, env), expr) if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))) else (lambda name: (aser_fragment(args, env) if sx_truthy((name == '<>')) else (aser_call(name, args, env) if sx_truthy(starts_with_p(name, '~')) else (aser_call(name, args, env) if sx_truthy(contains_p(HTML_TAGS, name)) else (aser_special(name, expr, env) if sx_truthy((is_special_form(name) if sx_truthy(is_special_form(name)) else is_ho_form(name))) else (aser(expand_macro(env_get(env, name), args, env), env) if sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))) else (lambda f: (lambda evaled_args: (apply(f, evaled_args) if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else ((not sx_truthy(is_lambda(f))) if not sx_truthy((not sx_truthy(is_lambda(f)))) else ((not sx_truthy(is_component(f))) if not sx_truthy((not sx_truthy(is_component(f)))) else (not sx_truthy(is_island(f))))))) else (trampoline(call_lambda(f, evaled_args, env)) if sx_truthy(is_lambda(f)) else (aser_call(sx_str('~', component_name(f)), args, env) if sx_truthy(is_component(f)) else (aser_call(sx_str('~', component_name(f)), args, env) if sx_truthy(is_island(f)) else error(sx_str('Not callable: ', inspect(f))))))))(map(lambda a: trampoline(eval_expr(a, env)), args)))(trampoline(eval_expr(head, env)))))))))(symbol_name(head))))(rest(expr)))(first(expr))
|
||||||
|
|
||||||
|
# aser-fragment
|
||||||
|
aser_fragment = lambda children, env: (lambda parts: ('' if sx_truthy(empty_p(parts)) else sx_str('(<> ', join(' ', map(serialize, parts)), ')')))(filter(lambda x: (not sx_truthy(is_nil(x))), map(lambda c: aser(c, env), children)))
|
||||||
|
|
||||||
|
# aser-call
|
||||||
|
aser_call = lambda name, args, env: (lambda parts: _sx_begin(reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda val: _sx_begin((_sx_begin(_sx_append(parts, sx_str(':', keyword_name(arg))), _sx_append(parts, serialize(val))) if sx_truthy((not sx_truthy(is_nil(val)))) else NIL), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(aser(nth(args, (get(state, 'i') + 1)), env)) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else (lambda val: _sx_begin((_sx_append(parts, serialize(val)) if sx_truthy((not sx_truthy(is_nil(val)))) else NIL), assoc(state, 'i', (get(state, 'i') + 1))))(aser(arg, env)))))(get(state, 'skip')), {'i': 0, 'skip': False}, args), sx_str('(', join(' ', parts), ')')))([name])
|
||||||
|
|
||||||
|
# SPECIAL_FORM_NAMES
|
||||||
|
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
|
||||||
|
HO_FORM_NAMES = ['map', 'map-indexed', 'filter', 'reduce', 'some', 'every?', 'for-each']
|
||||||
|
|
||||||
|
# special-form?
|
||||||
|
is_special_form = lambda name: contains_p(SPECIAL_FORM_NAMES, name)
|
||||||
|
|
||||||
|
# ho-form?
|
||||||
|
is_ho_form = lambda name: contains_p(HO_FORM_NAMES, name)
|
||||||
|
|
||||||
|
# aser-special
|
||||||
|
def aser_special(name, expr, env):
|
||||||
|
_cells = {}
|
||||||
|
args = rest(expr)
|
||||||
|
return ((aser(nth(args, 1), env) if sx_truthy(trampoline(eval_expr(first(args), env))) else (aser(nth(args, 2), env) if sx_truthy((len(args) > 2)) else NIL)) if sx_truthy((name == 'if')) else ((NIL if sx_truthy((not sx_truthy(trampoline(eval_expr(first(args), env))))) else _sx_begin(_sx_cell_set(_cells, 'result', NIL), _sx_begin(for_each(lambda body: _sx_cell_set(_cells, 'result', aser(body, env)), rest(args)), _cells['result']))) if sx_truthy((name == 'when')) else ((lambda branch: (aser(branch, env) if sx_truthy(branch) else NIL))(eval_cond(args, env)) if sx_truthy((name == 'cond')) else ((lambda match_val: (lambda clauses: eval_case_aser(match_val, clauses, env))(rest(args)))(trampoline(eval_expr(first(args), env))) if sx_truthy((name == 'case')) else ((lambda local: _sx_begin(_sx_cell_set(_cells, 'result', NIL), _sx_begin(for_each(lambda body: _sx_cell_set(_cells, 'result', aser(body, local)), rest(args)), _cells['result'])))(process_bindings(first(args), env)) if sx_truthy(((name == 'let') if sx_truthy((name == 'let')) else (name == 'let*'))) else (_sx_begin(_sx_cell_set(_cells, 'result', NIL), _sx_begin(for_each(lambda body: _sx_cell_set(_cells, 'result', aser(body, env)), args), _cells['result'])) if sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))) else (_sx_begin(_sx_cell_set(_cells, 'result', True), _sx_begin(some(_sx_fn(lambda arg: (
|
||||||
|
_sx_cell_set(_cells, 'result', trampoline(eval_expr(arg, env))),
|
||||||
|
(not sx_truthy(_cells['result']))
|
||||||
|
)[-1]), args), _cells['result'])) if sx_truthy((name == 'and')) else (_sx_begin(_sx_cell_set(_cells, 'result', False), _sx_begin(some(_sx_fn(lambda arg: (
|
||||||
|
_sx_cell_set(_cells, 'result', trampoline(eval_expr(arg, env))),
|
||||||
|
_cells['result']
|
||||||
|
)[-1]), args), _cells['result'])) if sx_truthy((name == 'or')) else ((lambda f: (lambda coll: map(lambda item: ((lambda local: _sx_begin(_sx_dict_set(local, first(lambda_params(f)), item), aser(lambda_body(f), local)))(env_merge(lambda_closure(f), env)) if sx_truthy(is_lambda(f)) else invoke(f, item)), coll))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(args), env))) if sx_truthy((name == 'map')) else ((lambda f: (lambda coll: map_indexed(lambda i, item: ((lambda local: _sx_begin(_sx_dict_set(local, first(lambda_params(f)), i), _sx_dict_set(local, nth(lambda_params(f), 1), item), aser(lambda_body(f), local)))(env_merge(lambda_closure(f), env)) if sx_truthy(is_lambda(f)) else invoke(f, i, item)), coll))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(args), env))) if sx_truthy((name == 'map-indexed')) else ((lambda f: (lambda coll: (lambda results: _sx_begin(for_each(lambda item: ((lambda local: _sx_begin(_sx_dict_set(local, first(lambda_params(f)), item), _sx_append(results, aser(lambda_body(f), local))))(env_merge(lambda_closure(f), env)) if sx_truthy(is_lambda(f)) else invoke(f, item)), coll), (NIL if sx_truthy(empty_p(results)) else results)))([]))(trampoline(eval_expr(nth(args, 1), env))))(trampoline(eval_expr(first(args), env))) if sx_truthy((name == 'for-each')) else (_sx_begin(trampoline(eval_expr(expr, env)), serialize(expr)) if sx_truthy((name == 'defisland')) else (_sx_begin(trampoline(eval_expr(expr, env)), NIL) if sx_truthy(((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else ((name == 'defhandler') if sx_truthy((name == 'defhandler')) else ((name == 'defpage') if sx_truthy((name == 'defpage')) else ((name == 'defquery') if sx_truthy((name == 'defquery')) else ((name == 'defaction') if sx_truthy((name == 'defaction')) else (name == 'defrelation')))))))))) else trampoline(eval_expr(expr, env)))))))))))))))
|
||||||
|
|
||||||
|
# eval-case-aser
|
||||||
|
eval_case_aser = lambda match_val, clauses, env: (NIL if sx_truthy((len(clauses) < 2)) else (lambda test: (lambda body: (aser(body, env) if sx_truthy((((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else')) if sx_truthy(((type_of(test) == 'keyword') if not sx_truthy((type_of(test) == 'keyword')) else (keyword_name(test) == 'else'))) else ((type_of(test) == 'symbol') if not sx_truthy((type_of(test) == 'symbol')) else ((symbol_name(test) == ':else') if sx_truthy((symbol_name(test) == ':else')) else (symbol_name(test) == 'else'))))) else (aser(body, env) if sx_truthy((match_val == trampoline(eval_expr(test, env)))) else eval_case_aser(match_val, slice(clauses, 2), env))))(nth(clauses, 1)))(first(clauses)))
|
||||||
|
|
||||||
|
|
||||||
# === Transpiled from deps (component dependency analysis) ===
|
# === Transpiled from deps (component dependency analysis) ===
|
||||||
|
|
||||||
# scan-refs
|
# scan-refs
|
||||||
@@ -1413,6 +1320,9 @@ render_target = lambda name, env, io_names: (lambda key: (lambda val: ('server'
|
|||||||
# page-render-plan
|
# page-render-plan
|
||||||
page_render_plan = lambda page_source, env, io_names: (lambda needed: (lambda comp_targets: (lambda server_list: (lambda client_list: (lambda io_deps: _sx_begin(for_each(lambda name: (lambda target: _sx_begin(_sx_dict_set(comp_targets, name, target), (_sx_begin(_sx_append(server_list, name), for_each(lambda io_ref: (_sx_append(io_deps, io_ref) if sx_truthy((not sx_truthy(contains_p(io_deps, io_ref)))) else NIL), transitive_io_refs(name, env, io_names))) if sx_truthy((target == 'server')) else _sx_append(client_list, name))))(render_target(name, env, io_names)), needed), {'components': comp_targets, 'server': server_list, 'client': client_list, 'io-deps': io_deps}))([]))([]))([]))({}))(components_needed(page_source, env))
|
page_render_plan = lambda page_source, env, io_names: (lambda needed: (lambda comp_targets: (lambda server_list: (lambda client_list: (lambda io_deps: _sx_begin(for_each(lambda name: (lambda target: _sx_begin(_sx_dict_set(comp_targets, name, target), (_sx_begin(_sx_append(server_list, name), for_each(lambda io_ref: (_sx_append(io_deps, io_ref) if sx_truthy((not sx_truthy(contains_p(io_deps, io_ref)))) else NIL), transitive_io_refs(name, env, io_names))) if sx_truthy((target == 'server')) else _sx_append(client_list, name))))(render_target(name, env, io_names)), needed), {'components': comp_targets, 'server': server_list, 'client': client_list, 'io-deps': io_deps}))([]))([]))([]))({}))(components_needed(page_source, env))
|
||||||
|
|
||||||
|
# env-components
|
||||||
|
env_components = lambda env: filter(lambda k: (lambda v: (is_component(v) if sx_truthy(is_component(v)) else is_macro(v)))(env_get(env, k)), keys(env))
|
||||||
|
|
||||||
|
|
||||||
# === Transpiled from signals (reactive signal runtime) ===
|
# === Transpiled from signals (reactive signal runtime) ===
|
||||||
|
|
||||||
@@ -1432,7 +1342,7 @@ swap_b = lambda s, f, *args: ((lambda old: (lambda new_val: (_sx_begin(signal_se
|
|||||||
computed = lambda compute_fn: (lambda s: (lambda deps: (lambda compute_ctx: (lambda recompute: _sx_begin(recompute(), s))(_sx_fn(lambda : (
|
computed = lambda compute_fn: (lambda s: (lambda deps: (lambda compute_ctx: (lambda recompute: _sx_begin(recompute(), s))(_sx_fn(lambda : (
|
||||||
for_each(lambda dep: signal_remove_sub(dep, recompute), signal_deps(s)),
|
for_each(lambda dep: signal_remove_sub(dep, recompute), signal_deps(s)),
|
||||||
signal_set_deps(s, []),
|
signal_set_deps(s, []),
|
||||||
(lambda ctx: (lambda prev: _sx_begin(set_tracking_context(ctx), (lambda new_val: _sx_begin(set_tracking_context(prev), signal_set_deps(s, tracking_context_deps(ctx)), (lambda old: _sx_begin(signal_set_value(s, new_val), (notify_subscribers(s) if sx_truthy((not sx_truthy(is_identical(old, new_val)))) else NIL)))(signal_value(s))))(compute_fn())))(get_tracking_context()))(make_tracking_context(recompute))
|
(lambda ctx: (lambda prev: _sx_begin(set_tracking_context(ctx), (lambda new_val: _sx_begin(set_tracking_context(prev), signal_set_deps(s, tracking_context_deps(ctx)), (lambda old: _sx_begin(signal_set_value(s, new_val), (notify_subscribers(s) if sx_truthy((not sx_truthy(is_identical(old, new_val)))) else NIL)))(signal_value(s))))(invoke(compute_fn))))(get_tracking_context()))(make_tracking_context(recompute))
|
||||||
)[-1])))(NIL))([]))(make_signal(NIL))
|
)[-1])))(NIL))([]))(make_signal(NIL))
|
||||||
|
|
||||||
# effect
|
# effect
|
||||||
@@ -1441,11 +1351,11 @@ def effect(effect_fn):
|
|||||||
_cells['deps'] = []
|
_cells['deps'] = []
|
||||||
_cells['disposed'] = False
|
_cells['disposed'] = False
|
||||||
_cells['cleanup_fn'] = NIL
|
_cells['cleanup_fn'] = NIL
|
||||||
run_effect = lambda : (_sx_begin((cleanup_fn() if sx_truthy(_cells['cleanup_fn']) else NIL), for_each(lambda dep: signal_remove_sub(dep, run_effect), _cells['deps']), _sx_cell_set(_cells, 'deps', []), (lambda ctx: (lambda prev: _sx_begin(set_tracking_context(ctx), (lambda result: _sx_begin(set_tracking_context(prev), _sx_cell_set(_cells, 'deps', tracking_context_deps(ctx)), (_sx_cell_set(_cells, 'cleanup_fn', result) if sx_truthy(is_callable(result)) else NIL)))(effect_fn())))(get_tracking_context()))(make_tracking_context(run_effect))) if sx_truthy((not sx_truthy(_cells['disposed']))) else NIL)
|
run_effect = lambda : (_sx_begin((invoke(_cells['cleanup_fn']) if sx_truthy(_cells['cleanup_fn']) else NIL), for_each(lambda dep: signal_remove_sub(dep, run_effect), _cells['deps']), _sx_cell_set(_cells, 'deps', []), (lambda ctx: (lambda prev: _sx_begin(set_tracking_context(ctx), (lambda result: _sx_begin(set_tracking_context(prev), _sx_cell_set(_cells, 'deps', tracking_context_deps(ctx)), (_sx_cell_set(_cells, 'cleanup_fn', result) if sx_truthy(is_callable(result)) else NIL)))(invoke(effect_fn))))(get_tracking_context()))(make_tracking_context(run_effect))) if sx_truthy((not sx_truthy(_cells['disposed']))) else NIL)
|
||||||
run_effect()
|
run_effect()
|
||||||
return _sx_fn(lambda : (
|
return _sx_fn(lambda : (
|
||||||
_sx_cell_set(_cells, 'disposed', True),
|
_sx_cell_set(_cells, 'disposed', True),
|
||||||
(cleanup_fn() if sx_truthy(_cells['cleanup_fn']) else NIL),
|
(invoke(_cells['cleanup_fn']) if sx_truthy(_cells['cleanup_fn']) else NIL),
|
||||||
for_each(lambda dep: signal_remove_sub(dep, run_effect), _cells['deps']),
|
for_each(lambda dep: signal_remove_sub(dep, run_effect), _cells['deps']),
|
||||||
_sx_cell_set(_cells, 'deps', [])
|
_sx_cell_set(_cells, 'deps', [])
|
||||||
)[-1])
|
)[-1])
|
||||||
@@ -1459,7 +1369,7 @@ _batch_queue = []
|
|||||||
# batch
|
# batch
|
||||||
def batch(thunk):
|
def batch(thunk):
|
||||||
_batch_depth = (_batch_depth + 1)
|
_batch_depth = (_batch_depth + 1)
|
||||||
thunk()
|
invoke(thunk)
|
||||||
_batch_depth = (_batch_depth - 1)
|
_batch_depth = (_batch_depth - 1)
|
||||||
return ((lambda queue: _sx_begin(_sx_cell_set(_cells, '_batch_queue', []), (lambda seen: (lambda pending: _sx_begin(for_each(lambda s: for_each(lambda sub: (_sx_begin(_sx_append(seen, sub), _sx_append(pending, sub)) if sx_truthy((not sx_truthy(contains_p(seen, sub)))) else NIL), signal_subscribers(s)), queue), for_each(lambda sub: sub(), pending)))([]))([])))(_batch_queue) if sx_truthy((_batch_depth == 0)) else NIL)
|
return ((lambda queue: _sx_begin(_sx_cell_set(_cells, '_batch_queue', []), (lambda seen: (lambda pending: _sx_begin(for_each(lambda s: for_each(lambda sub: (_sx_begin(_sx_append(seen, sub), _sx_append(pending, sub)) if sx_truthy((not sx_truthy(contains_p(seen, sub)))) else NIL), signal_subscribers(s)), queue), for_each(lambda sub: sub(), pending)))([]))([])))(_batch_queue) if sx_truthy((_batch_depth == 0)) else NIL)
|
||||||
|
|
||||||
@@ -1493,7 +1403,7 @@ _store_registry = {}
|
|||||||
def def_store(name, init_fn):
|
def def_store(name, init_fn):
|
||||||
registry = _store_registry
|
registry = _store_registry
|
||||||
if sx_truthy((not sx_truthy(has_key_p(registry, name)))):
|
if sx_truthy((not sx_truthy(has_key_p(registry, name)))):
|
||||||
_store_registry = assoc(registry, name, init_fn())
|
_store_registry = assoc(registry, name, invoke(init_fn))
|
||||||
return get(_store_registry, name)
|
return get(_store_registry, name)
|
||||||
|
|
||||||
# use-store
|
# use-store
|
||||||
@@ -1510,7 +1420,7 @@ emit_event = lambda el, event_name, detail: dom_dispatch(el, event_name, detail)
|
|||||||
on_event = lambda el, event_name, handler: dom_listen(el, event_name, handler)
|
on_event = lambda el, event_name, handler: dom_listen(el, event_name, handler)
|
||||||
|
|
||||||
# bridge-event
|
# bridge-event
|
||||||
bridge_event = lambda el, event_name, target_signal, transform_fn: effect(lambda : (lambda remove: remove)(dom_listen(el, event_name, lambda e: (lambda detail: (lambda new_val: reset_b(target_signal, new_val))((transform_fn(detail) if sx_truthy(transform_fn) else detail)))(event_detail(e)))))
|
bridge_event = lambda el, event_name, target_signal, transform_fn: effect(lambda : (lambda remove: remove)(dom_listen(el, event_name, lambda e: (lambda detail: (lambda new_val: reset_b(target_signal, new_val))((invoke(transform_fn, detail) if sx_truthy(transform_fn) else detail)))(event_detail(e)))))
|
||||||
|
|
||||||
|
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
@@ -1549,6 +1459,9 @@ def _wrap_aser_outputs():
|
|||||||
# Public API
|
# Public API
|
||||||
# =========================================================================
|
# =========================================================================
|
||||||
|
|
||||||
|
# Wrap aser outputs to return SxExpr
|
||||||
|
_wrap_aser_outputs()
|
||||||
|
|
||||||
# Set HTML as default adapter
|
# Set HTML as default adapter
|
||||||
_setup_html_adapter()
|
_setup_html_adapter()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user