Page helpers demo: defisland, map-in-children fix, _eval_slot ref evaluator

- Add page-helpers-demo page with defisland ~demo-client-runner (pure SX,
  zero JS files) showing spec functions running on both server and client
- Fix _aser_component children serialization: flatten list results from map
  instead of serialize(list) which wraps in parens creating ((div ...) ...)
  that re-parses as invalid function call. Fixed in adapter-async.sx spec
  and async_eval_ref.py
- Switch _eval_slot to use async_eval_ref.py when SX_USE_REF=1 (was
  hardcoded to async_eval.py)
- Add Island type support to async_eval_ref.py: import, SSR rendering,
  aser dispatch, thread-first, defisland in _ASER_FORMS
- Add server affinity check: components with :affinity :server expand
  even when _expand_components is False
- Add diagnostic _aser_stack context to EvalError messages
- New spec files: adapter-async.sx, page-helpers.sx, platform_js.py
- Bootstrappers: page-helpers module support, performance.now() timing
- 0-arity lambda event handler fix in adapter-dom.sx

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 14:30:12 +00:00
parent 29c90a625b
commit c95e19dcf2
16 changed files with 5584 additions and 781 deletions

View File

@@ -14,7 +14,7 @@
// =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-03-11T04:41:27Z";
var SX_VERSION = "2026-03-11T13:57:48Z";
function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -974,8 +974,8 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai
var head = first(template);
return (isSxTruthy((isSxTruthy((typeOf(head) == "symbol")) && (symbolName(head) == "unquote"))) ? trampoline(evalExpr(nth(template, 1), env)) : reduce(function(result, item) { return (isSxTruthy((isSxTruthy((typeOf(item) == "list")) && isSxTruthy((len(item) == 2)) && isSxTruthy((typeOf(first(item)) == "symbol")) && (symbolName(first(item)) == "splice-unquote"))) ? (function() {
var spliced = trampoline(evalExpr(nth(item, 1), env));
return (isSxTruthy((typeOf(spliced) == "list")) ? concat(result, spliced) : (isSxTruthy(isNil(spliced)) ? result : append(result, spliced)));
})() : append(result, qqExpand(item, env))); }, [], template));
return (isSxTruthy((typeOf(spliced) == "list")) ? concat(result, spliced) : (isSxTruthy(isNil(spliced)) ? result : concat(result, [spliced])));
})() : concat(result, [qqExpand(item, env)])); }, [], template));
})())); };
// sf-thread-first
@@ -1658,7 +1658,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
var attrExpr = nth(args, (get(state, "i") + 1));
(isSxTruthy(startsWith(attrName, "on-")) ? (function() {
var attrVal = trampoline(evalExpr(attrExpr, env));
return (isSxTruthy(isCallable(attrVal)) ? domListen(el, slice(attrName, 3), attrVal) : NIL);
return (isSxTruthy(isCallable(attrVal)) ? domListen(el, slice(attrName, 3), (isSxTruthy((isSxTruthy(isLambda(attrVal)) && (len(lambdaParams(attrVal)) == 0))) ? function(e) { return callLambda(attrVal, [], lambdaClosure(attrVal)); } : attrVal)) : NIL);
})() : (isSxTruthy((attrName == "bind")) ? (function() {
var attrVal = trampoline(evalExpr(attrExpr, env));
return (isSxTruthy(isSignal(attrVal)) ? bindInput(el, attrVal) : NIL);
@@ -3433,6 +3433,125 @@ callExpr.push(dictGet(kwargs, k)); } }
})(); }, keys(env)); };
// === Transpiled from page-helpers (pure data transformation helpers) ===
// special-form-category-map
var specialFormCategoryMap = {"if": "Control Flow", "when": "Control Flow", "cond": "Control Flow", "case": "Control Flow", "and": "Control Flow", "or": "Control Flow", "let": "Binding", "let*": "Binding", "letrec": "Binding", "define": "Binding", "set!": "Binding", "lambda": "Functions & Components", "fn": "Functions & Components", "defcomp": "Functions & Components", "defmacro": "Functions & Components", "begin": "Sequencing & Threading", "do": "Sequencing & Threading", "->": "Sequencing & Threading", "quote": "Quoting", "quasiquote": "Quoting", "reset": "Continuations", "shift": "Continuations", "dynamic-wind": "Guards", "map": "Higher-Order Forms", "map-indexed": "Higher-Order Forms", "filter": "Higher-Order Forms", "reduce": "Higher-Order Forms", "some": "Higher-Order Forms", "every?": "Higher-Order Forms", "for-each": "Higher-Order Forms", "defstyle": "Domain Definitions", "defhandler": "Domain Definitions", "defpage": "Domain Definitions", "defquery": "Domain Definitions", "defaction": "Domain Definitions"};
// extract-define-kwargs
var extractDefineKwargs = function(expr) { return (function() {
var result = {};
var items = slice(expr, 2);
var n = len(items);
{ var _c = range(0, n); for (var _i = 0; _i < _c.length; _i++) { var idx = _c[_i]; if (isSxTruthy((isSxTruthy(((idx + 1) < n)) && (typeOf(nth(items, idx)) == "keyword")))) {
(function() {
var key = keywordName(nth(items, idx));
var val = nth(items, (idx + 1));
return dictSet(result, key, (isSxTruthy((typeOf(val) == "list")) ? (String("(") + String(join(" ", map(serialize, val))) + String(")")) : (String(val))));
})();
} } }
return result;
})(); };
// categorize-special-forms
var categorizeSpecialForms = function(parsedExprs) { return (function() {
var categories = {};
{ var _c = parsedExprs; for (var _i = 0; _i < _c.length; _i++) { var expr = _c[_i]; if (isSxTruthy((isSxTruthy((typeOf(expr) == "list")) && isSxTruthy((len(expr) >= 2)) && isSxTruthy((typeOf(first(expr)) == "symbol")) && (symbolName(first(expr)) == "define-special-form")))) {
(function() {
var name = nth(expr, 1);
var kwargs = extractDefineKwargs(expr);
var category = sxOr(get(specialFormCategoryMap, name), "Other");
if (isSxTruthy(!isSxTruthy(dictHas(categories, category)))) {
categories[category] = [];
}
return append_b(get(categories, category), {"name": name, "syntax": sxOr(get(kwargs, "syntax"), ""), "doc": sxOr(get(kwargs, "doc"), ""), "tail-position": sxOr(get(kwargs, "tail-position"), ""), "example": sxOr(get(kwargs, "example"), "")});
})();
} } }
return categories;
})(); };
// build-ref-items-with-href
var buildRefItemsWithHref = function(items, basePath, detailKeys, nFields) { return map(function(item) { return (isSxTruthy((nFields == 3)) ? (function() {
var name = nth(item, 0);
var field2 = nth(item, 1);
var field3 = nth(item, 2);
return {"name": name, "desc": field2, "exists": field3, "href": (isSxTruthy((isSxTruthy(field3) && some(function(k) { return (k == name); }, detailKeys))) ? (String(basePath) + String(name)) : NIL)};
})() : (function() {
var name = nth(item, 0);
var desc = nth(item, 1);
return {"name": name, "desc": desc, "href": (isSxTruthy(some(function(k) { return (k == name); }, detailKeys)) ? (String(basePath) + String(name)) : NIL)};
})()); }, items); };
// build-reference-data
var buildReferenceData = function(slug, rawData, detailKeys) { return (function() { var _m = slug; if (_m == "attributes") return {"req-attrs": buildRefItemsWithHref(get(rawData, "req-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3), "beh-attrs": buildRefItemsWithHref(get(rawData, "beh-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3), "uniq-attrs": buildRefItemsWithHref(get(rawData, "uniq-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3)}; if (_m == "headers") return {"req-headers": buildRefItemsWithHref(get(rawData, "req-headers"), "/hypermedia/reference/headers/", detailKeys, 3), "resp-headers": buildRefItemsWithHref(get(rawData, "resp-headers"), "/hypermedia/reference/headers/", detailKeys, 3)}; if (_m == "events") return {"events-list": buildRefItemsWithHref(get(rawData, "events-list"), "/hypermedia/reference/events/", detailKeys, 2)}; if (_m == "js-api") return {"js-api-list": map(function(item) { return {"name": nth(item, 0), "desc": nth(item, 1)}; }, get(rawData, "js-api-list"))}; return {"req-attrs": buildRefItemsWithHref(get(rawData, "req-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3), "beh-attrs": buildRefItemsWithHref(get(rawData, "beh-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3), "uniq-attrs": buildRefItemsWithHref(get(rawData, "uniq-attrs"), "/hypermedia/reference/attributes/", detailKeys, 3)}; })(); };
// build-attr-detail
var buildAttrDetail = function(slug, detail) { return (isSxTruthy(isNil(detail)) ? {"attr-not-found": true} : {"attr-not-found": NIL, "attr-title": slug, "attr-description": get(detail, "description"), "attr-example": get(detail, "example"), "attr-handler": get(detail, "handler"), "attr-demo": get(detail, "demo"), "attr-wire-id": (isSxTruthy(dictHas(detail, "handler")) ? (String("ref-wire-") + String(replace_(replace_(slug, ":", "-"), "*", "star"))) : NIL)}); };
// build-header-detail
var buildHeaderDetail = function(slug, detail) { return (isSxTruthy(isNil(detail)) ? {"header-not-found": true} : {"header-not-found": NIL, "header-title": slug, "header-direction": get(detail, "direction"), "header-description": get(detail, "description"), "header-example": get(detail, "example"), "header-demo": get(detail, "demo")}); };
// build-event-detail
var buildEventDetail = function(slug, detail) { return (isSxTruthy(isNil(detail)) ? {"event-not-found": true} : {"event-not-found": NIL, "event-title": slug, "event-description": get(detail, "description"), "event-example": get(detail, "example"), "event-demo": get(detail, "demo")}); };
// build-component-source
var buildComponentSource = function(compData) { return (function() {
var compType = get(compData, "type");
var name = get(compData, "name");
var params = get(compData, "params");
var hasChildren = get(compData, "has-children");
var bodySx = get(compData, "body-sx");
var affinity = get(compData, "affinity");
return (isSxTruthy((compType == "not-found")) ? (String(";; component ") + String(name) + String(" not found")) : (function() {
var paramStrs = (isSxTruthy(isEmpty(params)) ? (isSxTruthy(hasChildren) ? ["&rest", "children"] : []) : (isSxTruthy(hasChildren) ? append(cons("&key", params), ["&rest", "children"]) : cons("&key", params)));
var paramsSx = (String("(") + String(join(" ", paramStrs)) + String(")"));
var formName = (isSxTruthy((compType == "island")) ? "defisland" : "defcomp");
var affinityStr = (isSxTruthy((isSxTruthy((compType == "component")) && isSxTruthy(!isSxTruthy(isNil(affinity))) && !isSxTruthy((affinity == "auto")))) ? (String(" :affinity ") + String(affinity)) : "");
return (String("(") + String(formName) + String(" ") + String(name) + String(" ") + String(paramsSx) + String(affinityStr) + String("\n ") + String(bodySx) + String(")"));
})());
})(); };
// build-bundle-analysis
var buildBundleAnalysis = function(pagesRaw, componentsRaw, totalComponents, totalMacros, pureCount, ioCount) { return (function() {
var pagesData = [];
{ var _c = pagesRaw; for (var _i = 0; _i < _c.length; _i++) { var page = _c[_i]; (function() {
var neededNames = get(page, "needed-names");
var n = len(neededNames);
var pct = (isSxTruthy((totalComponents > 0)) ? round(((n / totalComponents) * 100)) : 0);
var savings = (100 - pct);
var pureInPage = 0;
var ioInPage = 0;
var pageIoRefs = [];
var compDetails = [];
{ var _c = neededNames; for (var _i = 0; _i < _c.length; _i++) { var compName = _c[_i]; (function() {
var info = get(componentsRaw, compName);
return (isSxTruthy(!isSxTruthy(isNil(info))) ? ((isSxTruthy(get(info, "is-pure")) ? (pureInPage = (pureInPage + 1)) : ((ioInPage = (ioInPage + 1)), forEach(function(ref) { return (isSxTruthy(!isSxTruthy(some(function(r) { return (r == ref); }, pageIoRefs))) ? append_b(pageIoRefs, ref) : NIL); }, sxOr(get(info, "io-refs"), [])))), append_b(compDetails, {"name": compName, "is-pure": get(info, "is-pure"), "affinity": get(info, "affinity"), "render-target": get(info, "render-target"), "io-refs": sxOr(get(info, "io-refs"), []), "deps": sxOr(get(info, "deps"), []), "source": get(info, "source")})) : NIL);
})(); } }
return append_b(pagesData, {"name": get(page, "name"), "path": get(page, "path"), "direct": get(page, "direct"), "needed": n, "pct": pct, "savings": savings, "io-refs": len(pageIoRefs), "pure-in-page": pureInPage, "io-in-page": ioInPage, "components": compDetails});
})(); } }
return {"pages": pagesData, "total-components": totalComponents, "total-macros": totalMacros, "pure-count": pureCount, "io-count": ioCount};
})(); };
// build-routing-analysis
var buildRoutingAnalysis = function(pagesRaw) { return (function() {
var pagesData = [];
var clientCount = 0;
var serverCount = 0;
{ var _c = pagesRaw; for (var _i = 0; _i < _c.length; _i++) { var page = _c[_i]; (function() {
var hasData = get(page, "has-data");
var contentSrc = sxOr(get(page, "content-src"), "");
var mode = NIL;
var reason = "";
(isSxTruthy(hasData) ? ((mode = "server"), (reason = "Has :data expression — needs server IO"), (serverCount = (serverCount + 1))) : (isSxTruthy(isEmpty(contentSrc)) ? ((mode = "server"), (reason = "No content expression"), (serverCount = (serverCount + 1))) : ((mode = "client"), (clientCount = (clientCount + 1)))));
return append_b(pagesData, {"name": get(page, "name"), "path": get(page, "path"), "mode": mode, "has-data": hasData, "content-expr": (isSxTruthy((len(contentSrc) > 80)) ? (String(slice(contentSrc, 0, 80)) + String("...")) : contentSrc), "reason": reason});
})(); } }
return {"pages": pagesData, "total-pages": (clientCount + serverCount), "client-count": clientCount, "server-count": serverCount};
})(); };
// build-affinity-analysis
var buildAffinityAnalysis = function(demoComponents, pagePlans) { return {"components": demoComponents, "page-plans": pagePlans}; };
// === Transpiled from router (client-side route matching) ===
// split-path-segments
@@ -3947,7 +4066,7 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
}
}
function nowMs() { return Date.now(); }
function nowMs() { return (typeof performance !== "undefined") ? performance.now() : Date.now(); }
function parseHeaderValue(s) {
if (!s) return null;
@@ -5060,6 +5179,10 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
if (typeof elementValue === "function") PRIMITIVES["element-value"] = elementValue;
if (typeof domOuterHtml === "function") PRIMITIVES["dom-outer-html"] = domOuterHtml;
if (typeof domInnerHtml === "function") PRIMITIVES["dom-inner-html"] = domInnerHtml;
if (typeof domTextContent === "function") PRIMITIVES["dom-text-content"] = domTextContent;
if (typeof jsonParse === "function") PRIMITIVES["json-parse"] = jsonParse;
if (typeof nowMs === "function") PRIMITIVES["now-ms"] = nowMs;
PRIMITIVES["sx-parse"] = sxParse;
// Expose deps module functions as primitives so runtime-evaluated SX code
// (e.g. test-deps.sx in browser) can call them
@@ -5090,6 +5213,19 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
PRIMITIVES["render-target"] = renderTarget;
PRIMITIVES["page-render-plan"] = pageRenderPlan;
// Expose page-helper functions as primitives
PRIMITIVES["categorize-special-forms"] = categorizeSpecialForms;
PRIMITIVES["extract-define-kwargs"] = extractDefineKwargs;
PRIMITIVES["build-reference-data"] = buildReferenceData;
PRIMITIVES["build-ref-items-with-href"] = buildRefItemsWithHref;
PRIMITIVES["build-attr-detail"] = buildAttrDetail;
PRIMITIVES["build-header-detail"] = buildHeaderDetail;
PRIMITIVES["build-event-detail"] = buildEventDetail;
PRIMITIVES["build-component-source"] = buildComponentSource;
PRIMITIVES["build-bundle-analysis"] = buildBundleAnalysis;
PRIMITIVES["build-routing-analysis"] = buildRoutingAnalysis;
PRIMITIVES["build-affinity-analysis"] = buildAffinityAnalysis;
// =========================================================================
// Async IO: Promise-aware rendering for client-side IO primitives
// =========================================================================
@@ -5823,6 +5959,15 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
transitiveIoRefs: transitiveIoRefs,
computeAllIoRefs: computeAllIoRefs,
componentPure_p: componentPure_p,
categorizeSpecialForms: categorizeSpecialForms,
buildReferenceData: buildReferenceData,
buildAttrDetail: buildAttrDetail,
buildHeaderDetail: buildHeaderDetail,
buildEventDetail: buildEventDetail,
buildComponentSource: buildComponentSource,
buildBundleAnalysis: buildBundleAnalysis,
buildRoutingAnalysis: buildRoutingAnalysis,
buildAffinityAnalysis: buildAffinityAnalysis,
splitPathSegments: splitPathSegments,
parseRoutePattern: parseRoutePattern,
matchRoute: matchRoute,

View File

@@ -170,7 +170,11 @@ async def _eval_slot(expr: Any, env: dict, ctx: Any) -> str:
Expands component calls (so IO in the body executes) but serializes
the result as SX wire format, not HTML.
"""
from .async_eval import async_eval_slot_to_sx
import os
if os.environ.get("SX_USE_REF") == "1":
from .ref.async_eval_ref import async_eval_slot_to_sx
else:
from .async_eval import async_eval_slot_to_sx
return await async_eval_slot_to_sx(expr, env, ctx)

File diff suppressed because it is too large Load Diff

View File

@@ -186,10 +186,15 @@
(attr-expr (nth args (inc (get state "i")))))
(cond
;; Event handler: evaluate eagerly, bind listener
;; If handler is a 0-arity lambda, wrap to ignore the event arg
(starts-with? attr-name "on-")
(let ((attr-val (trampoline (eval-expr attr-expr env))))
(when (callable? attr-val)
(dom-listen el (slice attr-name 3) attr-val)))
(dom-listen el (slice attr-name 3)
(if (and (lambda? attr-val)
(= (len (lambda-params attr-val)) 0))
(fn (e) (call-lambda attr-val (list) (lambda-closure attr-val)))
attr-val))))
;; Two-way input binding: :bind signal
(= attr-name "bind")
(let ((attr-val (trampoline (eval-expr attr-expr env))))

View File

@@ -26,7 +26,7 @@ import contextvars
import inspect
from typing import Any
from ..types import Component, Keyword, Lambda, Macro, NIL, Symbol
from ..types import Component, Island, Keyword, Lambda, Macro, NIL, Symbol
from ..parser import SxExpr, serialize
from ..primitives_io import IO_PRIMITIVES, RequestContext, execute_io
from ..html import (
@@ -210,9 +210,11 @@ async def _arender_list(expr, env, ctx):
if name in HTML_TAGS:
return await _arender_element(name, expr[1:], env, ctx)
# Component
# Component / Island
if name.startswith("~"):
val = env.get(name)
if isinstance(val, Island):
return sx_ref.render_html_island(val, expr[1:], env)
if isinstance(val, Component):
return await _arender_component(val, expr[1:], env, ctx)
@@ -455,6 +457,7 @@ _ASYNC_RENDER_FORMS = {
"defcomp": _arsf_define,
"defmacro": _arsf_define,
"defhandler": _arsf_define,
"defisland": _arsf_define,
"map": _arsf_map,
"map-indexed": _arsf_map_indexed,
"filter": _arsf_filter,
@@ -505,6 +508,8 @@ async def _eval_slot_inner(expr, env, ctx):
if isinstance(result, str):
return SxExpr(result)
return SxExpr(serialize(result))
elif isinstance(comp, Island):
pass # Islands serialize as SX for client hydration
result = await _aser(expr, env, ctx)
result = await _maybe_expand_component_result(result, env, ctx)
if isinstance(result, SxExpr):
@@ -530,6 +535,9 @@ async def _maybe_expand_component_result(result, env, ctx):
return result
_aser_stack: list[str] = [] # diagnostic: track expression context
async def _aser(expr, env, ctx):
"""Evaluate for SX wire format — serialize rendering forms, evaluate control flow."""
if isinstance(expr, (int, float, bool)):
@@ -553,7 +561,8 @@ async def _aser(expr, env, ctx):
return False
if name == "nil":
return NIL
raise EvalError(f"Undefined symbol: {name}")
ctx_info = "".join(_aser_stack[-5:]) if _aser_stack else "(top)"
raise EvalError(f"Undefined symbol: {name} [aser context: {ctx_info}]")
if isinstance(expr, Keyword):
return expr.name
@@ -590,7 +599,7 @@ async def _aser(expr, env, ctx):
if name.startswith("html:"):
return await _aser_call(name[5:], expr[1:], env, ctx)
# Component call
# Component / Island call
if name.startswith("~"):
val = env.get(name)
if isinstance(val, Macro):
@@ -598,7 +607,10 @@ async def _aser(expr, env, ctx):
sx_ref.expand_macro(val, expr[1:], env)
)
return await _aser(expanded, env, ctx)
if isinstance(val, Component) and _expand_components.get():
if isinstance(val, Component) and (
_expand_components.get()
or getattr(val, "render_target", None) == "server"
):
return await _aser_component(val, expr[1:], env, ctx)
return await _aser_call(name, expr[1:], env, ctx)
@@ -633,11 +645,11 @@ async def _aser(expr, env, ctx):
if _svg_context.get(False):
return await _aser_call(name, expr[1:], env, ctx)
# Function/lambda call
# Function/lambda call — fallback: evaluate head as callable
fn = await async_eval(head, env, ctx)
args = [await async_eval(a, env, ctx) for a in expr[1:]]
if callable(fn) and not isinstance(fn, (Lambda, Component)):
if callable(fn) and not isinstance(fn, (Lambda, Component, Island)):
result = fn(*args)
if inspect.iscoroutine(result):
return await result
@@ -650,7 +662,9 @@ async def _aser(expr, env, ctx):
return await _aser(fn.body, local, ctx)
if isinstance(fn, Component):
return await _aser_call(f"~{fn.name}", expr[1:], env, ctx)
raise EvalError(f"Not callable: {fn!r}")
if isinstance(fn, Island):
return await _aser_call(f"~{fn.name}", expr[1:], env, ctx)
raise EvalError(f"Not callable in aser: {fn!r} (expr head: {head!r})")
async def _aser_fragment(children, env, ctx):
@@ -669,28 +683,41 @@ async def _aser_fragment(children, env, ctx):
async def _aser_component(comp, args, env, ctx):
kwargs = {}
children = []
i = 0
while i < len(args):
arg = args[i]
if isinstance(arg, Keyword) and i + 1 < len(args):
kwargs[arg.name] = await _aser(args[i + 1], env, ctx)
i += 2
else:
children.append(arg)
i += 1
local = dict(comp.closure)
local.update(env)
for p in comp.params:
local[p] = kwargs.get(p, NIL)
if comp.has_children:
child_parts = [serialize(await _aser(c, env, ctx)) for c in children]
local["children"] = SxExpr("(<> " + " ".join(child_parts) + ")")
return await _aser(comp.body, local, ctx)
_aser_stack.append(f"~{comp.name}")
try:
kwargs = {}
children = []
i = 0
while i < len(args):
arg = args[i]
if isinstance(arg, Keyword) and i + 1 < len(args):
kwargs[arg.name] = await _aser(args[i + 1], env, ctx)
i += 2
else:
children.append(arg)
i += 1
local = dict(comp.closure)
local.update(env)
for p in comp.params:
local[p] = kwargs.get(p, NIL)
if comp.has_children:
child_parts = []
for c in children:
result = await _aser(c, env, ctx)
if isinstance(result, list):
for item in result:
if item is not NIL and item is not None:
child_parts.append(serialize(item))
elif result is not NIL and result is not None:
child_parts.append(serialize(result))
local["children"] = SxExpr("(<> " + " ".join(child_parts) + ")")
return await _aser(comp.body, local, ctx)
finally:
_aser_stack.pop()
async def _aser_call(name, args, env, ctx):
_aser_stack.append(name)
token = None
if name in ("svg", "math"):
token = _svg_context.set(True)
@@ -730,6 +757,7 @@ async def _aser_call(name, args, env, ctx):
_merge_class_into_parts(parts, extra_class)
return SxExpr("(" + " ".join(parts) + ")")
finally:
_aser_stack.pop()
if token is not None:
_svg_context.reset(token)
@@ -885,7 +913,7 @@ async def _assf_thread_first(expr, env, ctx):
else:
fn = await async_eval(form, env, ctx)
fn_args = [result]
if callable(fn) and not isinstance(fn, (Lambda, Component)):
if callable(fn) and not isinstance(fn, (Lambda, Component, Island)):
result = fn(*fn_args)
if inspect.iscoroutine(result):
result = await result
@@ -981,6 +1009,7 @@ _ASER_FORMS = {
"defcomp": _assf_define,
"defmacro": _assf_define,
"defhandler": _assf_define,
"defisland": _assf_define,
"begin": _assf_begin,
"do": _assf_begin,
"quote": _assf_quote,

View File

@@ -1196,6 +1196,8 @@ def compile_ref_to_py(
spec_mod_set.add("deps")
if "signals" in SPEC_MODULES:
spec_mod_set.add("signals")
if "page-helpers" in SPEC_MODULES:
spec_mod_set.add("page-helpers")
has_deps = "deps" in spec_mod_set
# Core files always included, then selected adapters, then spec modules

View File

@@ -0,0 +1,368 @@
;; ==========================================================================
;; page-helpers.sx — Pure data-transformation page helpers
;;
;; These functions take raw data (from Python I/O edge) and return
;; structured dicts for page rendering. No I/O — pure transformations
;; only. Bootstrapped to every host.
;; ==========================================================================
;; --------------------------------------------------------------------------
;; categorize-special-forms
;;
;; Parses define-special-form declarations from special-forms.sx AST,
;; categorizes each form by name lookup, returns dict of category → forms.
;; --------------------------------------------------------------------------
(define special-form-category-map
{"if" "Control Flow" "when" "Control Flow" "cond" "Control Flow"
"case" "Control Flow" "and" "Control Flow" "or" "Control Flow"
"let" "Binding" "let*" "Binding" "letrec" "Binding"
"define" "Binding" "set!" "Binding"
"lambda" "Functions & Components" "fn" "Functions & Components"
"defcomp" "Functions & Components" "defmacro" "Functions & Components"
"begin" "Sequencing & Threading" "do" "Sequencing & Threading"
"->" "Sequencing & Threading"
"quote" "Quoting" "quasiquote" "Quoting"
"reset" "Continuations" "shift" "Continuations"
"dynamic-wind" "Guards"
"map" "Higher-Order Forms" "map-indexed" "Higher-Order Forms"
"filter" "Higher-Order Forms" "reduce" "Higher-Order Forms"
"some" "Higher-Order Forms" "every?" "Higher-Order Forms"
"for-each" "Higher-Order Forms"
"defstyle" "Domain Definitions"
"defhandler" "Domain Definitions" "defpage" "Domain Definitions"
"defquery" "Domain Definitions" "defaction" "Domain Definitions"})
(define extract-define-kwargs
(fn (expr)
;; Extract keyword args from a define-special-form expression.
;; Returns dict of keyword-name → string value.
;; Walks items pairwise: when item[i] is a keyword, item[i+1] is its value.
(let ((result {})
(items (slice expr 2))
(n (len items)))
(for-each
(fn (idx)
(when (and (< (+ idx 1) n)
(= (type-of (nth items idx)) "keyword"))
(let ((key (keyword-name (nth items idx)))
(val (nth items (+ idx 1))))
(dict-set! result key
(if (= (type-of val) "list")
(str "(" (join " " (map serialize val)) ")")
(str val))))))
(range 0 n))
result)))
(define categorize-special-forms
(fn (parsed-exprs)
;; parsed-exprs: result of parse-all on special-forms.sx
;; Returns dict of category-name → list of form dicts.
(let ((categories {}))
(for-each
(fn (expr)
(when (and (= (type-of expr) "list")
(>= (len expr) 2)
(= (type-of (first expr)) "symbol")
(= (symbol-name (first expr)) "define-special-form"))
(let ((name (nth expr 1))
(kwargs (extract-define-kwargs expr))
(category (or (get special-form-category-map name) "Other")))
(when (not (has-key? categories category))
(dict-set! categories category (list)))
(append! (get categories category)
{"name" name
"syntax" (or (get kwargs "syntax") "")
"doc" (or (get kwargs "doc") "")
"tail-position" (or (get kwargs "tail-position") "")
"example" (or (get kwargs "example") "")}))))
parsed-exprs)
categories)))
;; --------------------------------------------------------------------------
;; build-reference-data
;;
;; Takes a slug and raw reference data, returns structured dict for rendering.
;; --------------------------------------------------------------------------
(define build-ref-items-with-href
(fn (items base-path detail-keys n-fields)
;; items: list of lists (tuples), each with n-fields elements
;; base-path: e.g. "/hypermedia/reference/attributes/"
;; detail-keys: list of strings (keys that have detail pages)
;; n-fields: 2 or 3 (number of fields per tuple)
(map
(fn (item)
(if (= n-fields 3)
;; [name, desc/value, exists/desc]
(let ((name (nth item 0))
(field2 (nth item 1))
(field3 (nth item 2)))
{"name" name
"desc" field2
"exists" field3
"href" (if (and field3 (some (fn (k) (= k name)) detail-keys))
(str base-path name)
nil)})
;; [name, desc]
(let ((name (nth item 0))
(desc (nth item 1)))
{"name" name
"desc" desc
"href" (if (some (fn (k) (= k name)) detail-keys)
(str base-path name)
nil)})))
items)))
(define build-reference-data
(fn (slug raw-data detail-keys)
;; slug: "attributes", "headers", "events", "js-api"
;; raw-data: dict with the raw data lists for this slug
;; detail-keys: list of names that have detail pages
(case slug
"attributes"
{"req-attrs" (build-ref-items-with-href
(get raw-data "req-attrs")
"/hypermedia/reference/attributes/" detail-keys 3)
"beh-attrs" (build-ref-items-with-href
(get raw-data "beh-attrs")
"/hypermedia/reference/attributes/" detail-keys 3)
"uniq-attrs" (build-ref-items-with-href
(get raw-data "uniq-attrs")
"/hypermedia/reference/attributes/" detail-keys 3)}
"headers"
{"req-headers" (build-ref-items-with-href
(get raw-data "req-headers")
"/hypermedia/reference/headers/" detail-keys 3)
"resp-headers" (build-ref-items-with-href
(get raw-data "resp-headers")
"/hypermedia/reference/headers/" detail-keys 3)}
"events"
{"events-list" (build-ref-items-with-href
(get raw-data "events-list")
"/hypermedia/reference/events/" detail-keys 2)}
"js-api"
{"js-api-list" (map (fn (item) {"name" (nth item 0) "desc" (nth item 1)})
(get raw-data "js-api-list"))}
;; default: attributes
:else
{"req-attrs" (build-ref-items-with-href
(get raw-data "req-attrs")
"/hypermedia/reference/attributes/" detail-keys 3)
"beh-attrs" (build-ref-items-with-href
(get raw-data "beh-attrs")
"/hypermedia/reference/attributes/" detail-keys 3)
"uniq-attrs" (build-ref-items-with-href
(get raw-data "uniq-attrs")
"/hypermedia/reference/attributes/" detail-keys 3)})))
;; --------------------------------------------------------------------------
;; build-attr-detail / build-header-detail / build-event-detail
;;
;; Lookup a slug in a detail dict, reshape for page rendering.
;; --------------------------------------------------------------------------
(define build-attr-detail
(fn (slug detail)
;; detail: dict with "description", "example", "handler", "demo" keys or nil
(if (nil? detail)
{"attr-not-found" true}
{"attr-not-found" nil
"attr-title" slug
"attr-description" (get detail "description")
"attr-example" (get detail "example")
"attr-handler" (get detail "handler")
"attr-demo" (get detail "demo")
"attr-wire-id" (if (has-key? detail "handler")
(str "ref-wire-"
(replace (replace slug ":" "-") "*" "star"))
nil)})))
(define build-header-detail
(fn (slug detail)
(if (nil? detail)
{"header-not-found" true}
{"header-not-found" nil
"header-title" slug
"header-direction" (get detail "direction")
"header-description" (get detail "description")
"header-example" (get detail "example")
"header-demo" (get detail "demo")})))
(define build-event-detail
(fn (slug detail)
(if (nil? detail)
{"event-not-found" true}
{"event-not-found" nil
"event-title" slug
"event-description" (get detail "description")
"event-example" (get detail "example")
"event-demo" (get detail "demo")})))
;; --------------------------------------------------------------------------
;; build-component-source
;;
;; Reconstruct defcomp/defisland source from component metadata.
;; --------------------------------------------------------------------------
(define build-component-source
(fn (comp-data)
;; comp-data: dict with "type", "name", "params", "has-children", "body-sx", "affinity"
(let ((comp-type (get comp-data "type"))
(name (get comp-data "name"))
(params (get comp-data "params"))
(has-children (get comp-data "has-children"))
(body-sx (get comp-data "body-sx"))
(affinity (get comp-data "affinity")))
(if (= comp-type "not-found")
(str ";; component " name " not found")
(let ((param-strs (if (empty? params)
(if has-children
(list "&rest" "children")
(list))
(if has-children
(append (cons "&key" params) (list "&rest" "children"))
(cons "&key" params))))
(params-sx (str "(" (join " " param-strs) ")"))
(form-name (if (= comp-type "island") "defisland" "defcomp"))
(affinity-str (if (and (= comp-type "component")
(not (nil? affinity))
(not (= affinity "auto")))
(str " :affinity " affinity)
"")))
(str "(" form-name " " name " " params-sx affinity-str "\n " body-sx ")"))))))
;; --------------------------------------------------------------------------
;; build-bundle-analysis
;;
;; Compute per-page bundle stats from pre-extracted component data.
;; --------------------------------------------------------------------------
(define build-bundle-analysis
(fn (pages-raw components-raw total-components total-macros pure-count io-count)
;; pages-raw: list of {:name :path :direct :needed-names}
;; components-raw: dict of name → {:is-pure :affinity :render-target :io-refs :deps :source}
(let ((pages-data (list)))
(for-each
(fn (page)
(let ((needed-names (get page "needed-names"))
(n (len needed-names))
(pct (if (> total-components 0)
(round (* (/ n total-components) 100))
0))
(savings (- 100 pct))
(pure-in-page 0)
(io-in-page 0)
(page-io-refs (list))
(comp-details (list)))
;; Walk needed components
(for-each
(fn (comp-name)
(let ((info (get components-raw comp-name)))
(when (not (nil? info))
(if (get info "is-pure")
(set! pure-in-page (+ pure-in-page 1))
(do
(set! io-in-page (+ io-in-page 1))
(for-each
(fn (ref) (when (not (some (fn (r) (= r ref)) page-io-refs))
(append! page-io-refs ref)))
(or (get info "io-refs") (list)))))
(append! comp-details
{"name" comp-name
"is-pure" (get info "is-pure")
"affinity" (get info "affinity")
"render-target" (get info "render-target")
"io-refs" (or (get info "io-refs") (list))
"deps" (or (get info "deps") (list))
"source" (get info "source")}))))
needed-names)
(append! pages-data
{"name" (get page "name")
"path" (get page "path")
"direct" (get page "direct")
"needed" n
"pct" pct
"savings" savings
"io-refs" (len page-io-refs)
"pure-in-page" pure-in-page
"io-in-page" io-in-page
"components" comp-details})))
pages-raw)
{"pages" pages-data
"total-components" total-components
"total-macros" total-macros
"pure-count" pure-count
"io-count" io-count})))
;; --------------------------------------------------------------------------
;; build-routing-analysis
;;
;; Classify pages by routing mode (client vs server).
;; --------------------------------------------------------------------------
(define build-routing-analysis
(fn (pages-raw)
;; pages-raw: list of {:name :path :has-data :content-src}
(let ((pages-data (list))
(client-count 0)
(server-count 0))
(for-each
(fn (page)
(let ((has-data (get page "has-data"))
(content-src (or (get page "content-src") ""))
(mode nil)
(reason ""))
(cond
has-data
(do (set! mode "server")
(set! reason "Has :data expression — needs server IO")
(set! server-count (+ server-count 1)))
(empty? content-src)
(do (set! mode "server")
(set! reason "No content expression")
(set! server-count (+ server-count 1)))
:else
(do (set! mode "client")
(set! client-count (+ client-count 1))))
(append! pages-data
{"name" (get page "name")
"path" (get page "path")
"mode" mode
"has-data" has-data
"content-expr" (if (> (len content-src) 80)
(str (slice content-src 0 80) "...")
content-src)
"reason" reason})))
pages-raw)
{"pages" pages-data
"total-pages" (+ client-count server-count)
"client-count" client-count
"server-count" server-count})))
;; --------------------------------------------------------------------------
;; build-affinity-analysis
;;
;; Package component affinity info + page render plans for display.
;; --------------------------------------------------------------------------
(define build-affinity-analysis
(fn (demo-components page-plans)
{"components" demo-components
"page-plans" page-plans}))

3163
shared/sx/ref/platform_js.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1428,6 +1428,7 @@ SPEC_MODULES = {
"router": ("router.sx", "router (client-side route matching)"),
"engine": ("engine.sx", "engine (fetch/swap/trigger pure logic)"),
"signals": ("signals.sx", "signals (reactive signal runtime)"),
"page-helpers": ("page-helpers.sx", "page-helpers (pure data transformation helpers)"),
}
EXTENSION_NAMES = {"continuations"}

View File

@@ -103,8 +103,11 @@ def compile_ref_to_js(
if "boot" in adapter_set:
spec_mod_set.add("router")
spec_mod_set.add("deps")
if "page-helpers" in SPEC_MODULES:
spec_mod_set.add("page-helpers")
has_deps = "deps" in spec_mod_set
has_router = "router" in spec_mod_set
has_page_helpers = "page-helpers" in spec_mod_set
# Resolve extensions
ext_set = set()
@@ -198,12 +201,12 @@ def compile_ref_to_js(
if name in adapter_set and name in adapter_platform:
parts.append(adapter_platform[name])
parts.append(fixups_js(has_html, has_sx, has_dom, has_signals, has_deps))
parts.append(fixups_js(has_html, has_sx, has_dom, has_signals, has_deps, has_page_helpers))
if has_continuations:
parts.append(CONTINUATIONS_JS)
if has_dom:
parts.append(ASYNC_IO_JS)
parts.append(public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_boot, has_parser, adapter_label, has_deps, has_router, has_signals))
parts.append(public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_boot, has_parser, adapter_label, has_deps, has_router, has_signals, has_page_helpers))
parts.append(EPILOGUE)
build_ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")

View File

@@ -2342,452 +2342,147 @@ def env_components(env):
return filter(lambda k: (lambda v: (is_component(v) if sx_truthy(is_component(v)) else is_macro(v)))(env_get(env, k)), keys(env))
# === Transpiled from engine (fetch/swap/trigger pure logic) ===
# === Transpiled from page-helpers (pure data transformation helpers) ===
# ENGINE_VERBS
ENGINE_VERBS = ['get', 'post', 'put', 'delete', 'patch']
# special-form-category-map
special_form_category_map = {'if': 'Control Flow', 'when': 'Control Flow', 'cond': 'Control Flow', 'case': 'Control Flow', 'and': 'Control Flow', 'or': 'Control Flow', 'let': 'Binding', 'let*': 'Binding', 'letrec': 'Binding', 'define': 'Binding', 'set!': 'Binding', 'lambda': 'Functions & Components', 'fn': 'Functions & Components', 'defcomp': 'Functions & Components', 'defmacro': 'Functions & Components', 'begin': 'Sequencing & Threading', 'do': 'Sequencing & Threading', '->': 'Sequencing & Threading', 'quote': 'Quoting', 'quasiquote': 'Quoting', 'reset': 'Continuations', 'shift': 'Continuations', 'dynamic-wind': 'Guards', 'map': 'Higher-Order Forms', 'map-indexed': 'Higher-Order Forms', 'filter': 'Higher-Order Forms', 'reduce': 'Higher-Order Forms', 'some': 'Higher-Order Forms', 'every?': 'Higher-Order Forms', 'for-each': 'Higher-Order Forms', 'defstyle': 'Domain Definitions', 'defhandler': 'Domain Definitions', 'defpage': 'Domain Definitions', 'defquery': 'Domain Definitions', 'defaction': 'Domain Definitions'}
# DEFAULT_SWAP
DEFAULT_SWAP = 'outerHTML'
# extract-define-kwargs
def extract_define_kwargs(expr):
result = {}
items = slice(expr, 2)
n = len(items)
for idx in range(0, n):
if sx_truthy((((idx + 1) < n) if not sx_truthy(((idx + 1) < n)) else (type_of(nth(items, idx)) == 'keyword'))):
key = keyword_name(nth(items, idx))
val = nth(items, (idx + 1))
result[key] = (sx_str('(', join(' ', map(serialize, val)), ')') if sx_truthy((type_of(val) == 'list')) else sx_str(val))
return result
# parse-time
def parse_time(s):
if sx_truthy(is_nil(s)):
return 0
elif sx_truthy(ends_with_p(s, 'ms')):
return parse_int(s, 0)
elif sx_truthy(ends_with_p(s, 's')):
return (parse_int(replace(s, 's', ''), 0) * 1000)
# categorize-special-forms
def categorize_special_forms(parsed_exprs):
categories = {}
for expr in parsed_exprs:
if sx_truthy(((type_of(expr) == 'list') if not sx_truthy((type_of(expr) == 'list')) else ((len(expr) >= 2) if not sx_truthy((len(expr) >= 2)) else ((type_of(first(expr)) == 'symbol') if not sx_truthy((type_of(first(expr)) == 'symbol')) else (symbol_name(first(expr)) == 'define-special-form'))))):
name = nth(expr, 1)
kwargs = extract_define_kwargs(expr)
category = (get(special_form_category_map, name) if sx_truthy(get(special_form_category_map, name)) else 'Other')
if sx_truthy((not sx_truthy(has_key_p(categories, category)))):
categories[category] = []
get(categories, category).append({'name': name, 'syntax': (get(kwargs, 'syntax') if sx_truthy(get(kwargs, 'syntax')) else ''), 'doc': (get(kwargs, 'doc') if sx_truthy(get(kwargs, 'doc')) else ''), 'tail-position': (get(kwargs, 'tail-position') if sx_truthy(get(kwargs, 'tail-position')) else ''), 'example': (get(kwargs, 'example') if sx_truthy(get(kwargs, 'example')) else '')})
return categories
# build-ref-items-with-href
def build_ref_items_with_href(items, base_path, detail_keys, n_fields):
return map(lambda item: ((lambda name: (lambda field2: (lambda field3: {'name': name, 'desc': field2, 'exists': field3, 'href': (sx_str(base_path, name) if sx_truthy((field3 if not sx_truthy(field3) else some(lambda k: (k == name), detail_keys))) else NIL)})(nth(item, 2)))(nth(item, 1)))(nth(item, 0)) if sx_truthy((n_fields == 3)) else (lambda name: (lambda desc: {'name': name, 'desc': desc, 'href': (sx_str(base_path, name) if sx_truthy(some(lambda k: (k == name), detail_keys)) else NIL)})(nth(item, 1)))(nth(item, 0))), items)
# build-reference-data
def build_reference_data(slug, raw_data, detail_keys):
_match = slug
if _match == 'attributes':
return {'req-attrs': build_ref_items_with_href(get(raw_data, 'req-attrs'), '/hypermedia/reference/attributes/', detail_keys, 3), 'beh-attrs': build_ref_items_with_href(get(raw_data, 'beh-attrs'), '/hypermedia/reference/attributes/', detail_keys, 3), 'uniq-attrs': build_ref_items_with_href(get(raw_data, 'uniq-attrs'), '/hypermedia/reference/attributes/', detail_keys, 3)}
elif _match == 'headers':
return {'req-headers': build_ref_items_with_href(get(raw_data, 'req-headers'), '/hypermedia/reference/headers/', detail_keys, 3), 'resp-headers': build_ref_items_with_href(get(raw_data, 'resp-headers'), '/hypermedia/reference/headers/', detail_keys, 3)}
elif _match == 'events':
return {'events-list': build_ref_items_with_href(get(raw_data, 'events-list'), '/hypermedia/reference/events/', detail_keys, 2)}
elif _match == 'js-api':
return {'js-api-list': map(lambda item: {'name': nth(item, 0), 'desc': nth(item, 1)}, get(raw_data, 'js-api-list'))}
else:
return parse_int(s, 0)
return {'req-attrs': build_ref_items_with_href(get(raw_data, 'req-attrs'), '/hypermedia/reference/attributes/', detail_keys, 3), 'beh-attrs': build_ref_items_with_href(get(raw_data, 'beh-attrs'), '/hypermedia/reference/attributes/', detail_keys, 3), 'uniq-attrs': build_ref_items_with_href(get(raw_data, 'uniq-attrs'), '/hypermedia/reference/attributes/', detail_keys, 3)}
# parse-trigger-spec
def parse_trigger_spec(spec):
if sx_truthy(is_nil(spec)):
return NIL
# build-attr-detail
def build_attr_detail(slug, detail):
if sx_truthy(is_nil(detail)):
return {'attr-not-found': True}
else:
raw_parts = split(spec, ',')
return filter(lambda x: (not sx_truthy(is_nil(x))), map(lambda part: (lambda tokens: (NIL if sx_truthy(empty_p(tokens)) else ({'event': 'every', 'modifiers': {'interval': parse_time(nth(tokens, 1))}} if sx_truthy(((first(tokens) == 'every') if not sx_truthy((first(tokens) == 'every')) else (len(tokens) >= 2))) else (lambda mods: _sx_begin(for_each(lambda tok: (_sx_dict_set(mods, 'once', True) if sx_truthy((tok == 'once')) else (_sx_dict_set(mods, 'changed', True) if sx_truthy((tok == 'changed')) else (_sx_dict_set(mods, 'delay', parse_time(slice(tok, 6))) if sx_truthy(starts_with_p(tok, 'delay:')) else (_sx_dict_set(mods, 'from', slice(tok, 5)) if sx_truthy(starts_with_p(tok, 'from:')) else NIL)))), rest(tokens)), {'event': first(tokens), 'modifiers': mods}))({}))))(split(trim(part), ' ')), raw_parts))
return {'attr-not-found': NIL, 'attr-title': slug, 'attr-description': get(detail, 'description'), 'attr-example': get(detail, 'example'), 'attr-handler': get(detail, 'handler'), 'attr-demo': get(detail, 'demo'), 'attr-wire-id': (sx_str('ref-wire-', replace(replace(slug, ':', '-'), '*', 'star')) if sx_truthy(has_key_p(detail, 'handler')) else NIL)}
# default-trigger
def default_trigger(tag_name):
if sx_truthy((tag_name == 'FORM')):
return [{'event': 'submit', 'modifiers': {}}]
elif sx_truthy(((tag_name == 'INPUT') if sx_truthy((tag_name == 'INPUT')) else ((tag_name == 'SELECT') if sx_truthy((tag_name == 'SELECT')) else (tag_name == 'TEXTAREA')))):
return [{'event': 'change', 'modifiers': {}}]
# build-header-detail
def build_header_detail(slug, detail):
if sx_truthy(is_nil(detail)):
return {'header-not-found': True}
else:
return [{'event': 'click', 'modifiers': {}}]
return {'header-not-found': NIL, 'header-title': slug, 'header-direction': get(detail, 'direction'), 'header-description': get(detail, 'description'), 'header-example': get(detail, 'example'), 'header-demo': get(detail, 'demo')}
# get-verb-info
def get_verb_info(el):
return some(lambda verb: (lambda url: ({'method': upper(verb), 'url': url} if sx_truthy(url) else NIL))(dom_get_attr(el, sx_str('sx-', verb))), ENGINE_VERBS)
# build-event-detail
def build_event_detail(slug, detail):
if sx_truthy(is_nil(detail)):
return {'event-not-found': True}
else:
return {'event-not-found': NIL, 'event-title': slug, 'event-description': get(detail, 'description'), 'event-example': get(detail, 'example'), 'event-demo': get(detail, 'demo')}
# build-request-headers
def build_request_headers(el, loaded_components, css_hash):
headers = {'SX-Request': 'true', 'SX-Current-URL': browser_location_href()}
target_sel = dom_get_attr(el, 'sx-target')
if sx_truthy(target_sel):
headers['SX-Target'] = target_sel
if sx_truthy((not sx_truthy(empty_p(loaded_components)))):
headers['SX-Components'] = join(',', loaded_components)
if sx_truthy(css_hash):
headers['SX-Css'] = css_hash
extra_h = dom_get_attr(el, 'sx-headers')
if sx_truthy(extra_h):
parsed = parse_header_value(extra_h)
if sx_truthy(parsed):
for key in keys(parsed):
headers[key] = sx_str(get(parsed, key))
return headers
# build-component-source
def build_component_source(comp_data):
comp_type = get(comp_data, 'type')
name = get(comp_data, 'name')
params = get(comp_data, 'params')
has_children = get(comp_data, 'has-children')
body_sx = get(comp_data, 'body-sx')
affinity = get(comp_data, 'affinity')
if sx_truthy((comp_type == 'not-found')):
return sx_str(';; component ', name, ' not found')
else:
param_strs = ((['&rest', 'children'] if sx_truthy(has_children) else []) if sx_truthy(empty_p(params)) else (append(cons('&key', params), ['&rest', 'children']) if sx_truthy(has_children) else cons('&key', params)))
params_sx = sx_str('(', join(' ', param_strs), ')')
form_name = ('defisland' if sx_truthy((comp_type == 'island')) else 'defcomp')
affinity_str = (sx_str(' :affinity ', affinity) if sx_truthy(((comp_type == 'component') if not sx_truthy((comp_type == 'component')) else ((not sx_truthy(is_nil(affinity))) if not sx_truthy((not sx_truthy(is_nil(affinity)))) else (not sx_truthy((affinity == 'auto')))))) else '')
return sx_str('(', form_name, ' ', name, ' ', params_sx, affinity_str, '\n ', body_sx, ')')
# process-response-headers
def process_response_headers(get_header):
return {'redirect': get_header('SX-Redirect'), 'refresh': get_header('SX-Refresh'), 'trigger': get_header('SX-Trigger'), 'retarget': get_header('SX-Retarget'), 'reswap': get_header('SX-Reswap'), 'location': get_header('SX-Location'), 'replace-url': get_header('SX-Replace-Url'), 'css-hash': get_header('SX-Css-Hash'), 'trigger-swap': get_header('SX-Trigger-After-Swap'), 'trigger-settle': get_header('SX-Trigger-After-Settle'), 'content-type': get_header('Content-Type'), 'cache-invalidate': get_header('SX-Cache-Invalidate'), 'cache-update': get_header('SX-Cache-Update')}
# parse-swap-spec
def parse_swap_spec(raw_swap, global_transitions_p):
# build-bundle-analysis
def build_bundle_analysis(pages_raw, components_raw, total_components, total_macros, pure_count, io_count):
_cells = {}
parts = split((raw_swap if sx_truthy(raw_swap) else DEFAULT_SWAP), ' ')
style = first(parts)
_cells['use_transition'] = global_transitions_p
for p in rest(parts):
if sx_truthy((p == 'transition:true')):
_cells['use_transition'] = True
elif sx_truthy((p == 'transition:false')):
_cells['use_transition'] = False
return {'style': style, 'transition': _cells['use_transition']}
pages_data = []
for page in pages_raw:
needed_names = get(page, 'needed-names')
n = len(needed_names)
pct = (round(((n / total_components) * 100)) if sx_truthy((total_components > 0)) else 0)
savings = (100 - pct)
_cells['pure_in_page'] = 0
_cells['io_in_page'] = 0
page_io_refs = []
comp_details = []
for comp_name in needed_names:
info = get(components_raw, comp_name)
if sx_truthy((not sx_truthy(is_nil(info)))):
if sx_truthy(get(info, 'is-pure')):
_cells['pure_in_page'] = (_cells['pure_in_page'] + 1)
else:
_cells['io_in_page'] = (_cells['io_in_page'] + 1)
for ref in (get(info, 'io-refs') if sx_truthy(get(info, 'io-refs')) else []):
if sx_truthy((not sx_truthy(some(lambda r: (r == ref), page_io_refs)))):
page_io_refs.append(ref)
comp_details.append({'name': comp_name, 'is-pure': get(info, 'is-pure'), 'affinity': get(info, 'affinity'), 'render-target': get(info, 'render-target'), 'io-refs': (get(info, 'io-refs') if sx_truthy(get(info, 'io-refs')) else []), 'deps': (get(info, 'deps') if sx_truthy(get(info, 'deps')) else []), 'source': get(info, 'source')})
pages_data.append({'name': get(page, 'name'), 'path': get(page, 'path'), 'direct': get(page, 'direct'), 'needed': n, 'pct': pct, 'savings': savings, 'io-refs': len(page_io_refs), 'pure-in-page': _cells['pure_in_page'], 'io-in-page': _cells['io_in_page'], 'components': comp_details})
return {'pages': pages_data, 'total-components': total_components, 'total-macros': total_macros, 'pure-count': pure_count, 'io-count': io_count}
# parse-retry-spec
def parse_retry_spec(retry_attr):
if sx_truthy(is_nil(retry_attr)):
return NIL
else:
parts = split(retry_attr, ':')
return {'strategy': first(parts), 'start-ms': parse_int(nth(parts, 1), 1000), 'cap-ms': parse_int(nth(parts, 2), 30000)}
# next-retry-ms
def next_retry_ms(current_ms, cap_ms):
return min((current_ms * 2), cap_ms)
# filter-params
def filter_params(params_spec, all_params):
if sx_truthy(is_nil(params_spec)):
return all_params
elif sx_truthy((params_spec == 'none')):
return []
elif sx_truthy((params_spec == '*')):
return all_params
elif sx_truthy(starts_with_p(params_spec, 'not ')):
excluded = map(trim, split(slice(params_spec, 4), ','))
return filter(lambda p: (not sx_truthy(contains_p(excluded, first(p)))), all_params)
else:
allowed = map(trim, split(params_spec, ','))
return filter(lambda p: contains_p(allowed, first(p)), all_params)
# resolve-target
def resolve_target(el):
sel = dom_get_attr(el, 'sx-target')
if sx_truthy((is_nil(sel) if sx_truthy(is_nil(sel)) else (sel == 'this'))):
return el
elif sx_truthy((sel == 'closest')):
return dom_parent(el)
else:
return dom_query(sel)
# apply-optimistic
def apply_optimistic(el):
directive = dom_get_attr(el, 'sx-optimistic')
if sx_truthy(is_nil(directive)):
return NIL
else:
target = (resolve_target(el) if sx_truthy(resolve_target(el)) else el)
state = {'target': target, 'directive': directive}
(_sx_begin(_sx_dict_set(state, 'opacity', dom_get_style(target, 'opacity')), dom_set_style(target, 'opacity', '0'), dom_set_style(target, 'pointer-events', 'none')) if sx_truthy((directive == 'remove')) else (_sx_begin(_sx_dict_set(state, 'disabled', dom_get_prop(target, 'disabled')), dom_set_prop(target, 'disabled', True)) if sx_truthy((directive == 'disable')) else ((lambda cls: _sx_begin(_sx_dict_set(state, 'add-class', cls), dom_add_class(target, cls)))(slice(directive, 10)) if sx_truthy(starts_with_p(directive, 'add-class:')) else NIL)))
return state
# revert-optimistic
def revert_optimistic(state):
if sx_truthy(state):
target = get(state, 'target')
directive = get(state, 'directive')
if sx_truthy((directive == 'remove')):
dom_set_style(target, 'opacity', (get(state, 'opacity') if sx_truthy(get(state, 'opacity')) else ''))
return dom_set_style(target, 'pointer-events', '')
elif sx_truthy((directive == 'disable')):
return dom_set_prop(target, 'disabled', (get(state, 'disabled') if sx_truthy(get(state, 'disabled')) else False))
elif sx_truthy(get(state, 'add-class')):
return dom_remove_class(target, get(state, 'add-class'))
return NIL
return NIL
# find-oob-swaps
def find_oob_swaps(container):
results = []
for attr in ['sx-swap-oob', 'hx-swap-oob']:
oob_els = dom_query_all(container, sx_str('[', attr, ']'))
for oob in oob_els:
swap_type = (dom_get_attr(oob, attr) if sx_truthy(dom_get_attr(oob, attr)) else 'outerHTML')
target_id = dom_id(oob)
dom_remove_attr(oob, attr)
if sx_truthy(target_id):
results.append({'element': oob, 'swap-type': swap_type, 'target-id': target_id})
return results
# morph-node
def morph_node(old_node, new_node):
if sx_truthy((dom_has_attr_p(old_node, 'sx-preserve') if sx_truthy(dom_has_attr_p(old_node, 'sx-preserve')) else dom_has_attr_p(old_node, 'sx-ignore'))):
return NIL
elif sx_truthy((dom_has_attr_p(old_node, 'data-sx-island') if not sx_truthy(dom_has_attr_p(old_node, 'data-sx-island')) else (is_processed_p(old_node, 'island-hydrated') if not sx_truthy(is_processed_p(old_node, 'island-hydrated')) else (dom_has_attr_p(new_node, 'data-sx-island') if not sx_truthy(dom_has_attr_p(new_node, 'data-sx-island')) else (dom_get_attr(old_node, 'data-sx-island') == dom_get_attr(new_node, 'data-sx-island')))))):
return morph_island_children(old_node, new_node)
elif sx_truthy(((not sx_truthy((dom_node_type(old_node) == dom_node_type(new_node)))) if sx_truthy((not sx_truthy((dom_node_type(old_node) == dom_node_type(new_node))))) else (not sx_truthy((dom_node_name(old_node) == dom_node_name(new_node)))))):
return dom_replace_child(dom_parent(old_node), dom_clone(new_node), old_node)
elif sx_truthy(((dom_node_type(old_node) == 3) if sx_truthy((dom_node_type(old_node) == 3)) else (dom_node_type(old_node) == 8))):
if sx_truthy((not sx_truthy((dom_text_content(old_node) == dom_text_content(new_node))))):
return dom_set_text_content(old_node, dom_text_content(new_node))
return NIL
elif sx_truthy((dom_node_type(old_node) == 1)):
sync_attrs(old_node, new_node)
if sx_truthy((not sx_truthy((dom_is_active_element_p(old_node) if not sx_truthy(dom_is_active_element_p(old_node)) else dom_is_input_element_p(old_node))))):
return morph_children(old_node, new_node)
return NIL
return NIL
# sync-attrs
def sync_attrs(old_el, new_el):
ra_str = (dom_get_attr(old_el, 'data-sx-reactive-attrs') if sx_truthy(dom_get_attr(old_el, 'data-sx-reactive-attrs')) else '')
reactive_attrs = ([] if sx_truthy(empty_p(ra_str)) else split(ra_str, ','))
for attr in dom_attr_list(new_el):
name = first(attr)
val = nth(attr, 1)
if sx_truthy(((not sx_truthy((dom_get_attr(old_el, name) == val))) if not sx_truthy((not sx_truthy((dom_get_attr(old_el, name) == val)))) else (not sx_truthy(contains_p(reactive_attrs, name))))):
dom_set_attr(old_el, name, val)
return for_each(lambda attr: (lambda aname: (dom_remove_attr(old_el, aname) if sx_truthy(((not sx_truthy(dom_has_attr_p(new_el, aname))) if not sx_truthy((not sx_truthy(dom_has_attr_p(new_el, aname)))) else ((not sx_truthy(contains_p(reactive_attrs, aname))) if not sx_truthy((not sx_truthy(contains_p(reactive_attrs, aname)))) else (not sx_truthy((aname == 'data-sx-reactive-attrs')))))) else NIL))(first(attr)), dom_attr_list(old_el))
# morph-children
def morph_children(old_parent, new_parent):
# build-routing-analysis
def build_routing_analysis(pages_raw):
_cells = {}
old_kids = dom_child_list(old_parent)
new_kids = dom_child_list(new_parent)
old_by_id = reduce(lambda acc, kid: (lambda id_: (_sx_begin(_sx_dict_set(acc, id_, kid), acc) if sx_truthy(id_) else acc))(dom_id(kid)), {}, old_kids)
_cells['oi'] = 0
for new_child in new_kids:
match_id = dom_id(new_child)
match_by_id = (dict_get(old_by_id, match_id) if sx_truthy(match_id) else NIL)
if sx_truthy((match_by_id if not sx_truthy(match_by_id) else (not sx_truthy(is_nil(match_by_id))))):
if sx_truthy(((_cells['oi'] < len(old_kids)) if not sx_truthy((_cells['oi'] < len(old_kids))) else (not sx_truthy((match_by_id == nth(old_kids, _cells['oi'])))))):
dom_insert_before(old_parent, match_by_id, (nth(old_kids, _cells['oi']) if sx_truthy((_cells['oi'] < len(old_kids))) else NIL))
morph_node(match_by_id, new_child)
_cells['oi'] = (_cells['oi'] + 1)
elif sx_truthy((_cells['oi'] < len(old_kids))):
old_child = nth(old_kids, _cells['oi'])
if sx_truthy((dom_id(old_child) if not sx_truthy(dom_id(old_child)) else (not sx_truthy(match_id)))):
dom_insert_before(old_parent, dom_clone(new_child), old_child)
else:
morph_node(old_child, new_child)
_cells['oi'] = (_cells['oi'] + 1)
pages_data = []
_cells['client_count'] = 0
_cells['server_count'] = 0
for page in pages_raw:
has_data = get(page, 'has-data')
content_src = (get(page, 'content-src') if sx_truthy(get(page, 'content-src')) else '')
_cells['mode'] = NIL
_cells['reason'] = ''
if sx_truthy(has_data):
_cells['mode'] = 'server'
_cells['reason'] = 'Has :data expression — needs server IO'
_cells['server_count'] = (_cells['server_count'] + 1)
elif sx_truthy(empty_p(content_src)):
_cells['mode'] = 'server'
_cells['reason'] = 'No content expression'
_cells['server_count'] = (_cells['server_count'] + 1)
else:
dom_append(old_parent, dom_clone(new_child))
return for_each(lambda i: ((lambda leftover: (dom_remove_child(old_parent, leftover) if sx_truthy((dom_is_child_of_p(leftover, old_parent) if not sx_truthy(dom_is_child_of_p(leftover, old_parent)) else ((not sx_truthy(dom_has_attr_p(leftover, 'sx-preserve'))) if not sx_truthy((not sx_truthy(dom_has_attr_p(leftover, 'sx-preserve')))) else (not sx_truthy(dom_has_attr_p(leftover, 'sx-ignore')))))) else NIL))(nth(old_kids, i)) if sx_truthy((i >= _cells['oi'])) else NIL), range(_cells['oi'], len(old_kids)))
_cells['mode'] = 'client'
_cells['client_count'] = (_cells['client_count'] + 1)
pages_data.append({'name': get(page, 'name'), 'path': get(page, 'path'), 'mode': _cells['mode'], 'has-data': has_data, 'content-expr': (sx_str(slice(content_src, 0, 80), '...') if sx_truthy((len(content_src) > 80)) else content_src), 'reason': _cells['reason']})
return {'pages': pages_data, 'total-pages': (_cells['client_count'] + _cells['server_count']), 'client-count': _cells['client_count'], 'server-count': _cells['server_count']}
# morph-island-children
def morph_island_children(old_island, new_island):
old_lakes = dom_query_all(old_island, '[data-sx-lake]')
new_lakes = dom_query_all(new_island, '[data-sx-lake]')
old_marshes = dom_query_all(old_island, '[data-sx-marsh]')
new_marshes = dom_query_all(new_island, '[data-sx-marsh]')
new_lake_map = {}
new_marsh_map = {}
for lake in new_lakes:
id_ = dom_get_attr(lake, 'data-sx-lake')
if sx_truthy(id_):
new_lake_map[id_] = lake
for marsh in new_marshes:
id_ = dom_get_attr(marsh, 'data-sx-marsh')
if sx_truthy(id_):
new_marsh_map[id_] = marsh
for old_lake in old_lakes:
id_ = dom_get_attr(old_lake, 'data-sx-lake')
new_lake = dict_get(new_lake_map, id_)
if sx_truthy(new_lake):
sync_attrs(old_lake, new_lake)
morph_children(old_lake, new_lake)
for old_marsh in old_marshes:
id_ = dom_get_attr(old_marsh, 'data-sx-marsh')
new_marsh = dict_get(new_marsh_map, id_)
if sx_truthy(new_marsh):
morph_marsh(old_marsh, new_marsh, old_island)
return process_signal_updates(new_island)
# morph-marsh
def morph_marsh(old_marsh, new_marsh, island_el):
transform = dom_get_data(old_marsh, 'sx-marsh-transform')
env = dom_get_data(old_marsh, 'sx-marsh-env')
new_html = dom_inner_html(new_marsh)
if sx_truthy((env if not sx_truthy(env) else (new_html if not sx_truthy(new_html) else (not sx_truthy(empty_p(new_html)))))):
parsed = parse(new_html)
sx_content = (invoke(transform, parsed) if sx_truthy(transform) else parsed)
dispose_marsh_scope(old_marsh)
return with_marsh_scope(old_marsh, lambda : (lambda new_dom: _sx_begin(dom_remove_children_after(old_marsh, NIL), dom_append(old_marsh, new_dom)))(render_to_dom(sx_content, env, NIL)))
else:
sync_attrs(old_marsh, new_marsh)
return morph_children(old_marsh, new_marsh)
# process-signal-updates
def process_signal_updates(root):
signal_els = dom_query_all(root, '[data-sx-signal]')
return for_each(lambda el: (lambda spec: ((lambda colon_idx: ((lambda store_name: (lambda raw_value: _sx_begin((lambda parsed: reset_b(use_store(store_name), parsed))(json_parse(raw_value)), dom_remove_attr(el, 'data-sx-signal')))(slice(spec, (colon_idx + 1))))(slice(spec, 0, colon_idx)) if sx_truthy((colon_idx > 0)) else NIL))(index_of(spec, ':')) if sx_truthy(spec) else NIL))(dom_get_attr(el, 'data-sx-signal')), signal_els)
# swap-dom-nodes
def swap_dom_nodes(target, new_nodes, strategy):
_match = strategy
if _match == 'innerHTML':
if sx_truthy(dom_is_fragment_p(new_nodes)):
return morph_children(target, new_nodes)
else:
wrapper = dom_create_element('div', NIL)
dom_append(wrapper, new_nodes)
return morph_children(target, wrapper)
elif _match == 'outerHTML':
parent = dom_parent(target)
((lambda fc: (_sx_begin(morph_node(target, fc), (lambda sib: insert_remaining_siblings(parent, target, sib))(dom_next_sibling(fc))) if sx_truthy(fc) else dom_remove_child(parent, target)))(dom_first_child(new_nodes)) if sx_truthy(dom_is_fragment_p(new_nodes)) else morph_node(target, new_nodes))
return parent
elif _match == 'afterend':
return dom_insert_after(target, new_nodes)
elif _match == 'beforeend':
return dom_append(target, new_nodes)
elif _match == 'afterbegin':
return dom_prepend(target, new_nodes)
elif _match == 'beforebegin':
return dom_insert_before(dom_parent(target), new_nodes, target)
elif _match == 'delete':
return dom_remove_child(dom_parent(target), target)
elif _match == 'none':
return NIL
else:
if sx_truthy(dom_is_fragment_p(new_nodes)):
return morph_children(target, new_nodes)
else:
wrapper = dom_create_element('div', NIL)
dom_append(wrapper, new_nodes)
return morph_children(target, wrapper)
# insert-remaining-siblings
def insert_remaining_siblings(parent, ref_node, sib):
if sx_truthy(sib):
next = dom_next_sibling(sib)
dom_insert_after(ref_node, sib)
return insert_remaining_siblings(parent, sib, next)
return NIL
# swap-html-string
def swap_html_string(target, html, strategy):
_match = strategy
if _match == 'innerHTML':
return dom_set_inner_html(target, html)
elif _match == 'outerHTML':
parent = dom_parent(target)
dom_insert_adjacent_html(target, 'afterend', html)
dom_remove_child(parent, target)
return parent
elif _match == 'afterend':
return dom_insert_adjacent_html(target, 'afterend', html)
elif _match == 'beforeend':
return dom_insert_adjacent_html(target, 'beforeend', html)
elif _match == 'afterbegin':
return dom_insert_adjacent_html(target, 'afterbegin', html)
elif _match == 'beforebegin':
return dom_insert_adjacent_html(target, 'beforebegin', html)
elif _match == 'delete':
return dom_remove_child(dom_parent(target), target)
elif _match == 'none':
return NIL
else:
return dom_set_inner_html(target, html)
# handle-history
def handle_history(el, url, resp_headers):
push_url = dom_get_attr(el, 'sx-push-url')
replace_url = dom_get_attr(el, 'sx-replace-url')
hdr_replace = get(resp_headers, 'replace-url')
if sx_truthy(hdr_replace):
return browser_replace_state(hdr_replace)
elif sx_truthy((push_url if not sx_truthy(push_url) else (not sx_truthy((push_url == 'false'))))):
return browser_push_state((url if sx_truthy((push_url == 'true')) else push_url))
elif sx_truthy((replace_url if not sx_truthy(replace_url) else (not sx_truthy((replace_url == 'false'))))):
return browser_replace_state((url if sx_truthy((replace_url == 'true')) else replace_url))
return NIL
# PRELOAD_TTL
PRELOAD_TTL = 30000
# preload-cache-get
def preload_cache_get(cache, url):
entry = dict_get(cache, url)
if sx_truthy(is_nil(entry)):
return NIL
else:
if sx_truthy(((now_ms() - get(entry, 'timestamp')) > PRELOAD_TTL)):
dict_delete(cache, url)
return NIL
else:
dict_delete(cache, url)
return entry
# preload-cache-set
def preload_cache_set(cache, url, text, content_type):
return _sx_dict_set(cache, url, {'text': text, 'content-type': content_type, 'timestamp': now_ms()})
# classify-trigger
def classify_trigger(trigger):
event = get(trigger, 'event')
if sx_truthy((event == 'every')):
return 'poll'
elif sx_truthy((event == 'intersect')):
return 'intersect'
elif sx_truthy((event == 'load')):
return 'load'
elif sx_truthy((event == 'revealed')):
return 'revealed'
else:
return 'event'
# should-boost-link?
def should_boost_link_p(link):
href = dom_get_attr(link, 'href')
return (href if not sx_truthy(href) else ((not sx_truthy(starts_with_p(href, '#'))) if not sx_truthy((not sx_truthy(starts_with_p(href, '#')))) else ((not sx_truthy(starts_with_p(href, 'javascript:'))) if not sx_truthy((not sx_truthy(starts_with_p(href, 'javascript:')))) else ((not sx_truthy(starts_with_p(href, 'mailto:'))) if not sx_truthy((not sx_truthy(starts_with_p(href, 'mailto:')))) else (browser_same_origin_p(href) if not sx_truthy(browser_same_origin_p(href)) else ((not sx_truthy(dom_has_attr_p(link, 'sx-get'))) if not sx_truthy((not sx_truthy(dom_has_attr_p(link, 'sx-get')))) else ((not sx_truthy(dom_has_attr_p(link, 'sx-post'))) if not sx_truthy((not sx_truthy(dom_has_attr_p(link, 'sx-post')))) else (not sx_truthy(dom_has_attr_p(link, 'sx-disable'))))))))))
# should-boost-form?
def should_boost_form_p(form):
return ((not sx_truthy(dom_has_attr_p(form, 'sx-get'))) if not sx_truthy((not sx_truthy(dom_has_attr_p(form, 'sx-get')))) else ((not sx_truthy(dom_has_attr_p(form, 'sx-post'))) if not sx_truthy((not sx_truthy(dom_has_attr_p(form, 'sx-post')))) else (not sx_truthy(dom_has_attr_p(form, 'sx-disable')))))
# parse-sse-swap
def parse_sse_swap(el):
return (dom_get_attr(el, 'sx-sse-swap') if sx_truthy(dom_get_attr(el, 'sx-sse-swap')) else 'message')
# === Transpiled from router (client-side route matching) ===
# split-path-segments
def split_path_segments(path):
trimmed = (slice(path, 1) if sx_truthy(starts_with_p(path, '/')) else path)
trimmed2 = (slice(trimmed, 0, (len(trimmed) - 1)) if sx_truthy(((not sx_truthy(empty_p(trimmed))) if not sx_truthy((not sx_truthy(empty_p(trimmed)))) else ends_with_p(trimmed, '/'))) else trimmed)
if sx_truthy(empty_p(trimmed2)):
return []
else:
return split(trimmed2, '/')
# make-route-segment
def make_route_segment(seg):
if sx_truthy((starts_with_p(seg, '<') if not sx_truthy(starts_with_p(seg, '<')) else ends_with_p(seg, '>'))):
param_name = slice(seg, 1, (len(seg) - 1))
d = {}
d['type'] = 'param'
d['value'] = param_name
return d
else:
d = {}
d['type'] = 'literal'
d['value'] = seg
return d
# parse-route-pattern
def parse_route_pattern(pattern):
segments = split_path_segments(pattern)
return map(make_route_segment, segments)
# match-route-segments
def match_route_segments(path_segs, parsed_segs):
_cells = {}
if sx_truthy((not sx_truthy((len(path_segs) == len(parsed_segs))))):
return NIL
else:
params = {}
_cells['matched'] = True
for_each_indexed(lambda i, parsed_seg: ((lambda path_seg: (lambda seg_type: ((_sx_cell_set(_cells, 'matched', False) if sx_truthy((not sx_truthy((path_seg == get(parsed_seg, 'value'))))) else NIL) if sx_truthy((seg_type == 'literal')) else (_sx_dict_set(params, get(parsed_seg, 'value'), path_seg) if sx_truthy((seg_type == 'param')) else _sx_cell_set(_cells, 'matched', False))))(get(parsed_seg, 'type')))(nth(path_segs, i)) if sx_truthy(_cells['matched']) else NIL), parsed_segs)
if sx_truthy(_cells['matched']):
return params
else:
return NIL
# match-route
def match_route(path, pattern):
path_segs = split_path_segments(path)
parsed_segs = parse_route_pattern(pattern)
return match_route_segments(path_segs, parsed_segs)
# find-matching-route
def find_matching_route(path, routes):
_cells = {}
path_segs = split_path_segments(path)
_cells['result'] = NIL
for route in routes:
if sx_truthy(is_nil(_cells['result'])):
params = match_route_segments(path_segs, get(route, 'parsed'))
if sx_truthy((not sx_truthy(is_nil(params)))):
matched = merge(route, {})
matched['params'] = params
_cells['result'] = matched
return _cells['result']
# build-affinity-analysis
def build_affinity_analysis(demo_components, page_plans):
return {'components': demo_components, 'page-plans': page_plans}
# === Transpiled from signals (reactive signal runtime) ===
@@ -3058,4 +2753,4 @@ def render(expr, env=None):
def make_env(**kwargs):
"""Create an environment with initial bindings."""
return _Env(dict(kwargs))
return _Env(dict(kwargs))