Merge branch 'worktree-typed' into macros

This commit is contained in:
2026-03-11 20:27:43 +00:00
23 changed files with 532 additions and 498 deletions

View File

@@ -14,7 +14,7 @@
// =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-03-11T17:38:00Z";
var SX_VERSION = "2026-03-11T20:12:35Z";
function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -881,7 +881,7 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai
var paramsExpr = first(args);
var bodyExprs = rest(args);
var body = (isSxTruthy((len(bodyExprs) == 1)) ? first(bodyExprs) : cons(makeSymbol("begin"), bodyExprs));
var paramNames = map(function(p) { return (isSxTruthy((typeOf(p) == "symbol")) ? symbolName(p) : p); }, paramsExpr);
var paramNames = map(function(p) { return (isSxTruthy((typeOf(p) == "symbol")) ? symbolName(p) : (isSxTruthy((isSxTruthy((typeOf(p) == "list")) && isSxTruthy((len(p) == 3)) && isSxTruthy((typeOf(nth(p, 1)) == "keyword")) && (keywordName(nth(p, 1)) == "as"))) ? symbolName(first(p)) : p)); }, paramsExpr);
return makeLambda(paramNames, body, env);
})(); };
@@ -1200,7 +1200,7 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai
// process-bindings
var processBindings = function(bindings, env) { return (function() {
var local = envExtend(env);
{ var _c = bindings; for (var _i = 0; _i < _c.length; _i++) { var pair = _c[_i]; if (isSxTruthy((isSxTruthy((typeOf(pair) == "list")) && (len(pair) >= 2)))) {
{ var _c = bindings; for (var _i = 0; _i < _c.length; _i++) { var [Symbol('pair'), Keyword('as'), Symbol('list')] = _c[_i]; if (isSxTruthy((isSxTruthy((typeOf(pair) == "list")) && (len(pair) >= 2)))) {
(function() {
var name = (isSxTruthy((typeOf(first(pair)) == "symbol")) ? symbolName(first(pair)) : (String(first(pair))));
return envSet(local, name, trampoline(evalExpr(nth(pair, 1), local)));
@@ -2171,7 +2171,7 @@ 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)))); } }
{ var _c = rest(tokens); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('tok'), Keyword('as'), Symbol('string')] = _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));
@@ -2217,7 +2217,7 @@ 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)); } }
{ var _c = rest(parts); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('p'), Keyword('as'), Symbol('string')] = _c[_i]; (isSxTruthy((p == "transition:true")) ? (useTransition = true) : (isSxTruthy((p == "transition:false")) ? (useTransition = false) : NIL)); } }
return {["style"]: style, ["transition"]: useTransition};
})(); };
@@ -2270,7 +2270,7 @@ return (function() {
// 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 _c = ["sx-swap-oob", "hx-swap-oob"]; for (var _i = 0; _i < _c.length; _i++) { var [Symbol('attr'), Keyword('as'), Symbol('string')] = _c[_i]; (function() {
var oobEls = domQueryAll(container, (String("[") + String(attr) + String("]")));
return forEach(function(oob) { return (function() {
var swapType = sxOr(domGetAttr(oob, attr), "outerHTML");
@@ -2289,7 +2289,7 @@ return (function() {
var syncAttrs = function(oldEl, newEl) { return (function() {
var raStr = sxOr(domGetAttr(oldEl, "data-sx-reactive-attrs"), "");
var reactiveAttrs = (isSxTruthy(isEmpty(raStr)) ? [] : split(raStr, ","));
{ var _c = domAttrList(newEl); for (var _i = 0; _i < _c.length; _i++) { var attr = _c[_i]; (function() {
{ var _c = domAttrList(newEl); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('attr'), Keyword('as'), Symbol('list')] = _c[_i]; (function() {
var name = first(attr);
var val = nth(attr, 1);
return (isSxTruthy((isSxTruthy(!isSxTruthy((domGetAttr(oldEl, name) == val))) && !isSxTruthy(contains(reactiveAttrs, name)))) ? domSetAttr(oldEl, name, val) : NIL);
@@ -2543,7 +2543,7 @@ return (function() {
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); } }
{ var _c = keys(extraParams); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('k'), Keyword('as'), Symbol('string')] = _c[_i]; headers[k] = get(extraParams, k); } }
}
if (isSxTruthy(ct)) {
headers["Content-Type"] = ct;
@@ -2784,7 +2784,7 @@ return domAppendToHead(link); }, domQueryAll(container, "link[rel=\"stylesheet\"
var base = pageName;
return (isSxTruthy(sxOr(isNil(params), isEmpty(keys(params)))) ? base : (function() {
var parts = [];
{ var _c = keys(params); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; parts.push((String(k) + String("=") + String(get(params, k)))); } }
{ var _c = keys(params); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('k'), Keyword('as'), Symbol('string')] = _c[_i]; parts.push((String(k) + String("=") + String(get(params, k)))); } }
return (String(base) + String(":") + String(join("&", parts)));
})());
})(); };
@@ -2799,7 +2799,7 @@ return domAppendToHead(link); }, domQueryAll(container, "link[rel=\"stylesheet\"
var pageDataCacheSet = function(cacheKey, data) { return dictSet(_pageDataCache, cacheKey, {"data": data, "ts": nowMs()}); };
// invalidate-page-cache
var invalidatePageCache = function(pageName) { { var _c = keys(_pageDataCache); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; if (isSxTruthy(sxOr((k == pageName), startsWith(k, (String(pageName) + String(":")))))) {
var invalidatePageCache = function(pageName) { { var _c = keys(_pageDataCache); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('k'), Keyword('as'), Symbol('string')] = _c[_i]; if (isSxTruthy(sxOr((k == pageName), startsWith(k, (String(pageName) + String(":")))))) {
_pageDataCache[k] = NIL;
} } }
swPostMessage({"type": "invalidate", "page": pageName});
@@ -3200,7 +3200,7 @@ return (function() {
var comp = envGet(env, fullName);
return (isSxTruthy(!isSxTruthy(isComponent(comp))) ? error((String("Unknown component: ") + String(fullName))) : (function() {
var callExpr = [makeSymbol(fullName)];
{ var _c = keys(kwargs); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; callExpr.push(makeKeyword(toKebab(k)));
{ var _c = keys(kwargs); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('k'), Keyword('as'), Symbol('string')] = _c[_i]; callExpr.push(makeKeyword(toKebab(k)));
callExpr.push(dictGet(kwargs, k)); } }
return renderToDom(callExpr, env, NIL);
})());
@@ -3280,7 +3280,7 @@ callExpr.push(dictGet(kwargs, k)); } }
var kwargs = sxOr(first(sxParse(stateSx)), {});
var disposers = [];
var local = envMerge(componentClosure(comp), env);
{ var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } }
{ var _c = componentParams(comp); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('p'), Keyword('as'), Symbol('string')] = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } }
return (function() {
var bodyDom = withIslandScope(function(disposable) { return append_b(disposers, disposable); }, function() { return renderToDom(componentBody(comp), local, NIL); });
domSetTextContent(el, "");
@@ -3358,7 +3358,7 @@ callExpr.push(dictGet(kwargs, k)); } }
var componentsNeeded = function(pageSource, env) { return (function() {
var direct = scanComponentsFromSource(pageSource);
var allNeeded = [];
{ var _c = direct; for (var _i = 0; _i < _c.length; _i++) { var name = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(allNeeded, name)))) {
{ var _c = direct; for (var _i = 0; _i < _c.length; _i++) { var [Symbol('name'), Keyword('as'), Symbol('string')] = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(allNeeded, name)))) {
allNeeded.push(name);
}
(function() {
@@ -3378,11 +3378,11 @@ callExpr.push(dictGet(kwargs, k)); } }
var pageCssClasses = function(pageSource, env) { return (function() {
var needed = componentsNeeded(pageSource, env);
var classes = [];
{ var _c = needed; for (var _i = 0; _i < _c.length; _i++) { var name = _c[_i]; (function() {
{ var _c = needed; for (var _i = 0; _i < _c.length; _i++) { var [Symbol('name'), Keyword('as'), Symbol('string')] = _c[_i]; (function() {
var val = envGet(env, name);
return (isSxTruthy((typeOf(val) == "component")) ? forEach(function(cls) { return (isSxTruthy(!isSxTruthy(contains(classes, cls))) ? append_b(classes, cls) : NIL); }, componentCssClasses(val)) : NIL);
})(); } }
{ var _c = scanCssClasses(pageSource); for (var _i = 0; _i < _c.length; _i++) { var cls = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(classes, cls)))) {
{ var _c = scanCssClasses(pageSource); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('cls'), Keyword('as'), Symbol('string')] = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(classes, cls)))) {
classes.push(cls);
} } }
return classes;
@@ -3459,7 +3459,7 @@ callExpr.push(dictGet(kwargs, k)); } }
var serverList = [];
var clientList = [];
var ioDeps = [];
{ var _c = needed; for (var _i = 0; _i < _c.length; _i++) { var name = _c[_i]; (function() {
{ var _c = needed; for (var _i = 0; _i < _c.length; _i++) { var [Symbol('name'), Keyword('as'), Symbol('string')] = _c[_i]; (function() {
var target = renderTarget(name, env, ioNames);
compTargets[name] = target;
return (isSxTruthy((target == "server")) ? (append_b(serverList, name), forEach(function(ioRef) { return (isSxTruthy(!isSxTruthy(contains(ioDeps, ioRef))) ? append_b(ioDeps, ioRef) : NIL); }, componentIoRefsCached(name, env, ioNames))) : append_b(clientList, name));
@@ -3484,7 +3484,7 @@ callExpr.push(dictGet(kwargs, k)); } }
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")))) {
{ var _c = range(0, n); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('idx'), Keyword('as'), Symbol('number')] = _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));
@@ -3524,7 +3524,7 @@ callExpr.push(dictGet(kwargs, k)); } }
})()); }, 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)}; })(); };
var buildReferenceData = function(slug, rawData, detailKeys) { return (function() { var _m = slug; if (_m == "attributes") return {"req-attrs": buildRefItemsWithHref(get(rawData, "req-attrs"), "/geography/hypermedia/reference/attributes/", detailKeys, 3), "beh-attrs": buildRefItemsWithHref(get(rawData, "beh-attrs"), "/geography/hypermedia/reference/attributes/", detailKeys, 3), "uniq-attrs": buildRefItemsWithHref(get(rawData, "uniq-attrs"), "/geography/hypermedia/reference/attributes/", detailKeys, 3)}; if (_m == "headers") return {"req-headers": buildRefItemsWithHref(get(rawData, "req-headers"), "/geography/hypermedia/reference/headers/", detailKeys, 3), "resp-headers": buildRefItemsWithHref(get(rawData, "resp-headers"), "/geography/hypermedia/reference/headers/", detailKeys, 3)}; if (_m == "events") return {"events-list": buildRefItemsWithHref(get(rawData, "events-list"), "/geography/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"), "/geography/hypermedia/reference/attributes/", detailKeys, 3), "beh-attrs": buildRefItemsWithHref(get(rawData, "beh-attrs"), "/geography/hypermedia/reference/attributes/", detailKeys, 3), "uniq-attrs": buildRefItemsWithHref(get(rawData, "uniq-attrs"), "/geography/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)}); };
@@ -3555,7 +3555,7 @@ callExpr.push(dictGet(kwargs, k)); } }
// 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 _c = pagesRaw; for (var _i = 0; _i < _c.length; _i++) { var [Symbol('page'), Keyword('as'), Symbol('dict')] = _c[_i]; (function() {
var neededNames = get(page, "needed-names");
var n = len(neededNames);
var pct = (isSxTruthy((totalComponents > 0)) ? round(((n / totalComponents) * 100)) : 0);
@@ -3564,7 +3564,7 @@ callExpr.push(dictGet(kwargs, k)); } }
var ioInPage = 0;
var pageIoRefs = [];
var compDetails = [];
{ var _c = neededNames; for (var _i = 0; _i < _c.length; _i++) { var compName = _c[_i]; (function() {
{ var _c = neededNames; for (var _i = 0; _i < _c.length; _i++) { var [Symbol('compName'), Keyword('as'), Symbol('string')] = _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);
})(); } }
@@ -3578,7 +3578,7 @@ callExpr.push(dictGet(kwargs, k)); } }
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 _c = pagesRaw; for (var _i = 0; _i < _c.length; _i++) { var [Symbol('page'), Keyword('as'), Symbol('dict')] = _c[_i]; (function() {
var hasData = get(page, "has-data");
var contentSrc = sxOr(get(page, "content-src"), "");
var mode = NIL;
@@ -3649,7 +3649,7 @@ callExpr.push(dictGet(kwargs, k)); } }
var findMatchingRoute = function(path, routes) { return (function() {
var pathSegs = splitPathSegments(path);
var result = NIL;
{ var _c = routes; for (var _i = 0; _i < _c.length; _i++) { var route = _c[_i]; if (isSxTruthy(isNil(result))) {
{ var _c = routes; for (var _i = 0; _i < _c.length; _i++) { var [Symbol('route'), Keyword('as'), Symbol('dict')] = _c[_i]; if (isSxTruthy(isNil(result))) {
(function() {
var params = matchRouteSegments(pathSegs, get(route, "parsed"));
return (isSxTruthy(!isSxTruthy(isNil(params))) ? (function() {
@@ -3697,7 +3697,7 @@ callExpr.push(dictGet(kwargs, k)); } }
var deps = [];
var computeCtx = NIL;
return (function() {
var recompute = function() { { var _c = signalDeps(s); for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, recompute); } }
var recompute = function() { { var _c = signalDeps(s); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('dep'), Keyword('as'), Symbol('signal')] = _c[_i]; signalRemoveSub(dep, recompute); } }
signalSetDeps(s, []);
return (function() {
var ctx = makeTrackingContext(recompute);
@@ -3747,7 +3747,7 @@ return (function() {
if (isSxTruthy(cleanupFn)) {
invoke(cleanupFn);
}
{ var _c = deps; for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, runEffect); } }
{ var _c = deps; for (var _i = 0; _i < _c.length; _i++) { var [Symbol('dep'), Keyword('as'), Symbol('signal')] = _c[_i]; signalRemoveSub(dep, runEffect); } }
return (deps = []); };
registerInScope(disposeFn);
return disposeFn;
@@ -3771,7 +3771,7 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
return (function() {
var seen = [];
var pending = [];
{ var _c = queue; for (var _i = 0; _i < _c.length; _i++) { var s = _c[_i]; { var _c = signalSubscribers(s); for (var _i = 0; _i < _c.length; _i++) { var sub = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(seen, sub)))) {
{ var _c = queue; for (var _i = 0; _i < _c.length; _i++) { var [Symbol('s'), Keyword('as'), Symbol('signal')] = _c[_i]; { var _c = signalSubscribers(s); for (var _i = 0; _i < _c.length; _i++) { var [Symbol('sub'), Keyword('as'), Symbol('lambda')] = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(seen, sub)))) {
seen.push(sub);
pending.push(sub);
} } } } }

View File

@@ -41,7 +41,7 @@
;; --------------------------------------------------------------------------
(define-async async-render
(fn (expr env ctx)
(fn (expr (env :as dict) ctx)
(case (type-of expr)
"nil" ""
"boolean" ""
@@ -57,7 +57,7 @@
(define-async async-render-list
(fn (expr env ctx)
(fn (expr (env :as dict) ctx)
(let ((head (first expr)))
(if (not (= (type-of head) "symbol"))
;; Non-symbol head — data list, render each item
@@ -139,7 +139,7 @@
;; --------------------------------------------------------------------------
(define-async async-render-raw
(fn (args env ctx)
(fn ((args :as list) (env :as dict) ctx)
(let ((parts (list)))
(for-each
(fn (arg)
@@ -158,7 +158,7 @@
;; --------------------------------------------------------------------------
(define-async async-render-element
(fn (tag args env ctx)
(fn ((tag :as string) (args :as list) (env :as dict) ctx)
(let ((attrs (dict))
(children (list)))
;; Parse keyword attrs and children
@@ -186,7 +186,7 @@
;; compiles inline for-each lambdas as for loops (which can contain await).
(define-async async-parse-element-args
(fn (args attrs children env ctx)
(fn ((args :as list) (attrs :as dict) (children :as list) (env :as dict) ctx)
(let ((skip false)
(i 0))
(for-each
@@ -211,7 +211,7 @@
;; --------------------------------------------------------------------------
(define-async async-render-component
(fn (comp args env ctx)
(fn ((comp :as component) (args :as list) (env :as dict) ctx)
(let ((kwargs (dict))
(children (list)))
;; Parse keyword args and children
@@ -233,7 +233,7 @@
;; --------------------------------------------------------------------------
(define-async async-render-island
(fn (island args env ctx)
(fn (island (args :as list) (env :as dict) ctx)
(let ((kwargs (dict))
(children (list)))
(async-parse-kw-args args kwargs children env ctx)
@@ -262,7 +262,7 @@
;; --------------------------------------------------------------------------
(define-async async-render-lambda
(fn (f args env ctx)
(fn ((f :as lambda) (args :as list) (env :as dict) ctx)
(let ((local (env-merge (lambda-closure f) env)))
(for-each-indexed
(fn (i p) (env-set! local p (nth args i)))
@@ -275,7 +275,7 @@
;; --------------------------------------------------------------------------
(define-async async-parse-kw-args
(fn (args kwargs children env ctx)
(fn ((args :as list) (kwargs :as dict) (children :as list) (env :as dict) ctx)
(let ((skip false)
(i 0))
(for-each
@@ -301,7 +301,7 @@
;; Bootstrapper emits this as: [await async_render(x, env, ctx) for x in exprs]
(define-async async-map-render
(fn (exprs env ctx)
(fn ((exprs :as list) (env :as dict) ctx)
(let ((results (list)))
(for-each
(fn (x) (append! results (async-render x env ctx)))
@@ -319,7 +319,7 @@
"map" "map-indexed" "filter" "for-each"))
(define async-render-form?
(fn (name)
(fn ((name :as string))
(contains? ASYNC_RENDER_FORMS name)))
@@ -331,7 +331,7 @@
;; and eval-cond from render.sx for correct scheme/clojure classification.
(define-async dispatch-async-render-form
(fn (name expr env ctx)
(fn ((name :as string) expr (env :as dict) ctx)
(cond
;; if
(= name "if")
@@ -407,7 +407,7 @@
;; --------------------------------------------------------------------------
(define-async async-render-cond-scheme
(fn (clauses env ctx)
(fn ((clauses :as list) (env :as dict) ctx)
(if (empty? clauses)
""
(let ((clause (first clauses))
@@ -429,7 +429,7 @@
;; --------------------------------------------------------------------------
(define-async async-render-cond-clojure
(fn (clauses env ctx)
(fn ((clauses :as list) (env :as dict) ctx)
(if (< (len clauses) 2)
""
(let ((test (first clauses))
@@ -449,7 +449,7 @@
;; --------------------------------------------------------------------------
(define-async async-process-bindings
(fn (bindings env ctx)
(fn (bindings (env :as dict) ctx)
;; env-extend (not merge) — Env is not a dict subclass, so merge()
;; returns an empty dict, losing all parent scope bindings.
(let ((local (env-extend env)))
@@ -470,7 +470,7 @@
(define-async async-process-bindings-flat
(fn (bindings local ctx)
(fn ((bindings :as list) (local :as dict) ctx)
(let ((skip false)
(i 0))
(for-each
@@ -495,7 +495,7 @@
;; --------------------------------------------------------------------------
(define-async async-map-fn-render
(fn (f coll env ctx)
(fn (f (coll :as list) (env :as dict) ctx)
(let ((results (list)))
(for-each
(fn (item)
@@ -512,7 +512,7 @@
;; --------------------------------------------------------------------------
(define-async async-map-indexed-fn-render
(fn (f coll env ctx)
(fn (f (coll :as list) (env :as dict) ctx)
(let ((results (list))
(i 0))
(for-each
@@ -543,7 +543,7 @@
;; ==========================================================================
(define-async async-aser
(fn (expr env ctx)
(fn (expr (env :as dict) ctx)
(case (type-of expr)
"number" expr
"string" expr
@@ -573,7 +573,7 @@
(define-async async-aser-dict
(fn (expr env ctx)
(fn ((expr :as dict) (env :as dict) ctx)
(let ((result (dict)))
(for-each
(fn (key)
@@ -587,7 +587,7 @@
;; --------------------------------------------------------------------------
(define-async async-aser-list
(fn (expr env ctx)
(fn (expr (env :as dict) ctx)
(let ((head (first expr))
(args (rest expr)))
(if (not (= (type-of head) "symbol"))
@@ -666,7 +666,7 @@
;; --------------------------------------------------------------------------
(define-async async-aser-eval-call
(fn (head args env ctx)
(fn (head (args :as list) (env :as dict) ctx)
(let ((f (async-eval head env ctx))
(evaled-args (async-eval-args args env ctx)))
(cond
@@ -694,7 +694,7 @@
;; --------------------------------------------------------------------------
(define-async async-eval-args
(fn (args env ctx)
(fn ((args :as list) (env :as dict) ctx)
(let ((results (list)))
(for-each
(fn (a) (append! results (async-eval a env ctx)))
@@ -707,7 +707,7 @@
;; --------------------------------------------------------------------------
(define-async async-aser-map-list
(fn (exprs env ctx)
(fn ((exprs :as list) (env :as dict) ctx)
(let ((results (list)))
(for-each
(fn (x) (append! results (async-aser x env ctx)))
@@ -720,7 +720,7 @@
;; --------------------------------------------------------------------------
(define-async async-aser-fragment
(fn (children env ctx)
(fn ((children :as list) (env :as dict) ctx)
(let ((parts (list)))
(for-each
(fn (c)
@@ -744,7 +744,7 @@
;; --------------------------------------------------------------------------
(define-async async-aser-component
(fn (comp args env ctx)
(fn ((comp :as component) (args :as list) (env :as dict) ctx)
(let ((kwargs (dict))
(children (list)))
(async-parse-aser-kw-args args kwargs children env ctx)
@@ -776,7 +776,7 @@
;; --------------------------------------------------------------------------
(define-async async-parse-aser-kw-args
(fn (args kwargs children env ctx)
(fn ((args :as list) (kwargs :as dict) (children :as list) (env :as dict) ctx)
(let ((skip false)
(i 0))
(for-each
@@ -801,7 +801,7 @@
;; --------------------------------------------------------------------------
(define-async async-aser-call
(fn (name args env ctx)
(fn ((name :as string) (args :as list) (env :as dict) ctx)
(let ((token (if (or (= name "svg") (= name "math"))
(svg-context-set! true)
nil))
@@ -859,7 +859,7 @@
(list "map" "map-indexed" "filter" "for-each"))
(define async-aser-form?
(fn (name)
(fn ((name :as string))
(or (contains? ASYNC_ASER_FORM_NAMES name)
(contains? ASYNC_ASER_HO_NAMES name))))
@@ -871,7 +871,7 @@
;; Uses cond-scheme? from eval.sx (the FIXED version with every? check).
(define-async dispatch-async-aser-form
(fn (name expr env ctx)
(fn ((name :as string) expr (env :as dict) ctx)
(let ((args (rest expr)))
(cond
;; if
@@ -1000,7 +1000,7 @@
;; --------------------------------------------------------------------------
(define-async async-aser-cond-scheme
(fn (clauses env ctx)
(fn ((clauses :as list) (env :as dict) ctx)
(if (empty? clauses)
nil
(let ((clause (first clauses))
@@ -1022,7 +1022,7 @@
;; --------------------------------------------------------------------------
(define-async async-aser-cond-clojure
(fn (clauses env ctx)
(fn ((clauses :as list) (env :as dict) ctx)
(if (< (len clauses) 2)
nil
(let ((test (first clauses))
@@ -1042,7 +1042,7 @@
;; --------------------------------------------------------------------------
(define-async async-aser-case-loop
(fn (match-val clauses env ctx)
(fn (match-val (clauses :as list) (env :as dict) ctx)
(if (< (len clauses) 2)
nil
(let ((test (first clauses))
@@ -1062,7 +1062,7 @@
;; --------------------------------------------------------------------------
(define-async async-aser-thread-first
(fn (args env ctx)
(fn ((args :as list) (env :as dict) ctx)
(let ((result (async-eval (first args) env ctx)))
(for-each
(fn (form)
@@ -1082,7 +1082,7 @@
;; --------------------------------------------------------------------------
(define-async async-invoke-or-lambda
(fn (f args env ctx)
(fn (f (args :as list) (env :as dict) ctx)
(cond
(and (callable? f) (not (lambda? f)) (not (component? f)))
(let ((r (apply f args)))
@@ -1104,7 +1104,7 @@
;; --------------------------------------------------------------------------
(define-async async-aser-ho-map
(fn (args env ctx)
(fn ((args :as list) (env :as dict) ctx)
(let ((f (async-eval (first args) env ctx))
(coll (async-eval (nth args 1) env ctx))
(results (list)))
@@ -1120,7 +1120,7 @@
(define-async async-aser-ho-map-indexed
(fn (args env ctx)
(fn ((args :as list) (env :as dict) ctx)
(let ((f (async-eval (first args) env ctx))
(coll (async-eval (nth args 1) env ctx))
(results (list))
@@ -1139,7 +1139,7 @@
(define-async async-aser-ho-for-each
(fn (args env ctx)
(fn ((args :as list) (env :as dict) ctx)
(let ((f (async-eval (first args) env ctx))
(coll (async-eval (nth args 1) env ctx))
(results (list)))
@@ -1170,7 +1170,7 @@
;; (set-expand-components!) — enable component expansion context var
(define-async async-eval-slot-inner
(fn (expr env ctx)
(fn (expr (env :as dict) ctx)
;; NOTE: Uses statement-form let + set! to avoid expression-context
;; let (IIFE lambdas) which can't contain await in Python.
(let ((result nil))
@@ -1196,7 +1196,7 @@
(define-async async-maybe-expand-result
(fn (result env ctx)
(fn (result (env :as dict) ctx)
;; If the aser result is a component call string like "(~foo ...)",
;; re-parse and expand it. This handles indirect component references
;; (e.g. a let binding that evaluates to a component call).

View File

@@ -19,7 +19,7 @@
;; --------------------------------------------------------------------------
(define render-to-dom
(fn (expr env ns)
(fn (expr (env :as dict) ns)
(set-render-active! true)
(case (type-of expr)
;; nil / boolean false / boolean true → empty fragment
@@ -67,7 +67,7 @@
;; --------------------------------------------------------------------------
(define render-dom-list
(fn (expr env ns)
(fn (expr (env :as dict) ns)
(let ((head (first expr)))
(cond
;; Symbol head — dispatch on name
@@ -166,7 +166,7 @@
;; --------------------------------------------------------------------------
(define render-dom-element
(fn (tag args env ns)
(fn ((tag :as string) (args :as list) (env :as dict) ns)
;; Detect namespace from tag
(let ((new-ns (cond (= tag "svg") SVG_NS
(= tag "math") MATH_NS
@@ -237,7 +237,7 @@
;; --------------------------------------------------------------------------
(define render-dom-component
(fn (comp args env ns)
(fn ((comp :as component) (args :as list) (env :as dict) ns)
;; Parse kwargs and children, bind into component env, render body.
(let ((kwargs (dict))
(children (list)))
@@ -284,7 +284,7 @@
;; --------------------------------------------------------------------------
(define render-dom-fragment
(fn (args env ns)
(fn ((args :as list) (env :as dict) ns)
(let ((frag (create-fragment)))
(for-each
(fn (x) (dom-append frag (render-to-dom x env ns)))
@@ -297,7 +297,7 @@
;; --------------------------------------------------------------------------
(define render-dom-raw
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((frag (create-fragment)))
(for-each
(fn (arg)
@@ -318,7 +318,7 @@
;; --------------------------------------------------------------------------
(define render-dom-unknown-component
(fn (name)
(fn ((name :as string))
(error (str "Unknown component: " name))))
@@ -335,11 +335,11 @@
"error-boundary"))
(define render-dom-form?
(fn (name)
(fn ((name :as string))
(contains? RENDER_DOM_FORMS name)))
(define dispatch-render-form
(fn (name expr env ns)
(fn ((name :as string) expr (env :as dict) ns)
(cond
;; if — reactive inside islands (re-renders when signal deps change)
(= name "if")
@@ -581,7 +581,7 @@
;; --------------------------------------------------------------------------
(define render-lambda-dom
(fn (f args env ns)
(fn ((f :as lambda) (args :as list) (env :as dict) ns)
;; Bind lambda params and render body as DOM
(let ((local (env-merge (lambda-closure f) env)))
(for-each-indexed
@@ -605,7 +605,7 @@
;; - Conditional fragments: (when (deref sig) ...) → reactive show/hide
(define render-dom-island
(fn (island args env ns)
(fn (island (args :as list) (env :as dict) ns)
;; Parse kwargs and children (same as component)
(let ((kwargs (dict))
(children (list)))
@@ -679,7 +679,7 @@
;; Supports :tag keyword to change wrapper element (default "div").
(define render-dom-lake
(fn (args env ns)
(fn ((args :as list) (env :as dict) ns)
(let ((lake-id nil)
(lake-tag "div")
(children (list)))
@@ -723,7 +723,7 @@
;; Stores the island env and transform on the element for morph retrieval.
(define render-dom-marsh
(fn (args env ns)
(fn ((args :as list) (env :as dict) ns)
(let ((marsh-id nil)
(marsh-tag "div")
(marsh-transform nil)
@@ -781,7 +781,7 @@
;; Marks the attribute name on the element via data-sx-reactive-attrs so
;; the morph algorithm knows not to overwrite it with server content.
(define reactive-attr
(fn (el attr-name compute-fn)
(fn (el (attr-name :as string) compute-fn)
;; Mark this attribute as reactively managed
(let ((existing (or (dom-get-attr el "data-sx-reactive-attrs") ""))
(updated (if (empty? existing) attr-name (str existing "," attr-name))))
@@ -802,7 +802,7 @@
;; reactive-fragment — conditionally render a fragment based on a signal
;; Used for (when (deref sig) ...) or (if (deref sig) ...) inside an island.
(define reactive-fragment
(fn (test-fn render-fn env ns)
(fn (test-fn render-fn (env :as dict) ns)
(let ((marker (create-comment "island-fragment"))
(current-nodes (list)))
(effect (fn ()
@@ -824,13 +824,13 @@
;; and reorderings touch the DOM. Without keys, falls back to clear+rerender.
(define render-list-item
(fn (map-fn item env ns)
(fn (map-fn item (env :as dict) ns)
(if (lambda? map-fn)
(render-lambda-dom map-fn (list item) env ns)
(render-to-dom (apply map-fn (list item)) env ns))))
(define extract-key
(fn (node index)
(fn (node (index :as number))
;; Extract key from rendered node: :key attr, data-key, or index fallback
(let ((k (dom-get-attr node "key")))
(if k
@@ -839,7 +839,7 @@
(if dk (str dk) (str "__idx_" index)))))))
(define reactive-list
(fn (map-fn items-sig env ns)
(fn (map-fn items-sig (env :as dict) ns)
(let ((container (create-fragment))
(marker (create-comment "island-list"))
(key-map (dict))
@@ -960,7 +960,7 @@
;; teardown.
(define render-dom-portal
(fn (args env ns)
(fn ((args :as list) (env :as dict) ns)
(let ((selector (trampoline (eval-expr (first args) env)))
(target (or (dom-query selector)
(dom-ensure-element selector))))
@@ -1000,7 +1000,7 @@
;; Calling (retry) re-renders the body, replacing the fallback.
(define render-dom-error-boundary
(fn (args env ns)
(fn ((args :as list) (env :as dict) ns)
(let ((fallback-expr (first args))
(body-exprs (rest args))
(container (dom-create-element "div" nil))

View File

@@ -14,7 +14,7 @@
(define render-to-html
(fn (expr env)
(fn (expr (env :as dict))
(set-render-active! true)
(case (type-of expr)
;; Literals — render directly
@@ -34,7 +34,7 @@
:else (render-value-to-html (trampoline (eval-expr expr env)) env))))
(define render-value-to-html
(fn (val env)
(fn (val (env :as dict))
(case (type-of val)
"nil" ""
"string" (escape-html val)
@@ -55,7 +55,7 @@
"map" "map-indexed" "filter" "for-each"))
(define render-html-form?
(fn (name)
(fn ((name :as string))
(contains? RENDER_HTML_FORMS name)))
@@ -64,7 +64,7 @@
;; --------------------------------------------------------------------------
(define render-list-to-html
(fn (expr env)
(fn ((expr :as list) (env :as dict))
(if (empty? expr)
""
(let ((head (first expr)))
@@ -135,7 +135,7 @@
;; --------------------------------------------------------------------------
(define dispatch-html-form
(fn (name expr env)
(fn ((name :as string) (expr :as list) (env :as dict))
(cond
;; if
(= name "if")
@@ -235,7 +235,7 @@
;; --------------------------------------------------------------------------
(define render-lambda-html
(fn (f args env)
(fn ((f :as lambda) (args :as list) (env :as dict))
(let ((local (env-merge (lambda-closure f) env)))
(for-each-indexed
(fn (i p)
@@ -249,7 +249,7 @@
;; --------------------------------------------------------------------------
(define render-html-component
(fn (comp args env)
(fn ((comp :as component) (args :as list) (env :as dict))
;; Expand component and render body through HTML adapter.
;; Component body contains rendering forms (HTML tags) that only the
;; adapter understands, so expansion must happen here, not in eval-expr.
@@ -288,7 +288,7 @@
(define render-html-element
(fn (tag args env)
(fn ((tag :as string) (args :as list) (env :as dict))
(let ((parsed (parse-element-args args env))
(attrs (first parsed))
(children (nth parsed 1))
@@ -312,7 +312,7 @@
;; content while preserving surrounding reactive DOM.
(define render-html-lake
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((lake-id nil)
(lake-tag "div")
(children (list)))
@@ -351,7 +351,7 @@
;; the :transform is a client-only concern.
(define render-html-marsh
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((marsh-id nil)
(marsh-tag "div")
(children (list)))
@@ -394,7 +394,7 @@
;; (swap! s f) → no-op
(define render-html-island
(fn (island args env)
(fn ((island :as island) (args :as list) (env :as dict))
;; Parse kwargs and children (same pattern as render-html-component)
(let ((kwargs (dict))
(children (list)))
@@ -452,7 +452,7 @@
;; Handles all SX types natively: numbers, strings, booleans, nil, lists, dicts.
(define serialize-island-state
(fn (kwargs)
(fn ((kwargs :as dict))
(if (empty-dict? kwargs)
nil
(sx-serialize kwargs))))

View File

@@ -12,7 +12,7 @@
(define render-to-sx
(fn (expr env)
(fn (expr (env :as dict))
(let ((result (aser expr env)))
;; aser-call already returns serialized SX strings;
;; only serialize non-string values
@@ -21,7 +21,7 @@
(serialize result)))))
(define aser
(fn (expr env)
(fn (expr (env :as dict))
;; Evaluate for SX wire format — serialize rendering forms,
;; evaluate control flow and function calls.
(set-render-active! true)
@@ -52,7 +52,7 @@
(define aser-list
(fn (expr env)
(fn ((expr :as list) (env :as dict))
(let ((head (first expr))
(args (rest expr)))
(if (not (= (type-of head) "symbol"))
@@ -104,7 +104,7 @@
(define aser-fragment
(fn (children env)
(fn ((children :as list) (env :as dict))
;; Serialize (<> child1 child2 ...) to sx source string
;; Must flatten list results (e.g. from map/filter) to avoid nested parens
(let ((parts (list)))
@@ -126,7 +126,7 @@
(define aser-call
(fn (name args env)
(fn ((name :as string) (args :as list) (env :as dict))
;; Serialize (name :key val child ...) — evaluate args but keep as sx
;; Uses for-each + mutable state (not reduce) so bootstrapper emits for-loops
;; that can contain nested for-each for list flattening.
@@ -177,11 +177,11 @@
"some" "every?" "for-each"))
(define special-form?
(fn (name)
(fn ((name :as string))
(contains? SPECIAL_FORM_NAMES name)))
(define ho-form?
(fn (name)
(fn ((name :as string))
(contains? HO_FORM_NAMES name)))
@@ -194,7 +194,7 @@
;; Definition forms evaluate for side effects and return nil.
(define aser-special
(fn (name expr env)
(fn ((name :as string) (expr :as list) (env :as dict))
(let ((args (rest expr)))
(cond
;; if — evaluate condition, aser chosen branch
@@ -314,7 +314,7 @@
;; Helper: case dispatch for aser mode
(define eval-case-aser
(fn (match-val clauses env)
(fn (match-val (clauses :as list) (env :as dict))
(if (< (len clauses) 2)
nil
(let ((test (first clauses))

View File

@@ -72,7 +72,7 @@
;; --------------------------------------------------------------------------
(define sx-mount
(fn (target source extra-env)
(fn (target (source :as string) (extra-env :as dict))
;; Render SX source string into target element.
;; target: Element or CSS selector string
;; source: SX source string
@@ -101,7 +101,7 @@
;; new SX content, and replaces the wrapper's children.
(define resolve-suspense
(fn (id sx)
(fn ((id :as string) (sx :as string))
;; Process any new <script type="text/sx"> tags that arrived via
;; streaming (e.g. extra component defs) before resolving.
(process-sx-scripts nil)
@@ -166,7 +166,7 @@
;; --------------------------------------------------------------------------
(define sx-render-component
(fn (name kwargs extra-env)
(fn ((name :as string) (kwargs :as dict) (extra-env :as dict))
;; Render a named component with keyword args.
;; name: component name (with or without ~ prefix)
;; kwargs: dict of param-name → value
@@ -179,7 +179,7 @@
;; Build synthetic call expression
(let ((call-expr (list (make-symbol full-name))))
(for-each
(fn (k)
(fn ((k :as string))
(append! call-expr (make-keyword (to-kebab k)))
(append! call-expr (dict-get kwargs k)))
(keys kwargs))
@@ -236,7 +236,7 @@
;; --------------------------------------------------------------------------
(define process-component-script
(fn (script text)
(fn (script (text :as string))
;; Handle <script type="text/sx" data-components data-hash="...">
(let ((hash (dom-get-attr script "data-hash")))
(if (nil? hash)
@@ -304,7 +304,7 @@
(let ((pages (parse text)))
(log-info (str "pages: parsed " (len pages) " entries"))
(for-each
(fn (page)
(fn ((page :as dict))
(append! _page-routes
(merge page
{"parsed" (parse-route-pattern (get page "path"))})))
@@ -358,7 +358,7 @@
;; Bind params from kwargs
(for-each
(fn (p)
(fn ((p :as string))
(env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil)))
(component-params comp))
@@ -393,7 +393,7 @@
(let ((disposers (dom-get-data el "sx-disposers")))
(when disposers
(for-each
(fn (d)
(fn ((d :as lambda))
(when (callable? d) (d)))
disposers)
(dom-set-data el "sx-disposers" nil)))))

View File

@@ -442,6 +442,15 @@ class PyEmitter:
# --- Special form emitters ---
@staticmethod
def _extract_param_name(p):
"""Extract the name from a param, handling (name :as type) annotations."""
if isinstance(p, list) and len(p) == 3 and isinstance(p[1], Keyword) and p[1].name == "as":
return p[0].name if isinstance(p[0], Symbol) else str(p[0])
if isinstance(p, Symbol):
return p.name
return str(p)
def _emit_fn(self, expr) -> str:
params = expr[1]
body = expr[2:]
@@ -453,16 +462,13 @@ class PyEmitter:
if isinstance(p, Symbol) and p.name == "&rest":
# Next param is the rest parameter
if i + 1 < len(params):
rest_name = self._mangle(params[i + 1].name if isinstance(params[i + 1], Symbol) else str(params[i + 1]))
rest_name = self._mangle(self._extract_param_name(params[i + 1]))
i += 2
continue
else:
i += 1
continue
if isinstance(p, Symbol):
param_names.append(self._mangle(p.name))
else:
param_names.append(str(p))
param_names.append(self._mangle(self._extract_param_name(p)))
i += 1
if rest_name:
param_names.append(f"*{rest_name}")
@@ -708,17 +714,14 @@ class PyEmitter:
p = params[i]
if isinstance(p, Symbol) and p.name == "&rest":
if i + 1 < len(params):
rest_name = self._mangle(params[i + 1].name if isinstance(params[i + 1], Symbol) else str(params[i + 1]))
rest_name = self._mangle(self._extract_param_name(params[i + 1]))
param_names.append(f"*{rest_name}")
i += 2
continue
else:
i += 1
continue
if isinstance(p, Symbol):
param_names.append(self._mangle(p.name))
else:
param_names.append(str(p))
param_names.append(self._mangle(self._extract_param_name(p)))
i += 1
params_str = ", ".join(param_names)
py_name = self._mangle(name)
@@ -956,7 +959,7 @@ class PyEmitter:
if isinstance(fn_expr, list) and isinstance(fn_expr[0], Symbol) and fn_expr[0].name == "fn":
params = fn_expr[1]
body = fn_expr[2:]
p = params[0].name if isinstance(params[0], Symbol) else str(params[0])
p = self._extract_param_name(params[0])
p_py = self._mangle(p)
lines = [f"{pad}for {p_py} in {coll}:"]
# Emit body as statements with proper let/set! handling

View File

@@ -82,7 +82,7 @@
;; --------------------------------------------------------------------------
(define sf-reset
(fn (args env)
(fn ((args :as list) (env :as dict))
;; Single argument: the body expression.
;; Install a continuation delimiter, then evaluate body.
;; The implementation is target-specific:
@@ -136,7 +136,7 @@
;; --------------------------------------------------------------------------
(define sf-shift
(fn (args env)
(fn ((args :as list) (env :as dict))
;; Two arguments: the continuation variable name, and the body.
(let ((k-name (symbol-name (first args)))
(body (second args)))

View File

@@ -39,7 +39,7 @@
(define scan-refs-walk
(fn (node refs)
(fn (node (refs :as list))
(cond
;; Symbol starting with ~ → component reference
(= (type-of node) "symbol")
@@ -68,26 +68,26 @@
;; that it can transitively render. Handles cycles via seen-set.
(define transitive-deps-walk
(fn (n seen env)
(fn ((n :as string) (seen :as list) (env :as dict))
(when (not (contains? seen n))
(append! seen n)
(let ((val (env-get env n)))
(cond
(= (type-of val) "component")
(for-each (fn (ref) (transitive-deps-walk ref seen env))
(for-each (fn ((ref :as string)) (transitive-deps-walk ref seen env))
(scan-refs (component-body val)))
(= (type-of val) "macro")
(for-each (fn (ref) (transitive-deps-walk ref seen env))
(for-each (fn ((ref :as string)) (transitive-deps-walk ref seen env))
(scan-refs (macro-body val)))
:else nil)))))
(define transitive-deps
(fn (name env)
(fn ((name :as string) (env :as dict))
(let ((seen (list))
(key (if (starts-with? name "~") name (str "~" name))))
(transitive-deps-walk key seen env)
(filter (fn (x) (not (= x key))) seen))))
(filter (fn ((x :as string)) (not (= x key))) seen))))
;; --------------------------------------------------------------------------
@@ -101,9 +101,9 @@
;; (component-set-deps! comp deps) → store deps on component
(define compute-all-deps
(fn (env)
(fn ((env :as dict))
(for-each
(fn (name)
(fn ((name :as string))
(let ((val (env-get env name)))
(when (= (type-of val) "component")
(component-set-deps! val (transitive-deps name env)))))
@@ -120,9 +120,9 @@
;; (regex-find-all pattern source) → list of matched group strings
(define scan-components-from-source
(fn (source)
(fn ((source :as string))
(let ((matches (regex-find-all "\\(~([a-zA-Z_][a-zA-Z0-9_\\-]*)" source)))
(map (fn (m) (str "~" m)) matches))))
(map (fn ((m :as string)) (str "~" m)) matches))))
;; --------------------------------------------------------------------------
@@ -132,13 +132,13 @@
;; the transitive closure. Returns list of ~names.
(define components-needed
(fn (page-source env)
(fn ((page-source :as string) (env :as dict))
(let ((direct (scan-components-from-source page-source))
(all-needed (list)))
;; Add each direct ref + its transitive deps
(for-each
(fn (name)
(fn ((name :as string))
(when (not (contains? all-needed name))
(append! all-needed name))
(let ((val (env-get env name)))
@@ -147,7 +147,7 @@
(component-deps val)
(transitive-deps name env))))
(for-each
(fn (dep)
(fn ((dep :as string))
(when (not (contains? all-needed dep))
(append! all-needed dep)))
deps))))
@@ -166,7 +166,7 @@
;; This replaces the "send everything" approach with per-page bundles.
(define page-component-bundle
(fn (page-source env)
(fn ((page-source :as string) (env :as dict))
(components-needed page-source env)))
@@ -181,17 +181,17 @@
;; (scan-css-classes source) → set/list of class strings from source
(define page-css-classes
(fn (page-source env)
(fn ((page-source :as string) (env :as dict))
(let ((needed (components-needed page-source env))
(classes (list)))
;; Collect classes from needed components
(for-each
(fn (name)
(fn ((name :as string))
(let ((val (env-get env name)))
(when (= (type-of val) "component")
(for-each
(fn (cls)
(fn ((cls :as string))
(when (not (contains? classes cls))
(append! classes cls)))
(component-css-classes val)))))
@@ -199,7 +199,7 @@
;; Add classes from page source
(for-each
(fn (cls)
(fn ((cls :as string))
(when (not (contains? classes cls))
(append! classes cls)))
(scan-css-classes page-source))
@@ -219,7 +219,7 @@
;; (component-set-io-refs! c r) → cache IO refs on component
(define scan-io-refs-walk
(fn (node io-names refs)
(fn (node (io-names :as list) (refs :as list))
(cond
;; Symbol → check if name is in the IO set
(= (type-of node) "symbol")
@@ -242,7 +242,7 @@
(define scan-io-refs
(fn (node io-names)
(fn (node (io-names :as list))
(let ((refs (list)))
(scan-io-refs-walk node io-names refs)
refs)))
@@ -253,7 +253,7 @@
;; --------------------------------------------------------------------------
(define transitive-io-refs-walk
(fn (n seen all-refs env io-names)
(fn ((n :as string) (seen :as list) (all-refs :as list) (env :as dict) (io-names :as list))
(when (not (contains? seen n))
(append! seen n)
(let ((val (env-get env n)))
@@ -262,31 +262,31 @@
(do
;; Scan this component's body for IO refs
(for-each
(fn (ref)
(fn ((ref :as string))
(when (not (contains? all-refs ref))
(append! all-refs ref)))
(scan-io-refs (component-body val) io-names))
;; Recurse into component deps
(for-each
(fn (dep) (transitive-io-refs-walk dep seen all-refs env io-names))
(fn ((dep :as string)) (transitive-io-refs-walk dep seen all-refs env io-names))
(scan-refs (component-body val))))
(= (type-of val) "macro")
(do
(for-each
(fn (ref)
(fn ((ref :as string))
(when (not (contains? all-refs ref))
(append! all-refs ref)))
(scan-io-refs (macro-body val) io-names))
(for-each
(fn (dep) (transitive-io-refs-walk dep seen all-refs env io-names))
(fn ((dep :as string)) (transitive-io-refs-walk dep seen all-refs env io-names))
(scan-refs (macro-body val))))
:else nil)))))
(define transitive-io-refs
(fn (name env io-names)
(fn ((name :as string) (env :as dict) (io-names :as list))
(let ((all-refs (list))
(seen (list))
(key (if (starts-with? name "~") name (str "~" name))))
@@ -299,9 +299,9 @@
;; --------------------------------------------------------------------------
(define compute-all-io-refs
(fn (env io-names)
(fn ((env :as dict) (io-names :as list))
(for-each
(fn (name)
(fn ((name :as string))
(let ((val (env-get env name)))
(when (= (type-of val) "component")
(component-set-io-refs! val (transitive-io-refs name env io-names)))))
@@ -309,7 +309,7 @@
(define component-io-refs-cached
(fn (name env io-names)
(fn ((name :as string) (env :as dict) (io-names :as list))
(let ((key (if (starts-with? name "~") name (str "~" name))))
(let ((val (env-get env key)))
(if (and (= (type-of val) "component")
@@ -320,7 +320,7 @@
(transitive-io-refs name env io-names))))))
(define component-pure?
(fn (name env io-names)
(fn ((name :as string) (env :as dict) (io-names :as list))
(let ((key (if (starts-with? name "~") name (str "~" name))))
(let ((val (env-get env key)))
(if (and (= (type-of val) "component")
@@ -344,7 +344,7 @@
;; Returns: "server" | "client"
(define render-target
(fn (name env io-names)
(fn ((name :as string) (env :as dict) (io-names :as list))
(let ((key (if (starts-with? name "~") name (str "~" name))))
(let ((val (env-get env key)))
(if (not (= (type-of val) "component"))
@@ -373,7 +373,7 @@
;; without recomputing at every request.
(define page-render-plan
(fn (page-source env io-names)
(fn ((page-source :as string) (env :as dict) (io-names :as list))
(let ((needed (components-needed page-source env))
(comp-targets (dict))
(server-list (list))
@@ -381,7 +381,7 @@
(io-deps (list)))
(for-each
(fn (name)
(fn ((name :as string))
(let ((target (render-target name env io-names)))
(dict-set! comp-targets name target)
(if (= target "server")
@@ -389,7 +389,7 @@
(append! server-list name)
;; Collect IO deps from server components (use cache)
(for-each
(fn (io-ref)
(fn ((io-ref :as string))
(when (not (contains? io-deps io-ref))
(append! io-deps io-ref)))
(component-io-refs-cached name env io-names)))
@@ -451,9 +451,9 @@
;; Moved from platform to spec: pure logic using type predicates.
(define env-components
(fn (env)
(fn ((env :as dict))
(filter
(fn (k)
(fn ((k :as string))
(let ((v (env-get env k)))
(or (component? v) (macro? v))))
(keys env))))

View File

@@ -32,7 +32,7 @@
;; Each descriptor is a dict with "event" and "modifiers" keys.
(define parse-time
(fn (s)
(fn ((s :as string))
;; Parse time string: "2s" → 2000, "500ms" → 500
;; Uses nested if (not cond) because cond misclassifies 2-element
;; function calls like (nil? s) as scheme-style ((test body)) clauses.
@@ -43,7 +43,7 @@
(define parse-trigger-spec
(fn (spec)
(fn ((spec :as string))
;; Parse "click delay:500ms once,change" → list of trigger descriptors
(if (nil? spec)
nil
@@ -51,7 +51,7 @@
(filter
(fn (x) (not (nil? x)))
(map
(fn (part)
(fn ((part :as string))
(let ((tokens (split (trim part) " ")))
(if (empty? tokens)
nil
@@ -63,7 +63,7 @@
;; Normal trigger with optional modifiers
(let ((mods (dict)))
(for-each
(fn (tok)
(fn ((tok :as string))
(cond
(= tok "once")
(dict-set! mods "once" true)
@@ -81,7 +81,7 @@
(define default-trigger
(fn (tag-name)
(fn ((tag-name :as string))
;; Default trigger for element type
(cond
(= tag-name "FORM")
@@ -102,7 +102,7 @@
(fn (el)
;; Check element for sx-get, sx-post, etc. Returns (dict "method" "url") or nil.
(some
(fn (verb)
(fn ((verb :as string))
(let ((url (dom-get-attr el (str "sx-" verb))))
(if url
(dict "method" (upper verb) "url" url)
@@ -115,7 +115,7 @@
;; --------------------------------------------------------------------------
(define build-request-headers
(fn (el loaded-components css-hash)
(fn (el (loaded-components :as list) (css-hash :as string))
;; Build the SX request headers dict
(let ((headers (dict
"SX-Request" "true"
@@ -140,7 +140,7 @@
(let ((parsed (parse-header-value extra-h)))
(when parsed
(for-each
(fn (key) (dict-set! headers key (str (get parsed key))))
(fn ((key :as string)) (dict-set! headers key (str (get parsed key))))
(keys parsed))))))
headers)))
@@ -175,13 +175,13 @@
;; --------------------------------------------------------------------------
(define parse-swap-spec
(fn (raw-swap global-transitions?)
(fn ((raw-swap :as string) (global-transitions? :as boolean))
;; Parse "innerHTML transition:true" → dict with style + transition flag
(let ((parts (split (or raw-swap DEFAULT_SWAP) " "))
(style (first parts))
(use-transition global-transitions?))
(for-each
(fn (p)
(fn ((p :as string))
(cond
(= p "transition:true") (set! use-transition true)
(= p "transition:false") (set! use-transition false)))
@@ -194,7 +194,7 @@
;; --------------------------------------------------------------------------
(define parse-retry-spec
(fn (retry-attr)
(fn ((retry-attr :as string))
;; Parse "exponential:1000:30000" → spec dict or nil
(if (nil? retry-attr)
nil
@@ -206,7 +206,7 @@
(define next-retry-ms
(fn (current-ms cap-ms)
(fn ((current-ms :as number) (cap-ms :as number))
;; Exponential backoff: double current, cap at max
(min (* current-ms 2) cap-ms)))
@@ -216,7 +216,7 @@
;; --------------------------------------------------------------------------
(define filter-params
(fn (params-spec all-params)
(fn ((params-spec :as string) (all-params :as list))
;; Filter form parameters by sx-params spec.
;; all-params is a list of (key value) pairs.
;; Returns filtered list of (key value) pairs.
@@ -227,11 +227,11 @@
(if (starts-with? params-spec "not ")
(let ((excluded (map trim (split (slice params-spec 4) ","))))
(filter
(fn (p) (not (contains? excluded (first p))))
(fn ((p :as list)) (not (contains? excluded (first p))))
all-params))
(let ((allowed (map trim (split params-spec ","))))
(filter
(fn (p) (contains? allowed (first p)))
(fn ((p :as list)) (contains? allowed (first p)))
all-params))))))))
@@ -279,7 +279,7 @@
(define revert-optimistic
(fn (state)
(fn ((state :as dict))
;; Revert an optimistic update
(when state
(let ((target (get state "target"))
@@ -305,7 +305,7 @@
;; Returns list of (dict "element" el "swap-type" type "target-id" id).
(let ((results (list)))
(for-each
(fn (attr)
(fn ((attr :as string))
(let ((oob-els (dom-query-all container (str "[" attr "]"))))
(for-each
(fn (oob)
@@ -380,7 +380,7 @@
(reactive-attrs (if (empty? ra-str) (list) (split ra-str ","))))
;; Add/update attributes from new, skip reactive ones
(for-each
(fn (attr)
(fn ((attr :as list))
(let ((name (first attr))
(val (nth attr 1)))
(when (and (not (= (dom-get-attr old-el name) val))
@@ -389,7 +389,7 @@
(dom-attr-list new-el))
;; Remove attributes not in new, skip reactive + marker attrs
(for-each
(fn (attr)
(fn ((attr :as list))
(let ((aname (first attr)))
(when (and (not (dom-has-attr? new-el aname))
(not (contains? reactive-attrs aname))
@@ -406,7 +406,7 @@
(new-kids (dom-child-list new-parent))
;; Build ID map of old children for keyed matching
(old-by-id (reduce
(fn (acc kid)
(fn ((acc :as dict) kid)
(let ((id (dom-id kid)))
(if id (do (dict-set! acc id kid) acc) acc)))
(dict) old-kids))
@@ -447,7 +447,7 @@
;; Remove leftover old children
(for-each
(fn (i)
(fn ((i :as number))
(when (>= i oi)
(let ((leftover (nth old-kids i)))
(when (and (dom-is-child-of? leftover old-parent)
@@ -577,7 +577,7 @@
;; --------------------------------------------------------------------------
(define swap-dom-nodes
(fn (target new-nodes strategy)
(fn (target new-nodes (strategy :as string))
;; Execute a swap strategy on live DOM nodes.
;; new-nodes is typically a DocumentFragment or Element.
(case strategy
@@ -644,7 +644,7 @@
;; --------------------------------------------------------------------------
(define swap-html-string
(fn (target html strategy)
(fn (target (html :as string) (strategy :as string))
;; Execute a swap strategy using an HTML string (DOMParser pipeline).
(case strategy
"innerHTML"
@@ -675,7 +675,7 @@
;; --------------------------------------------------------------------------
(define handle-history
(fn (el url resp-headers)
(fn (el (url :as string) (resp-headers :as dict))
;; Process history push/replace based on element attrs and response headers
(let ((push-url (dom-get-attr el "sx-push-url"))
(replace-url (dom-get-attr el "sx-replace-url"))
@@ -701,7 +701,7 @@
(define PRELOAD_TTL 30000) ;; 30 seconds
(define preload-cache-get
(fn (cache url)
(fn ((cache :as dict) (url :as string))
;; Get and consume a cached preload response.
;; Returns (dict "text" ... "content-type" ...) or nil.
(let ((entry (dict-get cache url)))
@@ -713,7 +713,7 @@
(define preload-cache-set
(fn (cache url text content-type)
(fn ((cache :as dict) (url :as string) (text :as string) (content-type :as string))
;; Store a preloaded response
(dict-set! cache url
(dict "text" text "content-type" content-type "timestamp" (now-ms)))))
@@ -726,7 +726,7 @@
;; This is the logic; actual browser event binding is platform interface.
(define classify-trigger
(fn (trigger)
(fn ((trigger :as dict))
;; Classify a parsed trigger descriptor for binding.
;; Returns one of: "poll", "intersect", "load", "revealed", "event"
(let ((event (get trigger "event")))

View File

@@ -73,7 +73,7 @@
;; --------------------------------------------------------------------------
(define eval-expr
(fn (expr env)
(fn (expr (env :as dict))
(case (type-of expr)
;; --- literals pass through ---
@@ -116,7 +116,7 @@
;; --------------------------------------------------------------------------
(define eval-list
(fn (expr env)
(fn (expr (env :as dict))
(let ((head (first expr))
(args (rest expr)))
@@ -191,7 +191,7 @@
;; --------------------------------------------------------------------------
(define eval-call
(fn (head args env)
(fn (head (args :as list) (env :as dict))
(let ((f (trampoline (eval-expr head env)))
(evaluated-args (map (fn (a) (trampoline (eval-expr a env))) args)))
(cond
@@ -215,7 +215,7 @@
(define call-lambda
(fn (f args caller-env)
(fn ((f :as lambda) (args :as list) (caller-env :as dict))
(let ((params (lambda-params f))
(local (env-merge (lambda-closure f) caller-env)))
;; Too many args is an error; too few pads with nil
@@ -235,7 +235,7 @@
(define call-component
(fn (comp raw-args env)
(fn (comp (raw-args :as list) (env :as dict))
;; Parse keyword args and children from unevaluated arg list
(let ((parsed (parse-keyword-args raw-args env))
(kwargs (first parsed))
@@ -253,7 +253,7 @@
(define parse-keyword-args
(fn (raw-args env)
(fn ((raw-args :as list) (env :as dict))
;; Walk args: keyword + next-val → kwargs dict, else → children list
(let ((kwargs (dict))
(children (list))
@@ -287,7 +287,7 @@
;; --------------------------------------------------------------------------
(define sf-if
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((condition (trampoline (eval-expr (first args) env))))
(if (and condition (not (nil? condition)))
(make-thunk (nth args 1) env)
@@ -297,7 +297,7 @@
(define sf-when
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((condition (trampoline (eval-expr (first args) env))))
(if (and condition (not (nil? condition)))
(do
@@ -314,18 +314,18 @@
;; Checking only the first arg is ambiguous — (nil? x) is a 2-element
;; function call, not a scheme clause ((test body)).
(define cond-scheme?
(fn (clauses)
(fn ((clauses :as list))
(every? (fn (c) (and (= (type-of c) "list") (= (len c) 2)))
clauses)))
(define sf-cond
(fn (args env)
(fn ((args :as list) (env :as dict))
(if (cond-scheme? args)
(sf-cond-scheme args env)
(sf-cond-clojure args env))))
(define sf-cond-scheme
(fn (clauses env)
(fn ((clauses :as list) (env :as dict))
(if (empty? clauses)
nil
(let ((clause (first clauses))
@@ -342,7 +342,7 @@
(sf-cond-scheme (rest clauses) env)))))))
(define sf-cond-clojure
(fn (clauses env)
(fn ((clauses :as list) (env :as dict))
(if (< (len clauses) 2)
nil
(let ((test (first clauses))
@@ -358,13 +358,13 @@
(define sf-case
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((match-val (trampoline (eval-expr (first args) env)))
(clauses (rest args)))
(sf-case-loop match-val clauses env))))
(define sf-case-loop
(fn (match-val clauses env)
(fn (match-val (clauses :as list) (env :as dict))
(if (< (len clauses) 2)
nil
(let ((test (first clauses))
@@ -380,7 +380,7 @@
(define sf-and
(fn (args env)
(fn ((args :as list) (env :as dict))
(if (empty? args)
true
(let ((val (trampoline (eval-expr (first args) env))))
@@ -392,7 +392,7 @@
(define sf-or
(fn (args env)
(fn ((args :as list) (env :as dict))
(if (empty? args)
false
(let ((val (trampoline (eval-expr (first args) env))))
@@ -402,7 +402,7 @@
(define sf-let
(fn (args env)
(fn ((args :as list) (env :as dict))
;; Detect named let: (let name ((x 0) ...) body)
;; If first arg is a symbol, delegate to sf-named-let.
(if (= (type-of (first args)) "symbol")
@@ -443,7 +443,7 @@
;; Desugars to a self-recursive lambda called with initial values.
;; The loop name is bound in the body so recursive calls produce TCO thunks.
(define sf-named-let
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((loop-name (symbol-name (first args)))
(bindings (nth args 1))
(body (slice args 2))
@@ -483,22 +483,29 @@
(define sf-lambda
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((params-expr (first args))
(body-exprs (rest args))
(body (if (= (len body-exprs) 1)
(first body-exprs)
(cons (make-symbol "begin") body-exprs)))
(param-names (map (fn (p)
(if (= (type-of p) "symbol")
(symbol-name p)
p))
(cond
(= (type-of p) "symbol")
(symbol-name p)
;; Annotated param: (name :as type) → extract name
(and (= (type-of p) "list")
(= (len p) 3)
(= (type-of (nth p 1)) "keyword")
(= (keyword-name (nth p 1)) "as"))
(symbol-name (first p))
:else p))
params-expr)))
(make-lambda param-names body env))))
(define sf-define
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((name-sym (first args))
(value (trampoline (eval-expr (nth args 1) env))))
(when (and (lambda? value) (nil? (lambda-name value)))
@@ -508,7 +515,7 @@
(define sf-defcomp
(fn (args env)
(fn ((args :as list) (env :as dict))
;; (defcomp ~name (params) [:affinity :client|:server] body)
;; Body is always the last element. Optional keyword annotations
;; may appear between the params list and the body.
@@ -530,7 +537,7 @@
comp))))
(define defcomp-kwarg
(fn (args key default)
(fn ((args :as list) (key :as string) default)
;; Search for :key value between params (index 2) and body (last).
(let ((end (- (len args) 1))
(result default))
@@ -546,7 +553,7 @@
result)))
(define parse-comp-params
(fn (params-expr)
(fn ((params-expr :as list))
;; Parse (&key param1 param2 &children) → (params has-children param-types)
;; Also accepts &rest as synonym for &children.
;; Supports typed params: (name :as type) — a 3-element list where
@@ -588,7 +595,7 @@
(define sf-defisland
(fn (args env)
(fn ((args :as list) (env :as dict))
;; (defisland ~name (params) body)
;; Like defcomp but creates an island (reactive component).
;; Islands have the same calling convention as components but
@@ -606,7 +613,7 @@
(define sf-defmacro
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((name-sym (first args))
(params-raw (nth args 1))
(body (nth args 2))
@@ -618,7 +625,7 @@
mac))))
(define parse-macro-params
(fn (params-expr)
(fn ((params-expr :as list))
;; Parse (a b &rest rest) → ((a b) rest)
(let ((params (list))
(rest-param nil))
@@ -639,7 +646,7 @@
(define sf-defstyle
(fn (args env)
(fn ((args :as list) (env :as dict))
;; (defstyle name expr) — bind name to evaluated expr (string, function, etc.)
(let ((name-sym (first args))
(value (trampoline (eval-expr (nth args 1) env))))
@@ -648,7 +655,7 @@
(define sf-begin
(fn (args env)
(fn ((args :as list) (env :as dict))
(if (empty? args)
nil
(do
@@ -659,16 +666,16 @@
(define sf-quote
(fn (args env)
(fn ((args :as list) (env :as dict))
(if (empty? args) nil (first args))))
(define sf-quasiquote
(fn (args env)
(fn ((args :as list) (env :as dict))
(qq-expand (first args) env)))
(define qq-expand
(fn (template env)
(fn (template (env :as dict))
(if (not (= (type-of template) "list"))
template
(if (empty? template)
@@ -693,7 +700,7 @@
(define sf-thread-first
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((val (trampoline (eval-expr (first args) env))))
(reduce
(fn (result form)
@@ -720,7 +727,7 @@
(define sf-set!
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((name (symbol-name (first args)))
(value (trampoline (eval-expr (nth args 1) env))))
(env-set! env name value)
@@ -741,7 +748,7 @@
;; --------------------------------------------------------------------------
(define sf-letrec
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((bindings (first args))
(body (rest args))
(local (env-extend env))
@@ -816,7 +823,7 @@
;; --------------------------------------------------------------------------
(define sf-dynamic-wind
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((before (trampoline (eval-expr (first args) env)))
(body (trampoline (eval-expr (nth args 1) env)))
(after (trampoline (eval-expr (nth args 2) env))))
@@ -835,7 +842,7 @@
;; --------------------------------------------------------------------------
(define expand-macro
(fn (mac raw-args env)
(fn ((mac :as macro) (raw-args :as list) (env :as dict))
(let ((local (env-merge (macro-closure mac) env)))
;; Bind positional params (unevaluated)
(for-each
@@ -859,20 +866,20 @@
;; call-fn: unified caller for HO forms — handles both Lambda and native callable
(define call-fn
(fn (f args env)
(fn (f (args :as list) (env :as dict))
(cond
(lambda? f) (trampoline (call-lambda f args env))
(callable? f) (apply f args)
:else (error (str "Not callable in HO form: " (inspect f))))))
(define ho-map
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((f (trampoline (eval-expr (first args) env)))
(coll (trampoline (eval-expr (nth args 1) env))))
(map (fn (item) (call-fn f (list item) env)) coll))))
(define ho-map-indexed
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((f (trampoline (eval-expr (first args) env)))
(coll (trampoline (eval-expr (nth args 1) env))))
(map-indexed
@@ -880,7 +887,7 @@
coll))))
(define ho-filter
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((f (trampoline (eval-expr (first args) env)))
(coll (trampoline (eval-expr (nth args 1) env))))
(filter
@@ -888,7 +895,7 @@
coll))))
(define ho-reduce
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((f (trampoline (eval-expr (first args) env)))
(init (trampoline (eval-expr (nth args 1) env)))
(coll (trampoline (eval-expr (nth args 2) env))))
@@ -898,7 +905,7 @@
coll))))
(define ho-some
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((f (trampoline (eval-expr (first args) env)))
(coll (trampoline (eval-expr (nth args 1) env))))
(some
@@ -906,7 +913,7 @@
coll))))
(define ho-every
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((f (trampoline (eval-expr (first args) env)))
(coll (trampoline (eval-expr (nth args 1) env))))
(every?
@@ -915,7 +922,7 @@
(define ho-for-each
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((f (trampoline (eval-expr (first args) env)))
(coll (trampoline (eval-expr (nth args 1) env))))
(for-each

View File

@@ -22,7 +22,7 @@
;; --------------------------------------------------------------------------
(define parse-key-params
(fn (params-expr)
(fn ((params-expr :as list))
(let ((params (list))
(in-key false))
(for-each
@@ -42,7 +42,7 @@
;; --------------------------------------------------------------------------
(define sf-defhandler
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((name-sym (first args))
(params-raw (nth args 1))
(body (nth args 2))
@@ -58,7 +58,7 @@
;; --------------------------------------------------------------------------
(define sf-defquery
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((name-sym (first args))
(params-raw (nth args 1))
(name (symbol-name name-sym))
@@ -77,7 +77,7 @@
;; --------------------------------------------------------------------------
(define sf-defaction
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((name-sym (first args))
(params-raw (nth args 1))
(name (symbol-name name-sym))
@@ -98,7 +98,7 @@
;; --------------------------------------------------------------------------
(define sf-defpage
(fn (args env)
(fn ((args :as list) (env :as dict))
(let ((name-sym (first args))
(name (symbol-name name-sym))
(slots {}))
@@ -106,7 +106,7 @@
(let ((i 1)
(max-i (len args)))
(for-each
(fn (idx)
(fn ((idx :as number))
(when (and (< idx max-i)
(= (type-of (nth args idx)) "keyword"))
(when (< (+ idx 1) max-i)
@@ -195,28 +195,28 @@
;; Extract stream-id from a data chunk dict, defaulting to "stream-content"
(define stream-chunk-id
(fn (chunk)
(fn ((chunk :as dict))
(if (has-key? chunk "stream-id")
(get chunk "stream-id")
"stream-content")))
;; Remove stream-id from chunk, returning only the bindings
(define stream-chunk-bindings
(fn (chunk)
(fn ((chunk :as dict))
(dissoc chunk "stream-id")))
;; Normalize binding keys: underscore → hyphen
(define normalize-binding-key
(fn (key)
(fn ((key :as string))
(replace key "_" "-")))
;; Bind a data chunk's keys into a fresh env (isolated per chunk)
(define bind-stream-chunk
(fn (chunk base-env)
(fn ((chunk :as dict) (base-env :as dict))
(let ((env (merge {} base-env))
(bindings (stream-chunk-bindings chunk)))
(for-each
(fn (key)
(fn ((key :as string))
(env-set! env (normalize-binding-key key)
(get bindings key)))
(keys bindings))

View File

@@ -528,7 +528,7 @@
;; --------------------------------------------------------------------------
(define js-mangle
(fn (name)
(fn ((name :as string))
(let ((renamed (get js-renames name)))
(if (not (nil? renamed))
renamed
@@ -549,7 +549,7 @@
result))))))))
(define js-kebab-to-camel
(fn (s)
(fn ((s :as string))
(let ((parts (split s "-")))
(if (<= (len parts) 1)
s
@@ -557,7 +557,7 @@
(join "" (map (fn (p) (js-capitalize p)) (rest parts))))))))
(define js-capitalize
(fn (s)
(fn ((s :as string))
(if (empty? s) s
(str (upper (slice s 0 1)) (slice s 1)))))
@@ -567,7 +567,7 @@
;; --------------------------------------------------------------------------
(define js-quote-string
(fn (s)
(fn ((s :as string))
(str "\""
(replace (replace (replace (replace (replace (replace
s "\\" "\\\\") "\"" "\\\"") "\n" "\\n") "\r" "\\r") "\t" "\\t") "\0" "\\0")
@@ -582,11 +582,11 @@
(list "+" "-" "*" "/" "=" "!=" "<" ">" "<=" ">=" "mod"))
(define js-infix?
(fn (op)
(fn ((op :as string))
(some (fn (x) (= x op)) js-infix-ops)))
(define js-op-symbol
(fn (op)
(fn ((op :as string))
(case op
"=" "=="
"!=" "!="
@@ -599,13 +599,13 @@
;; --------------------------------------------------------------------------
(define js-is-self-tail-recursive?
(fn (name body)
(fn ((name :as string) (body :as list))
(if (empty? body)
false
(js-has-tail-call? name (last body)))))
(define js-has-tail-call?
(fn (name expr)
(fn ((name :as string) expr)
(if (not (and (list? expr) (not (empty? expr))))
false
(let ((head (first expr)))
@@ -642,7 +642,7 @@
;; --------------------------------------------------------------------------
(define js-emit-tail-as-stmt
(fn (name expr)
(fn ((name :as string) expr)
(if (not (and (list? expr) (not (empty? expr))))
(str "return " (js-expr expr) ";")
(let ((head (first expr)))
@@ -702,7 +702,7 @@
;; --------------------------------------------------------------------------
(define js-emit-cond-as-loop-stmt
(fn (name clauses)
(fn ((name :as string) (clauses :as list))
(if (empty? clauses)
"return NIL;"
;; Detect scheme vs clojure
@@ -714,7 +714,7 @@
(js-cond-clojure-loop name clauses 0 0 false))))))
(define js-cond-scheme-loop
(fn (name clauses i)
(fn ((name :as string) (clauses :as list) (i :as number))
(if (>= i (len clauses))
"else { return NIL; }"
(let ((clause (nth clauses i))
@@ -728,7 +728,7 @@
(js-cond-scheme-loop name clauses (+ i 1))))))))
(define js-cond-clojure-loop
(fn (name clauses i clause-idx has-else)
(fn ((name :as string) (clauses :as list) (i :as number) (clause-idx :as number) (has-else :as boolean))
(if (>= i (len clauses))
(if has-else "" " else { return NIL; }")
(let ((c (nth clauses i)))
@@ -749,7 +749,7 @@
;; --------------------------------------------------------------------------
(define js-emit-loop-body
(fn (name body)
(fn ((name :as string) (body :as list))
(if (empty? body)
"return NIL;"
(str (join "\n" (map (fn (e) (js-statement e))
@@ -805,7 +805,7 @@
;; --------------------------------------------------------------------------
(define js-emit-native-dict
(fn (d)
(fn ((d :as dict))
(let ((items (keys d)))
(str "{" (join ", " (map (fn (k)
(str (js-quote-string k) ": " (js-expr (get d k))))
@@ -963,11 +963,11 @@
(str "function(" params-str ") { " (join "\n" parts) " }")))))))))
(define js-collect-params
(fn (params)
(fn ((params :as list))
(js-collect-params-loop params 0 (list) nil)))
(define js-collect-params-loop
(fn (params i result rest-name)
(fn ((params :as list) (i :as number) (result :as list) rest-name)
(if (>= i (len params))
(list result rest-name)
(let ((p (nth params i)))
@@ -975,13 +975,25 @@
;; &rest marker
(and (= (type-of p) "symbol") (= (symbol-name p) "&rest"))
(if (< (+ i 1) (len params))
(js-collect-params-loop params (+ i 2) result
(js-mangle (symbol-name (nth params (+ i 1)))))
(let ((rp (nth params (+ i 1))))
(js-collect-params-loop params (+ i 2) result
(js-mangle
(if (and (= (type-of rp) "list") (= (len rp) 3)
(= (type-of (nth rp 1)) "keyword")
(= (keyword-name (nth rp 1)) "as"))
(symbol-name (first rp))
(if (= (type-of rp) "symbol") (symbol-name rp) (str rp))))))
(js-collect-params-loop params (+ i 1) result rest-name))
;; Normal param
(= (type-of p) "symbol")
(js-collect-params-loop params (+ i 1)
(append result (js-mangle (symbol-name p))) rest-name)
;; Annotated param: (name :as type) → extract name
(and (= (type-of p) "list") (= (len p) 3)
(= (type-of (nth p 1)) "keyword")
(= (keyword-name (nth p 1)) "as"))
(js-collect-params-loop params (+ i 1)
(append result (js-mangle (symbol-name (first p)))) rest-name)
;; Something else
:else
(js-collect-params-loop params (+ i 1)
@@ -1024,7 +1036,7 @@
(js-parse-clojure-let-bindings bindings 0 (list))))))
(define js-parse-clojure-let-bindings
(fn (bindings i result)
(fn (bindings (i :as number) (result :as list))
(if (>= i (- (len bindings) 1))
result
(let ((vname (if (= (type-of (nth bindings i)) "symbol")
@@ -1050,7 +1062,7 @@
(str (js-emit-clojure-let-vars bindings 0 (list)) " ")))))
(define js-emit-clojure-let-vars
(fn (bindings i result)
(fn (bindings (i :as number) (result :as list))
(if (>= i (- (len bindings) 1))
(join " " result)
(let ((vname (if (= (type-of (nth bindings i)) "symbol")
@@ -1062,7 +1074,7 @@
;; Helper to append let binding var declarations to a parts list
(define js-append-let-binding-parts
(fn (bindings parts)
(fn (bindings (parts :as list))
(when (and (list? bindings) (not (empty? bindings)))
(if (list? (first bindings))
;; Scheme-style
@@ -1076,7 +1088,7 @@
(js-append-clojure-bindings bindings parts 0)))))
(define js-append-clojure-bindings
(fn (bindings parts i)
(fn (bindings (parts :as list) (i :as number))
(when (< i (- (len bindings) 1))
(let ((vname (if (= (type-of (nth bindings i)) "symbol")
(symbol-name (nth bindings i))
@@ -1105,7 +1117,7 @@
;; --------------------------------------------------------------------------
(define js-emit-cond
(fn (clauses)
(fn ((clauses :as list))
(if (empty? clauses)
"NIL"
;; Detect scheme vs clojure style
@@ -1123,7 +1135,7 @@
(and (= (type-of test) "keyword") (= (keyword-name test) "else")))))
(define js-cond-scheme
(fn (clauses)
(fn ((clauses :as list))
(if (empty? clauses)
"NIL"
(let ((clause (first clauses))
@@ -1135,7 +1147,7 @@
" : " (js-cond-scheme (rest clauses)) ")"))))))
(define js-cond-clojure
(fn (clauses)
(fn ((clauses :as list))
(if (< (len clauses) 2)
"NIL"
(let ((test (first clauses))
@@ -1151,14 +1163,14 @@
;; --------------------------------------------------------------------------
(define js-emit-case
(fn (args)
(fn ((args :as list))
(let ((match-expr (js-expr (first args)))
(clauses (rest args)))
(str "(function() { var _m = " match-expr "; "
(js-case-chain clauses) " })()"))))
(define js-case-chain
(fn (clauses)
(fn ((clauses :as list))
(if (< (len clauses) 2)
"return NIL;"
(let ((test (nth clauses 0))
@@ -1175,7 +1187,7 @@
;; --------------------------------------------------------------------------
(define js-emit-and
(fn (args)
(fn ((args :as list))
(let ((parts (map js-expr args)))
(if (= (len parts) 1)
(first parts)
@@ -1190,7 +1202,7 @@
;; --------------------------------------------------------------------------
(define js-emit-or
(fn (args)
(fn ((args :as list))
(if (= (len args) 1)
(js-expr (first args))
(str "sxOr(" (join ", " (map js-expr args)) ")"))))
@@ -1201,7 +1213,7 @@
;; --------------------------------------------------------------------------
(define js-emit-do
(fn (args)
(fn ((args :as list))
(if (= (len args) 1)
(js-expr (first args))
(str "(" (join ", " (map js-expr args)) ")"))))
@@ -1212,11 +1224,11 @@
;; --------------------------------------------------------------------------
(define js-emit-dict-literal
(fn (pairs)
(fn ((pairs :as list))
(str "{" (js-dict-pairs-str pairs 0 (list)) "}")))
(define js-dict-pairs-str
(fn (pairs i result)
(fn ((pairs :as list) (i :as number) (result :as list))
(if (>= i (- (len pairs) 1))
(join ", " result)
(let ((key (nth pairs i))
@@ -1234,7 +1246,7 @@
;; --------------------------------------------------------------------------
(define js-emit-infix
(fn (op args)
(fn ((op :as string) (args :as list))
(let ((js-op (js-op-symbol op)))
(if (and (= (len args) 1) (= op "-"))
(str "(-" (js-expr (first args)) ")")
@@ -1374,7 +1386,7 @@
;; --------------------------------------------------------------------------
(define js-translate-file
(fn (defines)
(fn ((defines :as list))
(join "\n" (map (fn (pair)
(let ((name (first pair))
(expr (nth pair 1)))

View File

@@ -34,7 +34,7 @@
;; --------------------------------------------------------------------------
(define dispatch-trigger-events
(fn (el header-val)
(fn (el (header-val :as string))
;; Dispatch events from SX-Trigger / SX-Trigger-After-Swap headers.
;; Value can be JSON object (name → detail) or comma-separated names.
(when header-val
@@ -42,12 +42,12 @@
(if parsed
;; JSON object: keys are event names, values are detail
(for-each
(fn (key)
(fn ((key :as string))
(dom-dispatch el key (get parsed key)))
(keys parsed))
;; Comma-separated event names
(for-each
(fn (name)
(fn ((name :as string))
(let ((trimmed (trim name)))
(when (not (empty? trimmed))
(dom-dispatch el trimmed (dict)))))
@@ -73,7 +73,7 @@
;; --------------------------------------------------------------------------
(define execute-request
(fn (el verbInfo extraParams)
(fn (el (verbInfo :as dict) (extraParams :as dict))
;; Gate checks then delegate to do-fetch.
;; verbInfo: dict with "method" and "url" (or nil to read from element).
;; Re-read from element in case attributes were morphed since binding.
@@ -106,7 +106,7 @@
(define do-fetch
(fn (el verb method url extraParams)
(fn (el (verb :as string) (method :as string) (url :as string) (extraParams :as dict))
;; Execute the actual fetch. Manages abort, headers, body, loading state.
(let ((sync (dom-get-attr el "sx-sync")))
;; Abort previous if sync mode (per-element)
@@ -140,7 +140,7 @@
;; Merge extra params as headers
(when extraParams
(for-each
(fn (k) (dict-set! headers k (get extraParams k)))
(fn ((k :as string)) (dict-set! headers k (get extraParams k)))
(keys extraParams)))
;; Content-Type
@@ -172,7 +172,7 @@
"cross-origin" (cross-origin? final-url)
"preloaded" cached)
;; Success callback
(fn (resp-ok status get-header text)
(fn ((resp-ok :as boolean) (status :as number) get-header (text :as string))
(do
(clear-loading-state el indicator disabled-elts)
(revert-optimistic optimistic-state)
@@ -202,7 +202,7 @@
(define handle-fetch-success
(fn (el url verb extraParams get-header text)
(fn (el (url :as string) (verb :as string) (extraParams :as dict) get-header (text :as string))
;; Route a successful response through the appropriate handler.
(let ((resp-headers (process-response-headers get-header)))
;; CSS hash update
@@ -270,7 +270,7 @@
(define handle-sx-response
(fn (el target text swap-style use-transition)
(fn (el target (text :as string) (swap-style :as string) (use-transition :as boolean))
;; Handle SX-format response: strip components, extract CSS, render, swap.
(let ((cleaned (strip-component-scripts text)))
(let ((final (extract-response-css cleaned)))
@@ -281,7 +281,7 @@
(dom-append container rendered)
;; Process OOB swaps
(process-oob-swaps container
(fn (t oob s)
(fn (t oob (s :as string))
(dispose-islands-in t)
(swap-dom-nodes t oob s)
(sx-hydrate t)
@@ -301,7 +301,7 @@
(define handle-html-response
(fn (el target text swap-style use-transition)
(fn (el target (text :as string) (swap-style :as string) (use-transition :as boolean))
;; Handle HTML-format response: parse, OOB, select, swap.
(let ((doc (dom-parse-html-document text)))
(when doc
@@ -320,7 +320,7 @@
(dom-set-inner-html container (dom-body-inner-html doc))
;; Process OOB swaps
(process-oob-swaps container
(fn (t oob s)
(fn (t oob (s :as string))
(dispose-islands-in t)
(swap-dom-nodes t oob s)
(post-swap t)))
@@ -338,7 +338,7 @@
;; --------------------------------------------------------------------------
(define handle-retry
(fn (el verb method url extraParams)
(fn (el (verb :as string) (method :as string) (url :as string) (extraParams :as dict))
;; Handle retry on failure if sx-retry is configured
(let ((retry-attr (dom-get-attr el "sx-retry"))
(spec (parse-retry-spec retry-attr)))
@@ -358,12 +358,12 @@
;; --------------------------------------------------------------------------
(define bind-triggers
(fn (el verbInfo)
(fn (el (verbInfo :as dict))
;; Bind triggers from sx-trigger attribute (or defaults)
(let ((triggers (or (parse-trigger-spec (dom-get-attr el "sx-trigger"))
(default-trigger (dom-tag-name el)))))
(for-each
(fn (trigger)
(fn ((trigger :as dict))
(let ((kind (classify-trigger trigger))
(mods (get trigger "modifiers")))
(cond
@@ -393,7 +393,7 @@
(define bind-event
(fn (el event-name mods verbInfo)
(fn (el (event-name :as string) (mods :as dict) (verbInfo :as dict))
;; Bind a standard DOM event trigger.
;; Handles delay, once, changed, optimistic, preventDefault.
(let ((timer nil)
@@ -506,12 +506,12 @@
;; --------------------------------------------------------------------------
(define process-oob-swaps
(fn (container swap-fn)
(fn (container (swap-fn :as lambda))
;; Find and process out-of-band swaps in container.
;; swap-fn is (fn (target oob-element swap-type) ...).
(let ((oobs (find-oob-swaps container)))
(for-each
(fn (oob)
(fn ((oob :as dict))
(let ((target-id (get oob "target-id"))
(target (dom-query-by-id target-id))
(oob-el (get oob "element"))
@@ -610,7 +610,7 @@
(define _page-data-cache-ttl 30000) ;; 30 seconds in ms
(define page-data-cache-key
(fn (page-name params)
(fn ((page-name :as string) (params :as dict))
;; Build a cache key from page name + params.
;; Params are from route matching so order is deterministic.
(let ((base page-name))
@@ -618,13 +618,13 @@
base
(let ((parts (list)))
(for-each
(fn (k)
(fn ((k :as string))
(append! parts (str k "=" (get params k))))
(keys params))
(str base ":" (join "&" parts)))))))
(define page-data-cache-get
(fn (cache-key)
(fn ((cache-key :as string))
;; Return cached data if fresh, else nil.
(let ((entry (get _page-data-cache cache-key)))
(if (nil? entry)
@@ -636,7 +636,7 @@
(get entry "data"))))))
(define page-data-cache-set
(fn (cache-key data)
(fn ((cache-key :as string) data)
;; Store data with current timestamp.
(dict-set! _page-data-cache cache-key
{"data" data "ts" (now-ms)})))
@@ -647,12 +647,12 @@
;; --------------------------------------------------------------------------
(define invalidate-page-cache
(fn (page-name)
(fn ((page-name :as string))
;; Clear cached data for a page. Removes all cache entries whose key
;; matches page-name (exact) or starts with "page-name:" (with params).
;; Also notifies the service worker to clear its IndexedDB entries.
(for-each
(fn (k)
(fn ((k :as string))
(when (or (= k page-name) (starts-with? k (str page-name ":")))
(dict-set! _page-data-cache k nil)))
(keys _page-data-cache))
@@ -667,7 +667,7 @@
(log-info "sx:cache invalidate *")))
(define update-page-cache
(fn (page-name data)
(fn ((page-name :as string) data)
;; Replace cached data for a page with server-provided data.
;; Uses a bare page-name key (no params) — the server knows the
;; canonical data shape for the page.
@@ -676,7 +676,7 @@
(log-info (str "sx:cache update " page-name)))))
(define process-cache-directives
(fn (el resp-headers response-text)
(fn (el (resp-headers :as dict) (response-text :as string))
;; Process cache invalidation and update directives from both
;; element attributes and response headers.
;;
@@ -722,7 +722,7 @@
(define _optimistic-snapshots (dict))
(define optimistic-cache-update
(fn (cache-key mutator)
(fn ((cache-key :as string) (mutator :as lambda))
;; Apply predicted mutation to cached data. Saves snapshot for rollback.
;; Returns predicted data or nil if no cached data exists.
(let ((cached (page-data-cache-get cache-key)))
@@ -735,7 +735,7 @@
predicted)))))
(define optimistic-cache-revert
(fn (cache-key)
(fn ((cache-key :as string))
;; Revert to pre-mutation snapshot. Returns restored data or nil.
(let ((snapshot (get _optimistic-snapshots cache-key)))
(when snapshot
@@ -744,12 +744,12 @@
snapshot))))
(define optimistic-cache-confirm
(fn (cache-key)
(fn ((cache-key :as string))
;; Server accepted — discard the rollback snapshot.
(dict-delete! _optimistic-snapshots cache-key)))
(define submit-mutation
(fn (page-name params action-name payload mutator-fn on-complete)
(fn ((page-name :as string) (params :as dict) (action-name :as string) payload (mutator-fn :as lambda) (on-complete :as lambda))
;; Optimistic mutation: predict locally, send to server, confirm or revert.
;; on-complete is called with "confirmed" or "reverted" status.
(let ((cache-key (page-data-cache-key page-name params))
@@ -768,7 +768,7 @@
(try-rerender-page page-name params result))
(log-info (str "sx:optimistic confirmed " page-name))
(when on-complete (on-complete "confirmed")))
(fn (error)
(fn ((error :as string))
;; Failure: revert to snapshot
(let ((reverted (optimistic-cache-revert cache-key)))
(when reverted
@@ -791,11 +791,11 @@
(fn () _is-online))
(define offline-set-online!
(fn (val)
(fn ((val :as boolean))
(set! _is-online val)))
(define offline-queue-mutation
(fn (action-name payload page-name params mutator-fn)
(fn ((action-name :as string) payload (page-name :as string) (params :as dict) (mutator-fn :as lambda))
;; Queue a mutation for later sync. Apply optimistic update locally.
(let ((cache-key (page-data-cache-key page-name params))
(entry (dict
@@ -816,26 +816,26 @@
(define offline-sync
(fn ()
;; Replay all pending mutations. Called on reconnect.
(let ((pending (filter (fn (e) (= (get e "status") "pending")) _offline-queue)))
(let ((pending (filter (fn ((e :as dict)) (= (get e "status") "pending")) _offline-queue)))
(when (not (empty? pending))
(log-info (str "sx:offline syncing " (len pending) " mutations"))
(for-each
(fn (entry)
(fn ((entry :as dict))
(execute-action (get entry "action") (get entry "payload")
(fn (result)
(dict-set! entry "status" "synced")
(log-info (str "sx:offline synced " (get entry "action"))))
(fn (error)
(fn ((error :as string))
(dict-set! entry "status" "failed")
(log-warn (str "sx:offline sync failed " (get entry "action") ": " error)))))
pending)))))
(define offline-pending-count
(fn ()
(len (filter (fn (e) (= (get e "status") "pending")) _offline-queue))))
(len (filter (fn ((e :as dict)) (= (get e "status") "pending")) _offline-queue))))
(define offline-aware-mutation
(fn (page-name params action-name payload mutator-fn on-complete)
(fn ((page-name :as string) (params :as dict) (action-name :as string) payload (mutator-fn :as lambda) (on-complete :as lambda))
;; Top-level mutation function. Routes to submit-mutation when online,
;; offline-queue-mutation when offline.
(if _is-online
@@ -860,7 +860,7 @@
(define swap-rendered-content
(fn (target rendered pathname)
(fn (target rendered (pathname :as string))
;; Swap rendered DOM content into target and run post-processing.
;; Shared by pure and data page client routes.
(do
@@ -876,7 +876,7 @@
(define resolve-route-target
(fn (target-sel)
(fn ((target-sel :as string))
;; Resolve a target selector to a DOM element, or nil.
(if (and target-sel (not (= target-sel "true")))
(dom-query target-sel)
@@ -884,17 +884,17 @@
(define deps-satisfied?
(fn (match)
(fn ((match :as dict))
;; Check if all component deps for a page are loaded client-side.
(let ((deps (get match "deps"))
(loaded (loaded-component-names)))
(if (or (nil? deps) (empty? deps))
true
(every? (fn (dep) (contains? loaded dep)) deps)))))
(every? (fn ((dep :as string)) (contains? loaded dep)) deps)))))
(define try-client-route
(fn (pathname target-sel)
(fn ((pathname :as string) (target-sel :as string))
;; Try to render a page client-side. Returns true if successful, false otherwise.
;; target-sel is the CSS selector for the swap target (from sx-boost value).
;; For pure pages: renders immediately. For :data pages: fetches data then renders.
@@ -968,7 +968,7 @@
(do
(log-info (str "sx:route client+data " pathname))
(resolve-page-data page-name params
(fn (data)
(fn ((data :as dict))
(page-data-cache-set cache-key data)
(let ((env (merge closure params data)))
(if has-io
@@ -1012,7 +1012,7 @@
(define bind-client-route-link
(fn (link href)
(fn (link (href :as string))
;; Bind a boost link with client-side routing. If the route can be
;; rendered client-side (pure page, no :data), do so. Otherwise
;; fall back to standard server fetch via bind-boost-link.
@@ -1045,12 +1045,12 @@
(let ((source (event-source-connect url el))
(event-name (parse-sse-swap el)))
(event-source-listen source event-name
(fn (data)
(fn ((data :as string))
(bind-sse-swap el data))))))))
(define bind-sse-swap
(fn (el data)
(fn (el (data :as string))
;; Handle an SSE event: swap data into element
(let ((target (resolve-target el))
(swap-spec (parse-swap-spec
@@ -1089,7 +1089,7 @@
(for-each
(fn (el)
(for-each
(fn (attr)
(fn ((attr :as list))
(let ((name (first attr))
(body (nth attr 1)))
(when (starts-with? name "sx-on:")
@@ -1135,7 +1135,7 @@
(define do-preload
(fn (url headers)
(fn ((url :as string) (headers :as dict))
;; Execute a preload fetch into the cache
(when (nil? (preload-cache-get _preload-cache url))
(fetch-preload url headers _preload-cache))))
@@ -1215,7 +1215,7 @@
;; --------------------------------------------------------------------------
(define handle-popstate
(fn (scrollY)
(fn ((scrollY :as number))
;; Handle browser back/forward navigation.
;; Derive target from [sx-boost] container or fall back to #main-panel.
;; Try client-side route first, fall back to server fetch.

View File

@@ -36,7 +36,7 @@
(define extract-define-kwargs
(fn (expr)
(fn ((expr :as list))
;; 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.
@@ -44,7 +44,7 @@
(items (slice expr 2))
(n (len items)))
(for-each
(fn (idx)
(fn ((idx :as number))
(when (and (< (+ idx 1) n)
(= (type-of (nth items idx)) "keyword"))
(let ((key (keyword-name (nth items idx)))
@@ -58,7 +58,7 @@
(define categorize-special-forms
(fn (parsed-exprs)
(fn ((parsed-exprs :as list))
;; parsed-exprs: result of parse-all on special-forms.sx
;; Returns dict of category-name → list of form dicts.
(let ((categories {}))
@@ -90,13 +90,13 @@
;; --------------------------------------------------------------------------
(define build-ref-items-with-href
(fn (items base-path detail-keys n-fields)
(fn ((items :as list) (base-path :as string) (detail-keys :as list) (n-fields :as number))
;; items: list of lists (tuples), each with n-fields elements
;; base-path: e.g. "/geography/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)
(fn ((item :as list))
(if (= n-fields 3)
;; [name, desc/value, exists/desc]
(let ((name (nth item 0))
@@ -105,7 +105,7 @@
{"name" name
"desc" field2
"exists" field3
"href" (if (and field3 (some (fn (k) (= k name)) detail-keys))
"href" (if (and field3 (some (fn ((k :as string)) (= k name)) detail-keys))
(str base-path name)
nil)})
;; [name, desc]
@@ -113,14 +113,14 @@
(desc (nth item 1)))
{"name" name
"desc" desc
"href" (if (some (fn (k) (= k name)) detail-keys)
"href" (if (some (fn ((k :as string)) (= k name)) detail-keys)
(str base-path name)
nil)})))
items)))
(define build-reference-data
(fn (slug raw-data detail-keys)
(fn ((slug :as string) (raw-data :as dict) (detail-keys :as list))
;; 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
@@ -150,7 +150,7 @@
"/geography/hypermedia/reference/events/" detail-keys 2)}
"js-api"
{"js-api-list" (map (fn (item) {"name" (nth item 0) "desc" (nth item 1)})
{"js-api-list" (map (fn ((item :as list)) {"name" (nth item 0) "desc" (nth item 1)})
(get raw-data "js-api-list"))}
;; default: attributes
@@ -173,7 +173,7 @@
;; --------------------------------------------------------------------------
(define build-attr-detail
(fn (slug detail)
(fn ((slug :as string) detail)
;; detail: dict with "description", "example", "handler", "demo" keys or nil
(if (nil? detail)
{"attr-not-found" true}
@@ -190,7 +190,7 @@
(define build-header-detail
(fn (slug detail)
(fn ((slug :as string) detail)
(if (nil? detail)
{"header-not-found" true}
{"header-not-found" nil
@@ -202,7 +202,7 @@
(define build-event-detail
(fn (slug detail)
(fn ((slug :as string) detail)
(if (nil? detail)
{"event-not-found" true}
{"event-not-found" nil
@@ -219,7 +219,7 @@
;; --------------------------------------------------------------------------
(define build-component-source
(fn (comp-data)
(fn ((comp-data :as dict))
;; comp-data: dict with "type", "name", "params", "has-children", "body-sx", "affinity"
(let ((comp-type (get comp-data "type"))
(name (get comp-data "name"))
@@ -253,12 +253,12 @@
;; --------------------------------------------------------------------------
(define build-bundle-analysis
(fn (pages-raw components-raw total-components total-macros pure-count io-count)
(fn ((pages-raw :as list) (components-raw :as dict) (total-components :as number) (total-macros :as number) (pure-count :as number) (io-count :as number))
;; 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)
(fn ((page :as dict))
(let ((needed-names (get page "needed-names"))
(n (len needed-names))
(pct (if (> total-components 0)
@@ -271,7 +271,7 @@
(comp-details (list)))
;; Walk needed components
(for-each
(fn (comp-name)
(fn ((comp-name :as string))
(let ((info (get components-raw comp-name)))
(when (not (nil? info))
(if (get info "is-pure")
@@ -279,7 +279,7 @@
(do
(set! io-in-page (+ io-in-page 1))
(for-each
(fn (ref) (when (not (some (fn (r) (= r ref)) page-io-refs))
(fn ((ref :as string)) (when (not (some (fn ((r :as string)) (= r ref)) page-io-refs))
(append! page-io-refs ref)))
(or (get info "io-refs") (list)))))
(append! comp-details
@@ -317,13 +317,13 @@
;; --------------------------------------------------------------------------
(define build-routing-analysis
(fn (pages-raw)
(fn ((pages-raw :as list))
;; pages-raw: list of {:name :path :has-data :content-src}
(let ((pages-data (list))
(client-count 0)
(server-count 0))
(for-each
(fn (page)
(fn ((page :as dict))
(let ((has-data (get page "has-data"))
(content-src (or (get page "content-src") ""))
(mode nil)
@@ -363,6 +363,6 @@
;; --------------------------------------------------------------------------
(define build-affinity-analysis
(fn (demo-components page-plans)
(fn ((demo-components :as list) (page-plans :as list))
{"components" demo-components
"page-plans" page-plans}))

View File

@@ -50,7 +50,7 @@
;; Returns a list of top-level AST expressions.
(define sx-parse
(fn (source)
(fn ((source :as string))
(let ((pos 0)
(len-src (len source)))
@@ -170,7 +170,7 @@
;; -- Composite readers --
(define read-list
(fn (close-ch)
(fn ((close-ch :as string))
(let ((items (list)))
(define read-list-loop
(fn ()
@@ -352,11 +352,11 @@
(define sx-serialize-dict
(fn (d)
(fn ((d :as dict))
(str "{"
(join " "
(reduce
(fn (acc key)
(fn ((acc :as list) (key :as string))
(concat acc (list (str ":" key) (sx-serialize (dict-get d key)))))
(list)
(keys d)))

View File

@@ -25,7 +25,7 @@
;; Evaluate an SMT-LIB expression in a variable environment
(define smt-eval
(fn (expr env)
(fn (expr (env :as dict))
(cond
;; Numbers
(number? expr) expr
@@ -136,11 +136,11 @@
;; Bind parameter names to values
(define smt-bind-params
(fn (params vals)
(fn ((params :as list) (vals :as list))
(smt-bind-loop params vals {})))
(define smt-bind-loop
(fn (params vals acc)
(fn ((params :as list) (vals :as list) (acc :as dict))
(if (or (empty? params) (empty? vals))
acc
(smt-bind-loop (rest params) (rest vals)
@@ -153,11 +153,11 @@
;; Extract declarations and assertions from parsed SMT-LIB
(define smt-extract-statements
(fn (exprs)
(fn ((exprs :as list))
(smt-extract-loop exprs {} (list))))
(define smt-extract-loop
(fn (exprs decls assertions)
(fn ((exprs :as list) (decls :as dict) (assertions :as list))
(if (empty? exprs)
{:decls decls :assertions assertions}
(let ((expr (first exprs))
@@ -286,7 +286,7 @@
;; Verify a single definitional assertion by construction + evaluation
(define smt-verify-definition
(fn (def-info decls)
(fn ((def-info :as dict) (decls :as dict))
(let ((name (get def-info "name"))
(params (get def-info "params"))
(body (get def-info "body"))
@@ -295,10 +295,10 @@
;; Build the model: define f = λparams.body
(let ((model (assoc decls name {:params params :body body}))
;; Select test values matching arity
(tests (filter (fn (tv) (= (len tv) n-params)) smt-test-values))
(tests (filter (fn ((tv :as list)) (= (len tv) n-params)) smt-test-values))
;; Run tests
(results (map
(fn (test-vals)
(fn ((test-vals :as list))
(let ((env (merge model (smt-bind-params params test-vals)))
;; Evaluate body directly
(body-result (smt-eval body env))
@@ -311,9 +311,9 @@
:equal (= body-result call-result)}))
tests)))
{:name name
:status (if (every? (fn (r) (get r "equal")) results) "sat" "FAIL")
:status (if (every? (fn ((r :as dict)) (get r "equal")) results) "sat" "FAIL")
:proof "by construction (definition is the model)"
:tests-passed (len (filter (fn (r) (get r "equal")) results))
:tests-passed (len (filter (fn ((r :as dict)) (get r "equal")) results))
:tests-total (len results)
:sample (if (empty? results) nil (first results))}))))
@@ -325,16 +325,16 @@
;; Strip SMT-LIB comment lines (starting with ;) and return only actual forms.
;; Handles comments that contain ( characters.
(define smt-strip-comments
(fn (s)
(fn ((s :as string))
(let ((lines (split s "\n"))
(non-comment (filter
(fn (line) (not (starts-with? (trim line) ";")))
(fn ((line :as string)) (not (starts-with? (trim line) ";")))
lines)))
(join "\n" non-comment))))
;; Verify SMT-LIB output (string) — parse, classify, prove
(define prove-check
(fn (smtlib-str)
(fn ((smtlib-str :as string))
(let ((parsed (sx-parse (smt-strip-comments smtlib-str)))
(stmts (smt-extract-statements parsed))
(decls (get stmts "decls"))
@@ -351,7 +351,7 @@
{:status "unknown"
:reason "non-definitional assertion (needs full SMT solver)"}))
assertions)))
{:status (if (every? (fn (r) (= (get r "status") "sat")) results)
{:status (if (every? (fn ((r :as dict)) (= (get r "status") "sat")) results)
"sat" "unknown")
:assertions (len assertions)
:results results})))))
@@ -377,7 +377,7 @@
;; Batch verify: translate and prove all define-* forms
(define prove-file
(fn (exprs)
(fn ((exprs :as list))
(let ((translatable
(filter
(fn (expr)
@@ -396,7 +396,7 @@
(name (nth expr 1)))
(assoc proof "name" name)))
translatable))
(sat-count (len (filter (fn (r) (= (get r "status") "sat")) results)))
(sat-count (len (filter (fn ((r :as dict)) (= (get r "status") "sat")) results)))
(total (len results)))
{:total total
:sat sat-count
@@ -424,7 +424,7 @@
;; Default domain bounds by arity — balance coverage vs. combinatorics
(define prove-domain-for
(fn (arity)
(fn ((arity :as number))
(cond
(<= arity 1) (range -50 51) ;; 101 values
(= arity 2) (range -20 21) ;; 41^2 = 1,681 pairs
@@ -433,7 +433,7 @@
;; Cartesian product: all n-tuples from a domain
(define prove-tuples
(fn (domain arity)
(fn ((domain :as list) (arity :as number))
(if (<= arity 0) (list (list))
(if (= arity 1)
(map (fn (x) (list x)) domain)
@@ -441,12 +441,12 @@
(prove-tuples-expand domain sub (list)))))))
(define prove-tuples-expand
(fn (domain sub acc)
(fn ((domain :as list) (sub :as list) (acc :as list))
(if (empty? domain) acc
(prove-tuples-expand
(rest domain) sub
(append acc
(map (fn (t) (cons (first domain) t)) sub))))))
(map (fn ((t :as list)) (cons (first domain) t)) sub))))))
;; --------------------------------------------------------------------------
@@ -454,7 +454,7 @@
;; --------------------------------------------------------------------------
(define prove-call
(fn (f vals)
(fn ((f :as lambda) (vals :as list))
(let ((n (len vals)))
(cond
(= n 0) (f)
@@ -472,13 +472,13 @@
;; Search for a counterexample. Returns nil if property holds for all tested
;; values, or the first counterexample found.
(define prove-search
(fn (test-fn given-fn domain vars)
(fn ((test-fn :as lambda) given-fn (domain :as list) (vars :as list))
(let ((arity (len vars))
(tuples (prove-tuples domain arity)))
(prove-search-loop test-fn given-fn tuples 0 0))))
(define prove-search-loop
(fn (test-fn given-fn tuples tested skipped)
(fn ((test-fn :as lambda) given-fn (tuples :as list) (tested :as number) (skipped :as number))
(if (empty? tuples)
{:status "verified" :tested tested :skipped skipped}
(let ((vals (first tuples))
@@ -505,7 +505,7 @@
;; Verify a single property via bounded model checking
(define prove-property
(fn (prop)
(fn ((prop :as dict))
(let ((name (get prop "name"))
(vars (get prop "vars"))
(test-fn (get prop "test"))
@@ -519,10 +519,10 @@
;; Batch verify a list of properties
(define prove-properties
(fn (props)
(fn ((props :as list))
(let ((results (map prove-property props))
(verified (filter (fn (r) (= (get r "status") "verified")) results))
(falsified (filter (fn (r) (= (get r "status") "falsified")) results)))
(verified (filter (fn ((r :as dict)) (= (get r "status") "verified")) results))
(falsified (filter (fn ((r :as dict)) (= (get r "status") "falsified")) results)))
{:total (len results)
:verified (len verified)
:falsified (len falsified)
@@ -537,13 +537,13 @@
;; Generate SMT-LIB for a property — asserts (not (forall ...)) so that
;; Z3 returning "unsat" proves the property holds universally.
(define prove-property-smtlib
(fn (prop)
(fn ((prop :as dict))
(let ((name (get prop "name"))
(vars (get prop "vars"))
(holds (get prop "holds"))
(given-e (get prop "given-expr" nil))
(bindings (join " "
(map (fn (v) (str "(" v " Int)")) vars)))
(map (fn ((v :as string)) (str "(" v " Int)")) vars)))
(holds-smt (z3-expr holds))
(body (if (nil? given-e)
holds-smt
@@ -556,7 +556,7 @@
;; Generate SMT-LIB for all properties, including necessary definitions
(define prove-properties-smtlib
(fn (props primitives-exprs)
(fn ((props :as list) (primitives-exprs :as list))
(let ((defs (z3-translate-file primitives-exprs))
(prop-smts (map prove-property-smtlib props)))
(str ";; ================================================================\n"

View File

@@ -253,7 +253,7 @@
;; --------------------------------------------------------------------------
(define py-mangle
(fn (name)
(fn ((name :as string))
(let ((renamed (get py-renames name)))
(if (not (nil? renamed))
renamed
@@ -279,7 +279,7 @@
;; --------------------------------------------------------------------------
(define py-quote-string
(fn (s)
(fn ((s :as string))
;; Produce a Python repr-style string literal
(str "'" (replace (replace (replace (replace s "\\" "\\\\") "'" "\\'") "\n" "\\n") "\t" "\\t") "'")))
@@ -292,11 +292,11 @@
(list "+" "-" "*" "/" "=" "!=" "<" ">" "<=" ">=" "mod"))
(define py-infix?
(fn (op)
(fn ((op :as string))
(some (fn (x) (= x op)) py-infix-ops)))
(define py-op-symbol
(fn (op)
(fn ((op :as string))
(case op
"=" "=="
"!=" "!="
@@ -309,7 +309,7 @@
;; --------------------------------------------------------------------------
(define py-find-nested-set-vars
(fn (body)
(fn ((body :as list))
;; Returns a list of mangled variable names that are set! from within
;; nested fn/lambda bodies
(let ((result (list)))
@@ -318,7 +318,7 @@
result))))
(define py-scan-set-vars
(fn (node in-nested result)
(fn (node (in-nested :as boolean) (result :as list))
(when (and (list? node) (not (empty? node)))
(let ((head (first node)))
(cond
@@ -353,7 +353,7 @@
(py-has-set? body))))
(define py-has-set?
(fn (nodes)
(fn ((nodes :as list))
(some (fn (node)
(and (list? node)
(not (empty? node))
@@ -372,7 +372,7 @@
(py-expr-with-cells expr (list))))
(define py-expr-with-cells
(fn (expr cell-vars)
(fn (expr (cell-vars :as list))
(cond
;; Bool MUST come before number check (Python: bool is subclass of int)
(= (type-of expr) "boolean")
@@ -417,7 +417,7 @@
;; --------------------------------------------------------------------------
(define py-emit-native-dict
(fn (d cell-vars)
(fn ((d :as dict) (cell-vars :as list))
(let ((items (keys d)))
(str "{" (join ", " (map (fn (k)
(str (py-quote-string k) ": " (py-expr-with-cells (get d k) cell-vars)))
@@ -429,7 +429,7 @@
;; --------------------------------------------------------------------------
(define py-emit-list
(fn (expr cell-vars)
(fn (expr (cell-vars :as list))
(let ((head (first expr))
(args (rest expr)))
(if (not (= (type-of head) "symbol"))
@@ -548,7 +548,7 @@
;; --------------------------------------------------------------------------
(define py-emit-fn
(fn (expr cell-vars)
(fn (expr (cell-vars :as list))
(let ((params (nth expr 1))
(body (rest (rest expr)))
(param-strs (py-collect-params params)))
@@ -562,11 +562,11 @@
"\n)[-1])"))))))
(define py-collect-params
(fn (params)
(fn ((params :as list))
(py-collect-params-loop params 0 (list))))
(define py-collect-params-loop
(fn (params i result)
(fn ((params :as list) (i :as number) (result :as list))
(if (>= i (len params))
result
(let ((p (nth params i)))
@@ -574,13 +574,25 @@
;; &rest marker
(and (= (type-of p) "symbol") (= (symbol-name p) "&rest"))
(if (< (+ i 1) (len params))
(py-collect-params-loop params (+ i 2)
(append result (str "*" (py-mangle (symbol-name (nth params (+ i 1)))))))
(let ((rp (nth params (+ i 1))))
(py-collect-params-loop params (+ i 2)
(append result (str "*" (py-mangle
(if (and (= (type-of rp) "list") (= (len rp) 3)
(= (type-of (nth rp 1)) "keyword")
(= (keyword-name (nth rp 1)) "as"))
(symbol-name (first rp))
(if (= (type-of rp) "symbol") (symbol-name rp) (str rp))))))))
(py-collect-params-loop params (+ i 1) result))
;; Normal param
(= (type-of p) "symbol")
(py-collect-params-loop params (+ i 1)
(append result (py-mangle (symbol-name p))))
;; Annotated param: (name :as type) → extract name
(and (= (type-of p) "list") (= (len p) 3)
(= (type-of (nth p 1)) "keyword")
(= (keyword-name (nth p 1)) "as"))
(py-collect-params-loop params (+ i 1)
(append result (py-mangle (symbol-name (first p)))))
;; Something else
:else
(py-collect-params-loop params (+ i 1)
@@ -592,7 +604,7 @@
;; --------------------------------------------------------------------------
(define py-emit-let
(fn (expr cell-vars)
(fn (expr (cell-vars :as list))
(let ((bindings (nth expr 1))
(body (rest (rest expr))))
(let ((assignments (py-parse-bindings bindings cell-vars)))
@@ -603,7 +615,7 @@
(py-wrap-let-bindings assignments body-str cell-vars))))))
(define py-parse-bindings
(fn (bindings cell-vars)
(fn (bindings (cell-vars :as list))
(if (and (list? bindings) (not (empty? bindings)))
(if (list? (first bindings))
;; Scheme-style: ((name val) ...)
@@ -618,7 +630,7 @@
(list))))
(define py-parse-clojure-bindings
(fn (bindings i result cell-vars)
(fn (bindings (i :as number) (result :as list) (cell-vars :as list))
(if (>= i (- (len bindings) 1))
result
(let ((vname (if (= (type-of (nth bindings i)) "symbol")
@@ -629,7 +641,7 @@
cell-vars)))))
(define py-wrap-let-bindings
(fn (assignments body-str cell-vars)
(fn ((assignments :as list) (body-str :as string) (cell-vars :as list))
(if (empty? assignments)
body-str
(let ((binding (last assignments))
@@ -649,7 +661,7 @@
;; --------------------------------------------------------------------------
(define py-emit-when
(fn (expr cell-vars)
(fn (expr (cell-vars :as list))
(let ((cond-e (py-expr-with-cells (nth expr 1) cell-vars))
(body-parts (rest (rest expr))))
(if (= (len body-parts) 1)
@@ -663,7 +675,7 @@
;; --------------------------------------------------------------------------
(define py-emit-cond
(fn (clauses cell-vars)
(fn ((clauses :as list) (cell-vars :as list))
(if (empty? clauses)
"NIL"
;; Detect scheme vs clojure style
@@ -681,7 +693,7 @@
(and (= (type-of test) "keyword") (= (keyword-name test) "else")))))
(define py-cond-scheme
(fn (clauses cell-vars)
(fn ((clauses :as list) (cell-vars :as list))
(if (empty? clauses)
"NIL"
(let ((clause (first clauses))
@@ -694,7 +706,7 @@
") else " (py-cond-scheme (rest clauses) cell-vars) ")"))))))
(define py-cond-clojure
(fn (clauses cell-vars)
(fn ((clauses :as list) (cell-vars :as list))
(if (< (len clauses) 2)
"NIL"
(let ((test (first clauses))
@@ -711,17 +723,17 @@
;; --------------------------------------------------------------------------
(define py-emit-case
(fn (args cell-vars)
(fn ((args :as list) (cell-vars :as list))
(let ((match-expr (py-expr-with-cells (first args) cell-vars))
(clauses (rest args)))
(str "_sx_case(" match-expr ", [" (py-case-pairs clauses cell-vars) "])"))))
(define py-case-pairs
(fn (clauses cell-vars)
(fn ((clauses :as list) (cell-vars :as list))
(py-case-pairs-loop clauses 0 (list) cell-vars)))
(define py-case-pairs-loop
(fn (clauses i result cell-vars)
(fn ((clauses :as list) (i :as number) (result :as list) (cell-vars :as list))
(if (>= i (- (len clauses) 1))
(join ", " result)
(let ((test (nth clauses i))
@@ -738,28 +750,28 @@
;; --------------------------------------------------------------------------
(define py-emit-and
(fn (args cell-vars)
(fn ((args :as list) (cell-vars :as list))
(let ((parts (map (fn (x) (py-expr-with-cells x cell-vars)) args)))
(if (= (len parts) 1)
(first parts)
(py-and-chain parts)))))
(define py-and-chain
(fn (parts)
(fn ((parts :as list))
(if (= (len parts) 1)
(first parts)
(let ((p (first parts)))
(str "(" p " if not sx_truthy(" p ") else " (py-and-chain (rest parts)) ")")))))
(define py-emit-or
(fn (args cell-vars)
(fn ((args :as list) (cell-vars :as list))
(if (= (len args) 1)
(py-expr-with-cells (first args) cell-vars)
(let ((parts (map (fn (x) (py-expr-with-cells x cell-vars)) args)))
(py-or-chain parts)))))
(define py-or-chain
(fn (parts)
(fn ((parts :as list))
(if (= (len parts) 1)
(first parts)
(let ((p (first parts)))
@@ -771,7 +783,7 @@
;; --------------------------------------------------------------------------
(define py-emit-do
(fn (args cell-vars)
(fn ((args :as list) (cell-vars :as list))
(if (= (len args) 1)
(py-expr-with-cells (first args) cell-vars)
(str "_sx_begin(" (join ", " (map (fn (e) (py-expr-with-cells e cell-vars)) args)) ")"))))
@@ -782,11 +794,11 @@
;; --------------------------------------------------------------------------
(define py-emit-dict-literal
(fn (pairs cell-vars)
(fn ((pairs :as list) (cell-vars :as list))
(str "{" (py-dict-pairs-str pairs 0 (list) cell-vars) "}")))
(define py-dict-pairs-str
(fn (pairs i result cell-vars)
(fn ((pairs :as list) (i :as number) (result :as list) (cell-vars :as list))
(if (>= i (- (len pairs) 1))
(join ", " result)
(let ((key (nth pairs i))
@@ -805,7 +817,7 @@
;; --------------------------------------------------------------------------
(define py-emit-infix
(fn (op args cell-vars)
(fn ((op :as string) (args :as list) (cell-vars :as list))
(let ((py-op (py-op-symbol op)))
(if (and (= (len args) 1) (= op "-"))
(str "(-" (py-expr-with-cells (first args) cell-vars) ")")
@@ -839,15 +851,15 @@
;; --------------------------------------------------------------------------
(define py-pad
(fn (indent)
(fn ((indent :as number))
(join "" (map (fn (i) " ") (range 0 indent)))))
(define py-statement
(fn (expr indent)
(fn (expr (indent :as number))
(py-statement-with-cells expr indent (list))))
(define py-statement-with-cells
(fn (expr indent cell-vars)
(fn (expr (indent :as number) (cell-vars :as list))
(let ((pad (py-pad indent)))
(if (and (list? expr) (not (empty? expr))
(= (type-of (first expr)) "symbol"))
@@ -889,7 +901,7 @@
;; --------------------------------------------------------------------------
(define py-emit-define
(fn (expr indent cell-vars)
(fn (expr (indent :as number) (cell-vars :as list))
(let ((pad (py-pad indent))
(name (if (= (type-of (nth expr 1)) "symbol")
(symbol-name (nth expr 1))
@@ -911,7 +923,7 @@
;; --------------------------------------------------------------------------
(define py-emit-define-as-def
(fn (name fn-expr indent)
(fn ((name :as string) fn-expr (indent :as number))
(let ((pad (py-pad indent))
(params (nth fn-expr 1))
(body (rest (rest fn-expr)))
@@ -932,13 +944,13 @@
;; --------------------------------------------------------------------------
(define py-emit-body-stmts
(fn (body lines indent cell-vars)
(fn ((body :as list) (lines :as list) (indent :as number) (cell-vars :as list))
(let ((pad (py-pad indent))
(total (len body)))
(py-emit-body-stmts-loop body lines indent cell-vars 0 total pad))))
(define py-emit-body-stmts-loop
(fn (body lines indent cell-vars i total pad)
(fn ((body :as list) (lines :as list) (indent :as number) (cell-vars :as list) (i :as number) (total :as number) (pad :as string))
(when (< i total)
(let ((expr (nth body i))
(is-last (= i (- total 1))))
@@ -968,7 +980,7 @@
;; --------------------------------------------------------------------------
(define py-emit-let-as-stmts
(fn (expr lines indent is-last cell-vars)
(fn (expr (lines :as list) (indent :as number) (is-last :as boolean) (cell-vars :as list))
(let ((pad (py-pad indent))
(bindings (nth expr 1))
(body (rest (rest expr))))
@@ -981,7 +993,7 @@
(for-each (fn (b) (py-emit-stmt-recursive b lines indent cell-vars)) body))))))
(define py-emit-binding-assignments
(fn (bindings lines indent cell-vars)
(fn (bindings (lines :as list) (indent :as number) (cell-vars :as list))
(let ((pad (py-pad indent)))
(when (and (list? bindings) (not (empty? bindings)))
(if (list? (first bindings))
@@ -1002,7 +1014,7 @@
(py-emit-clojure-binding-assignments bindings lines indent 0 cell-vars))))))
(define py-emit-clojure-binding-assignments
(fn (bindings lines indent i cell-vars)
(fn (bindings (lines :as list) (indent :as number) (i :as number) (cell-vars :as list))
(let ((pad (py-pad indent)))
(when (< i (- (len bindings) 1))
(let ((vname (if (= (type-of (nth bindings i)) "symbol")
@@ -1024,7 +1036,7 @@
;; --------------------------------------------------------------------------
(define py-emit-stmt-recursive
(fn (expr lines indent cell-vars)
(fn (expr (lines :as list) (indent :as number) (cell-vars :as list))
(let ((pad (py-pad indent)))
(if (not (and (list? expr) (not (empty? expr))))
(append! lines (py-statement-with-cells expr indent cell-vars))
@@ -1082,7 +1094,7 @@
;; --------------------------------------------------------------------------
(define py-emit-cond-stmt
(fn (expr lines indent cell-vars)
(fn (expr (lines :as list) (indent :as number) (cell-vars :as list))
(let ((pad (py-pad indent))
(clauses (rest expr)))
;; Detect scheme vs clojure
@@ -1094,7 +1106,7 @@
(py-cond-stmt-clojure clauses lines indent 0 true cell-vars))))))
(define py-cond-stmt-scheme
(fn (clauses lines indent first-clause cell-vars)
(fn ((clauses :as list) (lines :as list) (indent :as number) (first-clause :as boolean) (cell-vars :as list))
(let ((pad (py-pad indent)))
(when (not (empty? clauses))
(let ((clause (first clauses))
@@ -1111,7 +1123,7 @@
(py-cond-stmt-scheme (rest clauses) lines indent false cell-vars)))))))
(define py-cond-stmt-clojure
(fn (clauses lines indent i first-clause cell-vars)
(fn ((clauses :as list) (lines :as list) (indent :as number) (i :as number) (first-clause :as boolean) (cell-vars :as list))
(let ((pad (py-pad indent)))
(when (< i (- (len clauses) 1))
(let ((test (nth clauses i))
@@ -1132,7 +1144,7 @@
;; --------------------------------------------------------------------------
(define py-emit-when-stmt
(fn (expr indent cell-vars)
(fn (expr (indent :as number) (cell-vars :as list))
(let ((pad (py-pad indent))
(cond-e (py-expr-with-cells (nth expr 1) cell-vars))
(body-parts (rest (rest expr))))
@@ -1146,7 +1158,7 @@
;; --------------------------------------------------------------------------
(define py-emit-for-each-stmt
(fn (expr indent cell-vars)
(fn (expr (indent :as number) (cell-vars :as list))
(let ((pad (py-pad indent))
(fn-expr (nth expr 1))
(coll-expr (nth expr 2))
@@ -1175,7 +1187,7 @@
;; --------------------------------------------------------------------------
(define py-translate-file
(fn (defines)
(fn ((defines :as list))
(join "\n" (map (fn (pair)
(let ((name (first pair))
(expr (nth pair 1)))

View File

@@ -72,18 +72,18 @@
;; --------------------------------------------------------------------------
(define definition-form?
(fn (name)
(fn ((name :as string))
(or (= name "define") (= name "defcomp") (= name "defisland")
(= name "defmacro") (= name "defstyle") (= name "defhandler"))))
(define parse-element-args
(fn (args env)
(fn ((args :as list) (env :as dict))
;; Parse (:key val :key2 val2 child1 child2) into (attrs-dict children-list)
(let ((attrs (dict))
(children (list)))
(reduce
(fn (state arg)
(fn ((state :as dict) arg)
(let ((skip (get state "skip")))
(if skip
(assoc state "skip" false "i" (inc (get state "i")))
@@ -101,12 +101,12 @@
(define render-attrs
(fn (attrs)
(fn ((attrs :as dict))
;; Render an attrs dict to an HTML attribute string.
;; Used by adapter-html.sx and adapter-sx.sx.
(join ""
(map
(fn (key)
(fn ((key :as string))
(let ((val (dict-get attrs key)))
(cond
;; Boolean attrs
@@ -133,13 +133,13 @@
;; Handles both scheme-style ((test body) ...) and clojure-style
;; (test body test body ...).
(define eval-cond
(fn (clauses env)
(fn ((clauses :as list) (env :as dict))
(if (cond-scheme? clauses)
(eval-cond-scheme clauses env)
(eval-cond-clojure clauses env))))
(define eval-cond-scheme
(fn (clauses env)
(fn ((clauses :as list) (env :as dict))
(if (empty? clauses)
nil
(let ((clause (first clauses))
@@ -156,7 +156,7 @@
(eval-cond-scheme (rest clauses) env)))))))
(define eval-cond-clojure
(fn (clauses env)
(fn ((clauses :as list) (env :as dict))
(if (< (len clauses) 2)
nil
(let ((test (first clauses))
@@ -173,12 +173,12 @@
;; process-bindings: evaluate let-binding pairs, return extended env.
;; bindings = ((name1 expr1) (name2 expr2) ...)
(define process-bindings
(fn (bindings env)
(fn ((bindings :as list) (env :as dict))
;; env-extend (not merge) — Env is not a dict subclass, so merge()
;; returns an empty dict, losing all parent scope bindings.
(let ((local (env-extend env)))
(for-each
(fn (pair)
(fn ((pair :as list))
(when (and (= (type-of pair) "list") (>= (len pair) 2))
(let ((name (if (= (type-of (first pair)) "symbol")
(symbol-name (first pair))

View File

@@ -18,7 +18,7 @@
;; "/docs/" → ("docs")
(define split-path-segments
(fn (path)
(fn ((path :as string))
(let ((trimmed (if (starts-with? path "/") (slice path 1) path)))
(let ((trimmed2 (if (and (not (empty? trimmed))
(ends-with? trimmed "/"))
@@ -36,7 +36,7 @@
;; {"type" "param" "value" "slug"})
(define make-route-segment
(fn (seg)
(fn ((seg :as string))
(if (and (starts-with? seg "<") (ends-with? seg ">"))
(let ((param-name (slice seg 1 (- (len seg) 1))))
(let ((d {}))
@@ -49,7 +49,7 @@
d))))
(define parse-route-pattern
(fn (pattern)
(fn ((pattern :as string))
(let ((segments (split-path-segments pattern)))
(map make-route-segment segments))))
@@ -60,13 +60,13 @@
;; Returns params dict if match, nil if no match.
(define match-route-segments
(fn (path-segs parsed-segs)
(fn ((path-segs :as list) (parsed-segs :as list))
(if (not (= (len path-segs) (len parsed-segs)))
nil
(let ((params {})
(matched true))
(for-each-indexed
(fn (i parsed-seg)
(fn ((i :as number) (parsed-seg :as dict))
(when matched
(let ((path-seg (nth path-segs i))
(seg-type (get parsed-seg "type")))
@@ -88,7 +88,7 @@
;; Returns params dict (may be empty for exact matches) or nil.
(define match-route
(fn (path pattern)
(fn ((path :as string) (pattern :as string))
(let ((path-segs (split-path-segments path))
(parsed-segs (parse-route-pattern pattern)))
(match-route-segments path-segs parsed-segs))))
@@ -101,11 +101,11 @@
;; Returns matching entry with "params" added, or nil.
(define find-matching-route
(fn (path routes)
(fn ((path :as string) (routes :as list))
(let ((path-segs (split-path-segments path))
(result nil))
(for-each
(fn (route)
(fn ((route :as dict))
(when (nil? result)
(let ((params (match-route-segments path-segs (get route "parsed"))))
(when (not (nil? params))

View File

@@ -72,7 +72,7 @@
;; --------------------------------------------------------------------------
(define reset!
(fn (s value)
(fn ((s :as signal) value)
(when (signal? s)
(let ((old (signal-value s)))
(when (not (identical? old value))
@@ -85,7 +85,7 @@
;; --------------------------------------------------------------------------
(define swap!
(fn (s f &rest args)
(fn ((s :as signal) (f :as lambda) &rest args)
(when (signal? s)
(let ((old (signal-value s))
(new-val (apply f (cons old args))))
@@ -103,7 +103,7 @@
;; by tracking deref calls during evaluation.
(define computed
(fn (compute-fn)
(fn ((compute-fn :as lambda))
(let ((s (make-signal nil))
(deps (list))
(compute-ctx nil))
@@ -113,7 +113,7 @@
(fn ()
;; Unsubscribe from old deps
(for-each
(fn (dep) (signal-remove-sub! dep recompute))
(fn ((dep :as signal)) (signal-remove-sub! dep recompute))
(signal-deps s))
(signal-set-deps! s (list))
@@ -146,7 +146,7 @@
;; function that tears down the effect.
(define effect
(fn (effect-fn)
(fn ((effect-fn :as lambda))
(let ((deps (list))
(disposed false)
(cleanup-fn nil))
@@ -159,7 +159,7 @@
;; Unsubscribe from old deps
(for-each
(fn (dep) (signal-remove-sub! dep run-effect))
(fn ((dep :as signal)) (signal-remove-sub! dep run-effect))
deps)
(set! deps (list))
@@ -183,7 +183,7 @@
(set! disposed true)
(when cleanup-fn (invoke cleanup-fn))
(for-each
(fn (dep) (signal-remove-sub! dep run-effect))
(fn ((dep :as signal)) (signal-remove-sub! dep run-effect))
deps)
(set! deps (list)))))
;; Auto-register with island scope so disposal happens on swap
@@ -202,7 +202,7 @@
(define *batch-queue* (list))
(define batch
(fn (thunk)
(fn ((thunk :as lambda))
(set! *batch-depth* (+ *batch-depth* 1))
(invoke thunk)
(set! *batch-depth* (- *batch-depth* 1))
@@ -214,15 +214,15 @@
(let ((seen (list))
(pending (list)))
(for-each
(fn (s)
(fn ((s :as signal))
(for-each
(fn (sub)
(fn ((sub :as lambda))
(when (not (contains? seen sub))
(append! seen sub)
(append! pending sub)))
(signal-subscribers s)))
queue)
(for-each (fn (sub) (sub)) pending))))))
(for-each (fn ((sub :as lambda)) (sub)) pending))))))
;; --------------------------------------------------------------------------
@@ -232,16 +232,16 @@
;; If inside a batch, queues the signal. Otherwise, notifies immediately.
(define notify-subscribers
(fn (s)
(fn ((s :as signal))
(if (> *batch-depth* 0)
(when (not (contains? *batch-queue* s))
(append! *batch-queue* s))
(flush-subscribers s))))
(define flush-subscribers
(fn (s)
(fn ((s :as signal))
(for-each
(fn (sub) (sub))
(fn ((sub :as lambda)) (sub))
(signal-subscribers s))))
@@ -269,10 +269,10 @@
;; For effects, the dispose function is returned by effect itself.
(define dispose-computed
(fn (s)
(fn ((s :as signal))
(when (signal? s)
(for-each
(fn (dep) (signal-remove-sub! dep nil))
(fn ((dep :as signal)) (signal-remove-sub! dep nil))
(signal-deps s))
(signal-set-deps! s (list)))))
@@ -288,7 +288,7 @@
(define *island-scope* nil)
(define with-island-scope
(fn (scope-fn body-fn)
(fn ((scope-fn :as lambda) (body-fn :as lambda))
(let ((prev *island-scope*))
(set! *island-scope* scope-fn)
(let ((result (body-fn)))
@@ -300,7 +300,7 @@
;; *island-scope* is non-nil.
(define register-in-scope
(fn (disposable)
(fn ((disposable :as lambda))
(when *island-scope*
(*island-scope* disposable))))
@@ -323,7 +323,7 @@
;; (dom-get-data el key) → any — retrieve stored value
(define with-marsh-scope
(fn (marsh-el body-fn)
(fn (marsh-el (body-fn :as lambda))
;; Execute body-fn collecting all disposables into a marsh-local list.
;; Nested under the current island scope — if the island is disposed,
;; the marsh is disposed too (because island scope collected the marsh's
@@ -341,7 +341,7 @@
;; Parent island scope and sibling marshes are unaffected.
(let ((disposers (dom-get-data marsh-el "sx-marsh-disposers")))
(when disposers
(for-each (fn (d) (invoke d)) disposers)
(for-each (fn ((d :as lambda)) (invoke d)) disposers)
(dom-set-data marsh-el "sx-marsh-disposers" nil)))))
@@ -359,7 +359,7 @@
(define *store-registry* (dict))
(define def-store
(fn (name init-fn)
(fn ((name :as string) (init-fn :as lambda))
(let ((registry *store-registry*))
;; Only create the store once — subsequent calls return existing
(when (not (has-key? registry name))
@@ -367,7 +367,7 @@
(get *store-registry* name))))
(define use-store
(fn (name)
(fn ((name :as string))
(if (has-key? *store-registry* name)
(get *store-registry* name)
(error (str "Store not found: " name
@@ -402,11 +402,11 @@
;; These are platform primitives because they require browser DOM APIs.
(define emit-event
(fn (el event-name detail)
(fn (el (event-name :as string) detail)
(dom-dispatch el event-name detail)))
(define on-event
(fn (el event-name handler)
(fn (el (event-name :as string) (handler :as lambda))
(dom-listen el event-name handler)))
;; Convenience: create an effect that listens for a DOM event on an
@@ -416,7 +416,7 @@
;; removed automatically via the cleanup return.
(define bridge-event
(fn (el event-name target-signal transform-fn)
(fn (el (event-name :as string) (target-signal :as signal) transform-fn)
(effect (fn ()
(let ((remove (dom-listen el event-name
(fn (e)
@@ -450,7 +450,7 @@
;; (promise-then promise on-resolve on-reject) → void
(define resource
(fn (fetch-fn)
(fn ((fetch-fn :as lambda))
(let ((state (signal (dict "loading" true "data" nil "error" nil))))
;; Kick off the async operation
(promise-then (invoke fetch-fn)

View File

@@ -1498,7 +1498,7 @@ def sf_lambda(args, env):
params_expr = first(args)
body_exprs = rest(args)
body = (first(body_exprs) if sx_truthy((len(body_exprs) == 1)) else cons(make_symbol('begin'), body_exprs))
param_names = map(lambda p: (symbol_name(p) if sx_truthy((type_of(p) == 'symbol')) else p), params_expr)
param_names = map(lambda p: (symbol_name(p) if sx_truthy((type_of(p) == 'symbol')) else (symbol_name(first(p)) if sx_truthy(((type_of(p) == 'list') if not sx_truthy((type_of(p) == 'list')) else ((len(p) == 3) if not sx_truthy((len(p) == 3)) else ((type_of(nth(p, 1)) == 'keyword') if not sx_truthy((type_of(nth(p, 1)) == 'keyword')) else (keyword_name(nth(p, 1)) == 'as'))))) else p)), params_expr)
return make_lambda(param_names, body, env)
# sf-define
@@ -2661,15 +2661,15 @@ def build_ref_items_with_href(items, base_path, detail_keys, n_fields):
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)}
return {'req-attrs': build_ref_items_with_href(get(raw_data, 'req-attrs'), '/geography/hypermedia/reference/attributes/', detail_keys, 3), 'beh-attrs': build_ref_items_with_href(get(raw_data, 'beh-attrs'), '/geography/hypermedia/reference/attributes/', detail_keys, 3), 'uniq-attrs': build_ref_items_with_href(get(raw_data, 'uniq-attrs'), '/geography/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)}
return {'req-headers': build_ref_items_with_href(get(raw_data, 'req-headers'), '/geography/hypermedia/reference/headers/', detail_keys, 3), 'resp-headers': build_ref_items_with_href(get(raw_data, 'resp-headers'), '/geography/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)}
return {'events-list': build_ref_items_with_href(get(raw_data, 'events-list'), '/geography/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 {'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)}
return {'req-attrs': build_ref_items_with_href(get(raw_data, 'req-attrs'), '/geography/hypermedia/reference/attributes/', detail_keys, 3), 'beh-attrs': build_ref_items_with_href(get(raw_data, 'beh-attrs'), '/geography/hypermedia/reference/attributes/', detail_keys, 3), 'uniq-attrs': build_ref_items_with_href(get(raw_data, 'uniq-attrs'), '/geography/hypermedia/reference/attributes/', detail_keys, 3)}
# build-attr-detail
def build_attr_detail(slug, detail):

View File

@@ -165,7 +165,7 @@
;; --------------------------------------------------------------------------
(define narrow-type
(fn (t predicate-name)
(fn (t (predicate-name :as string))
;; Narrow type based on a predicate test in a truthy branch.
;; (if (nil? x) ..then.. ..else..) → in else, x excludes nil.
;; Returns (narrowed-then narrowed-else).
@@ -224,7 +224,7 @@
;; type-env is a dict mapping variable names → types.
(define infer-type
(fn (node type-env prim-types)
(fn (node (type-env :as dict) (prim-types :as dict))
(let ((kind (type-of node)))
(if (= kind "number") "number"
(if (= kind "string") "string"
@@ -251,7 +251,7 @@
(define infer-list-type
(fn (node type-env prim-types)
(fn (node (type-env :as dict) (prim-types :as dict))
;; Infer type of a list expression (function call, special form, etc.)
(if (empty? node) "list"
(let ((head (first node))
@@ -320,7 +320,7 @@
(define infer-if-type
(fn (args type-env prim-types)
(fn ((args :as list) (type-env :as dict) (prim-types :as dict))
;; (if test then else?) → union of then and else types
(if (< (len args) 2) "nil"
(let ((then-type (infer-type (nth args 1) type-env prim-types)))
@@ -330,7 +330,7 @@
(define infer-let-type
(fn (args type-env prim-types)
(fn ((args :as list) (type-env :as dict) (prim-types :as dict))
;; (let ((x expr) ...) body) → type of body in extended type-env
(if (< (len args) 2) "nil"
(let ((bindings (first args))
@@ -359,7 +359,7 @@
;; :expr <the offending AST node>}
(define make-diagnostic
(fn (level message component expr)
(fn ((level :as string) (message :as string) component expr)
{:level level
:message message
:component component
@@ -371,7 +371,7 @@
;; --------------------------------------------------------------------------
(define check-primitive-call
(fn (name args type-env prim-types prim-param-types comp-name)
(fn ((name :as string) (args :as list) (type-env :as dict) (prim-types :as dict) prim-param-types (comp-name :as string))
;; Check a primitive call site against declared param types.
;; prim-param-types is a dict: {prim-name → {:positional [...] :rest-type type-or-nil}}
;; Each positional entry is a list (name type-or-nil).
@@ -418,7 +418,7 @@
(define check-component-call
(fn (comp-name comp call-args type-env prim-types)
(fn ((comp-name :as string) (comp :as component) (call-args :as list) (type-env :as dict) (prim-types :as dict))
;; Check a component call site against its declared param types.
;; comp is the component value, call-args is the list of args
;; from the call site (after the component name).
@@ -482,7 +482,7 @@
;; --------------------------------------------------------------------------
(define check-body-walk
(fn (node comp-name type-env prim-types prim-param-types env diagnostics)
(fn (node (comp-name :as string) (type-env :as dict) (prim-types :as dict) prim-param-types env (diagnostics :as list))
;; Recursively walk an AST and collect diagnostics.
;; prim-param-types: dict of {name → {:positional [...] :rest-type t}} or nil
(let ((kind (type-of node)))
@@ -556,7 +556,7 @@
;; --------------------------------------------------------------------------
(define check-component
(fn (comp-name env prim-types prim-param-types)
(fn ((comp-name :as string) env (prim-types :as dict) prim-param-types)
;; Type-check a component's body. Returns list of diagnostics.
;; prim-param-types: dict of param type info, or nil to skip primitive checking.
(let ((comp (env-get env comp-name))
@@ -589,7 +589,7 @@
;; --------------------------------------------------------------------------
(define check-all
(fn (env prim-types prim-param-types)
(fn (env (prim-types :as dict) prim-param-types)
;; Type-check every component in the environment.
;; prim-param-types: dict of param type info, or nil to skip primitive checking.
;; Returns list of all diagnostics.
@@ -613,7 +613,7 @@
;; This is called by the host at startup with the parsed declarations.
(define build-type-registry
(fn (prim-declarations io-declarations)
(fn ((prim-declarations :as list) (io-declarations :as list))
;; Both are lists of dicts: {:name "+" :returns "number" :params (...)}
;; Returns a flat dict: {"+" "number", "str" "string", ...}
(let ((registry (dict)))