Merge branch 'worktree-typed' into macros
This commit is contained in:
@@ -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);
|
||||
} } } } }
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)))))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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))))
|
||||
|
||||
@@ -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")))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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}))
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)))
|
||||
|
||||
Reference in New Issue
Block a user