diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 3c4c937..7c93daf 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -10,6 +10,7 @@ x-dev-env: &dev-env RELOAD: "true" WORKERS: "1" + SX_USE_REF: "1" x-sibling-models: &sibling-models # Every app needs all sibling __init__.py + models/ for cross-domain SQLAlchemy imports diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index 80f1fca..641f426 100644 --- a/shared/static/scripts/sx-browser.js +++ b/shared/static/scripts/sx-browser.js @@ -508,6 +508,25 @@ return NIL; } + // ========================================================================= + // Platform interface — Parser + // ========================================================================= + // Character classification derived from the grammar: + // ident-start → [a-zA-Z_~*+\-><=/!?&] + // ident-char → ident-start + [0-9.:\/\[\]#,] + + var _identStartRe = /[a-zA-Z_~*+\-><=/!?&]/; + var _identCharRe = /[a-zA-Z0-9_~*+\-><=/!?.:&/\[\]#,]/; + + function isIdentStart(ch) { return _identStartRe.test(ch); } + function isIdentChar(ch) { return _identCharRe.test(ch); } + function parseNumber(s) { return Number(s); } + function escapeString(s) { + return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\t/g, "\\t"); + } + function sxExprSource(e) { return typeof e === "string" ? e : String(e); } + + // === Transpiled from eval === // trampoline @@ -528,10 +547,10 @@ var args = rest(expr); return (isSxTruthy(!sxOr((typeOf(head) == "symbol"), (typeOf(head) == "lambda"), (typeOf(head) == "list"))) ? map(function(x) { return trampoline(evalExpr(x, env)); }, expr) : (isSxTruthy((typeOf(head) == "symbol")) ? (function() { var name = symbolName(head); - return (isSxTruthy((name == "if")) ? sfIf(args, env) : (isSxTruthy((name == "when")) ? sfWhen(args, env) : (isSxTruthy((name == "cond")) ? sfCond(args, env) : (isSxTruthy((name == "case")) ? sfCase(args, env) : (isSxTruthy((name == "and")) ? sfAnd(args, env) : (isSxTruthy((name == "or")) ? sfOr(args, env) : (isSxTruthy((name == "let")) ? sfLet(args, env) : (isSxTruthy((name == "let*")) ? sfLet(args, env) : (isSxTruthy((name == "lambda")) ? sfLambda(args, env) : (isSxTruthy((name == "fn")) ? sfLambda(args, env) : (isSxTruthy((name == "define")) ? sfDefine(args, env) : (isSxTruthy((name == "defcomp")) ? sfDefcomp(args, env) : (isSxTruthy((name == "defmacro")) ? sfDefmacro(args, env) : (isSxTruthy((name == "defstyle")) ? sfDefstyle(args, env) : (isSxTruthy((name == "defkeyframes")) ? sfDefkeyframes(args, env) : (isSxTruthy((name == "defhandler")) ? sfDefine(args, env) : (isSxTruthy((name == "begin")) ? sfBegin(args, env) : (isSxTruthy((name == "do")) ? sfBegin(args, env) : (isSxTruthy((name == "quote")) ? sfQuote(args, env) : (isSxTruthy((name == "quasiquote")) ? sfQuasiquote(args, env) : (isSxTruthy((name == "->")) ? sfThreadFirst(args, env) : (isSxTruthy((name == "set!")) ? sfSetBang(args, env) : (isSxTruthy((name == "map")) ? hoMap(args, env) : (isSxTruthy((name == "map-indexed")) ? hoMapIndexed(args, env) : (isSxTruthy((name == "filter")) ? hoFilter(args, env) : (isSxTruthy((name == "reduce")) ? hoReduce(args, env) : (isSxTruthy((name == "some")) ? hoSome(args, env) : (isSxTruthy((name == "every?")) ? hoEvery(args, env) : (isSxTruthy((name == "for-each")) ? hoForEach(args, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? (function() { + return (isSxTruthy((name == "if")) ? sfIf(args, env) : (isSxTruthy((name == "when")) ? sfWhen(args, env) : (isSxTruthy((name == "cond")) ? sfCond(args, env) : (isSxTruthy((name == "case")) ? sfCase(args, env) : (isSxTruthy((name == "and")) ? sfAnd(args, env) : (isSxTruthy((name == "or")) ? sfOr(args, env) : (isSxTruthy((name == "let")) ? sfLet(args, env) : (isSxTruthy((name == "let*")) ? sfLet(args, env) : (isSxTruthy((name == "lambda")) ? sfLambda(args, env) : (isSxTruthy((name == "fn")) ? sfLambda(args, env) : (isSxTruthy((name == "define")) ? sfDefine(args, env) : (isSxTruthy((name == "defcomp")) ? sfDefcomp(args, env) : (isSxTruthy((name == "defmacro")) ? sfDefmacro(args, env) : (isSxTruthy((name == "defstyle")) ? sfDefstyle(args, env) : (isSxTruthy((name == "defkeyframes")) ? sfDefkeyframes(args, env) : (isSxTruthy((name == "defhandler")) ? sfDefhandler(args, env) : (isSxTruthy((name == "defpage")) ? sfDefpage(args, env) : (isSxTruthy((name == "defquery")) ? sfDefquery(args, env) : (isSxTruthy((name == "defaction")) ? sfDefaction(args, env) : (isSxTruthy((name == "begin")) ? sfBegin(args, env) : (isSxTruthy((name == "do")) ? sfBegin(args, env) : (isSxTruthy((name == "quote")) ? sfQuote(args, env) : (isSxTruthy((name == "quasiquote")) ? sfQuasiquote(args, env) : (isSxTruthy((name == "->")) ? sfThreadFirst(args, env) : (isSxTruthy((name == "set!")) ? sfSetBang(args, env) : (isSxTruthy((name == "map")) ? hoMap(args, env) : (isSxTruthy((name == "map-indexed")) ? hoMapIndexed(args, env) : (isSxTruthy((name == "filter")) ? hoFilter(args, env) : (isSxTruthy((name == "reduce")) ? hoReduce(args, env) : (isSxTruthy((name == "some")) ? hoSome(args, env) : (isSxTruthy((name == "every?")) ? hoEvery(args, env) : (isSxTruthy((name == "for-each")) ? hoForEach(args, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? (function() { var mac = envGet(env, name); return makeThunk(expandMacro(mac, args, env), env); -})() : (isSxTruthy(isRenderExpr(expr)) ? renderExpr(expr, env) : evalCall(head, args, env)))))))))))))))))))))))))))))))); +})() : (isSxTruthy(isRenderExpr(expr)) ? renderExpr(expr, env) : evalCall(head, args, env))))))))))))))))))))))))))))))))))); })() : evalCall(head, args, env))); })(); }; @@ -788,25 +807,28 @@ return trampoline(evalExpr(macroBody(mac), local)); })(); }; + // call-fn + var callFn = function(f, args, env) { return (isSxTruthy(isLambda(f)) ? trampoline(callLambda(f, args, env)) : (isSxTruthy(isCallable(f)) ? apply(f, args) : error((String("Not callable in HO form: ") + String(inspect(f)))))); }; + // ho-map var hoMap = function(args, env) { return (function() { var f = trampoline(evalExpr(first(args), env)); var coll = trampoline(evalExpr(nth(args, 1), env)); - return map(function(item) { return trampoline(callLambda(f, [item], env)); }, coll); + return map(function(item) { return callFn(f, [item], env); }, coll); })(); }; // ho-map-indexed var hoMapIndexed = function(args, env) { return (function() { var f = trampoline(evalExpr(first(args), env)); var coll = trampoline(evalExpr(nth(args, 1), env)); - return mapIndexed(function(i, item) { return trampoline(callLambda(f, [i, item], env)); }, coll); + return mapIndexed(function(i, item) { return callFn(f, [i, item], env); }, coll); })(); }; // ho-filter var hoFilter = function(args, env) { return (function() { var f = trampoline(evalExpr(first(args), env)); var coll = trampoline(evalExpr(nth(args, 1), env)); - return filter(function(item) { return trampoline(callLambda(f, [item], env)); }, coll); + return filter(function(item) { return callFn(f, [item], env); }, coll); })(); }; // ho-reduce @@ -814,28 +836,28 @@ var f = trampoline(evalExpr(first(args), env)); var init = trampoline(evalExpr(nth(args, 1), env)); var coll = trampoline(evalExpr(nth(args, 2), env)); - return reduce(function(acc, item) { return trampoline(callLambda(f, [acc, item], env)); }, init, coll); + return reduce(function(acc, item) { return callFn(f, [acc, item], env); }, init, coll); })(); }; // ho-some var hoSome = function(args, env) { return (function() { var f = trampoline(evalExpr(first(args), env)); var coll = trampoline(evalExpr(nth(args, 1), env)); - return some(function(item) { return trampoline(callLambda(f, [item], env)); }, coll); + return some(function(item) { return callFn(f, [item], env); }, coll); })(); }; // ho-every var hoEvery = function(args, env) { return (function() { var f = trampoline(evalExpr(first(args), env)); var coll = trampoline(evalExpr(nth(args, 1), env)); - return isEvery(function(item) { return trampoline(callLambda(f, [item], env)); }, coll); + return isEvery(function(item) { return callFn(f, [item], env); }, coll); })(); }; // ho-for-each var hoForEach = function(args, env) { return (function() { var f = trampoline(evalExpr(first(args), env)); var coll = trampoline(evalExpr(nth(args, 1), env)); - return forEach(function(item) { return trampoline(callLambda(f, [item], env)); }, coll); + return forEach(function(item) { return callFn(f, [item], env); }, coll); })(); }; @@ -875,6 +897,117 @@ })(); }, keys(attrs))); }; + // === Transpiled from parser === + + // sx-parse + var sxParse = function(source) { return (function() { + var pos = 0; + var lenSrc = len(source); + var skipComment = function() { while(true) { if (isSxTruthy((isSxTruthy((pos < lenSrc)) && !(nth(source, pos) == "\n")))) { pos = (pos + 1); +continue; } else { return NIL; } } }; + var skipWs = function() { while(true) { if (isSxTruthy((pos < lenSrc))) { { var ch = nth(source, pos); +if (isSxTruthy(sxOr((ch == " "), (ch == "\t"), (ch == "\n"), (ch == "\\r")))) { pos = (pos + 1); +continue; } else if (isSxTruthy((ch == ";"))) { pos = (pos + 1); +skipComment(); +continue; } else { return NIL; } } } else { return NIL; } } }; + var readString = function() { pos = (pos + 1); +return (function() { + var buf = ""; + var readStrLoop = function() { while(true) { if (isSxTruthy((pos >= lenSrc))) { return error("Unterminated string"); } else { { var ch = nth(source, pos); +if (isSxTruthy((ch == "\""))) { pos = (pos + 1); +return NIL; } else if (isSxTruthy((ch == "\\"))) { pos = (pos + 1); +{ var esc = nth(source, pos); +buf = (String(buf) + String((isSxTruthy((esc == "n")) ? "\n" : (isSxTruthy((esc == "t")) ? "\t" : (isSxTruthy((esc == "r")) ? "\\r" : esc))))); +pos = (pos + 1); +continue; } } else { buf = (String(buf) + String(ch)); +pos = (pos + 1); +continue; } } } } }; + readStrLoop(); + return buf; +})(); }; + var readIdent = function() { return (function() { + var start = pos; + var readIdentLoop = function() { while(true) { if (isSxTruthy((isSxTruthy((pos < lenSrc)) && isIdentChar(nth(source, pos))))) { pos = (pos + 1); +continue; } else { return NIL; } } }; + readIdentLoop(); + return slice(source, start, pos); +})(); }; + var readKeyword = function() { pos = (pos + 1); +return makeKeyword(readIdent()); }; + var readNumber = function() { return (function() { + var start = pos; + if (isSxTruthy((isSxTruthy((pos < lenSrc)) && (nth(source, pos) == "-")))) { + pos = (pos + 1); +} + var readDigits = function() { while(true) { if (isSxTruthy((isSxTruthy((pos < lenSrc)) && (function() { + var c = nth(source, pos); + return (isSxTruthy((c >= "0")) && (c <= "9")); +})()))) { pos = (pos + 1); +continue; } else { return NIL; } } }; + readDigits(); + if (isSxTruthy((isSxTruthy((pos < lenSrc)) && (nth(source, pos) == ".")))) { + pos = (pos + 1); + readDigits(); +} + if (isSxTruthy((isSxTruthy((pos < lenSrc)) && sxOr((nth(source, pos) == "e"), (nth(source, pos) == "E"))))) { + pos = (pos + 1); + if (isSxTruthy((isSxTruthy((pos < lenSrc)) && sxOr((nth(source, pos) == "+"), (nth(source, pos) == "-"))))) { + pos = (pos + 1); +} + readDigits(); +} + return parseNumber(slice(source, start, pos)); +})(); }; + var readSymbol = function() { return (function() { + var name = readIdent(); + return (isSxTruthy((name == "true")) ? true : (isSxTruthy((name == "false")) ? false : (isSxTruthy((name == "nil")) ? NIL : makeSymbol(name)))); +})(); }; + var readList = function(closeCh) { return (function() { + var items = []; + var readListLoop = function() { while(true) { skipWs(); +if (isSxTruthy((pos >= lenSrc))) { return error("Unterminated list"); } else { if (isSxTruthy((nth(source, pos) == closeCh))) { pos = (pos + 1); +return NIL; } else { items.push(readExpr()); +continue; } } } }; + readListLoop(); + return items; +})(); }; + var readMap = function() { return (function() { + var result = {}; + var readMapLoop = function() { while(true) { skipWs(); +if (isSxTruthy((pos >= lenSrc))) { return error("Unterminated map"); } else { if (isSxTruthy((nth(source, pos) == "}"))) { pos = (pos + 1); +return NIL; } else { { var keyExpr = readExpr(); +var keyStr = (isSxTruthy((typeOf(keyExpr) == "keyword")) ? keywordName(keyExpr) : (String(keyExpr))); +var valExpr = readExpr(); +result[keyStr] = valExpr; +continue; } } } } }; + readMapLoop(); + return result; +})(); }; + var readExpr = function() { skipWs(); +return (isSxTruthy((pos >= lenSrc)) ? error("Unexpected end of input") : (function() { + var ch = nth(source, pos); + return (isSxTruthy((ch == "(")) ? ((pos = (pos + 1)), readList(")")) : (isSxTruthy((ch == "[")) ? ((pos = (pos + 1)), readList("]")) : (isSxTruthy((ch == "{")) ? ((pos = (pos + 1)), readMap()) : (isSxTruthy((ch == "\"")) ? readString() : (isSxTruthy((ch == ":")) ? readKeyword() : (isSxTruthy((ch == "`")) ? ((pos = (pos + 1)), [makeSymbol("quasiquote"), readExpr()]) : (isSxTruthy((ch == ",")) ? ((pos = (pos + 1)), (isSxTruthy((isSxTruthy((pos < lenSrc)) && (nth(source, pos) == "@"))) ? ((pos = (pos + 1)), [makeSymbol("splice-unquote"), readExpr()]) : [makeSymbol("unquote"), readExpr()])) : (isSxTruthy(sxOr((isSxTruthy((ch >= "0")) && (ch <= "9")), (isSxTruthy((ch == "-")) && isSxTruthy(((pos + 1) < lenSrc)) && (function() { + var nextCh = nth(source, (pos + 1)); + return (isSxTruthy((nextCh >= "0")) && (nextCh <= "9")); +})()))) ? readNumber() : (isSxTruthy(isIdentStart(ch)) ? readSymbol() : error((String("Unexpected character: ") + String(ch)))))))))))); +})()); }; + return (function() { + var exprs = []; + var parseLoop = function() { while(true) { skipWs(); +if (isSxTruthy((pos < lenSrc))) { exprs.push(readExpr()); +continue; } else { return NIL; } } }; + parseLoop(); + return exprs; +})(); +})(); }; + + // sx-serialize + var sxSerialize = function(val) { return (function() { var _m = typeOf(val); if (_m == "nil") return "nil"; if (_m == "boolean") return (isSxTruthy(val) ? "true" : "false"); if (_m == "number") return (String(val)); if (_m == "string") return (String("\"") + String(escapeString(val)) + String("\"")); if (_m == "symbol") return symbolName(val); if (_m == "keyword") return (String(":") + String(keywordName(val))); if (_m == "list") return (String("(") + String(join(" ", map(sxSerialize, val))) + String(")")); if (_m == "dict") return sxSerializeDict(val); if (_m == "sx-expr") return sxExprSource(val); return (String(val)); })(); }; + + // sx-serialize-dict + var sxSerializeDict = function(d) { return (String("{") + String(join(" ", reduce(function(acc, key) { return concat(acc, [(String(":") + String(key)), sxSerialize(dictGet(d, key))]); }, [], keys(d)))) + String("}")); }; + + // === Transpiled from adapter-dom === // SVG_NS @@ -1284,7 +1417,7 @@ return forEach(function(attr) { return (isSxTruthy(!domHasAttr(newEl, first(attr var _preloadCache = {}; // _css-hash - var _cssHash = ""; + var _cssHash = NIL; // dispatch-trigger-events var dispatchTriggerEvents = function(el, headerVal) { return (isSxTruthy(headerVal) ? (function() { @@ -1747,7 +1880,7 @@ return (_styleCache = {}); }; return (isSxTruthy(decls) ? ((isSxTruthy(startsWith(base, "animate-")) ? (function() { var kfName = slice(base, 8); return (isSxTruthy(dictHas(_styleKeyframes, kfName)) ? append_b(kfNeeded, [kfName, dictGet(_styleKeyframes, kfName)]) : NIL); -})() : NIL), (isSxTruthy(isNil(variant)) ? append_b(baseDecls, decls) : (isSxTruthy(dictHas(_responsiveBreakpoints, variant)) ? append_b(mediaRules, [dictGet(_responsiveBreakpoints, variant), decls]) : (isSxTruthy(dictHas(_pseudoVariants, variant)) ? append_b(pseudoRules, [dictGet(_pseudoVariants, variant), decls]) : (function() { +})() : NIL), (isSxTruthy(isNil(variant)) ? (isSxTruthy(isChildSelectorAtom(base)) ? append_b(pseudoRules, [">:not(:first-child)", decls]) : append_b(baseDecls, decls)) : (isSxTruthy(dictHas(_responsiveBreakpoints, variant)) ? append_b(mediaRules, [dictGet(_responsiveBreakpoints, variant), decls]) : (isSxTruthy(dictHas(_pseudoVariants, variant)) ? append_b(pseudoRules, [dictGet(_pseudoVariants, variant), decls]) : (function() { var vparts = split(variant, ":"); var mediaPart = NIL; var pseudoPart = NIL; @@ -1766,12 +1899,12 @@ return (_styleCache = {}); }; } } } return (function() { var hashInput = join(";", baseDecls); - { var _c = chunkEvery(mediaRules, 2); for (var _i = 0; _i < _c.length; _i++) { var mr = _c[_i]; hashInput = (String(hashInput) + String("@") + String(first(mr)) + String("{") + String(nth(mr, 1)) + String("}")); } } - { var _c = chunkEvery(pseudoRules, 2); for (var _i = 0; _i < _c.length; _i++) { var pr = _c[_i]; hashInput = (String(hashInput) + String(first(pr)) + String("{") + String(nth(pr, 1)) + String("}")); } } - { var _c = chunkEvery(kfNeeded, 2); for (var _i = 0; _i < _c.length; _i++) { var kf = _c[_i]; hashInput = (String(hashInput) + String(nth(kf, 1))); } } + { var _c = mediaRules; for (var _i = 0; _i < _c.length; _i++) { var mr = _c[_i]; hashInput = (String(hashInput) + String("@") + String(first(mr)) + String("{") + String(nth(mr, 1)) + String("}")); } } + { var _c = pseudoRules; for (var _i = 0; _i < _c.length; _i++) { var pr = _c[_i]; hashInput = (String(hashInput) + String(first(pr)) + String("{") + String(nth(pr, 1)) + String("}")); } } + { var _c = kfNeeded; for (var _i = 0; _i < _c.length; _i++) { var kf = _c[_i]; hashInput = (String(hashInput) + String(nth(kf, 1))); } } return (function() { var cn = (String("sx-") + String(hashStyle(hashInput))); - var sv = makeStyleValue_(cn, join(";", baseDecls), chunkEvery(mediaRules, 2), chunkEvery(pseudoRules, 2), chunkEvery(kfNeeded, 2)); + var sv = makeStyleValue_(cn, join(";", baseDecls), mediaRules, pseudoRules, kfNeeded); _styleCache[key] = sv; injectStyleValue(sv, atoms); return sv; @@ -2740,18 +2873,11 @@ callExpr.push(dictGet(kwargs, k)); } } if (!cssTarget) return; var rules = []; + // Child-selector atoms are now routed to pseudoRules by the resolver + // with selector ">:not(:first-child)", so base declarations are always + // applied directly to the class. if (sv.declarations) { - var hasChild = false; - if (atoms) { - for (var ai = 0; ai < atoms.length; ai++) { - if (isChildSelectorAtom(atoms[ai])) { hasChild = true; break; } - } - } - if (hasChild) { - rules.push("." + sv.className + ">:not(:first-child){" + sv.declarations + "}"); - } else { - rules.push("." + sv.className + "{" + sv.declarations + "}"); - } + rules.push("." + sv.className + "{" + sv.declarations + "}"); } for (var pi = 0; pi < sv.pseudoRules.length; pi++) { var sel = sv.pseudoRules[pi][0], decls = sv.pseudoRules[pi][1]; @@ -2952,107 +3078,8 @@ callExpr.push(dictGet(kwargs, k)); } } // Expose render functions as primitives so SX code can call them if (typeof renderToDom === "function") PRIMITIVES["render-to-dom"] = renderToDom; - // ========================================================================= - // Parser - // ========================================================================= - - function parse(text) { - var pos = 0; - function skipWs() { - while (pos < text.length) { - var ch = text[pos]; - if (ch === " " || ch === "\t" || ch === "\n" || ch === "\r") { pos++; continue; } - if (ch === ";") { while (pos < text.length && text[pos] !== "\n") pos++; continue; } - break; - } - } - function readExpr() { - skipWs(); - if (pos >= text.length) return undefined; - var ch = text[pos]; - if (ch === "(") { pos++; return readList(")"); } - if (ch === "[") { pos++; return readList("]"); } - if (ch === "{") { pos++; return readMap(); } - if (ch === '"') return readString(); - if (ch === ":") return readKeyword(); - if (ch === "`") { pos++; return [new Symbol("quasiquote"), readExpr()]; } - if (ch === ",") { - pos++; - if (pos < text.length && text[pos] === "@") { pos++; return [new Symbol("splice-unquote"), readExpr()]; } - return [new Symbol("unquote"), readExpr()]; - } - if (ch === "-" && pos + 1 < text.length && text[pos + 1] >= "0" && text[pos + 1] <= "9") return readNumber(); - if (ch >= "0" && ch <= "9") return readNumber(); - return readSymbol(); - } - function readList(close) { - var items = []; - while (true) { - skipWs(); - if (pos >= text.length) throw new Error("Unterminated list"); - if (text[pos] === close) { pos++; return items; } - items.push(readExpr()); - } - } - function readMap() { - var result = {}; - while (true) { - skipWs(); - if (pos >= text.length) throw new Error("Unterminated map"); - if (text[pos] === "}") { pos++; return result; } - var key = readExpr(); - var keyStr = (key && key._kw) ? key.name : String(key); - result[keyStr] = readExpr(); - } - } - function readString() { - pos++; // skip " - var s = ""; - while (pos < text.length) { - var ch = text[pos]; - if (ch === '"') { pos++; return s; } - if (ch === "\\") { pos++; var esc = text[pos]; s += esc === "n" ? "\n" : esc === "t" ? "\t" : esc === "r" ? "\r" : esc; pos++; continue; } - s += ch; pos++; - } - throw new Error("Unterminated string"); - } - function readKeyword() { - pos++; // skip : - var name = readIdent(); - return new Keyword(name); - } - function readNumber() { - var start = pos; - if (text[pos] === "-") pos++; - while (pos < text.length && text[pos] >= "0" && text[pos] <= "9") pos++; - if (pos < text.length && text[pos] === ".") { pos++; while (pos < text.length && text[pos] >= "0" && text[pos] <= "9") pos++; } - if (pos < text.length && (text[pos] === "e" || text[pos] === "E")) { - pos++; - if (pos < text.length && (text[pos] === "+" || text[pos] === "-")) pos++; - while (pos < text.length && text[pos] >= "0" && text[pos] <= "9") pos++; - } - return Number(text.slice(start, pos)); - } - function readIdent() { - var start = pos; - while (pos < text.length && /[a-zA-Z0-9_~*+\-><=/!?.:&]/.test(text[pos])) pos++; - return text.slice(start, pos); - } - function readSymbol() { - var name = readIdent(); - if (name === "true") return true; - if (name === "false") return false; - if (name === "nil") return NIL; - return new Symbol(name); - } - var exprs = []; - while (true) { - skipWs(); - if (pos >= text.length) break; - exprs.push(readExpr()); - } - return exprs; - } + // Parser — compiled from parser.sx (see PLATFORM_PARSER_JS for ident char classes) + var parse = sxParse; // ========================================================================= // Public API @@ -3104,7 +3131,7 @@ callExpr.push(dictGet(kwargs, k)); } } renderComponent: typeof sxRenderComponent === "function" ? sxRenderComponent : null, getEnv: function() { return componentEnv; }, init: typeof bootInit === "function" ? bootInit : null, - _version: "ref-2.0 (boot+cssx+dom+engine+orchestration, bootstrap-compiled)" + _version: "ref-2.0 (boot+cssx+dom+engine+orchestration+parser, bootstrap-compiled)" }; diff --git a/shared/static/scripts/sx-ref.js b/shared/static/scripts/sx-ref.js index 47c31a3..54e95ad 100644 --- a/shared/static/scripts/sx-ref.js +++ b/shared/static/scripts/sx-ref.js @@ -103,6 +103,7 @@ if (x._macro) return "macro"; if (x._raw) return "raw-html"; if (x._styleValue) return "style-value"; + if (typeof Node !== "undefined" && x instanceof Node) return "dom-node"; if (Array.isArray(x)) return "list"; if (typeof x === "object") return "dict"; return "unknown"; @@ -178,6 +179,29 @@ function dictSet(d, k, v) { d[k] = v; } function dictGet(d, k) { var v = d[k]; return v !== undefined ? v : NIL; } + // Render-expression detection — lets the evaluator delegate to the active adapter. + // Matches HTML tags, SVG tags, <>, raw!, ~components, html: prefix, custom elements. + function isRenderExpr(expr) { + if (!Array.isArray(expr) || !expr.length) 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. + // Set by each adapter when loaded; defaults to identity (no rendering). + var _renderExprFn = null; + function renderExpr(expr, env) { + if (_renderExprFn) return _renderExprFn(expr, env); + // No adapter loaded — just return the expression as-is + return expr; + } + function stripPrefix(s, prefix) { return s.indexOf(prefix) === 0 ? s.slice(prefix.length) : s; } @@ -504,10 +528,10 @@ var args = rest(expr); return (isSxTruthy(!sxOr((typeOf(head) == "symbol"), (typeOf(head) == "lambda"), (typeOf(head) == "list"))) ? map(function(x) { return trampoline(evalExpr(x, env)); }, expr) : (isSxTruthy((typeOf(head) == "symbol")) ? (function() { var name = symbolName(head); - return (isSxTruthy((name == "if")) ? sfIf(args, env) : (isSxTruthy((name == "when")) ? sfWhen(args, env) : (isSxTruthy((name == "cond")) ? sfCond(args, env) : (isSxTruthy((name == "case")) ? sfCase(args, env) : (isSxTruthy((name == "and")) ? sfAnd(args, env) : (isSxTruthy((name == "or")) ? sfOr(args, env) : (isSxTruthy((name == "let")) ? sfLet(args, env) : (isSxTruthy((name == "let*")) ? sfLet(args, env) : (isSxTruthy((name == "lambda")) ? sfLambda(args, env) : (isSxTruthy((name == "fn")) ? sfLambda(args, env) : (isSxTruthy((name == "define")) ? sfDefine(args, env) : (isSxTruthy((name == "defcomp")) ? sfDefcomp(args, env) : (isSxTruthy((name == "defmacro")) ? sfDefmacro(args, env) : (isSxTruthy((name == "defstyle")) ? sfDefstyle(args, env) : (isSxTruthy((name == "defkeyframes")) ? sfDefkeyframes(args, env) : (isSxTruthy((name == "defhandler")) ? sfDefine(args, env) : (isSxTruthy((name == "begin")) ? sfBegin(args, env) : (isSxTruthy((name == "do")) ? sfBegin(args, env) : (isSxTruthy((name == "quote")) ? sfQuote(args, env) : (isSxTruthy((name == "quasiquote")) ? sfQuasiquote(args, env) : (isSxTruthy((name == "->")) ? sfThreadFirst(args, env) : (isSxTruthy((name == "set!")) ? sfSetBang(args, env) : (isSxTruthy((name == "map")) ? hoMap(args, env) : (isSxTruthy((name == "map-indexed")) ? hoMapIndexed(args, env) : (isSxTruthy((name == "filter")) ? hoFilter(args, env) : (isSxTruthy((name == "reduce")) ? hoReduce(args, env) : (isSxTruthy((name == "some")) ? hoSome(args, env) : (isSxTruthy((name == "every?")) ? hoEvery(args, env) : (isSxTruthy((name == "for-each")) ? hoForEach(args, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? (function() { + return (isSxTruthy((name == "if")) ? sfIf(args, env) : (isSxTruthy((name == "when")) ? sfWhen(args, env) : (isSxTruthy((name == "cond")) ? sfCond(args, env) : (isSxTruthy((name == "case")) ? sfCase(args, env) : (isSxTruthy((name == "and")) ? sfAnd(args, env) : (isSxTruthy((name == "or")) ? sfOr(args, env) : (isSxTruthy((name == "let")) ? sfLet(args, env) : (isSxTruthy((name == "let*")) ? sfLet(args, env) : (isSxTruthy((name == "lambda")) ? sfLambda(args, env) : (isSxTruthy((name == "fn")) ? sfLambda(args, env) : (isSxTruthy((name == "define")) ? sfDefine(args, env) : (isSxTruthy((name == "defcomp")) ? sfDefcomp(args, env) : (isSxTruthy((name == "defmacro")) ? sfDefmacro(args, env) : (isSxTruthy((name == "defstyle")) ? sfDefstyle(args, env) : (isSxTruthy((name == "defkeyframes")) ? sfDefkeyframes(args, env) : (isSxTruthy((name == "defhandler")) ? sfDefhandler(args, env) : (isSxTruthy((name == "defpage")) ? sfDefpage(args, env) : (isSxTruthy((name == "defquery")) ? sfDefquery(args, env) : (isSxTruthy((name == "defaction")) ? sfDefaction(args, env) : (isSxTruthy((name == "begin")) ? sfBegin(args, env) : (isSxTruthy((name == "do")) ? sfBegin(args, env) : (isSxTruthy((name == "quote")) ? sfQuote(args, env) : (isSxTruthy((name == "quasiquote")) ? sfQuasiquote(args, env) : (isSxTruthy((name == "->")) ? sfThreadFirst(args, env) : (isSxTruthy((name == "set!")) ? sfSetBang(args, env) : (isSxTruthy((name == "map")) ? hoMap(args, env) : (isSxTruthy((name == "map-indexed")) ? hoMapIndexed(args, env) : (isSxTruthy((name == "filter")) ? hoFilter(args, env) : (isSxTruthy((name == "reduce")) ? hoReduce(args, env) : (isSxTruthy((name == "some")) ? hoSome(args, env) : (isSxTruthy((name == "every?")) ? hoEvery(args, env) : (isSxTruthy((name == "for-each")) ? hoForEach(args, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? (function() { var mac = envGet(env, name); return makeThunk(expandMacro(mac, args, env), env); -})() : evalCall(head, args, env))))))))))))))))))))))))))))))); +})() : (isSxTruthy(isRenderExpr(expr)) ? renderExpr(expr, env) : evalCall(head, args, env))))))))))))))))))))))))))))))))))); })() : evalCall(head, args, env))); })(); }; @@ -764,25 +788,28 @@ return trampoline(evalExpr(macroBody(mac), local)); })(); }; + // call-fn + var callFn = function(f, args, env) { return (isSxTruthy(isLambda(f)) ? trampoline(callLambda(f, args, env)) : (isSxTruthy(isCallable(f)) ? apply(f, args) : error((String("Not callable in HO form: ") + String(inspect(f)))))); }; + // ho-map var hoMap = function(args, env) { return (function() { var f = trampoline(evalExpr(first(args), env)); var coll = trampoline(evalExpr(nth(args, 1), env)); - return map(function(item) { return trampoline(callLambda(f, [item], env)); }, coll); + return map(function(item) { return callFn(f, [item], env); }, coll); })(); }; // ho-map-indexed var hoMapIndexed = function(args, env) { return (function() { var f = trampoline(evalExpr(first(args), env)); var coll = trampoline(evalExpr(nth(args, 1), env)); - return mapIndexed(function(i, item) { return trampoline(callLambda(f, [i, item], env)); }, coll); + return mapIndexed(function(i, item) { return callFn(f, [i, item], env); }, coll); })(); }; // ho-filter var hoFilter = function(args, env) { return (function() { var f = trampoline(evalExpr(first(args), env)); var coll = trampoline(evalExpr(nth(args, 1), env)); - return filter(function(item) { return trampoline(callLambda(f, [item], env)); }, coll); + return filter(function(item) { return callFn(f, [item], env); }, coll); })(); }; // ho-reduce @@ -790,28 +817,28 @@ var f = trampoline(evalExpr(first(args), env)); var init = trampoline(evalExpr(nth(args, 1), env)); var coll = trampoline(evalExpr(nth(args, 2), env)); - return reduce(function(acc, item) { return trampoline(callLambda(f, [acc, item], env)); }, init, coll); + return reduce(function(acc, item) { return callFn(f, [acc, item], env); }, init, coll); })(); }; // ho-some var hoSome = function(args, env) { return (function() { var f = trampoline(evalExpr(first(args), env)); var coll = trampoline(evalExpr(nth(args, 1), env)); - return some(function(item) { return trampoline(callLambda(f, [item], env)); }, coll); + return some(function(item) { return callFn(f, [item], env); }, coll); })(); }; // ho-every var hoEvery = function(args, env) { return (function() { var f = trampoline(evalExpr(first(args), env)); var coll = trampoline(evalExpr(nth(args, 1), env)); - return isEvery(function(item) { return trampoline(callLambda(f, [item], env)); }, coll); + return isEvery(function(item) { return callFn(f, [item], env); }, coll); })(); }; // ho-for-each var hoForEach = function(args, env) { return (function() { var f = trampoline(evalExpr(first(args), env)); var coll = trampoline(evalExpr(nth(args, 1), env)); - return forEach(function(item) { return trampoline(callLambda(f, [item], env)); }, coll); + return forEach(function(item) { return callFn(f, [item], env); }, coll); })(); }; @@ -851,154 +878,6 @@ })(); }, keys(attrs))); }; - // === 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); if (_m == "style-value") return styleValueClass(val); return escapeHtml((String(val))); })(); }; - - // RENDER_HTML_FORMS - var RENDER_HTML_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defmacro", "defstyle", "defkeyframes", "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(!(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(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(!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(""))))); -})(); }; - - - // === 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(!(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(!isLambda(f)) && !isComponent(f))) ? apply(f, evaledArgs) : (isSxTruthy(isLambda(f)) ? trampoline(callLambda(f, evaledArgs, env)) : (isSxTruthy(isComponent(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 !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(!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(!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(")")); -})(); }; - - // === Transpiled from adapter-dom === // SVG_NS @@ -1164,851 +1043,15 @@ })(); }; - // === Transpiled from engine === - - // ENGINE_VERBS - var ENGINE_VERBS = ["get", "post", "put", "delete", "patch"]; - - // DEFAULT_SWAP - var DEFAULT_SWAP = "outerHTML"; - - // parse-time - var parseTime = function(s) { return (isSxTruthy(isNil(s)) ? 0 : (isSxTruthy(endsWith(s, "ms")) ? parseInt_(s, 0) : (isSxTruthy(endsWith(s, "s")) ? (parseInt_(replace_(s, "s", ""), 0) * 1000) : parseInt_(s, 0)))); }; - - // parse-trigger-spec - var parseTriggerSpec = function(spec) { return (isSxTruthy(isNil(spec)) ? NIL : (function() { - var rawParts = split(spec, ","); - return filter(function(x) { return !isNil(x); }, map(function(part) { return (function() { - var tokens = split(trim(part), " "); - return (isSxTruthy(isEmpty(tokens)) ? NIL : (isSxTruthy((isSxTruthy((first(tokens) == "every")) && (len(tokens) >= 2))) ? {["event"]: "every", ["modifiers"]: {["interval"]: parseTime(nth(tokens, 1))}} : (function() { - var mods = {}; - { var _c = rest(tokens); for (var _i = 0; _i < _c.length; _i++) { var tok = _c[_i]; (isSxTruthy((tok == "once")) ? dictSet(mods, "once", true) : (isSxTruthy((tok == "changed")) ? dictSet(mods, "changed", true) : (isSxTruthy(startsWith(tok, "delay:")) ? dictSet(mods, "delay", parseTime(slice(tok, 6))) : (isSxTruthy(startsWith(tok, "from:")) ? dictSet(mods, "from", slice(tok, 5)) : NIL)))); } } - return {["event"]: first(tokens), ["modifiers"]: mods}; -})())); -})(); }, rawParts)); -})()); }; - - // default-trigger - var defaultTrigger = function(tagName) { return (isSxTruthy((tagName == "FORM")) ? [{["event"]: "submit", ["modifiers"]: {}}] : (isSxTruthy(sxOr((tagName == "INPUT"), (tagName == "SELECT"), (tagName == "TEXTAREA"))) ? [{["event"]: "change", ["modifiers"]: {}}] : [{["event"]: "click", ["modifiers"]: {}}])); }; - - // get-verb-info - var getVerbInfo = function(el) { return some(function(verb) { return (function() { - var url = domGetAttr(el, (String("sx-") + String(verb))); - return (isSxTruthy(url) ? {["method"]: upper(verb), ["url"]: url} : NIL); -})(); }, ENGINE_VERBS); }; - - // build-request-headers - var buildRequestHeaders = function(el, loadedComponents, cssHash) { return (function() { - var headers = {["SX-Request"]: "true", ["SX-Current-URL"]: browserLocationHref()}; - (function() { - var targetSel = domGetAttr(el, "sx-target"); - return (isSxTruthy(targetSel) ? dictSet(headers, "SX-Target", targetSel) : NIL); -})(); - if (isSxTruthy(!isEmpty(loadedComponents))) { - headers["SX-Components"] = join(",", loadedComponents); -} - if (isSxTruthy(cssHash)) { - headers["SX-Css"] = cssHash; -} - (function() { - var extraH = domGetAttr(el, "sx-headers"); - return (isSxTruthy(extraH) ? (function() { - var parsed = parseHeaderValue(extraH); - return (isSxTruthy(parsed) ? forEach(function(key) { return dictSet(headers, key, (String(get(parsed, key)))); }, keys(parsed)) : NIL); -})() : NIL); -})(); - return headers; -})(); }; - - // process-response-headers - var processResponseHeaders = function(getHeader) { return {["redirect"]: getHeader("SX-Redirect"), ["refresh"]: getHeader("SX-Refresh"), ["trigger"]: getHeader("SX-Trigger"), ["retarget"]: getHeader("SX-Retarget"), ["reswap"]: getHeader("SX-Reswap"), ["location"]: getHeader("SX-Location"), ["replace-url"]: getHeader("SX-Replace-Url"), ["css-hash"]: getHeader("SX-Css-Hash"), ["trigger-swap"]: getHeader("SX-Trigger-After-Swap"), ["trigger-settle"]: getHeader("SX-Trigger-After-Settle"), ["content-type"]: getHeader("Content-Type")}; }; - - // parse-swap-spec - var parseSwapSpec = function(rawSwap, globalTransitions_p) { return (function() { - var parts = split(sxOr(rawSwap, DEFAULT_SWAP), " "); - var style = first(parts); - var useTransition = globalTransitions_p; - { var _c = rest(parts); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; (isSxTruthy((p == "transition:true")) ? (useTransition = true) : (isSxTruthy((p == "transition:false")) ? (useTransition = false) : NIL)); } } - return {["style"]: style, ["transition"]: useTransition}; -})(); }; - - // parse-retry-spec - var parseRetrySpec = function(retryAttr) { return (isSxTruthy(isNil(retryAttr)) ? NIL : (function() { - var parts = split(retryAttr, ":"); - return {["strategy"]: first(parts), ["start-ms"]: parseInt_(nth(parts, 1), 1000), ["cap-ms"]: parseInt_(nth(parts, 2), 30000)}; -})()); }; - - // next-retry-ms - var nextRetryMs = function(currentMs, capMs) { return min((currentMs * 2), capMs); }; - - // filter-params - var filterParams = function(paramsSpec, allParams) { return (isSxTruthy(isNil(paramsSpec)) ? allParams : (isSxTruthy((paramsSpec == "none")) ? [] : (isSxTruthy((paramsSpec == "*")) ? allParams : (isSxTruthy(startsWith(paramsSpec, "not ")) ? (function() { - var excluded = map(trim, split(slice(paramsSpec, 4), ",")); - return filter(function(p) { return !contains(excluded, first(p)); }, allParams); -})() : (function() { - var allowed = map(trim, split(paramsSpec, ",")); - return filter(function(p) { return contains(allowed, first(p)); }, allParams); -})())))); }; - - // resolve-target - var resolveTarget = function(el) { return (function() { - var sel = domGetAttr(el, "sx-target"); - return (isSxTruthy(sxOr(isNil(sel), (sel == "this"))) ? el : (isSxTruthy((sel == "closest")) ? domParent(el) : domQuery(sel))); -})(); }; - - // apply-optimistic - var applyOptimistic = function(el) { return (function() { - var directive = domGetAttr(el, "sx-optimistic"); - return (isSxTruthy(isNil(directive)) ? NIL : (function() { - var target = sxOr(resolveTarget(el), el); - var state = {["target"]: target, ["directive"]: directive}; - (isSxTruthy((directive == "remove")) ? (dictSet(state, "opacity", domGetStyle(target, "opacity")), domSetStyle(target, "opacity", "0"), domSetStyle(target, "pointer-events", "none")) : (isSxTruthy((directive == "disable")) ? (dictSet(state, "disabled", domGetProp(target, "disabled")), domSetProp(target, "disabled", true)) : (isSxTruthy(startsWith(directive, "add-class:")) ? (function() { - var cls = slice(directive, 10); - state["add-class"] = cls; - return domAddClass(target, cls); -})() : NIL))); - return state; -})()); -})(); }; - - // revert-optimistic - var revertOptimistic = function(state) { return (isSxTruthy(state) ? (function() { - var target = get(state, "target"); - var directive = get(state, "directive"); - return (isSxTruthy((directive == "remove")) ? (domSetStyle(target, "opacity", sxOr(get(state, "opacity"), "")), domSetStyle(target, "pointer-events", "")) : (isSxTruthy((directive == "disable")) ? domSetProp(target, "disabled", sxOr(get(state, "disabled"), false)) : (isSxTruthy(get(state, "add-class")) ? domRemoveClass(target, get(state, "add-class")) : NIL))); -})() : NIL); }; - - // find-oob-swaps - var findOobSwaps = function(container) { return (function() { - var results = []; - { var _c = ["sx-swap-oob", "hx-swap-oob"]; for (var _i = 0; _i < _c.length; _i++) { var attr = _c[_i]; (function() { - var oobEls = domQueryAll(container, (String("[") + String(attr) + String("]"))); - return forEach(function(oob) { return (function() { - var swapType = sxOr(domGetAttr(oob, attr), "outerHTML"); - var targetId = domId(oob); - domRemoveAttr(oob, attr); - return (isSxTruthy(targetId) ? append_b(results, {["element"]: oob, ["swap-type"]: swapType, ["target-id"]: targetId}) : NIL); -})(); }, oobEls); -})(); } } - return results; -})(); }; - - // morph-node - var morphNode = function(oldNode, newNode) { return (isSxTruthy(sxOr(domHasAttr(oldNode, "sx-preserve"), domHasAttr(oldNode, "sx-ignore"))) ? NIL : (isSxTruthy(sxOr(!(domNodeType(oldNode) == domNodeType(newNode)), !(domNodeName(oldNode) == domNodeName(newNode)))) ? domReplaceChild(domParent(oldNode), domClone(newNode), oldNode) : (isSxTruthy(sxOr((domNodeType(oldNode) == 3), (domNodeType(oldNode) == 8))) ? (isSxTruthy(!(domTextContent(oldNode) == domTextContent(newNode))) ? domSetTextContent(oldNode, domTextContent(newNode)) : NIL) : (isSxTruthy((domNodeType(oldNode) == 1)) ? (syncAttrs(oldNode, newNode), (isSxTruthy(!(isSxTruthy(domIsActiveElement(oldNode)) && domIsInputElement(oldNode))) ? morphChildren(oldNode, newNode) : NIL)) : NIL)))); }; - - // sync-attrs - var syncAttrs = function(oldEl, newEl) { return forEach(function(attr) { return (function() { - var name = first(attr); - var val = nth(attr, 1); - return (isSxTruthy(!(domGetAttr(oldEl, name) == val)) ? domSetAttr(oldEl, name, val) : NIL); -})(); }, domAttrList(newEl)); }; - - // morph-children - var morphChildren = function(oldParent, newParent) { return (function() { - var oldKids = domChildList(oldParent); - var newKids = domChildList(newParent); - var oldById = reduce(function(acc, kid) { return (function() { - var id = domId(kid); - return (isSxTruthy(id) ? (dictSet(acc, id, kid), acc) : acc); -})(); }, {}, oldKids); - var oi = 0; - { var _c = newKids; for (var _i = 0; _i < _c.length; _i++) { var newChild = _c[_i]; (function() { - var matchId = domId(newChild); - var matchById = (isSxTruthy(matchId) ? dictGet(oldById, matchId) : NIL); - return (isSxTruthy((isSxTruthy(matchById) && !isNil(matchById))) ? ((isSxTruthy((isSxTruthy((oi < len(oldKids))) && !(matchById == nth(oldKids, oi)))) ? domInsertBefore(oldParent, matchById, (isSxTruthy((oi < len(oldKids))) ? nth(oldKids, oi) : NIL)) : NIL), morphNode(matchById, newChild), (oi = (oi + 1))) : (isSxTruthy((oi < len(oldKids))) ? (function() { - var oldChild = nth(oldKids, oi); - return (isSxTruthy((isSxTruthy(domId(oldChild)) && !matchId)) ? domInsertBefore(oldParent, domClone(newChild), oldChild) : (morphNode(oldChild, newChild), (oi = (oi + 1)))); -})() : domAppend(oldParent, domClone(newChild)))); -})(); } } - return forEach(function(i) { return (isSxTruthy((i >= oi)) ? (function() { - var leftover = nth(oldKids, i); - return (isSxTruthy((isSxTruthy(domIsChildOf(leftover, oldParent)) && isSxTruthy(!domHasAttr(leftover, "sx-preserve")) && !domHasAttr(leftover, "sx-ignore"))) ? domRemoveChild(oldParent, leftover) : NIL); -})() : NIL); }, range(oi, len(oldKids))); -})(); }; - - // swap-dom-nodes - var swapDomNodes = function(target, newNodes, strategy) { return (function() { var _m = strategy; if (_m == "innerHTML") return (isSxTruthy(domIsFragment(newNodes)) ? morphChildren(target, newNodes) : (function() { - var wrapper = domCreateElement("div", NIL); - domAppend(wrapper, newNodes); - return morphChildren(target, wrapper); -})()); if (_m == "outerHTML") return (function() { - var parent = domParent(target); - (isSxTruthy(domIsFragment(newNodes)) ? (function() { - var fc = domFirstChild(newNodes); - return (isSxTruthy(fc) ? (morphNode(target, fc), (function() { - var sib = domNextSibling(fc); - return insertRemainingSiblings(parent, target, sib); -})()) : domRemoveChild(parent, target)); -})() : morphNode(target, newNodes)); - return parent; -})(); if (_m == "afterend") return domInsertAfter(target, newNodes); if (_m == "beforeend") return domAppend(target, newNodes); if (_m == "afterbegin") return domPrepend(target, newNodes); if (_m == "beforebegin") return domInsertBefore(domParent(target), newNodes, target); if (_m == "delete") return domRemoveChild(domParent(target), target); if (_m == "none") return NIL; return (isSxTruthy(domIsFragment(newNodes)) ? morphChildren(target, newNodes) : (function() { - var wrapper = domCreateElement("div", NIL); - domAppend(wrapper, newNodes); - return morphChildren(target, wrapper); -})()); })(); }; - - // insert-remaining-siblings - var insertRemainingSiblings = function(parent, refNode, sib) { return (isSxTruthy(sib) ? (function() { - var next = domNextSibling(sib); - domInsertAfter(refNode, sib); - return insertRemainingSiblings(parent, sib, next); -})() : NIL); }; - - // swap-html-string - var swapHtmlString = function(target, html, strategy) { return (function() { var _m = strategy; if (_m == "innerHTML") return domSetInnerHtml(target, html); if (_m == "outerHTML") return (function() { - var parent = domParent(target); - domInsertAdjacentHtml(target, "afterend", html); - domRemoveChild(parent, target); - return parent; -})(); if (_m == "afterend") return domInsertAdjacentHtml(target, "afterend", html); if (_m == "beforeend") return domInsertAdjacentHtml(target, "beforeend", html); if (_m == "afterbegin") return domInsertAdjacentHtml(target, "afterbegin", html); if (_m == "beforebegin") return domInsertAdjacentHtml(target, "beforebegin", html); if (_m == "delete") return domRemoveChild(domParent(target), target); if (_m == "none") return NIL; return domSetInnerHtml(target, html); })(); }; - - // handle-history - var handleHistory = function(el, url, respHeaders) { return (function() { - var pushUrl = domGetAttr(el, "sx-push-url"); - var replaceUrl = domGetAttr(el, "sx-replace-url"); - var hdrReplace = get(respHeaders, "replace-url"); - return (isSxTruthy(hdrReplace) ? browserReplaceState(hdrReplace) : (isSxTruthy((isSxTruthy(pushUrl) && !(pushUrl == "false"))) ? browserPushState((isSxTruthy((pushUrl == "true")) ? url : pushUrl)) : (isSxTruthy((isSxTruthy(replaceUrl) && !(replaceUrl == "false"))) ? browserReplaceState((isSxTruthy((replaceUrl == "true")) ? url : replaceUrl)) : NIL))); -})(); }; - - // PRELOAD_TTL - var PRELOAD_TTL = 30000; - - // preload-cache-get - var preloadCacheGet = function(cache, url) { return (function() { - var entry = dictGet(cache, url); - return (isSxTruthy(isNil(entry)) ? NIL : (isSxTruthy(((nowMs() - get(entry, "timestamp")) > PRELOAD_TTL)) ? (dictDelete(cache, url), NIL) : (dictDelete(cache, url), entry))); -})(); }; - - // preload-cache-set - var preloadCacheSet = function(cache, url, text, contentType) { return dictSet(cache, url, {["text"]: text, ["content-type"]: contentType, ["timestamp"]: nowMs()}); }; - - // classify-trigger - var classifyTrigger = function(trigger) { return (function() { - var event = get(trigger, "event"); - return (isSxTruthy((event == "every")) ? "poll" : (isSxTruthy((event == "intersect")) ? "intersect" : (isSxTruthy((event == "load")) ? "load" : (isSxTruthy((event == "revealed")) ? "revealed" : "event")))); -})(); }; - - // should-boost-link? - var shouldBoostLink = function(link) { return (function() { - var href = domGetAttr(link, "href"); - return (isSxTruthy(href) && isSxTruthy(!startsWith(href, "#")) && isSxTruthy(!startsWith(href, "javascript:")) && isSxTruthy(!startsWith(href, "mailto:")) && isSxTruthy(browserSameOrigin(href)) && isSxTruthy(!domHasAttr(link, "sx-get")) && isSxTruthy(!domHasAttr(link, "sx-post")) && !domHasAttr(link, "sx-disable")); -})(); }; - - // should-boost-form? - var shouldBoostForm = function(form) { return (isSxTruthy(!domHasAttr(form, "sx-get")) && isSxTruthy(!domHasAttr(form, "sx-post")) && !domHasAttr(form, "sx-disable")); }; - - // parse-sse-swap - var parseSseSwap = function(el) { return sxOr(domGetAttr(el, "sx-sse-swap"), "message"); }; - - - // === Transpiled from orchestration === - - // _preload-cache - var _preloadCache = {}; - - // _css-hash - var _cssHash = ""; - - // dispatch-trigger-events - var dispatchTriggerEvents = function(el, headerVal) { return (isSxTruthy(headerVal) ? (function() { - var parsed = tryParseJson(headerVal); - return (isSxTruthy(parsed) ? forEach(function(key) { return domDispatch(el, key, get(parsed, key)); }, keys(parsed)) : forEach(function(name) { return (function() { - var trimmed = trim(name); - return (isSxTruthy(!isEmpty(trimmed)) ? domDispatch(el, trimmed, {}) : NIL); -})(); }, split(headerVal, ","))); -})() : NIL); }; - - // init-css-tracking - var initCssTracking = function() { return (function() { - var meta = domQuery("meta[name=\"sx-css-hash\"]"); - return (isSxTruthy(meta) ? (function() { - var content = domGetAttr(meta, "content"); - return (isSxTruthy(content) ? (_cssHash = content) : NIL); -})() : NIL); -})(); }; - - // execute-request - var executeRequest = function(el, verbInfo, extraParams) { return (function() { - var info = sxOr(verbInfo, getVerbInfo(el)); - return (isSxTruthy(isNil(info)) ? promiseResolve(NIL) : (function() { - var verb = get(info, "method"); - var url = get(info, "url"); - return (isSxTruthy((function() { - var media = domGetAttr(el, "sx-media"); - return (isSxTruthy(media) && !browserMediaMatches(media)); -})()) ? promiseResolve(NIL) : (isSxTruthy((function() { - var confirmMsg = domGetAttr(el, "sx-confirm"); - return (isSxTruthy(confirmMsg) && !browserConfirm(confirmMsg)); -})()) ? promiseResolve(NIL) : (function() { - var promptMsg = domGetAttr(el, "sx-prompt"); - var promptVal = (isSxTruthy(promptMsg) ? browserPrompt(promptMsg) : NIL); - return (isSxTruthy((isSxTruthy(promptMsg) && isNil(promptVal))) ? promiseResolve(NIL) : (isSxTruthy(!validateForRequest(el)) ? promiseResolve(NIL) : doFetch(el, verb, verb, url, (isSxTruthy(promptVal) ? assoc(sxOr(extraParams, {}), "SX-Prompt", promptVal) : extraParams)))); -})())); -})()); -})(); }; - - // do-fetch - var doFetch = function(el, verb, method, url, extraParams) { return (function() { - var sync = domGetAttr(el, "sx-sync"); - if (isSxTruthy((sync == "replace"))) { - abortPrevious(el); -} - return (function() { - var ctrl = newAbortController(); - trackController(el, ctrl); - return (function() { - var bodyInfo = buildRequestBody(el, method, url); - var finalUrl = get(bodyInfo, "url"); - var body = get(bodyInfo, "body"); - var ct = get(bodyInfo, "content-type"); - var headers = buildRequestHeaders(el, loadedComponentNames(), _cssHash); - var csrf = csrfToken(); - if (isSxTruthy(extraParams)) { - { var _c = keys(extraParams); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; headers[k] = get(extraParams, k); } } -} - if (isSxTruthy(ct)) { - headers["Content-Type"] = ct; -} - if (isSxTruthy(csrf)) { - headers["X-CSRFToken"] = csrf; -} - return (function() { - var cached = preloadCacheGet(_preloadCache, finalUrl); - var optimisticState = applyOptimistic(el); - var indicator = showIndicator(el); - var disabledElts = disableElements(el); - domAddClass(el, "sx-request"); - domSetAttr(el, "aria-busy", "true"); - domDispatch(el, "sx:beforeRequest", {["url"]: finalUrl, ["method"]: method}); - return fetchRequest({["url"]: finalUrl, ["method"]: method, ["headers"]: headers, ["body"]: body, ["signal"]: controllerSignal(ctrl), ["cross-origin"]: isCrossOrigin(finalUrl), ["preloaded"]: cached}, function(respOk, status, getHeader, text) { return (clearLoadingState(el, indicator, disabledElts), revertOptimistic(optimisticState), (isSxTruthy(!respOk) ? (domDispatch(el, "sx:responseError", {["status"]: status, ["text"]: text}), handleRetry(el, verb, method, finalUrl, extraParams)) : (domDispatch(el, "sx:afterRequest", {["status"]: status}), handleFetchSuccess(el, finalUrl, verb, extraParams, getHeader, text)))); }, function(err) { return (clearLoadingState(el, indicator, disabledElts), revertOptimistic(optimisticState), (isSxTruthy(!isAbortError(err)) ? domDispatch(el, "sx:requestError", {["error"]: err}) : NIL)); }); -})(); -})(); -})(); -})(); }; - - // handle-fetch-success - var handleFetchSuccess = function(el, url, verb, extraParams, getHeader, text) { return (function() { - var respHeaders = processResponseHeaders(getHeader); - (function() { - var newHash = get(respHeaders, "css-hash"); - return (isSxTruthy(newHash) ? (_cssHash = newHash) : NIL); -})(); - dispatchTriggerEvents(el, get(respHeaders, "trigger")); - return (isSxTruthy(get(respHeaders, "redirect")) ? browserNavigate(get(respHeaders, "redirect")) : (isSxTruthy(get(respHeaders, "refresh")) ? browserReload() : (isSxTruthy(get(respHeaders, "location")) ? fetchLocation(get(respHeaders, "location")) : (function() { - var targetEl = (isSxTruthy(get(respHeaders, "retarget")) ? domQuery(get(respHeaders, "retarget")) : resolveTarget(el)); - var swapSpec = parseSwapSpec(sxOr(get(respHeaders, "reswap"), domGetAttr(el, "sx-swap")), domHasClass(domBody(), "sx-transitions")); - var swapStyle = get(swapSpec, "style"); - var useTransition = get(swapSpec, "transition"); - var ct = sxOr(get(respHeaders, "content-type"), ""); - (isSxTruthy(contains(ct, "text/sx")) ? handleSxResponse(el, targetEl, text, swapStyle, useTransition) : handleHtmlResponse(el, targetEl, text, swapStyle, useTransition)); - dispatchTriggerEvents(el, get(respHeaders, "trigger-swap")); - handleHistory(el, url, respHeaders); - if (isSxTruthy(get(respHeaders, "trigger-settle"))) { - setTimeout_(function() { return dispatchTriggerEvents(el, get(respHeaders, "trigger-settle")); }, 20); -} - return domDispatch(el, "sx:afterSwap", {["target"]: targetEl, ["swap"]: swapStyle}); -})()))); -})(); }; - - // handle-sx-response - var handleSxResponse = function(el, target, text, swapStyle, useTransition) { return (function() { - var cleaned = stripComponentScripts(text); - return (function() { - var final = extractResponseCss(cleaned); - return (function() { - var trimmed = trim(final); - return (isSxTruthy(!isEmpty(trimmed)) ? (function() { - var rendered = sxRender(trimmed); - var container = domCreateElement("div", NIL); - domAppend(container, rendered); - processOobSwaps(container, function(t, oob, s) { return swapDomNodes(t, oob, s); }); - return (function() { - var selectSel = domGetAttr(el, "sx-select"); - var content = (isSxTruthy(selectSel) ? selectFromContainer(container, selectSel) : childrenToFragment(container)); - return withTransition(useTransition, function() { return swapDomNodes(target, content, swapStyle); }); -})(); -})() : NIL); -})(); -})(); -})(); }; - - // handle-html-response - var handleHtmlResponse = function(el, target, text, swapStyle, useTransition) { return (function() { - var doc = domParseHtmlDocument(text); - return (isSxTruthy(doc) ? (function() { - var selectSel = domGetAttr(el, "sx-select"); - return (isSxTruthy(selectSel) ? (function() { - var html = selectHtmlFromDoc(doc, selectSel); - return withTransition(useTransition, function() { return swapHtmlString(target, html, swapStyle); }); -})() : (function() { - var container = domCreateElement("div", NIL); - domSetInnerHtml(container, domBodyInnerHtml(doc)); - processOobSwaps(container, function(t, oob, s) { return swapDomNodes(t, oob, s); }); - hoistHeadElements(container); - return withTransition(useTransition, function() { return swapDomNodes(target, childrenToFragment(container), swapStyle); }); -})()); -})() : NIL); -})(); }; - - // handle-retry - var handleRetry = function(el, verb, method, url, extraParams) { return (function() { - var retryAttr = domGetAttr(el, "sx-retry"); - var spec = parseRetrySpec(retryAttr); - return (isSxTruthy(spec) ? (function() { - var currentMs = sxOr(domGetAttr(el, "data-sx-retry-ms"), get(spec, "start-ms")); - return (function() { - var ms = parseInt_(currentMs, get(spec, "start-ms")); - domSetAttr(el, "data-sx-retry-ms", (String(nextRetryMs(ms, get(spec, "cap-ms"))))); - return setTimeout_(function() { return doFetch(el, verb, method, url, extraParams); }, ms); -})(); -})() : NIL); -})(); }; - - // bind-triggers - var bindTriggers = function(el, verbInfo) { return (function() { - var triggers = sxOr(parseTriggerSpec(domGetAttr(el, "sx-trigger")), defaultTrigger(domTagName(el))); - return forEach(function(trigger) { return (function() { - var kind = classifyTrigger(trigger); - var mods = get(trigger, "modifiers"); - return (isSxTruthy((kind == "poll")) ? setInterval_(function() { return executeRequest(el, NIL, NIL); }, get(mods, "interval")) : (isSxTruthy((kind == "intersect")) ? observeIntersection(el, function() { return executeRequest(el, NIL, NIL); }, false, get(mods, "delay")) : (isSxTruthy((kind == "load")) ? setTimeout_(function() { return executeRequest(el, NIL, NIL); }, sxOr(get(mods, "delay"), 0)) : (isSxTruthy((kind == "revealed")) ? observeIntersection(el, function() { return executeRequest(el, NIL, NIL); }, true, get(mods, "delay")) : (isSxTruthy((kind == "event")) ? bindEvent(el, get(trigger, "event"), mods, verbInfo) : NIL))))); -})(); }, triggers); -})(); }; - - // bind-event - var bindEvent = function(el, eventName, mods, verbInfo) { return (function() { - var timer = NIL; - var lastVal = NIL; - var listenTarget = (isSxTruthy(get(mods, "from")) ? domQuery(get(mods, "from")) : el); - return (isSxTruthy(listenTarget) ? domAddListener(listenTarget, eventName, function(e) { return (function() { - var shouldFire = true; - if (isSxTruthy(get(mods, "changed"))) { - (function() { - var val = elementValue(el); - return (isSxTruthy((val == lastVal)) ? (shouldFire = false) : (lastVal = val)); -})(); -} - return (isSxTruthy(shouldFire) ? ((isSxTruthy(sxOr((eventName == "submit"), (isSxTruthy((eventName == "click")) && domHasAttr(el, "href")))) ? preventDefault_(e) : NIL), (isSxTruthy(get(mods, "delay")) ? (clearTimeout_(timer), (timer = setTimeout_(function() { return executeRequest(el, verbInfo, NIL); }, get(mods, "delay")))) : executeRequest(el, verbInfo, NIL))) : NIL); -})(); }, (isSxTruthy(get(mods, "once")) ? {["once"]: true} : NIL)) : NIL); -})(); }; - - // post-swap - var postSwap = function(root) { return activateScripts(root); }; - - // activate-scripts - var activateScripts = function(root) { return (isSxTruthy(root) ? (function() { - var scripts = domQueryAll(root, "script"); - return forEach(function(dead) { return (isSxTruthy((isSxTruthy(!domHasAttr(dead, "data-components")) && !domHasAttr(dead, "data-sx-activated"))) ? (function() { - var live = createScriptClone(dead); - domSetAttr(live, "data-sx-activated", "true"); - return domReplaceChild(domParent(dead), live, dead); -})() : NIL); }, scripts); -})() : NIL); }; - - // process-oob-swaps - var processOobSwaps = function(container, swapFn) { return (function() { - var oobs = findOobSwaps(container); - return forEach(function(oob) { return (function() { - var targetId = get(oob, "target-id"); - var target = domQueryById(targetId); - var oobEl = get(oob, "element"); - var swapType = get(oob, "swap-type"); - if (isSxTruthy(domParent(oobEl))) { - domRemoveChild(domParent(oobEl), oobEl); -} - return (isSxTruthy(target) ? swapFn(target, oobEl, swapType) : NIL); -})(); }, oobs); -})(); }; - - // hoist-head-elements - var hoistHeadElements = function(container) { return forEach(function(style) { return (isSxTruthy(domParent(style)) ? domRemoveChild(domParent(style), style) : NIL); }, domQueryAll(container, "style[data-sx-css]")); }; - - // process-boosted - var processBoosted = function(root) { return forEach(function(container) { return boostDescendants(container); }, domQueryAll(sxOr(root, domBody()), "[sx-boost]")); }; - - // boost-descendants - var boostDescendants = function(container) { return forEach(function(link) { return (isSxTruthy((isSxTruthy(!isProcessed(link, "boost")) && shouldBoostLink(link))) ? (markProcessed(link, "boost"), (isSxTruthy(!domHasAttr(link, "sx-target")) ? domSetAttr(link, "sx-target", "#main-panel") : NIL), (isSxTruthy(!domHasAttr(link, "sx-swap")) ? domSetAttr(link, "sx-swap", "innerHTML") : NIL), (isSxTruthy(!domHasAttr(link, "sx-push-url")) ? domSetAttr(link, "sx-push-url", "true") : NIL), bindBoostLink(link, domGetAttr(link, "href"))) : NIL); }, domQueryAll(container, "a[href]")); }; - - // process-sse - var processSse = function(root) { return forEach(function(el) { return (isSxTruthy(!isProcessed(el, "sse")) ? (markProcessed(el, "sse"), bindSse(el)) : NIL); }, domQueryAll(sxOr(root, domBody()), "[sx-sse]")); }; - - // bind-sse - var bindSse = function(el) { return (function() { - var url = domGetAttr(el, "sx-sse"); - return (isSxTruthy(url) ? (function() { - var source = eventSourceConnect(url, el); - var eventName = parseSseSwap(el); - return eventSourceListen(source, eventName, function(data) { return bindSseSwap(el, data); }); -})() : NIL); -})(); }; - - // bind-sse-swap - var bindSseSwap = function(el, data) { return (function() { - var target = resolveTarget(el); - var swapSpec = parseSwapSpec(domGetAttr(el, "sx-swap"), domHasClass(domBody(), "sx-transitions")); - var swapStyle = get(swapSpec, "style"); - var useTransition = get(swapSpec, "transition"); - var trimmed = trim(data); - return (isSxTruthy(!isEmpty(trimmed)) ? (isSxTruthy(startsWith(trimmed, "(")) ? (function() { - var rendered = sxRender(trimmed); - var container = domCreateElement("div", NIL); - domAppend(container, rendered); - return withTransition(useTransition, function() { return swapDomNodes(target, childrenToFragment(container), swapStyle); }); -})() : withTransition(useTransition, function() { return swapHtmlString(target, trimmed, swapStyle); })) : NIL); -})(); }; - - // bind-inline-handlers - var bindInlineHandlers = function(root) { return forEach(function(el) { return forEach(function(attr) { return (function() { - var name = first(attr); - var body = nth(attr, 1); - return (isSxTruthy(startsWith(name, "sx-on:")) ? (function() { - var eventName = slice(name, 6); - return (isSxTruthy(!isProcessed(el, (String("on:") + String(eventName)))) ? (markProcessed(el, (String("on:") + String(eventName))), bindInlineHandler(el, eventName, body)) : NIL); -})() : NIL); -})(); }, domAttrList(el)); }, domQueryAll(sxOr(root, domBody()), "[sx-on\\:beforeRequest],[sx-on\\:afterRequest],[sx-on\\:afterSwap],[sx-on\\:afterSettle],[sx-on\\:load]")); }; - - // bind-preload-for - var bindPreloadFor = function(el) { return (function() { - var preloadAttr = domGetAttr(el, "sx-preload"); - return (isSxTruthy(preloadAttr) ? (function() { - var info = getVerbInfo(el); - return (isSxTruthy(info) ? (function() { - var url = get(info, "url"); - var headers = buildRequestHeaders(el, loadedComponentNames(), _cssHash); - var events = (isSxTruthy((preloadAttr == "mousedown")) ? ["mousedown", "touchstart"] : ["mouseover"]); - var debounceMs = (isSxTruthy((preloadAttr == "mousedown")) ? 0 : 100); - return bindPreload(el, events, debounceMs, function() { return doPreload(url, headers); }); -})() : NIL); -})() : NIL); -})(); }; - - // do-preload - var doPreload = function(url, headers) { return (isSxTruthy(isNil(preloadCacheGet(_preloadCache, url))) ? fetchPreload(url, headers, _preloadCache) : NIL); }; - - // VERB_SELECTOR - var VERB_SELECTOR = (String("[sx-get],[sx-post],[sx-put],[sx-delete],[sx-patch]")); - - // process-elements - var processElements = function(root) { return (function() { - var els = domQueryAll(sxOr(root, domBody()), VERB_SELECTOR); - return forEach(function(el) { return (isSxTruthy(!isProcessed(el, "verb")) ? (markProcessed(el, "verb"), processOne(el)) : NIL); }, els); -})(); }; - - // process-one - var processOne = function(el) { return (function() { - var verbInfo = getVerbInfo(el); - return (isSxTruthy(verbInfo) ? (isSxTruthy(!domHasAttr(el, "sx-disable")) ? (bindTriggers(el, verbInfo), bindPreloadFor(el)) : NIL) : NIL); -})(); }; - - // handle-popstate - var handlePopstate = function(scrollY) { return (function() { - var main = domQueryById("main-panel"); - var url = browserLocationHref(); - return (isSxTruthy(main) ? (function() { - var headers = buildRequestHeaders(main, loadedComponentNames(), _cssHash); - return fetchAndRestore(main, url, headers, scrollY); -})() : NIL); -})(); }; - - // engine-init - var engineInit = function() { return (initCssTracking(), sxProcessScripts(NIL), sxHydrate(NIL), processElements(NIL)); }; - - - // === Transpiled from cssx === - - // _style-atoms - var _styleAtoms = {}; - - // _pseudo-variants - var _pseudoVariants = {}; - - // _responsive-breakpoints - var _responsiveBreakpoints = {}; - - // _style-keyframes - var _styleKeyframes = {}; - - // _arbitrary-patterns - var _arbitraryPatterns = []; - - // _child-selector-prefixes - var _childSelectorPrefixes = []; - - // _style-cache - var _styleCache = {}; - - // _injected-styles - var _injectedStyles = {}; - - // load-style-dict - var loadStyleDict = function(data) { return (_styleAtoms = sxOr(get(data, "a"), {})); }; - - // split-variant - var splitVariant = function(atom) { return (function() { - var result = NIL; - { var _c = keys(_responsiveBreakpoints); for (var _i = 0; _i < _c.length; _i++) { var bp = _c[_i]; if (isSxTruthy(isNil(result))) { - (function() { - var prefix = (String(bp) + String(":")); - return (isSxTruthy(startsWith(atom, prefix)) ? (function() { - var restAtom = slice(atom, len(prefix)); - return (function() { - var innerMatch = NIL; - { var _c = keys(_pseudoVariants); for (var _i = 0; _i < _c.length; _i++) { var pv = _c[_i]; if (isSxTruthy(isNil(innerMatch))) { - (function() { - var innerPrefix = (String(pv) + String(":")); - return (isSxTruthy(startsWith(restAtom, innerPrefix)) ? (innerMatch = [(String(bp) + String(":") + String(pv)), slice(restAtom, len(innerPrefix))]) : NIL); -})(); -} } } - return (result = sxOr(innerMatch, [bp, restAtom])); -})(); -})() : NIL); -})(); -} } } - if (isSxTruthy(isNil(result))) { - { var _c = keys(_pseudoVariants); for (var _i = 0; _i < _c.length; _i++) { var pv = _c[_i]; if (isSxTruthy(isNil(result))) { - (function() { - var prefix = (String(pv) + String(":")); - return (isSxTruthy(startsWith(atom, prefix)) ? (result = [pv, slice(atom, len(prefix))]) : NIL); -})(); -} } } -} - return sxOr(result, [NIL, atom]); -})(); }; - - // resolve-atom - var resolveAtom = function(atom) { return (function() { - var decls = dictGet(_styleAtoms, atom); - return (isSxTruthy(!isNil(decls)) ? decls : (isSxTruthy(startsWith(atom, "animate-")) ? (function() { - var kfName = slice(atom, 8); - return (isSxTruthy(dictHas(_styleKeyframes, kfName)) ? (String("animation-name:") + String(kfName)) : NIL); -})() : (function() { - var matchResult = NIL; - { var _c = _arbitraryPatterns; for (var _i = 0; _i < _c.length; _i++) { var pat = _c[_i]; if (isSxTruthy(isNil(matchResult))) { - (function() { - var m = regexMatch(get(pat, "re"), atom); - return (isSxTruthy(m) ? (matchResult = regexReplaceGroups(get(pat, "tmpl"), m)) : NIL); -})(); -} } } - return matchResult; -})())); -})(); }; - - // is-child-selector-atom? - var isChildSelectorAtom = function(atom) { return some(function(prefix) { return startsWith(atom, prefix); }, _childSelectorPrefixes); }; - - // hash-style - var hashStyle = function(input) { return fnv1aHash(input); }; - - // resolve-style - var resolveStyle = function(atoms) { return (function() { - var key = join("\\0", atoms); - return (function() { - var cached = dictGet(_styleCache, key); - return (isSxTruthy(!isNil(cached)) ? cached : (function() { - var baseDecls = []; - var mediaRules = []; - var pseudoRules = []; - var kfNeeded = []; - { var _c = atoms; for (var _i = 0; _i < _c.length; _i++) { var a = _c[_i]; if (isSxTruthy(a)) { - (function() { - var clean = (isSxTruthy(startsWith(a, ":")) ? slice(a, 1) : a); - return (function() { - var parts = splitVariant(clean); - return (function() { - var variant = first(parts); - var base = nth(parts, 1); - var decls = resolveAtom(base); - return (isSxTruthy(decls) ? ((isSxTruthy(startsWith(base, "animate-")) ? (function() { - var kfName = slice(base, 8); - return (isSxTruthy(dictHas(_styleKeyframes, kfName)) ? append_b(kfNeeded, [kfName, dictGet(_styleKeyframes, kfName)]) : NIL); -})() : NIL), (isSxTruthy(isNil(variant)) ? append_b(baseDecls, decls) : (isSxTruthy(dictHas(_responsiveBreakpoints, variant)) ? append_b(mediaRules, [dictGet(_responsiveBreakpoints, variant), decls]) : (isSxTruthy(dictHas(_pseudoVariants, variant)) ? append_b(pseudoRules, [dictGet(_pseudoVariants, variant), decls]) : (function() { - var vparts = split(variant, ":"); - var mediaPart = NIL; - var pseudoPart = NIL; - { var _c = vparts; for (var _i = 0; _i < _c.length; _i++) { var vp = _c[_i]; (isSxTruthy(dictHas(_responsiveBreakpoints, vp)) ? (mediaPart = dictGet(_responsiveBreakpoints, vp)) : (isSxTruthy(dictHas(_pseudoVariants, vp)) ? (pseudoPart = dictGet(_pseudoVariants, vp)) : NIL)); } } - if (isSxTruthy(mediaPart)) { - mediaRules.push([mediaPart, decls]); -} - if (isSxTruthy(pseudoPart)) { - pseudoRules.push([pseudoPart, decls]); -} - return (isSxTruthy((isSxTruthy(isNil(mediaPart)) && isNil(pseudoPart))) ? append_b(baseDecls, decls) : NIL); -})())))) : NIL); -})(); -})(); -})(); -} } } - return (function() { - var hashInput = join(";", baseDecls); - { var _c = chunkEvery(mediaRules, 2); for (var _i = 0; _i < _c.length; _i++) { var mr = _c[_i]; hashInput = (String(hashInput) + String("@") + String(first(mr)) + String("{") + String(nth(mr, 1)) + String("}")); } } - { var _c = chunkEvery(pseudoRules, 2); for (var _i = 0; _i < _c.length; _i++) { var pr = _c[_i]; hashInput = (String(hashInput) + String(first(pr)) + String("{") + String(nth(pr, 1)) + String("}")); } } - { var _c = chunkEvery(kfNeeded, 2); for (var _i = 0; _i < _c.length; _i++) { var kf = _c[_i]; hashInput = (String(hashInput) + String(nth(kf, 1))); } } - return (function() { - var cn = (String("sx-") + String(hashStyle(hashInput))); - var sv = makeStyleValue_(cn, join(";", baseDecls), chunkEvery(mediaRules, 2), chunkEvery(pseudoRules, 2), chunkEvery(kfNeeded, 2)); - _styleCache[key] = sv; - injectStyleValue(sv, atoms); - return sv; -})(); -})(); -})()); -})(); -})(); }; - - // merge-style-values - var mergeStyleValues = function(styles) { return (isSxTruthy((len(styles) == 1)) ? first(styles) : (function() { - var allDecls = []; - var allMedia = []; - var allPseudo = []; - var allKf = []; - { var _c = styles; for (var _i = 0; _i < _c.length; _i++) { var sv = _c[_i]; if (isSxTruthy(styleValueDeclarations(sv))) { - allDecls.push(styleValueDeclarations(sv)); -} } } - return (function() { - var hashInput = join(";", allDecls); - { var _c = allMedia; for (var _i = 0; _i < _c.length; _i++) { var mr = _c[_i]; hashInput = (String(hashInput) + String("@") + String(first(mr)) + String("{") + String(nth(mr, 1)) + String("}")); } } - { var _c = allPseudo; for (var _i = 0; _i < _c.length; _i++) { var pr = _c[_i]; hashInput = (String(hashInput) + String(first(pr)) + String("{") + String(nth(pr, 1)) + String("}")); } } - { var _c = allKf; for (var _i = 0; _i < _c.length; _i++) { var kf = _c[_i]; hashInput = (String(hashInput) + String(nth(kf, 1))); } } - return (function() { - var cn = (String("sx-") + String(hashStyle(hashInput))); - var merged = makeStyleValue_(cn, join(";", allDecls), allMedia, allPseudo, allKf); - injectStyleValue(merged, []); - return merged; -})(); -})(); -})()); }; - - - // === Transpiled from boot === - - // HEAD_HOIST_SELECTOR - var HEAD_HOIST_SELECTOR = "meta, title, link[rel='canonical'], script[type='application/ld+json']"; - - // hoist-head-elements-full - var hoistHeadElementsFull = function(root) { return (function() { - var els = domQueryAll(root, HEAD_HOIST_SELECTOR); - return forEach(function(el) { return (function() { - var tag = lower(domTagName(el)); - return (isSxTruthy((tag == "title")) ? (setDocumentTitle(domTextContent(el)), domRemoveChild(domParent(el), el)) : (isSxTruthy((tag == "meta")) ? ((function() { - var name = domGetAttr(el, "name"); - var prop = domGetAttr(el, "property"); - if (isSxTruthy(name)) { - removeHeadElement((String("meta[name=\"") + String(name) + String("\"]"))); -} - return (isSxTruthy(prop) ? removeHeadElement((String("meta[property=\"") + String(prop) + String("\"]"))) : NIL); -})(), domRemoveChild(domParent(el), el), domAppendToHead(el)) : (isSxTruthy((isSxTruthy((tag == "link")) && (domGetAttr(el, "rel") == "canonical"))) ? (removeHeadElement("link[rel=\"canonical\"]"), domRemoveChild(domParent(el), el), domAppendToHead(el)) : (domRemoveChild(domParent(el), el), domAppendToHead(el))))); -})(); }, els); -})(); }; - - // sx-mount - var sxMount = function(target, source, extraEnv) { return (function() { - var el = resolveMountTarget(target); - return (isSxTruthy(el) ? (function() { - var node = sxRenderWithEnv(source, extraEnv); - domSetTextContent(el, ""); - domAppend(el, node); - hoistHeadElementsFull(el); - processElements(el); - return sxHydrateElements(el); -})() : NIL); -})(); }; - - // sx-hydrate-elements - var sxHydrateElements = function(root) { return (function() { - var els = domQueryAll(sxOr(root, domBody()), "[data-sx]"); - return forEach(function(el) { return (isSxTruthy(!isProcessed(el, "hydrated")) ? (markProcessed(el, "hydrated"), sxUpdateElement(el, NIL)) : NIL); }, els); -})(); }; - - // sx-update-element - var sxUpdateElement = function(el, newEnv) { return (function() { - var target = resolveMountTarget(el); - return (isSxTruthy(target) ? (function() { - var source = domGetAttr(target, "data-sx"); - return (isSxTruthy(source) ? (function() { - var baseEnv = parseEnvAttr(target); - var env = mergeEnvs(baseEnv, newEnv); - return (function() { - var node = sxRenderWithEnv(source, env); - domSetTextContent(target, ""); - domAppend(target, node); - return (isSxTruthy(newEnv) ? storeEnvAttr(target, baseEnv, newEnv) : NIL); -})(); -})() : NIL); -})() : NIL); -})(); }; - - // sx-render-component - var sxRenderComponent = function(name, kwargs, extraEnv) { return (function() { - var fullName = (isSxTruthy(startsWith(name, "~")) ? name : (String("~") + String(name))); - return (function() { - var env = getRenderEnv(extraEnv); - var comp = envGet(env, fullName); - return (isSxTruthy(!isComponent(comp)) ? error((String("Unknown component: ") + String(fullName))) : (function() { - var callExpr = [makeSymbol(fullName)]; - { var _c = keys(kwargs); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; callExpr.push(makeKeyword(toKebab(k))); } } - return renderToDom(callExpr, env, NIL); -})()); -})(); -})(); }; - - // process-sx-scripts - var processSxScripts = function(root) { return (function() { - var scripts = querySxScripts(root); - return forEach(function(s) { return (isSxTruthy(!isProcessed(s, "script")) ? (markProcessed(s, "script"), (function() { - var text = domTextContent(s); - return (isSxTruthy(domHasAttr(s, "data-components")) ? processComponentScript(s, text) : (isSxTruthy(sxOr(isNil(text), isEmpty(trim(text)))) ? NIL : (isSxTruthy(domHasAttr(s, "data-mount")) ? (function() { - var mountSel = domGetAttr(s, "data-mount"); - var target = domQuery(mountSel); - return (isSxTruthy(target) ? sxMount(target, text, NIL) : NIL); -})() : sxLoadComponents(text)))); -})()) : NIL); }, scripts); -})(); }; - - // process-component-script - var processComponentScript = function(script, text) { return (function() { - var hash = domGetAttr(script, "data-hash"); - return (isSxTruthy(isNil(hash)) ? (isSxTruthy((isSxTruthy(text) && !isEmpty(trim(text)))) ? sxLoadComponents(text) : NIL) : (function() { - var hasInline = (isSxTruthy(text) && !isEmpty(trim(text))); - (function() { - var cachedHash = localStorageGet("sx-components-hash"); - return (isSxTruthy((cachedHash == hash)) ? (isSxTruthy(hasInline) ? (localStorageSet("sx-components-hash", hash), localStorageSet("sx-components-src", text), sxLoadComponents(text), logInfo("components: downloaded (cookie stale)")) : (function() { - var cached = localStorageGet("sx-components-src"); - return (isSxTruthy(cached) ? (sxLoadComponents(cached), logInfo((String("components: cached (") + String(hash) + String(")")))) : (clearSxCompCookie(), browserReload())); -})()) : (isSxTruthy(hasInline) ? (localStorageSet("sx-components-hash", hash), localStorageSet("sx-components-src", text), sxLoadComponents(text), logInfo((String("components: downloaded (") + String(hash) + String(")")))) : (localStorageRemove("sx-components-hash"), localStorageRemove("sx-components-src"), clearSxCompCookie(), browserReload()))); -})(); - return setSxCompCookie(hash); -})()); -})(); }; - - // init-style-dict - var initStyleDict = function() { return (function() { - var scripts = queryStyleScripts(); - return forEach(function(s) { return (isSxTruthy(!isProcessed(s, "styles")) ? (markProcessed(s, "styles"), (function() { - var text = domTextContent(s); - var hash = domGetAttr(s, "data-hash"); - return (isSxTruthy(isNil(hash)) ? (isSxTruthy((isSxTruthy(text) && !isEmpty(trim(text)))) ? parseAndLoadStyleDict(text) : NIL) : (function() { - var hasInline = (isSxTruthy(text) && !isEmpty(trim(text))); - (function() { - var cachedHash = localStorageGet("sx-styles-hash"); - return (isSxTruthy((cachedHash == hash)) ? (isSxTruthy(hasInline) ? (localStorageSet("sx-styles-src", text), parseAndLoadStyleDict(text), logInfo("styles: downloaded (cookie stale)")) : (function() { - var cached = localStorageGet("sx-styles-src"); - return (isSxTruthy(cached) ? (parseAndLoadStyleDict(cached), logInfo((String("styles: cached (") + String(hash) + String(")")))) : (clearSxStylesCookie(), browserReload())); -})()) : (isSxTruthy(hasInline) ? (localStorageSet("sx-styles-hash", hash), localStorageSet("sx-styles-src", text), parseAndLoadStyleDict(text), logInfo((String("styles: downloaded (") + String(hash) + String(")")))) : (localStorageRemove("sx-styles-hash"), localStorageRemove("sx-styles-src"), clearSxStylesCookie(), browserReload()))); -})(); - return setSxStylesCookie(hash); -})()); -})()) : NIL); }, scripts); -})(); }; - - // boot-init - var bootInit = function() { return (initCssTracking(), initStyleDict(), processSxScripts(NIL), sxHydrateElements(NIL), processElements(NIL)); }; - - // ========================================================================= // Platform interface — DOM adapter (browser-only) // ========================================================================= var _hasDom = typeof document !== "undefined"; + // Register DOM adapter as the render dispatch target for the evaluator. + _renderExprFn = function(expr, env) { return renderToDom(expr, env, null); }; + var SVG_NS = "http://www.w3.org/2000/svg"; var MATH_NS = "http://www.w3.org/1998/Math/MathML"; @@ -2157,850 +1200,6 @@ function domTagName(el) { return el && el.tagName ? el.tagName : ""; } - // ========================================================================= - // Platform interface — Engine pure logic (browser + node compatible) - // ========================================================================= - - function browserLocationHref() { - return typeof location !== "undefined" ? location.href : ""; - } - - function browserSameOrigin(url) { - try { return new URL(url, location.href).origin === location.origin; } - catch (e) { return true; } - } - - function browserPushState(url) { - if (typeof history !== "undefined") { - try { history.pushState({ sxUrl: url, scrollY: typeof window !== "undefined" ? window.scrollY : 0 }, "", url); } - catch (e) {} - } - } - - function browserReplaceState(url) { - if (typeof history !== "undefined") { - try { history.replaceState({ sxUrl: url, scrollY: typeof window !== "undefined" ? window.scrollY : 0 }, "", url); } - catch (e) {} - } - } - - function nowMs() { return Date.now(); } - - function parseHeaderValue(s) { - if (!s) return null; - try { - if (s.charAt(0) === "{" && s.charAt(1) === ":") return parse(s); - return JSON.parse(s); - } catch (e) { return null; } - } - - - // ========================================================================= - // Platform interface — Orchestration (browser-only) - // ========================================================================= - - // --- Browser/Network --- - - function browserNavigate(url) { - if (typeof location !== "undefined") location.assign(url); - } - - function browserReload() { - if (typeof location !== "undefined") location.reload(); - } - - function browserScrollTo(x, y) { - if (typeof window !== "undefined") window.scrollTo(x, y); - } - - function browserMediaMatches(query) { - if (typeof window === "undefined") return false; - return window.matchMedia(query).matches; - } - - function browserConfirm(msg) { - if (typeof window === "undefined") return false; - return window.confirm(msg); - } - - function browserPrompt(msg) { - if (typeof window === "undefined") return NIL; - var r = window.prompt(msg); - return r === null ? NIL : r; - } - - function csrfToken() { - if (!_hasDom) return NIL; - var m = document.querySelector('meta[name="csrf-token"]'); - return m ? m.getAttribute("content") : NIL; - } - - function isCrossOrigin(url) { - try { - var h = new URL(url, location.href).hostname; - return h !== location.hostname && - (h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0); - } catch (e) { return false; } - } - - // --- Promises --- - - function promiseResolve(val) { return Promise.resolve(val); } - - function promiseCatch(p, fn) { return p && p.catch ? p.catch(fn) : p; } - - // --- Abort controllers --- - - var _controllers = typeof WeakMap !== "undefined" ? new WeakMap() : null; - - function abortPrevious(el) { - if (_controllers) { - var prev = _controllers.get(el); - if (prev) prev.abort(); - } - } - - function trackController(el, ctrl) { - if (_controllers) _controllers.set(el, ctrl); - } - - function newAbortController() { - return typeof AbortController !== "undefined" ? new AbortController() : { signal: null, abort: function() {} }; - } - - function controllerSignal(ctrl) { return ctrl ? ctrl.signal : null; } - - function isAbortError(err) { return err && err.name === "AbortError"; } - - // --- Timers --- - - function setTimeout_(fn, ms) { return setTimeout(fn, ms || 0); } - function setInterval_(fn, ms) { return setInterval(fn, ms || 1000); } - function clearTimeout_(id) { clearTimeout(id); } - function requestAnimationFrame_(fn) { - if (typeof requestAnimationFrame !== "undefined") requestAnimationFrame(fn); - else setTimeout(fn, 16); - } - - // --- Fetch --- - - function fetchRequest(config, successFn, errorFn) { - var opts = { method: config.method, headers: config.headers }; - if (config.signal) opts.signal = config.signal; - if (config.body && config.method !== "GET") opts.body = config.body; - if (config["cross-origin"]) opts.credentials = "include"; - - var p = config.preloaded - ? Promise.resolve({ - ok: true, status: 200, - headers: new Headers({ "Content-Type": config.preloaded["content-type"] || "" }), - text: function() { return Promise.resolve(config.preloaded.text); } - }) - : fetch(config.url, opts); - - return p.then(function(resp) { - return resp.text().then(function(text) { - var getHeader = function(name) { - var v = resp.headers.get(name); - return v === null ? NIL : v; - }; - return successFn(resp.ok, resp.status, getHeader, text); - }); - }).catch(function(err) { - return errorFn(err); - }); - } - - function fetchLocation(headerVal) { - if (!_hasDom) return; - var locUrl = headerVal; - try { var obj = JSON.parse(headerVal); locUrl = obj.path || obj; } catch (e) {} - fetch(locUrl, { headers: { "SX-Request": "true" } }).then(function(r) { - return r.text().then(function(t) { - var main = document.getElementById("main-panel"); - if (main) { - main.innerHTML = t; - postSwap(main); - try { history.pushState({ sxUrl: locUrl }, "", locUrl); } catch (e) {} - } - }); - }); - } - - function fetchAndRestore(main, url, headers, scrollY) { - var opts = { headers: headers }; - try { - var h = new URL(url, location.href).hostname; - if (h !== location.hostname && - (h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0)) { - opts.credentials = "include"; - } - } catch (e) {} - - fetch(url, opts).then(function(resp) { - return resp.text().then(function(text) { - text = stripComponentScripts(text); - text = extractResponseCss(text); - text = text.trim(); - if (text.charAt(0) === "(") { - try { - var dom = sxRender(text); - var container = document.createElement("div"); - container.appendChild(dom); - processOobSwaps(container, function(t, oob, s) { - swapDomNodes(t, oob, s); - sxHydrate(t); - processElements(t); - }); - var newMain = container.querySelector("#main-panel"); - morphChildren(main, newMain || container); - postSwap(main); - if (typeof window !== "undefined") window.scrollTo(0, scrollY || 0); - } catch (err) { - console.error("sx-ref popstate error:", err); - location.reload(); - } - } else { - var parser = new DOMParser(); - var doc = parser.parseFromString(text, "text/html"); - var newMain = doc.getElementById("main-panel"); - if (newMain) { - morphChildren(main, newMain); - postSwap(main); - if (typeof window !== "undefined") window.scrollTo(0, scrollY || 0); - } else { - location.reload(); - } - } - }); - }).catch(function() { location.reload(); }); - } - - function fetchPreload(url, headers, cache) { - fetch(url, { headers: headers }).then(function(resp) { - if (!resp.ok) return; - var ct = resp.headers.get("Content-Type") || ""; - return resp.text().then(function(text) { - preloadCacheSet(cache, url, text, ct); - }); - }).catch(function() { /* ignore */ }); - } - - // --- Request body building --- - - function buildRequestBody(el, method, url) { - if (!_hasDom) return { body: null, url: url, "content-type": NIL }; - var body = null; - var ct = NIL; - var finalUrl = url; - var isJson = el.getAttribute("sx-encoding") === "json"; - - if (method !== "GET") { - var form = el.closest("form") || (el.tagName === "FORM" ? el : null); - if (form) { - if (isJson) { - var fd = new FormData(form); - var obj = {}; - fd.forEach(function(v, k) { - if (obj[k] !== undefined) { - if (!Array.isArray(obj[k])) obj[k] = [obj[k]]; - obj[k].push(v); - } else { obj[k] = v; } - }); - body = JSON.stringify(obj); - ct = "application/json"; - } else { - body = new URLSearchParams(new FormData(form)); - ct = "application/x-www-form-urlencoded"; - } - } - } - - // sx-params - var paramsSpec = el.getAttribute("sx-params"); - if (paramsSpec && body instanceof URLSearchParams) { - if (paramsSpec === "none") { - body = new URLSearchParams(); - } else if (paramsSpec.indexOf("not ") === 0) { - paramsSpec.substring(4).split(",").forEach(function(k) { body.delete(k.trim()); }); - } else if (paramsSpec !== "*") { - var allowed = paramsSpec.split(",").map(function(s) { return s.trim(); }); - var filtered = new URLSearchParams(); - allowed.forEach(function(k) { - body.getAll(k).forEach(function(v) { filtered.append(k, v); }); - }); - body = filtered; - } - } - - // sx-include - var includeSel = el.getAttribute("sx-include"); - if (includeSel && method !== "GET") { - if (!body) body = new URLSearchParams(); - document.querySelectorAll(includeSel).forEach(function(inp) { - if (inp.name) body.append(inp.name, inp.value); - }); - } - - // sx-vals - var valsAttr = el.getAttribute("sx-vals"); - if (valsAttr) { - try { - var vals = valsAttr.charAt(0) === "{" && valsAttr.charAt(1) === ":" ? parse(valsAttr) : JSON.parse(valsAttr); - if (method === "GET") { - for (var vk in vals) finalUrl += (finalUrl.indexOf("?") >= 0 ? "&" : "?") + encodeURIComponent(vk) + "=" + encodeURIComponent(vals[vk]); - } else if (body instanceof URLSearchParams) { - for (var vk2 in vals) body.append(vk2, vals[vk2]); - } else if (!body) { - body = new URLSearchParams(); - for (var vk3 in vals) body.append(vk3, vals[vk3]); - ct = "application/x-www-form-urlencoded"; - } - } catch (e) {} - } - - // GET form data → URL - if (method === "GET") { - var form2 = el.closest("form") || (el.tagName === "FORM" ? el : null); - if (form2) { - var qs = new URLSearchParams(new FormData(form2)).toString(); - if (qs) finalUrl += (finalUrl.indexOf("?") >= 0 ? "&" : "?") + qs; - } - if ((el.tagName === "INPUT" || el.tagName === "SELECT" || el.tagName === "TEXTAREA") && el.name) { - finalUrl += (finalUrl.indexOf("?") >= 0 ? "&" : "?") + encodeURIComponent(el.name) + "=" + encodeURIComponent(el.value); - } - } - - return { body: body, url: finalUrl, "content-type": ct }; - } - - // --- Loading state --- - - function showIndicator(el) { - if (!_hasDom) return NIL; - var sel = el.getAttribute("sx-indicator"); - var ind = sel ? (document.querySelector(sel) || el.closest(sel)) : null; - if (ind) { ind.classList.add("sx-request"); ind.style.display = ""; } - return ind || NIL; - } - - function disableElements(el) { - if (!_hasDom) return []; - var sel = el.getAttribute("sx-disabled-elt"); - if (!sel) return []; - var elts = Array.prototype.slice.call(document.querySelectorAll(sel)); - elts.forEach(function(e) { e.disabled = true; }); - return elts; - } - - function clearLoadingState(el, indicator, disabledElts) { - el.classList.remove("sx-request"); - el.removeAttribute("aria-busy"); - if (indicator && !isNil(indicator)) { - indicator.classList.remove("sx-request"); - indicator.style.display = "none"; - } - if (disabledElts) { - for (var i = 0; i < disabledElts.length; i++) disabledElts[i].disabled = false; - } - } - - // --- DOM extras --- - - function domQueryById(id) { - return _hasDom ? document.getElementById(id) : null; - } - - function domMatches(el, sel) { - return el && el.matches ? el.matches(sel) : false; - } - - function domClosest(el, sel) { - return el && el.closest ? el.closest(sel) : null; - } - - function domBody() { - return _hasDom ? document.body : null; - } - - function domHasClass(el, cls) { - return el && el.classList ? el.classList.contains(cls) : false; - } - - function domAppendToHead(el) { - if (_hasDom && document.head) document.head.appendChild(el); - } - - function domParseHtmlDocument(text) { - if (!_hasDom) return null; - return new DOMParser().parseFromString(text, "text/html"); - } - - function domOuterHtml(el) { - return el ? el.outerHTML : ""; - } - - function domBodyInnerHtml(doc) { - return doc && doc.body ? doc.body.innerHTML : ""; - } - - // --- Events --- - - function preventDefault_(e) { if (e && e.preventDefault) e.preventDefault(); } - function elementValue(el) { return el && el.value !== undefined ? el.value : NIL; } - - function domAddListener(el, event, fn, opts) { - if (!el || !el.addEventListener) return; - var o = {}; - if (opts && !isNil(opts)) { - if (opts.once || opts["once"]) o.once = true; - } - el.addEventListener(event, fn, o); - } - - // --- Validation --- - - function validateForRequest(el) { - if (!_hasDom) return true; - var attr = el.getAttribute("sx-validate"); - if (attr === null) { - var vForm = el.closest("[sx-validate]"); - if (vForm) attr = vForm.getAttribute("sx-validate"); - } - if (attr === null) return true; // no validation configured - var form = el.tagName === "FORM" ? el : el.closest("form"); - if (form && !form.reportValidity()) return false; - if (attr && attr !== "true" && attr !== "") { - var fn = window[attr]; - if (typeof fn === "function" && !fn(el)) return false; - } - return true; - } - - // --- View Transitions --- - - function withTransition(enabled, fn) { - if (enabled && _hasDom && document.startViewTransition) { - document.startViewTransition(fn); - } else { - fn(); - } - } - - // --- IntersectionObserver --- - - function observeIntersection(el, fn, once, delay) { - if (!_hasDom || !("IntersectionObserver" in window)) { fn(); return; } - var fired = false; - var d = isNil(delay) ? 0 : delay; - var obs = new IntersectionObserver(function(entries) { - entries.forEach(function(entry) { - if (!entry.isIntersecting) return; - if (once && fired) return; - fired = true; - if (once) obs.unobserve(el); - if (d) setTimeout(fn, d); else fn(); - }); - }); - obs.observe(el); - } - - // --- EventSource --- - - function eventSourceConnect(url, el) { - var source = new EventSource(url); - source.addEventListener("error", function() { domDispatch(el, "sx:sseError", {}); }); - source.addEventListener("open", function() { domDispatch(el, "sx:sseOpen", {}); }); - if (typeof MutationObserver !== "undefined") { - var obs = new MutationObserver(function() { - if (!document.body.contains(el)) { source.close(); obs.disconnect(); } - }); - obs.observe(document.body, { childList: true, subtree: true }); - } - return source; - } - - function eventSourceListen(source, event, fn) { - source.addEventListener(event, function(e) { fn(e.data); }); - } - - // --- Boost bindings --- - - function bindBoostLink(el, href) { - el.addEventListener("click", function(e) { - e.preventDefault(); - executeRequest(el, { method: "GET", url: href }).then(function() { - try { history.pushState({ sxUrl: href, scrollY: window.scrollY }, "", href); } catch (err) {} - }); - }); - } - - function bindBoostForm(form, method, action) { - form.addEventListener("submit", function(e) { - e.preventDefault(); - executeRequest(form, { method: method, url: action }).then(function() { - try { history.pushState({ sxUrl: action, scrollY: window.scrollY }, "", action); } catch (err) {} - }); - }); - } - - // --- Inline handlers --- - - function bindInlineHandler(el, eventName, body) { - el.addEventListener(eventName, new Function("event", body)); - } - - // --- Preload binding --- - - function bindPreload(el, events, debounceMs, fn) { - var timer = null; - events.forEach(function(evt) { - el.addEventListener(evt, function() { - if (debounceMs) { - clearTimeout(timer); - timer = setTimeout(fn, debounceMs); - } else { - fn(); - } - }); - }); - } - - // --- Processing markers --- - - var PROCESSED = "_sxBound"; - - function markProcessed(el, key) { el[PROCESSED + key] = true; } - function isProcessed(el, key) { return !!el[PROCESSED + key]; } - - // --- Script cloning --- - - function createScriptClone(dead) { - var live = document.createElement("script"); - for (var i = 0; i < dead.attributes.length; i++) - live.setAttribute(dead.attributes[i].name, dead.attributes[i].value); - live.textContent = dead.textContent; - return live; - } - - // --- SX API references --- - - function sxRender(source) { - var SxObj = typeof Sx !== "undefined" ? Sx : (typeof SxRef !== "undefined" ? SxRef : null); - if (SxObj && SxObj.render) return SxObj.render(source); - throw new Error("No SX renderer available"); - } - - function sxProcessScripts(root) { - var SxObj = typeof Sx !== "undefined" ? Sx : (typeof SxRef !== "undefined" ? SxRef : null); - if (SxObj && SxObj.processScripts) SxObj.processScripts(root || undefined); - } - - function sxHydrate(root) { - var SxObj = typeof Sx !== "undefined" ? Sx : (typeof SxRef !== "undefined" ? SxRef : null); - if (SxObj && SxObj.hydrate) SxObj.hydrate(root || undefined); - } - - function loadedComponentNames() { - var SxObj = typeof Sx !== "undefined" ? Sx : (typeof SxRef !== "undefined" ? SxRef : null); - if (!SxObj) return []; - var env = SxObj.componentEnv || (SxObj.getEnv ? SxObj.getEnv() : {}); - return Object.keys(env).filter(function(k) { return k.charAt(0) === "~"; }); - } - - // --- Response processing --- - - function stripComponentScripts(text) { - var SxObj = typeof Sx !== "undefined" ? Sx : (typeof SxRef !== "undefined" ? SxRef : null); - return text.replace(/]*type="text\/sx"[^>]*data-components[^>]*>([\s\S]*?)<\/script>/gi, - function(_, defs) { if (SxObj && SxObj.loadComponents) SxObj.loadComponents(defs); return ""; }); - } - - function extractResponseCss(text) { - if (!_hasDom) return text; - var target = document.getElementById("sx-css"); - if (!target) return text; - return text.replace(/]*data-sx-css[^>]*>([\s\S]*?)<\/style>/gi, - function(_, css) { target.textContent += css; return ""; }); - } - - function selectFromContainer(container, sel) { - var frag = document.createDocumentFragment(); - sel.split(",").forEach(function(s) { - container.querySelectorAll(s.trim()).forEach(function(m) { frag.appendChild(m); }); - }); - return frag; - } - - function childrenToFragment(container) { - var frag = document.createDocumentFragment(); - while (container.firstChild) frag.appendChild(container.firstChild); - return frag; - } - - function selectHtmlFromDoc(doc, sel) { - var parts = sel.split(",").map(function(s) { return s.trim(); }); - var frags = []; - parts.forEach(function(s) { - doc.querySelectorAll(s).forEach(function(m) { frags.push(m.outerHTML); }); - }); - return frags.join(""); - } - - // --- Parsing --- - - function tryParseJson(s) { - if (!s) return NIL; - try { return JSON.parse(s); } catch (e) { return NIL; } - } - - - // ========================================================================= - // Platform interface — CSSX (style dictionary) - // ========================================================================= - - function fnv1aHash(input) { - var h = 0x811c9dc5; - for (var i = 0; i < input.length; i++) { - h ^= input.charCodeAt(i); - h = (h * 0x01000193) >>> 0; - } - return h.toString(16).padStart(8, "0").substring(0, 6); - } - - function compileRegex(pattern) { - try { return new RegExp(pattern); } catch (e) { return null; } - } - - function regexMatch(re, s) { - if (!re) return NIL; - var m = s.match(re); - return m ? Array.prototype.slice.call(m) : NIL; - } - - function regexReplaceGroups(tmpl, match) { - var result = tmpl; - for (var j = 1; j < match.length; j++) { - result = result.split("{" + (j - 1) + "}").join(match[j]); - } - return result; - } - - function makeStyleValue_(cn, decls, media, pseudo, kf) { - return new StyleValue(cn, decls || "", media || [], pseudo || [], kf || []); - } - - function styleValueDeclarations(sv) { return sv.declarations; } - function styleValueMediaRules(sv) { return sv.mediaRules; } - function styleValuePseudoRules(sv) { return sv.pseudoRules; } - function styleValueKeyframes_(sv) { return sv.keyframes; } - - function injectStyleValue(sv, atoms) { - if (_injectedStyles[sv.className]) return; - _injectedStyles[sv.className] = true; - - if (!_hasDom) return; - var cssTarget = document.getElementById("sx-css"); - if (!cssTarget) return; - - var rules = []; - if (sv.declarations) { - var hasChild = false; - if (atoms) { - for (var ai = 0; ai < atoms.length; ai++) { - if (isChildSelectorAtom(atoms[ai])) { hasChild = true; break; } - } - } - if (hasChild) { - rules.push("." + sv.className + ">:not(:first-child){" + sv.declarations + "}"); - } else { - rules.push("." + sv.className + "{" + sv.declarations + "}"); - } - } - for (var pi = 0; pi < sv.pseudoRules.length; pi++) { - var sel = sv.pseudoRules[pi][0], decls = sv.pseudoRules[pi][1]; - if (sel.indexOf("&") >= 0) { - rules.push(sel.replace(/&/g, "." + sv.className) + "{" + decls + "}"); - } else { - rules.push("." + sv.className + sel + "{" + decls + "}"); - } - } - for (var mi = 0; mi < sv.mediaRules.length; mi++) { - rules.push("@media " + sv.mediaRules[mi][0] + "{." + sv.className + "{" + sv.mediaRules[mi][1] + "}}"); - } - for (var ki = 0; ki < sv.keyframes.length; ki++) { - rules.push(sv.keyframes[ki][1]); - } - cssTarget.textContent += rules.join(""); - } - - // Replace stub css primitive with real CSSX implementation - PRIMITIVES["css"] = function() { - var atoms = []; - for (var i = 0; i < arguments.length; i++) { - var a = arguments[i]; - if (isNil(a) || a === false) continue; - atoms.push(isKw(a) ? a.name : String(a)); - } - if (!atoms.length) return NIL; - return resolveStyle(atoms); - }; - - PRIMITIVES["merge-styles"] = function() { - var valid = []; - for (var i = 0; i < arguments.length; i++) { - if (isStyleValue(arguments[i])) valid.push(arguments[i]); - } - if (!valid.length) return NIL; - if (valid.length === 1) return valid[0]; - return mergeStyleValues(valid); - }; - - - // ========================================================================= - // Platform interface — Boot (mount, hydrate, scripts, cookies) - // ========================================================================= - - function resolveMountTarget(target) { - if (typeof target === "string") return _hasDom ? document.querySelector(target) : null; - return target; - } - - function sxRenderWithEnv(source, extraEnv) { - var env = extraEnv ? merge(componentEnv, extraEnv) : componentEnv; - var exprs = parse(source); - if (!_hasDom) return null; - var frag = document.createDocumentFragment(); - for (var i = 0; i < exprs.length; i++) { - var node = renderToDom(exprs[i], env, null); - if (node) frag.appendChild(node); - } - return frag; - } - - function getRenderEnv(extraEnv) { - return extraEnv ? merge(componentEnv, extraEnv) : componentEnv; - } - - function mergeEnvs(base, newEnv) { - return newEnv ? merge(componentEnv, base, newEnv) : merge(componentEnv, base); - } - - function sxLoadComponents(text) { - try { - var exprs = parse(text); - for (var i = 0; i < exprs.length; i++) trampoline(evalExpr(exprs[i], componentEnv)); - } catch (err) { - logParseError("loadComponents", text, err); - throw err; - } - } - - function setDocumentTitle(s) { - if (_hasDom) document.title = s || ""; - } - - function removeHeadElement(sel) { - if (!_hasDom) return; - var old = document.head.querySelector(sel); - if (old) old.parentNode.removeChild(old); - } - - function querySxScripts(root) { - if (!_hasDom) return []; - return Array.prototype.slice.call( - (root || document).querySelectorAll('script[type="text/sx"]')); - } - - function queryStyleScripts() { - if (!_hasDom) return []; - return Array.prototype.slice.call( - document.querySelectorAll('script[type="text/sx-styles"]')); - } - - // --- localStorage --- - - function localStorageGet(key) { - try { var v = localStorage.getItem(key); return v === null ? NIL : v; } - catch (e) { return NIL; } - } - - function localStorageSet(key, val) { - try { localStorage.setItem(key, val); } catch (e) {} - } - - function localStorageRemove(key) { - try { localStorage.removeItem(key); } catch (e) {} - } - - // --- Cookies --- - - function setSxCompCookie(hash) { - if (_hasDom) document.cookie = "sx-comp-hash=" + hash + ";path=/;max-age=31536000;SameSite=Lax"; - } - - function clearSxCompCookie() { - if (_hasDom) document.cookie = "sx-comp-hash=;path=/;max-age=0;SameSite=Lax"; - } - - function setSxStylesCookie(hash) { - if (_hasDom) document.cookie = "sx-styles-hash=" + hash + ";path=/;max-age=31536000;SameSite=Lax"; - } - - function clearSxStylesCookie() { - if (_hasDom) document.cookie = "sx-styles-hash=;path=/;max-age=0;SameSite=Lax"; - } - - // --- Env helpers --- - - function parseEnvAttr(el) { - var attr = el && el.getAttribute ? el.getAttribute("data-sx-env") : null; - if (!attr) return {}; - try { return JSON.parse(attr); } catch (e) { return {}; } - } - - function storeEnvAttr(el, base, newEnv) { - var merged = merge(base, newEnv); - if (el && el.setAttribute) el.setAttribute("data-sx-env", JSON.stringify(merged)); - } - - function toKebab(s) { return s.replace(/_/g, "-"); } - - // --- Logging --- - - function logInfo(msg) { - if (typeof console !== "undefined") console.log("[sx-ref] " + msg); - } - - function logParseError(label, text, err) { - if (typeof console === "undefined") return; - var msg = err && err.message ? err.message : String(err); - var colMatch = msg.match(/col (\d+)/); - var lineMatch = msg.match(/line (\d+)/); - if (colMatch && text) { - var errLine = lineMatch ? parseInt(lineMatch[1]) : 1; - var errCol = parseInt(colMatch[1]); - var lines = text.split("\n"); - var pos = 0; - for (var i = 0; i < errLine - 1 && i < lines.length; i++) pos += lines[i].length + 1; - pos += errCol; - var ws = 80; - var start = Math.max(0, pos - ws); - var end = Math.min(text.length, pos + ws); - console.error("[sx-ref] " + label + ":", msg, - "\n around error (pos ~" + pos + "):", - "\n \u00ab" + text.substring(start, pos) + "\u26d4" + text.substring(pos, end) + "\u00bb"); - } else { - console.error("[sx-ref] " + label + ":", msg); - } - } - - function parseAndLoadStyleDict(text) { - try { loadStyleDict(JSON.parse(text)); } - catch (e) { if (typeof console !== "undefined") console.warn("[sx-ref] style dict parse error", e); } - } - - // ========================================================================= // Post-transpilation fixups // ========================================================================= @@ -3013,111 +1212,11 @@ }; // 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; - // ========================================================================= - // Parser - // ========================================================================= - + // Minimal fallback parser (no parser adapter) function parse(text) { - var pos = 0; - function skipWs() { - while (pos < text.length) { - var ch = text[pos]; - if (ch === " " || ch === "\t" || ch === "\n" || ch === "\r") { pos++; continue; } - if (ch === ";") { while (pos < text.length && text[pos] !== "\n") pos++; continue; } - break; - } - } - function readExpr() { - skipWs(); - if (pos >= text.length) return undefined; - var ch = text[pos]; - if (ch === "(") { pos++; return readList(")"); } - if (ch === "[") { pos++; return readList("]"); } - if (ch === "{") { pos++; return readMap(); } - if (ch === '"') return readString(); - if (ch === ":") return readKeyword(); - if (ch === "`") { pos++; return [new Symbol("quasiquote"), readExpr()]; } - if (ch === ",") { - pos++; - if (pos < text.length && text[pos] === "@") { pos++; return [new Symbol("splice-unquote"), readExpr()]; } - return [new Symbol("unquote"), readExpr()]; - } - if (ch === "-" && pos + 1 < text.length && text[pos + 1] >= "0" && text[pos + 1] <= "9") return readNumber(); - if (ch >= "0" && ch <= "9") return readNumber(); - return readSymbol(); - } - function readList(close) { - var items = []; - while (true) { - skipWs(); - if (pos >= text.length) throw new Error("Unterminated list"); - if (text[pos] === close) { pos++; return items; } - items.push(readExpr()); - } - } - function readMap() { - var result = {}; - while (true) { - skipWs(); - if (pos >= text.length) throw new Error("Unterminated map"); - if (text[pos] === "}") { pos++; return result; } - var key = readExpr(); - var keyStr = (key && key._kw) ? key.name : String(key); - result[keyStr] = readExpr(); - } - } - function readString() { - pos++; // skip " - var s = ""; - while (pos < text.length) { - var ch = text[pos]; - if (ch === '"') { pos++; return s; } - if (ch === "\\") { pos++; var esc = text[pos]; s += esc === "n" ? "\n" : esc === "t" ? "\t" : esc === "r" ? "\r" : esc; pos++; continue; } - s += ch; pos++; - } - throw new Error("Unterminated string"); - } - function readKeyword() { - pos++; // skip : - var name = readIdent(); - return new Keyword(name); - } - function readNumber() { - var start = pos; - if (text[pos] === "-") pos++; - while (pos < text.length && text[pos] >= "0" && text[pos] <= "9") pos++; - if (pos < text.length && text[pos] === ".") { pos++; while (pos < text.length && text[pos] >= "0" && text[pos] <= "9") pos++; } - if (pos < text.length && (text[pos] === "e" || text[pos] === "E")) { - pos++; - if (pos < text.length && (text[pos] === "+" || text[pos] === "-")) pos++; - while (pos < text.length && text[pos] >= "0" && text[pos] <= "9") pos++; - } - return Number(text.slice(start, pos)); - } - function readIdent() { - var start = pos; - while (pos < text.length && /[a-zA-Z0-9_~*+\-><=/!?.:&]/.test(text[pos])) pos++; - return text.slice(start, pos); - } - function readSymbol() { - var name = readIdent(); - if (name === "true") return true; - if (name === "false") return false; - if (name === "nil") return NIL; - return new Symbol(name); - } - var exprs = []; - while (true) { - skipWs(); - if (pos >= text.length) break; - exprs.push(readExpr()); - } - return exprs; + throw new Error("Parser adapter not included — cannot parse SX source at runtime"); } // ========================================================================= @@ -3134,74 +1233,32 @@ } function render(source) { - if (!_hasDom) { - var exprs = parse(source); - var parts = []; - for (var i = 0; i < exprs.length; i++) parts.push(renderToHtml(exprs[i], merge(componentEnv))); - return parts.join(""); - } var exprs = parse(source); var frag = document.createDocumentFragment(); for (var i = 0; i < exprs.length; i++) frag.appendChild(renderToDom(exprs[i], merge(componentEnv), null)); return frag; } - function renderToString(source) { - var exprs = parse(source); - var parts = []; - for (var i = 0; i < exprs.length; i++) parts.push(renderToHtml(exprs[i], merge(componentEnv))); - return parts.join(""); - } - - var SxRef = { + var Sx = { + VERSION: "ref-2.0", parse: parse, + parseAll: parse, eval: function(expr, env) { return trampoline(evalExpr(expr, env || merge(componentEnv))); }, loadComponents: loadComponents, render: render, - renderToString: renderToString, + serialize: serialize, NIL: NIL, Symbol: Symbol, Keyword: Keyword, + isTruthy: isSxTruthy, + isNil: isNil, componentEnv: componentEnv, - renderToHtml: function(expr, env) { return renderToHtml(expr, env || merge(componentEnv)); }, - renderToSx: function(expr, env) { return renderToSx(expr, env || merge(componentEnv)); }, renderToDom: _hasDom ? function(expr, env, ns) { return renderToDom(expr, env || merge(componentEnv), ns || null); } : null, - parseTriggerSpec: typeof parseTriggerSpec === "function" ? parseTriggerSpec : null, - morphNode: typeof morphNode === "function" ? morphNode : null, - morphChildren: typeof morphChildren === "function" ? morphChildren : null, - swapDomNodes: typeof swapDomNodes === "function" ? swapDomNodes : null, - process: typeof processElements === "function" ? processElements : null, - executeRequest: typeof executeRequest === "function" ? executeRequest : null, - postSwap: typeof postSwap === "function" ? postSwap : null, - processScripts: typeof processSxScripts === "function" ? processSxScripts : null, - mount: typeof sxMount === "function" ? sxMount : null, - hydrate: typeof sxHydrateElements === "function" ? sxHydrateElements : null, - update: typeof sxUpdateElement === "function" ? sxUpdateElement : null, - renderComponent: typeof sxRenderComponent === "function" ? sxRenderComponent : null, - getEnv: function() { return componentEnv; }, - init: typeof bootInit === "function" ? bootInit : null, - _version: "ref-2.0 (boot+cssx+dom+engine+html+orchestration+sx, bootstrap-compiled)" + _version: "ref-2.0 (dom, bootstrap-compiled)" }; - - // --- Popstate listener --- - if (typeof window !== "undefined") { - window.addEventListener("popstate", function(e) { - handlePopstate(e && e.state ? e.state.scrollY || 0 : 0); - }); - } - - // --- Auto-init --- - if (typeof document !== "undefined") { - var _sxRefInit = function() { bootInit(); }; - if (document.readyState === "loading") { - document.addEventListener("DOMContentLoaded", _sxRefInit); - } else { - _sxRefInit(); - } - } - if (typeof module !== "undefined" && module.exports) module.exports = SxRef; - else global.SxRef = SxRef; + if (typeof module !== "undefined" && module.exports) module.exports = Sx; + else global.Sx = Sx; })(typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : this); \ No newline at end of file diff --git a/shared/sx/ref/bootstrap_js.py b/shared/sx/ref/bootstrap_js.py index 32db2a9..88c6239 100644 --- a/shared/sx/ref/bootstrap_js.py +++ b/shared/sx/ref/bootstrap_js.py @@ -717,9 +717,211 @@ class JSEmitter: def _emit_define(self, expr) -> str: name = expr[1].name if isinstance(expr[1], Symbol) else str(expr[1]) - val = self.emit(expr[2]) + # Detect zero-arg self-tail-recursive functions and emit as while loops + fn_expr = expr[2] if len(expr) > 2 else None + if (fn_expr and isinstance(fn_expr, list) and fn_expr + and isinstance(fn_expr[0], Symbol) and fn_expr[0].name in ("fn", "lambda") + and isinstance(fn_expr[1], list) and len(fn_expr[1]) == 0 + and self._is_self_tail_recursive(name, fn_expr[2:])): + body = fn_expr[2:] + loop_body = self._emit_loop_body(name, body) + return f"var {self._mangle(name)} = function() {{ while(true) {{ {loop_body} }} }};" + val = self.emit(fn_expr) if fn_expr else "NIL" return f"var {self._mangle(name)} = {val};" + def _is_self_tail_recursive(self, name: str, body: list) -> bool: + """Check if a function body contains tail calls to itself.""" + if not body: + return False + last = body[-1] + return self._has_tail_call(name, last) + + def _has_tail_call(self, name: str, expr) -> bool: + """Check if expr has a tail call to name in any branch.""" + if not isinstance(expr, list) or not expr: + return False + head = expr[0] + if not isinstance(head, Symbol): + return False + h = head.name + # Direct tail call + if h == name: + return True + # Branching forms — check if any branch tail-calls + if h == "if": + return (self._has_tail_call(name, expr[2]) + or (len(expr) > 3 and self._has_tail_call(name, expr[3]))) + if h == "when": + return any(self._has_tail_call(name, e) for e in expr[2:]) + if h == "cond": + for clause in expr[1:]: + if isinstance(clause, list) and len(clause) == 2: + if self._has_tail_call(name, clause[1]): + return True + elif isinstance(clause, Keyword): + continue + elif isinstance(clause, list): + if self._has_tail_call(name, clause): + return True + else: + if self._has_tail_call(name, clause): + return True + return False + if h in ("do", "begin"): + return self._has_tail_call(name, expr[-1]) if len(expr) > 1 else False + if h == "let" or h == "let*": + return self._has_tail_call(name, expr[-1]) if len(expr) > 2 else False + return False + + def _emit_loop_body(self, name: str, body: list) -> str: + """Emit a function body as while-loop statements. + + Replaces tail-self-calls with `continue` and non-recursive exits with + `return`. + """ + if not body: + return "return NIL;" + # Emit side-effect statements first, then the tail expression as loop logic + parts = [] + for b in body[:-1]: + parts.append(self.emit_statement(b)) + parts.append(self._emit_tail_as_stmt(name, body[-1])) + return "\n".join(parts) + + def _emit_tail_as_stmt(self, name: str, expr) -> str: + """Emit an expression in tail position as loop statements. + + Tail-self-calls → continue; other exits → return expr; + """ + if not isinstance(expr, list) or not expr: + return f"return {self.emit(expr)};" + + head = expr[0] + if not isinstance(head, Symbol): + return f"return {self.emit(expr)};" + + h = head.name + + # Direct tail call to self → continue + if h == name: + return "continue;" + + # (do stmt1 stmt2 ... tail) → emit stmts then recurse on tail + if h in ("do", "begin"): + stmts = [] + for e in expr[1:-1]: + stmts.append(self.emit_statement(e)) + stmts.append(self._emit_tail_as_stmt(name, expr[-1])) + return "\n".join(stmts) + + # (if cond then else) → if/else with tail handling in each branch + if h == "if": + cond = self.emit(expr[1]) + then_branch = self._emit_tail_as_stmt(name, expr[2]) + else_branch = self._emit_tail_as_stmt(name, expr[3]) if len(expr) > 3 else "return NIL;" + return f"if (isSxTruthy({cond})) {{ {then_branch} }} else {{ {else_branch} }}" + + # (when cond body...) → if (cond) { body... } else { return NIL; } + if h == "when": + cond = self.emit(expr[1]) + body_parts = expr[2:] + if not body_parts: + return f"if (isSxTruthy({cond})) {{}} else {{ return NIL; }}" + stmts = [] + for e in body_parts[:-1]: + stmts.append(self.emit_statement(e)) + stmts.append(self._emit_tail_as_stmt(name, body_parts[-1])) + inner = "\n".join(stmts) + return f"if (isSxTruthy({cond})) {{ {inner} }} else {{ return NIL; }}" + + # (cond clause1 clause2 ...) → if/else if/else chain + if h == "cond": + return self._emit_cond_as_loop_stmt(name, expr[1:]) + + # (let ((bindings)) body...) → { var ...; tail } + if h in ("let", "let*"): + bindings = expr[1] + body = expr[2:] + parts = [] + if isinstance(bindings, list): + if bindings and isinstance(bindings[0], list): + for b in bindings: + vname = b[0].name if isinstance(b[0], Symbol) else str(b[0]) + parts.append(f"var {self._mangle(vname)} = {self.emit(b[1])};") + else: + for i in range(0, len(bindings), 2): + vname = bindings[i].name if isinstance(bindings[i], Symbol) else str(bindings[i]) + parts.append(f"var {self._mangle(vname)} = {self.emit(bindings[i + 1])};") + for b_expr in body[:-1]: + parts.append(self.emit_statement(b_expr)) + parts.append(self._emit_tail_as_stmt(name, body[-1])) + inner = "\n".join(parts) + return f"{{ {inner} }}" + + # Not a tail call to self — regular return + return f"return {self.emit(expr)};" + + def _emit_cond_as_loop_stmt(self, name: str, clauses) -> str: + """Emit cond clauses as if/else if/else for loop body.""" + if not clauses: + return "return NIL;" + + # Detect style: Scheme vs Clojure (same as _emit_cond) + is_scheme = ( + all(isinstance(c, list) and len(c) == 2 for c in clauses) + and not any(isinstance(c, Keyword) for c in clauses) + ) + if is_scheme: + return self._cond_scheme_loop(name, clauses) + return self._cond_clojure_loop(name, clauses) + + def _cond_scheme_loop(self, name: str, clauses) -> str: + parts = [] + for i, clause in enumerate(clauses): + cond_expr = clause[0] + body_expr = clause[1] + # Check for :else / else + is_else = (isinstance(cond_expr, Keyword) and cond_expr.name == "else") or \ + (isinstance(cond_expr, Symbol) and cond_expr.name == "else") or \ + (isinstance(cond_expr, bool) and cond_expr is True) + if is_else: + parts.append(f"{{ {self._emit_tail_as_stmt(name, body_expr)} }}") + break + prefix = "if" if i == 0 else "else if" + cond = self.emit(cond_expr) + body = self._emit_tail_as_stmt(name, body_expr) + parts.append(f"{prefix} (isSxTruthy({cond})) {{ {body} }}") + else: + parts.append("else { return NIL; }") + return " ".join(parts) + + def _cond_clojure_loop(self, name: str, clauses) -> str: + parts = [] + i = 0 + clause_idx = 0 + has_else = False + while i < len(clauses): + c = clauses[i] + if isinstance(c, Keyword) and c.name == "else": + if i + 1 < len(clauses): + parts.append(f"else {{ {self._emit_tail_as_stmt(name, clauses[i + 1])} }}") + has_else = True + break + if i + 1 < len(clauses): + prefix = "if" if clause_idx == 0 else "else if" + cond = self.emit(c) + body = self._emit_tail_as_stmt(name, clauses[i + 1]) + parts.append(f"{prefix} (isSxTruthy({cond})) {{ {body} }}") + i += 2 + else: + parts.append(f"else {{ {self._emit_tail_as_stmt(name, c)} }}") + has_else = True + i += 1 + clause_idx += 1 + if not has_else: + parts.append("else { return NIL; }") + return " ".join(parts) + def _emit_for_each_stmt(self, expr) -> str: fn_expr = expr[1] coll_expr = expr[2] @@ -774,6 +976,7 @@ def extract_defines(source: str) -> list[tuple[str, list]]: ADAPTER_FILES = { + "parser": ("parser.sx", "parser"), "html": ("adapter-html.sx", "adapter-html"), "sx": ("adapter-sx.sx", "adapter-sx"), "dom": ("adapter-dom.sx", "adapter-dom"), @@ -788,7 +991,8 @@ ADAPTER_DEPS = { "engine": ["dom"], "orchestration": ["engine", "dom"], "cssx": [], - "boot": ["dom", "engine", "orchestration", "cssx"], + "boot": ["dom", "engine", "orchestration", "cssx", "parser"], + "parser": [], } @@ -805,6 +1009,7 @@ def compile_ref_to_js(adapters: list[str] | None = None) -> str: # Platform JS blocks keyed by adapter name adapter_platform = { + "parser": PLATFORM_PARSER_JS, "dom": PLATFORM_DOM_JS, "engine": PLATFORM_ENGINE_PURE_JS, "orchestration": PLATFORM_ORCHESTRATION_JS, @@ -830,7 +1035,7 @@ def compile_ref_to_js(adapters: list[str] | None = None) -> str: ("eval.sx", "eval"), ("render.sx", "render (core)"), ] - for name in ("html", "sx", "dom", "engine", "orchestration", "cssx", "boot"): + for name in ("parser", "html", "sx", "dom", "engine", "orchestration", "cssx", "boot"): if name in adapter_set: sx_files.append(ADAPTER_FILES[name]) @@ -852,11 +1057,17 @@ def compile_ref_to_js(adapters: list[str] | None = None) -> str: has_orch = "orchestration" in adapter_set has_cssx = "cssx" in adapter_set has_boot = "boot" in adapter_set + has_parser = "parser" in adapter_set adapter_label = "+".join(sorted(adapter_set)) if adapter_set else "core-only" parts = [] parts.append(PREAMBLE) parts.append(PLATFORM_JS) + + # Parser platform must come before compiled parser.sx + if has_parser: + parts.append(adapter_platform["parser"]) + for label, defines in all_sections: parts.append(f"\n // === Transpiled from {label} ===\n") for name, expr in defines: @@ -872,7 +1083,7 @@ def compile_ref_to_js(adapters: list[str] | None = None) -> str: parts.append(adapter_platform[name]) parts.append(fixups_js(has_html, has_sx, has_dom)) - parts.append(public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_cssx, has_boot, adapter_label)) + parts.append(public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_cssx, has_boot, has_parser, adapter_label)) parts.append(EPILOGUE) return "\n".join(parts) @@ -1393,6 +1604,26 @@ PLATFORM_JS = ''' return NIL; }''' +PLATFORM_PARSER_JS = r""" + // ========================================================================= + // Platform interface — Parser + // ========================================================================= + // Character classification derived from the grammar: + // ident-start → [a-zA-Z_~*+\-><=/!?&] + // ident-char → ident-start + [0-9.:\/\[\]#,] + + var _identStartRe = /[a-zA-Z_~*+\-><=/!?&]/; + var _identCharRe = /[a-zA-Z0-9_~*+\-><=/!?.:&/\[\]#,]/; + + function isIdentStart(ch) { return _identStartRe.test(ch); } + function isIdentChar(ch) { return _identCharRe.test(ch); } + function parseNumber(s) { return Number(s); } + function escapeString(s) { + return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\t/g, "\\t"); + } + function sxExprSource(e) { return typeof e === "string" ? e : String(e); } +""" + PLATFORM_DOM_JS = """ // ========================================================================= // Platform interface — DOM adapter (browser-only) @@ -2203,18 +2434,11 @@ PLATFORM_CSSX_JS = """ if (!cssTarget) return; var rules = []; + // Child-selector atoms are now routed to pseudoRules by the resolver + // with selector ">:not(:first-child)", so base declarations are always + // applied directly to the class. if (sv.declarations) { - var hasChild = false; - if (atoms) { - for (var ai = 0; ai < atoms.length; ai++) { - if (isChildSelectorAtom(atoms[ai])) { hasChild = true; break; } - } - } - if (hasChild) { - rules.push("." + sv.className + ">:not(:first-child){" + sv.declarations + "}"); - } else { - rules.push("." + sv.className + "{" + sv.declarations + "}"); - } + rules.push("." + sv.className + "{" + sv.declarations + "}"); } for (var pi = 0; pi < sv.pseudoRules.length; pi++) { var sel = sv.pseudoRules[pi][0], decls = sv.pseudoRules[pi][1]; @@ -2426,109 +2650,17 @@ def fixups_js(has_html, has_sx, has_dom): return "\n".join(lines) -def public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_cssx, has_boot, adapter_label): - # Parser is always included - parser = r''' - // ========================================================================= - // Parser - // ========================================================================= - +def public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_cssx, has_boot, has_parser, adapter_label): + # Parser: use compiled sxParse from parser.sx, or inline a minimal fallback + if has_parser: + parser = ''' + // Parser — compiled from parser.sx (see PLATFORM_PARSER_JS for ident char classes) + var parse = sxParse;''' + else: + parser = r''' + // Minimal fallback parser (no parser adapter) function parse(text) { - var pos = 0; - function skipWs() { - while (pos < text.length) { - var ch = text[pos]; - if (ch === " " || ch === "\t" || ch === "\n" || ch === "\r") { pos++; continue; } - if (ch === ";") { while (pos < text.length && text[pos] !== "\n") pos++; continue; } - break; - } - } - function readExpr() { - skipWs(); - if (pos >= text.length) return undefined; - var ch = text[pos]; - if (ch === "(") { pos++; return readList(")"); } - if (ch === "[") { pos++; return readList("]"); } - if (ch === "{") { pos++; return readMap(); } - if (ch === '"') return readString(); - if (ch === ":") return readKeyword(); - if (ch === "`") { pos++; return [new Symbol("quasiquote"), readExpr()]; } - if (ch === ",") { - pos++; - if (pos < text.length && text[pos] === "@") { pos++; return [new Symbol("splice-unquote"), readExpr()]; } - return [new Symbol("unquote"), readExpr()]; - } - if (ch === "-" && pos + 1 < text.length && text[pos + 1] >= "0" && text[pos + 1] <= "9") return readNumber(); - if (ch >= "0" && ch <= "9") return readNumber(); - return readSymbol(); - } - function readList(close) { - var items = []; - while (true) { - skipWs(); - if (pos >= text.length) throw new Error("Unterminated list"); - if (text[pos] === close) { pos++; return items; } - items.push(readExpr()); - } - } - function readMap() { - var result = {}; - while (true) { - skipWs(); - if (pos >= text.length) throw new Error("Unterminated map"); - if (text[pos] === "}") { pos++; return result; } - var key = readExpr(); - var keyStr = (key && key._kw) ? key.name : String(key); - result[keyStr] = readExpr(); - } - } - function readString() { - pos++; // skip " - var s = ""; - while (pos < text.length) { - var ch = text[pos]; - if (ch === '"') { pos++; return s; } - if (ch === "\\") { pos++; var esc = text[pos]; s += esc === "n" ? "\n" : esc === "t" ? "\t" : esc === "r" ? "\r" : esc; pos++; continue; } - s += ch; pos++; - } - throw new Error("Unterminated string"); - } - function readKeyword() { - pos++; // skip : - var name = readIdent(); - return new Keyword(name); - } - function readNumber() { - var start = pos; - if (text[pos] === "-") pos++; - while (pos < text.length && text[pos] >= "0" && text[pos] <= "9") pos++; - if (pos < text.length && text[pos] === ".") { pos++; while (pos < text.length && text[pos] >= "0" && text[pos] <= "9") pos++; } - if (pos < text.length && (text[pos] === "e" || text[pos] === "E")) { - pos++; - if (pos < text.length && (text[pos] === "+" || text[pos] === "-")) pos++; - while (pos < text.length && text[pos] >= "0" && text[pos] <= "9") pos++; - } - return Number(text.slice(start, pos)); - } - function readIdent() { - var start = pos; - while (pos < text.length && /[a-zA-Z0-9_~*+\-><=/!?.:&]/.test(text[pos])) pos++; - return text.slice(start, pos); - } - function readSymbol() { - var name = readIdent(); - if (name === "true") return true; - if (name === "false") return false; - if (name === "nil") return NIL; - return new Symbol(name); - } - var exprs = []; - while (true) { - skipWs(); - if (pos >= text.length) break; - exprs.push(readExpr()); - } - return exprs; + throw new Error("Parser adapter not included — cannot parse SX source at runtime"); }''' # Public API — conditional on adapters