Refactor spread to use provide/emit! internally

Spreads now emit their attrs into the nearest element's provide scope
instead of requiring per-child spread? checks at every intermediate
layer. emit! is tolerant (no-op when no provider), so spreads in
non-element contexts silently vanish.

- adapter-html: element/lake/marsh wrap children in provide, collect
  emitted; removed 14 spread filters from fragment, forms, components
- adapter-sx: aser wraps result to catch spread values from fn calls;
  aser-call uses provide with attr-parts/child-parts ordering
- adapter-async: same pattern for both render and aser paths
- adapter-dom: added emit! in spread dispatch + provide in element
  rendering; kept spread? checks for reactive/island and DOM safety
- platform: emit! returns NIL when no provider instead of erroring
- 3 new aser tests: stored spread, nested element, silent drop

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 15:41:32 +00:00
parent 859aad4333
commit 2de4ba8c57
9 changed files with 444 additions and 526 deletions

View File

@@ -14,7 +14,7 @@
// ========================================================================= // =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } }); var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-03-13T12:16:43Z"; var SX_VERSION = "2026-03-13T15:35:20Z";
function isNil(x) { return x === NIL || x === null || x === undefined; } function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); } function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -180,8 +180,6 @@
function sxEmit(name, value) { function sxEmit(name, value) {
if (_provideStacks[name] && _provideStacks[name].length) { if (_provideStacks[name] && _provideStacks[name].length) {
_provideStacks[name][_provideStacks[name].length - 1].emitted.push(value); _provideStacks[name][_provideStacks[name].length - 1].emitted.push(value);
} else {
throw new Error("No provider for emit!: " + name);
} }
return NIL; return NIL;
} }
@@ -1523,10 +1521,10 @@ continue; } else { return NIL; } } };
// render-to-html // render-to-html
var renderToHtml = function(expr, env) { setRenderActiveB(true); var renderToHtml = function(expr, env) { setRenderActiveB(true);
return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(expr); if (_m == "number") return (String(expr)); if (_m == "boolean") return (isSxTruthy(expr) ? "true" : "false"); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? "" : renderListToHtml(expr, env)); if (_m == "symbol") return renderValueToHtml(trampoline(evalExpr(expr, env)), env); if (_m == "keyword") return escapeHtml(keywordName(expr)); if (_m == "raw-html") return rawHtmlContent(expr); if (_m == "spread") return expr; return renderValueToHtml(trampoline(evalExpr(expr, env)), env); })(); }; return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(expr); if (_m == "number") return (String(expr)); if (_m == "boolean") return (isSxTruthy(expr) ? "true" : "false"); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? "" : renderListToHtml(expr, env)); if (_m == "symbol") return renderValueToHtml(trampoline(evalExpr(expr, env)), env); if (_m == "keyword") return escapeHtml(keywordName(expr)); if (_m == "raw-html") return rawHtmlContent(expr); if (_m == "spread") return (sxEmit("element-attrs", spreadAttrs(expr)), ""); return renderValueToHtml(trampoline(evalExpr(expr, env)), env); })(); };
// render-value-to-html // render-value-to-html
var renderValueToHtml = function(val, env) { return (function() { var _m = typeOf(val); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(val); if (_m == "number") return (String(val)); if (_m == "boolean") return (isSxTruthy(val) ? "true" : "false"); if (_m == "list") return renderListToHtml(val, env); if (_m == "raw-html") return rawHtmlContent(val); if (_m == "spread") return val; return escapeHtml((String(val))); })(); }; var renderValueToHtml = function(val, env) { return (function() { var _m = typeOf(val); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(val); if (_m == "number") return (String(val)); if (_m == "boolean") return (isSxTruthy(val) ? "true" : "false"); if (_m == "list") return renderListToHtml(val, env); if (_m == "raw-html") return rawHtmlContent(val); if (_m == "spread") return (sxEmit("element-attrs", spreadAttrs(val)), ""); return escapeHtml((String(val))); })(); };
// RENDER_HTML_FORMS // RENDER_HTML_FORMS
var RENDER_HTML_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "defhandler", "deftype", "defeffect", "map", "map-indexed", "filter", "for-each", "provide"]; var RENDER_HTML_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "defhandler", "deftype", "defeffect", "map", "map-indexed", "filter", "for-each", "provide"];
@@ -1537,10 +1535,10 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m =
// render-list-to-html // render-list-to-html
var renderListToHtml = function(expr, env) { return (isSxTruthy(isEmpty(expr)) ? "" : (function() { var renderListToHtml = function(expr, env) { return (isSxTruthy(isEmpty(expr)) ? "" : (function() {
var head = first(expr); var head = first(expr);
return (isSxTruthy(!isSxTruthy((typeOf(head) == "symbol"))) ? join("", filter(function(x) { return !isSxTruthy(isSpread(x)); }, map(function(x) { return renderValueToHtml(x, env); }, expr))) : (function() { return (isSxTruthy(!isSxTruthy((typeOf(head) == "symbol"))) ? join("", map(function(x) { return renderValueToHtml(x, env); }, expr)) : (function() {
var name = symbolName(head); var name = symbolName(head);
var args = rest(expr); var args = rest(expr);
return (isSxTruthy((name == "<>")) ? join("", filter(function(x) { return !isSxTruthy(isSpread(x)); }, map(function(x) { return renderToHtml(x, env); }, args))) : (isSxTruthy((name == "raw!")) ? join("", map(function(x) { return (String(trampoline(evalExpr(x, env)))); }, args)) : (isSxTruthy((name == "lake")) ? renderHtmlLake(args, env) : (isSxTruthy((name == "marsh")) ? renderHtmlMarsh(args, env) : (isSxTruthy(contains(HTML_TAGS, name)) ? renderHtmlElement(name, args, env) : (isSxTruthy((isSxTruthy(startsWith(name, "~")) && isSxTruthy(envHas(env, name)) && isIsland(envGet(env, name)))) ? renderHtmlIsland(envGet(env, name), args, env) : (isSxTruthy(startsWith(name, "~")) ? (function() { return (isSxTruthy((name == "<>")) ? join("", map(function(x) { return renderToHtml(x, env); }, args)) : (isSxTruthy((name == "raw!")) ? join("", map(function(x) { return (String(trampoline(evalExpr(x, env)))); }, args)) : (isSxTruthy((name == "lake")) ? renderHtmlLake(args, env) : (isSxTruthy((name == "marsh")) ? renderHtmlMarsh(args, env) : (isSxTruthy(contains(HTML_TAGS, name)) ? renderHtmlElement(name, args, env) : (isSxTruthy((isSxTruthy(startsWith(name, "~")) && isSxTruthy(envHas(env, name)) && isIsland(envGet(env, name)))) ? renderHtmlIsland(envGet(env, name), args, env) : (isSxTruthy(startsWith(name, "~")) ? (function() {
var val = envGet(env, name); var val = envGet(env, name);
return (isSxTruthy(isComponent(val)) ? renderHtmlComponent(val, args, env) : (isSxTruthy(isMacro(val)) ? renderToHtml(expandMacro(val, args, env), env) : error((String("Unknown component: ") + String(name))))); return (isSxTruthy(isComponent(val)) ? renderHtmlComponent(val, args, env) : (isSxTruthy(isMacro(val)) ? renderToHtml(expandMacro(val, args, env), env) : error((String("Unknown component: ") + String(name)))));
})() : (isSxTruthy(isRenderHtmlForm(name)) ? dispatchHtmlForm(name, expr, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? renderToHtml(expandMacro(envGet(env, name), args, env), env) : renderValueToHtml(trampoline(evalExpr(expr, env)), env)))))))))); })() : (isSxTruthy(isRenderHtmlForm(name)) ? dispatchHtmlForm(name, expr, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? renderToHtml(expandMacro(envGet(env, name), args, env), env) : renderValueToHtml(trampoline(evalExpr(expr, env)), env))))))))));
@@ -1551,33 +1549,24 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m =
var dispatchHtmlForm = function(name, expr, env) { return (isSxTruthy((name == "if")) ? (function() { var dispatchHtmlForm = function(name, expr, env) { return (isSxTruthy((name == "if")) ? (function() {
var condVal = trampoline(evalExpr(nth(expr, 1), env)); var condVal = trampoline(evalExpr(nth(expr, 1), env));
return (isSxTruthy(condVal) ? renderToHtml(nth(expr, 2), env) : (isSxTruthy((len(expr) > 3)) ? renderToHtml(nth(expr, 3), env) : "")); return (isSxTruthy(condVal) ? renderToHtml(nth(expr, 2), env) : (isSxTruthy((len(expr) > 3)) ? renderToHtml(nth(expr, 3), env) : ""));
})() : (isSxTruthy((name == "when")) ? (isSxTruthy(!isSxTruthy(trampoline(evalExpr(nth(expr, 1), env)))) ? "" : (isSxTruthy((len(expr) == 3)) ? renderToHtml(nth(expr, 2), env) : (function() { })() : (isSxTruthy((name == "when")) ? (isSxTruthy(!isSxTruthy(trampoline(evalExpr(nth(expr, 1), env)))) ? "" : (isSxTruthy((len(expr) == 3)) ? renderToHtml(nth(expr, 2), env) : join("", map(function(i) { return renderToHtml(nth(expr, i), env); }, range(2, len(expr)))))) : (isSxTruthy((name == "cond")) ? (function() {
var results = map(function(i) { return renderToHtml(nth(expr, i), env); }, range(2, len(expr)));
return join("", filter(function(r) { return !isSxTruthy(isSpread(r)); }, results));
})())) : (isSxTruthy((name == "cond")) ? (function() {
var branch = evalCond(rest(expr), env); var branch = evalCond(rest(expr), env);
return (isSxTruthy(branch) ? renderToHtml(branch, env) : ""); return (isSxTruthy(branch) ? renderToHtml(branch, env) : "");
})() : (isSxTruthy((name == "case")) ? renderToHtml(trampoline(evalExpr(expr, env)), env) : (isSxTruthy(sxOr((name == "let"), (name == "let*"))) ? (function() { })() : (isSxTruthy((name == "case")) ? renderToHtml(trampoline(evalExpr(expr, env)), env) : (isSxTruthy(sxOr((name == "let"), (name == "let*"))) ? (function() {
var local = processBindings(nth(expr, 1), env); var local = processBindings(nth(expr, 1), env);
return (isSxTruthy((len(expr) == 3)) ? renderToHtml(nth(expr, 2), local) : (function() { return (isSxTruthy((len(expr) == 3)) ? renderToHtml(nth(expr, 2), local) : join("", map(function(i) { return renderToHtml(nth(expr, i), local); }, range(2, len(expr)))));
var results = map(function(i) { return renderToHtml(nth(expr, i), local); }, range(2, len(expr))); })() : (isSxTruthy(sxOr((name == "begin"), (name == "do"))) ? (isSxTruthy((len(expr) == 2)) ? renderToHtml(nth(expr, 1), env) : join("", map(function(i) { return renderToHtml(nth(expr, i), env); }, range(1, len(expr))))) : (isSxTruthy(isDefinitionForm(name)) ? (trampoline(evalExpr(expr, env)), "") : (isSxTruthy((name == "map")) ? (function() {
return join("", filter(function(r) { return !isSxTruthy(isSpread(r)); }, results));
})());
})() : (isSxTruthy(sxOr((name == "begin"), (name == "do"))) ? (isSxTruthy((len(expr) == 2)) ? renderToHtml(nth(expr, 1), env) : (function() {
var results = map(function(i) { return renderToHtml(nth(expr, i), env); }, range(1, len(expr)));
return join("", filter(function(r) { return !isSxTruthy(isSpread(r)); }, results));
})()) : (isSxTruthy(isDefinitionForm(name)) ? (trampoline(evalExpr(expr, env)), "") : (isSxTruthy((name == "map")) ? (function() {
var f = trampoline(evalExpr(nth(expr, 1), env)); var f = trampoline(evalExpr(nth(expr, 1), env));
var coll = trampoline(evalExpr(nth(expr, 2), env)); var coll = trampoline(evalExpr(nth(expr, 2), env));
return join("", filter(function(r) { return !isSxTruthy(isSpread(r)); }, map(function(item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [item], env) : renderToHtml(apply(f, [item]), env)); }, coll))); return join("", map(function(item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [item], env) : renderToHtml(apply(f, [item]), env)); }, coll));
})() : (isSxTruthy((name == "map-indexed")) ? (function() { })() : (isSxTruthy((name == "map-indexed")) ? (function() {
var f = trampoline(evalExpr(nth(expr, 1), env)); var f = trampoline(evalExpr(nth(expr, 1), env));
var coll = trampoline(evalExpr(nth(expr, 2), env)); var coll = trampoline(evalExpr(nth(expr, 2), env));
return join("", filter(function(r) { return !isSxTruthy(isSpread(r)); }, mapIndexed(function(i, item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [i, item], env) : renderToHtml(apply(f, [i, item]), env)); }, coll))); return join("", mapIndexed(function(i, item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [i, item], env) : renderToHtml(apply(f, [i, item]), env)); }, coll));
})() : (isSxTruthy((name == "filter")) ? renderToHtml(trampoline(evalExpr(expr, env)), env) : (isSxTruthy((name == "for-each")) ? (function() { })() : (isSxTruthy((name == "filter")) ? renderToHtml(trampoline(evalExpr(expr, env)), env) : (isSxTruthy((name == "for-each")) ? (function() {
var f = trampoline(evalExpr(nth(expr, 1), env)); var f = trampoline(evalExpr(nth(expr, 1), env));
var coll = trampoline(evalExpr(nth(expr, 2), env)); var coll = trampoline(evalExpr(nth(expr, 2), env));
return join("", filter(function(r) { return !isSxTruthy(isSpread(r)); }, map(function(item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [item], env) : renderToHtml(apply(f, [item]), env)); }, coll))); return join("", map(function(item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [item], env) : renderToHtml(apply(f, [item]), env)); }, coll));
})() : (isSxTruthy((name == "provide")) ? (function() { })() : (isSxTruthy((name == "provide")) ? (function() {
var provName = trampoline(evalExpr(nth(expr, 1), env)); var provName = trampoline(evalExpr(nth(expr, 1), env));
var provVal = trampoline(evalExpr(nth(expr, 2), env)); var provVal = trampoline(evalExpr(nth(expr, 2), env));
@@ -1585,7 +1574,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m =
var bodyCount = (len(expr) - 3); var bodyCount = (len(expr) - 3);
providePush(provName, provVal); providePush(provName, provVal);
return (function() { return (function() {
var result = (isSxTruthy((bodyCount == 1)) ? renderToHtml(nth(expr, bodyStart), env) : join("", filter(function(r) { return !isSxTruthy(isSpread(r)); }, map(function(i) { return renderToHtml(nth(expr, i), env); }, range(bodyStart, (bodyStart + bodyCount)))))); var result = (isSxTruthy((bodyCount == 1)) ? renderToHtml(nth(expr, bodyStart), env) : join("", map(function(i) { return renderToHtml(nth(expr, i), env); }, range(bodyStart, (bodyStart + bodyCount)))));
providePop(provName); providePop(provName);
return result; return result;
})(); })();
@@ -1614,14 +1603,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m =
var local = envMerge(componentClosure(comp), env); 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 p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } }
if (isSxTruthy(componentHasChildren(comp))) { if (isSxTruthy(componentHasChildren(comp))) {
(function() { envSet(local, "children", makeRawHtml(join("", map(function(c) { return renderToHtml(c, env); }, children))));
var parts = [];
{ var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; (function() {
var r = renderToHtml(c, env);
return (isSxTruthy(!isSxTruthy(isSpread(r))) ? append_b(parts, r) : NIL);
})(); } }
return envSet(local, "children", makeRawHtml(join("", parts)));
})();
} }
return renderToHtml(componentBody(comp), local); return renderToHtml(componentBody(comp), local);
})(); })();
@@ -1633,14 +1615,12 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m =
var attrs = first(parsed); var attrs = first(parsed);
var children = nth(parsed, 1); var children = nth(parsed, 1);
var isVoid = contains(VOID_ELEMENTS, tag); var isVoid = contains(VOID_ELEMENTS, tag);
return (isSxTruthy(isVoid) ? (String("<") + String(tag) + String(renderAttrs(attrs)) + String(" />")) : (function() { return (isSxTruthy(isVoid) ? (String("<") + String(tag) + String(renderAttrs(attrs)) + String(" />")) : (providePush("element-attrs", NIL), (function() {
var contentParts = []; var content = join("", map(function(c) { return renderToHtml(c, env); }, children));
{ var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; (function() { { var _c = sxEmitted("element-attrs"); for (var _i = 0; _i < _c.length; _i++) { var spreadDict = _c[_i]; mergeSpreadAttrs(attrs, spreadDict); } }
var result = renderToHtml(c, env); providePop("element-attrs");
return (isSxTruthy(isSpread(result)) ? mergeSpreadAttrs(attrs, spreadAttrs(result)) : append_b(contentParts, result)); return (String("<") + String(tag) + String(renderAttrs(attrs)) + String(">") + String(content) + String("</") + String(tag) + String(">"));
})(); } } })()));
return (String("<") + String(tag) + String(renderAttrs(attrs)) + String(">") + String(join("", contentParts)) + String("</") + String(tag) + String(">"));
})());
})(); }; })(); };
// render-html-lake // render-html-lake
@@ -1659,12 +1639,13 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m =
})(); }, {["i"]: 0, ["skip"]: false}, args); })(); }, {["i"]: 0, ["skip"]: false}, args);
return (function() { return (function() {
var lakeAttrs = {["data-sx-lake"]: sxOr(lakeId, "")}; var lakeAttrs = {["data-sx-lake"]: sxOr(lakeId, "")};
var contentParts = []; providePush("element-attrs", NIL);
{ var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; (function() { return (function() {
var result = renderToHtml(c, env); var content = join("", map(function(c) { return renderToHtml(c, env); }, children));
return (isSxTruthy(isSpread(result)) ? mergeSpreadAttrs(lakeAttrs, spreadAttrs(result)) : append_b(contentParts, result)); { var _c = sxEmitted("element-attrs"); for (var _i = 0; _i < _c.length; _i++) { var spreadDict = _c[_i]; mergeSpreadAttrs(lakeAttrs, spreadDict); } }
})(); } } providePop("element-attrs");
return (String("<") + String(lakeTag) + String(renderAttrs(lakeAttrs)) + String(">") + String(join("", contentParts)) + String("</") + String(lakeTag) + String(">")); return (String("<") + String(lakeTag) + String(renderAttrs(lakeAttrs)) + String(">") + String(content) + String("</") + String(lakeTag) + String(">"));
})();
})(); })();
})(); }; })(); };
@@ -1684,12 +1665,13 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m =
})(); }, {["i"]: 0, ["skip"]: false}, args); })(); }, {["i"]: 0, ["skip"]: false}, args);
return (function() { return (function() {
var marshAttrs = {["data-sx-marsh"]: sxOr(marshId, "")}; var marshAttrs = {["data-sx-marsh"]: sxOr(marshId, "")};
var contentParts = []; providePush("element-attrs", NIL);
{ var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; (function() { return (function() {
var result = renderToHtml(c, env); var content = join("", map(function(c) { return renderToHtml(c, env); }, children));
return (isSxTruthy(isSpread(result)) ? mergeSpreadAttrs(marshAttrs, spreadAttrs(result)) : append_b(contentParts, result)); { var _c = sxEmitted("element-attrs"); for (var _i = 0; _i < _c.length; _i++) { var spreadDict = _c[_i]; mergeSpreadAttrs(marshAttrs, spreadDict); } }
})(); } } providePop("element-attrs");
return (String("<") + String(marshTag) + String(renderAttrs(marshAttrs)) + String(">") + String(join("", contentParts)) + String("</") + String(marshTag) + String(">")); return (String("<") + String(marshTag) + String(renderAttrs(marshAttrs)) + String(">") + String(content) + String("</") + String(marshTag) + String(">"));
})();
})(); })();
})(); }; })(); };
@@ -1710,14 +1692,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m =
var islandName = componentName(island); var islandName = componentName(island);
{ var _c = componentParams(island); 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(island); for (var _i = 0; _i < _c.length; _i++) { var p = _c[_i]; envSet(local, p, (isSxTruthy(dictHas(kwargs, p)) ? dictGet(kwargs, p) : NIL)); } }
if (isSxTruthy(componentHasChildren(island))) { if (isSxTruthy(componentHasChildren(island))) {
(function() { envSet(local, "children", makeRawHtml(join("", map(function(c) { return renderToHtml(c, env); }, children))));
var parts = [];
{ var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; (function() {
var r = renderToHtml(c, env);
return (isSxTruthy(!isSxTruthy(isSpread(r))) ? append_b(parts, r) : NIL);
})(); } }
return envSet(local, "children", makeRawHtml(join("", parts)));
})();
} }
return (function() { return (function() {
var bodyHtml = renderToHtml(componentBody(island), local); var bodyHtml = renderToHtml(componentBody(island), local);
@@ -1741,10 +1716,13 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m =
// aser // aser
var aser = function(expr, env) { setRenderActiveB(true); var aser = function(expr, env) { setRenderActiveB(true);
return (function() { var _m = typeOf(expr); if (_m == "number") return expr; if (_m == "string") return expr; if (_m == "boolean") return expr; if (_m == "nil") return NIL; if (_m == "symbol") return (function() { return (function() {
var result = (function() { var _m = typeOf(expr); if (_m == "number") return expr; if (_m == "string") return expr; if (_m == "boolean") return expr; if (_m == "nil") return NIL; if (_m == "symbol") return (function() {
var name = symbolName(expr); var name = symbolName(expr);
return (isSxTruthy(envHas(env, name)) ? envGet(env, name) : (isSxTruthy(isPrimitive(name)) ? getPrimitive(name) : (isSxTruthy((name == "true")) ? true : (isSxTruthy((name == "false")) ? false : (isSxTruthy((name == "nil")) ? NIL : error((String("Undefined symbol: ") + String(name)))))))); return (isSxTruthy(envHas(env, name)) ? envGet(env, name) : (isSxTruthy(isPrimitive(name)) ? getPrimitive(name) : (isSxTruthy((name == "true")) ? true : (isSxTruthy((name == "false")) ? false : (isSxTruthy((name == "nil")) ? NIL : error((String("Undefined symbol: ") + String(name))))))));
})(); if (_m == "keyword") return keywordName(expr); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? [] : aserList(expr, env)); if (_m == "spread") return expr; return expr; })(); }; })(); if (_m == "keyword") return keywordName(expr); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? [] : aserList(expr, env)); if (_m == "spread") return (sxEmit("element-attrs", spreadAttrs(expr)), NIL); return expr; })();
return (isSxTruthy(isSpread(result)) ? (sxEmit("element-attrs", spreadAttrs(result)), NIL) : result);
})(); };
// aser-list // aser-list
var aserList = function(expr, env) { return (function() { var aserList = function(expr, env) { return (function() {
@@ -1772,29 +1750,36 @@ return (function() { var _m = typeOf(expr); if (_m == "number") return expr; if
// aser-call // aser-call
var aserCall = function(name, args, env) { return (function() { var aserCall = function(name, args, env) { return (function() {
var parts = [name]; var attrParts = [];
var childParts = [];
var skip = false; var skip = false;
var i = 0; var i = 0;
providePush("element-attrs", NIL);
{ var _c = args; for (var _i = 0; _i < _c.length; _i++) { var arg = _c[_i]; (isSxTruthy(skip) ? ((skip = false), (i = (i + 1))) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((i + 1) < len(args)))) ? (function() { { var _c = args; for (var _i = 0; _i < _c.length; _i++) { var arg = _c[_i]; (isSxTruthy(skip) ? ((skip = false), (i = (i + 1))) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((i + 1) < len(args)))) ? (function() {
var val = aser(nth(args, (i + 1)), env); var val = aser(nth(args, (i + 1)), env);
if (isSxTruthy(!isSxTruthy(isNil(val)))) { if (isSxTruthy(!isSxTruthy(isNil(val)))) {
parts.push((String(":") + String(keywordName(arg)))); attrParts.push((String(":") + String(keywordName(arg))));
parts.push(serialize(val)); attrParts.push(serialize(val));
} }
skip = true; skip = true;
return (i = (i + 1)); return (i = (i + 1));
})() : (function() { })() : (function() {
var val = aser(arg, env); var val = aser(arg, env);
if (isSxTruthy(!isSxTruthy(isNil(val)))) { if (isSxTruthy(!isSxTruthy(isNil(val)))) {
(isSxTruthy(isSpread(val)) ? forEach(function(k) { return (function() { (isSxTruthy((typeOf(val) == "list")) ? forEach(function(item) { return (isSxTruthy(!isSxTruthy(isNil(item))) ? append_b(childParts, serialize(item)) : NIL); }, val) : append_b(childParts, serialize(val)));
var v = dictGet(spreadAttrs(val), k);
parts.push((String(":") + String(k)));
return append_b(parts, serialize(v));
})(); }, keys(spreadAttrs(val))) : (isSxTruthy((typeOf(val) == "list")) ? forEach(function(item) { return (isSxTruthy(!isSxTruthy(isNil(item))) ? append_b(parts, serialize(item)) : NIL); }, val) : append_b(parts, serialize(val))));
} }
return (i = (i + 1)); return (i = (i + 1));
})())); } } })())); } }
{ var _c = sxEmitted("element-attrs"); for (var _i = 0; _i < _c.length; _i++) { var spreadDict = _c[_i]; { var _c = keys(spreadDict); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; (function() {
var v = dictGet(spreadDict, k);
attrParts.push((String(":") + String(k)));
return append_b(attrParts, serialize(v));
})(); } } } }
providePop("element-attrs");
return (function() {
var parts = concat([name], attrParts, childParts);
return (String("(") + String(join(" ", parts)) + String(")")); return (String("(") + String(join(" ", parts)) + String(")"));
})();
})(); }; })(); };
// SPECIAL_FORM_NAMES // SPECIAL_FORM_NAMES
@@ -1898,7 +1883,7 @@ return result; }, args);
// render-to-dom // render-to-dom
var renderToDom = function(expr, env, ns) { setRenderActiveB(true); var renderToDom = function(expr, env, ns) { setRenderActiveB(true);
return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragment(); if (_m == "boolean") return createFragment(); if (_m == "raw-html") return domParseHtml(rawHtmlContent(expr)); if (_m == "string") return createTextNode(expr); if (_m == "number") return createTextNode((String(expr))); if (_m == "symbol") return renderToDom(trampoline(evalExpr(expr, env)), env, ns); if (_m == "keyword") return createTextNode(keywordName(expr)); if (_m == "dom-node") return expr; if (_m == "spread") return expr; if (_m == "dict") return createFragment(); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? createFragment() : renderDomList(expr, env, ns)); return (isSxTruthy(isSignal(expr)) ? (isSxTruthy(_islandScope) ? reactiveText(expr) : createTextNode((String(deref(expr))))) : createTextNode((String(expr)))); })(); }; return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragment(); if (_m == "boolean") return createFragment(); if (_m == "raw-html") return domParseHtml(rawHtmlContent(expr)); if (_m == "string") return createTextNode(expr); if (_m == "number") return createTextNode((String(expr))); if (_m == "symbol") return renderToDom(trampoline(evalExpr(expr, env)), env, ns); if (_m == "keyword") return createTextNode(keywordName(expr)); if (_m == "dom-node") return expr; if (_m == "spread") return (sxEmit("element-attrs", spreadAttrs(expr)), expr); if (_m == "dict") return createFragment(); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? createFragment() : renderDomList(expr, env, ns)); return (isSxTruthy(isSignal(expr)) ? (isSxTruthy(_islandScope) ? reactiveText(expr) : createTextNode((String(deref(expr))))) : createTextNode((String(expr)))); })(); };
// render-dom-list // render-dom-list
var renderDomList = function(expr, env, ns) { return (function() { var renderDomList = function(expr, env, ns) { return (function() {
@@ -1927,6 +1912,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
var renderDomElement = function(tag, args, env, ns) { return (function() { var renderDomElement = function(tag, args, env, ns) { return (function() {
var newNs = (isSxTruthy((tag == "svg")) ? SVG_NS : (isSxTruthy((tag == "math")) ? MATH_NS : ns)); var newNs = (isSxTruthy((tag == "svg")) ? SVG_NS : (isSxTruthy((tag == "math")) ? MATH_NS : ns));
var el = domCreateElement(tag, newNs); var el = domCreateElement(tag, newNs);
providePush("element-attrs", NIL);
reduce(function(state, arg) { return (function() { reduce(function(state, arg) { return (function() {
var skip = get(state, "skip"); var skip = get(state, "skip");
return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() { return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() {
@@ -1951,8 +1937,11 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
return assoc(state, "skip", true, "i", (get(state, "i") + 1)); return assoc(state, "skip", true, "i", (get(state, "i") + 1));
})() : ((isSxTruthy(!isSxTruthy(contains(VOID_ELEMENTS, tag))) ? (function() { })() : ((isSxTruthy(!isSxTruthy(contains(VOID_ELEMENTS, tag))) ? (function() {
var child = renderToDom(arg, env, newNs); var child = renderToDom(arg, env, newNs);
return (isSxTruthy((isSxTruthy(isSpread(child)) && _islandScope)) ? reactiveSpread(el, function() { return renderToDom(arg, env, newNs); }) : (isSxTruthy(isSpread(child)) ? forEach(function(key) { return (function() { return (isSxTruthy((isSxTruthy(isSpread(child)) && _islandScope)) ? reactiveSpread(el, function() { return renderToDom(arg, env, newNs); }) : (isSxTruthy(isSpread(child)) ? NIL : domAppend(el, child)));
var val = dictGet(spreadAttrs(child), key); })() : NIL), assoc(state, "i", (get(state, "i") + 1)))));
})(); }, {["i"]: 0, ["skip"]: false}, args);
{ var _c = sxEmitted("element-attrs"); for (var _i = 0; _i < _c.length; _i++) { var spreadDict = _c[_i]; { var _c = keys(spreadDict); for (var _i = 0; _i < _c.length; _i++) { var key = _c[_i]; (function() {
var val = dictGet(spreadDict, key);
return (isSxTruthy((key == "class")) ? (function() { return (isSxTruthy((key == "class")) ? (function() {
var existing = domGetAttr(el, "class"); var existing = domGetAttr(el, "class");
return domSetAttr(el, "class", (isSxTruthy((isSxTruthy(existing) && !isSxTruthy((existing == "")))) ? (String(existing) + String(" ") + String(val)) : val)); return domSetAttr(el, "class", (isSxTruthy((isSxTruthy(existing) && !isSxTruthy((existing == "")))) ? (String(existing) + String(" ") + String(val)) : val));
@@ -1960,9 +1949,8 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
var existing = domGetAttr(el, "style"); var existing = domGetAttr(el, "style");
return domSetAttr(el, "style", (isSxTruthy((isSxTruthy(existing) && !isSxTruthy((existing == "")))) ? (String(existing) + String(";") + String(val)) : val)); return domSetAttr(el, "style", (isSxTruthy((isSxTruthy(existing) && !isSxTruthy((existing == "")))) ? (String(existing) + String(";") + String(val)) : val));
})() : domSetAttr(el, key, (String(val))))); })() : domSetAttr(el, key, (String(val)))));
})(); }, keys(spreadAttrs(child))) : domAppend(el, child))); })(); } } } }
})() : NIL), assoc(state, "i", (get(state, "i") + 1))))); providePop("element-attrs");
})(); }, {["i"]: 0, ["skip"]: false}, args);
return el; return el;
})(); }; })(); };

View File

@@ -48,7 +48,7 @@
"string" (escape-html expr) "string" (escape-html expr)
"number" (escape-html (str expr)) "number" (escape-html (str expr))
"raw-html" (raw-html-content expr) "raw-html" (raw-html-content expr)
"spread" expr "spread" (do (emit! "element-attrs" (spread-attrs expr)) "")
"symbol" (let ((val (async-eval expr env ctx))) "symbol" (let ((val (async-eval expr env ctx)))
(async-render val env ctx)) (async-render val env ctx))
"keyword" (escape-html (keyword-name expr)) "keyword" (escape-html (keyword-name expr))
@@ -80,10 +80,9 @@
(= name "raw!") (= name "raw!")
(async-render-raw args env ctx) (async-render-raw args env ctx)
;; Fragment (spreads filtered — no parent element) ;; Fragment
(= name "<>") (= name "<>")
(join "" (filter (fn (r) (not (spread? r))) (join "" (async-map-render args env ctx))
(async-map-render args env ctx)))
;; html: prefix ;; html: prefix
(starts-with? name "html:") (starts-with? name "html:")
@@ -171,18 +170,19 @@
(css-class-collect! (str class-val)))) (css-class-collect! (str class-val))))
(if (contains? VOID_ELEMENTS tag) (if (contains? VOID_ELEMENTS tag)
(str "<" tag (render-attrs attrs) ">") (str "<" tag (render-attrs attrs) ">")
;; Render children, collecting spreads and content separately ;; Provide scope for spread emit!
(let ((token (if (or (= tag "svg") (= tag "math")) (let ((token (if (or (= tag "svg") (= tag "math"))
(svg-context-set! true) (svg-context-set! true)
nil)) nil))
(content-parts (list))) (content-parts (list)))
(provide-push! "element-attrs" nil)
(for-each (for-each
(fn (c) (fn (c) (append! content-parts (async-render c env ctx)))
(let ((result (async-render c env ctx)))
(if (spread? result)
(merge-spread-attrs attrs (spread-attrs result))
(append! content-parts result))))
children) children)
(for-each
(fn (spread-dict) (merge-spread-attrs attrs spread-dict))
(emitted "element-attrs"))
(provide-pop! "element-attrs")
(when token (svg-context-reset! token)) (when token (svg-context-reset! token))
(str "<" tag (render-attrs attrs) ">" (str "<" tag (render-attrs attrs) ">"
(join "" content-parts) (join "" content-parts)
@@ -231,14 +231,11 @@
(for-each (for-each
(fn (p) (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) (fn (p) (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil)))
(component-params comp)) (component-params comp))
;; Pre-render children to raw HTML (filter spreads — no parent element) ;; Pre-render children to raw HTML
(when (component-has-children? comp) (when (component-has-children? comp)
(let ((parts (list))) (let ((parts (list)))
(for-each (for-each
(fn (c) (fn (c) (append! parts (async-render c env ctx)))
(let ((r (async-render c env ctx)))
(when (not (spread? r))
(append! parts r))))
children) children)
(env-set! local "children" (env-set! local "children"
(make-raw-html (join "" parts))))) (make-raw-html (join "" parts)))))
@@ -259,14 +256,11 @@
(for-each (for-each
(fn (p) (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) (fn (p) (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil)))
(component-params island)) (component-params island))
;; Pre-render children (filter spreads — no parent element) ;; Pre-render children
(when (component-has-children? island) (when (component-has-children? island)
(let ((parts (list))) (let ((parts (list)))
(for-each (for-each
(fn (c) (fn (c) (append! parts (async-render c env ctx)))
(let ((r (async-render c env ctx)))
(when (not (spread? r))
(append! parts r))))
children) children)
(env-set! local "children" (env-set! local "children"
(make-raw-html (join "" parts))))) (make-raw-html (join "" parts)))))
@@ -367,14 +361,13 @@
(async-render (nth expr 3) env ctx) (async-render (nth expr 3) env ctx)
""))) "")))
;; when — single body: pass through (spread propagates). Multi: join strings. ;; when — single body: pass through. Multi: join strings.
(= name "when") (= name "when")
(if (not (async-eval (nth expr 1) env ctx)) (if (not (async-eval (nth expr 1) env ctx))
"" ""
(if (= (len expr) 3) (if (= (len expr) 3)
(async-render (nth expr 2) env ctx) (async-render (nth expr 2) env ctx)
(let ((results (async-map-render (slice expr 2) env ctx))) (join "" (async-map-render (slice expr 2) env ctx))))
(join "" (filter (fn (r) (not (spread? r))) results)))))
;; cond — uses cond-scheme? (every? check) from eval.sx ;; cond — uses cond-scheme? (every? check) from eval.sx
(= name "cond") (= name "cond")
@@ -392,47 +385,39 @@
(let ((local (async-process-bindings (nth expr 1) env ctx))) (let ((local (async-process-bindings (nth expr 1) env ctx)))
(if (= (len expr) 3) (if (= (len expr) 3)
(async-render (nth expr 2) local ctx) (async-render (nth expr 2) local ctx)
(let ((results (async-map-render (slice expr 2) local ctx))) (join "" (async-map-render (slice expr 2) local ctx))))
(join "" (filter (fn (r) (not (spread? r))) results)))))
;; begin / do — single body: pass through. Multi: join strings. ;; begin / do — single body: pass through. Multi: join strings.
(or (= name "begin") (= name "do")) (or (= name "begin") (= name "do"))
(if (= (len expr) 2) (if (= (len expr) 2)
(async-render (nth expr 1) env ctx) (async-render (nth expr 1) env ctx)
(let ((results (async-map-render (rest expr) env ctx))) (join "" (async-map-render (rest expr) env ctx)))
(join "" (filter (fn (r) (not (spread? r))) results))))
;; Definition forms ;; Definition forms
(definition-form? name) (definition-form? name)
(do (async-eval expr env ctx) "") (do (async-eval expr env ctx) "")
;; map — spreads filtered ;; map
(= name "map") (= name "map")
(let ((f (async-eval (nth expr 1) env ctx)) (let ((f (async-eval (nth expr 1) env ctx))
(coll (async-eval (nth expr 2) env ctx))) (coll (async-eval (nth expr 2) env ctx)))
(join "" (join "" (async-map-fn-render f coll env ctx)))
(filter (fn (r) (not (spread? r)))
(async-map-fn-render f coll env ctx))))
;; map-indexed — spreads filtered ;; map-indexed
(= name "map-indexed") (= name "map-indexed")
(let ((f (async-eval (nth expr 1) env ctx)) (let ((f (async-eval (nth expr 1) env ctx))
(coll (async-eval (nth expr 2) env ctx))) (coll (async-eval (nth expr 2) env ctx)))
(join "" (join "" (async-map-indexed-fn-render f coll env ctx)))
(filter (fn (r) (not (spread? r)))
(async-map-indexed-fn-render f coll env ctx))))
;; filter — eval fully then render ;; filter — eval fully then render
(= name "filter") (= name "filter")
(async-render (async-eval expr env ctx) env ctx) (async-render (async-eval expr env ctx) env ctx)
;; for-each (render variant) — spreads filtered ;; for-each (render variant)
(= name "for-each") (= name "for-each")
(let ((f (async-eval (nth expr 1) env ctx)) (let ((f (async-eval (nth expr 1) env ctx))
(coll (async-eval (nth expr 2) env ctx))) (coll (async-eval (nth expr 2) env ctx)))
(join "" (join "" (async-map-fn-render f coll env ctx)))
(filter (fn (r) (not (spread? r)))
(async-map-fn-render f coll env ctx))))
;; provide — render-time dynamic scope ;; provide — render-time dynamic scope
(= name "provide") (= name "provide")
@@ -443,8 +428,7 @@
(provide-push! prov-name prov-val) (provide-push! prov-name prov-val)
(let ((result (if (= body-count 1) (let ((result (if (= body-count 1)
(async-render (nth expr body-start) env ctx) (async-render (nth expr body-start) env ctx)
(let ((results (async-map-render (slice expr body-start) env ctx))) (join "" (async-map-render (slice expr body-start) env ctx)))))
(join "" (filter (fn (r) (not (spread? r))) results))))))
(provide-pop! prov-name) (provide-pop! prov-name)
result)) result))
@@ -595,35 +579,34 @@
(define-async async-aser :effects [render io] (define-async async-aser :effects [render io]
(fn (expr (env :as dict) ctx) (fn (expr (env :as dict) ctx)
(case (type-of expr) (let ((t (type-of expr))
"number" expr (result nil))
"string" expr (cond
"boolean" expr (= t "number") (set! result expr)
"nil" nil (= t "string") (set! result expr)
(= t "boolean") (set! result expr)
"symbol" (= t "nil") (set! result nil)
(let ((name (symbol-name expr))) (= t "symbol")
(cond (let ((name (symbol-name expr)))
(env-has? env name) (env-get env name) (set! result
(primitive? name) (get-primitive name) (cond
(= name "true") true (env-has? env name) (env-get env name)
(= name "false") false (primitive? name) (get-primitive name)
(= name "nil") nil (= name "true") true
:else (error (str "Undefined symbol: " name)))) (= name "false") false
(= name "nil") nil
"keyword" (keyword-name expr) :else (error (str "Undefined symbol: " name)))))
(= t "keyword") (set! result (keyword-name expr))
"dict" (async-aser-dict expr env ctx) (= t "dict") (set! result (async-aser-dict expr env ctx))
;; Spread — emit attrs to nearest element provider
;; Spread — pass through for client rendering (= t "spread") (do (emit! "element-attrs" (spread-attrs expr))
"spread" expr (set! result nil))
(= t "list") (set! result (if (empty? expr) (list) (async-aser-list expr env ctx)))
"list" :else (set! result expr))
(if (empty? expr) ;; Catch spread values from function calls and symbol lookups
(list) (if (spread? result)
(async-aser-list expr env ctx)) (do (emit! "element-attrs" (spread-attrs result)) nil)
result))))
:else expr)))
(define-async async-aser-dict :effects [render io] (define-async async-aser-dict :effects [render io]
@@ -775,7 +758,6 @@
(define-async async-aser-fragment :effects [render io] (define-async async-aser-fragment :effects [render io]
(fn ((children :as list) (env :as dict) ctx) (fn ((children :as list) (env :as dict) ctx)
;; Spreads are filtered — fragments have no parent element to merge into
(let ((parts (list))) (let ((parts (list)))
(for-each (for-each
(fn (c) (fn (c)
@@ -783,10 +765,10 @@
(if (= (type-of result) "list") (if (= (type-of result) "list")
(for-each (for-each
(fn (item) (fn (item)
(when (and (not (nil? item)) (not (spread? item))) (when (not (nil? item))
(append! parts (serialize item)))) (append! parts (serialize item))))
result) result)
(when (and (not (nil? result)) (not (spread? result))) (when (not (nil? result))
(append! parts (serialize result)))))) (append! parts (serialize result))))))
children) children)
(if (empty? parts) (if (empty? parts)
@@ -860,9 +842,12 @@
(let ((token (if (or (= name "svg") (= name "math")) (let ((token (if (or (= name "svg") (= name "math"))
(svg-context-set! true) (svg-context-set! true)
nil)) nil))
(parts (list name)) (attr-parts (list))
(child-parts (list))
(skip false) (skip false)
(i 0)) (i 0))
;; Provide scope for spread emit!
(provide-push! "element-attrs" nil)
(for-each (for-each
(fn (arg) (fn (arg)
(if skip (if skip
@@ -872,39 +857,43 @@
(< (inc i) (len args))) (< (inc i) (len args)))
(let ((val (async-aser (nth args (inc i)) env ctx))) (let ((val (async-aser (nth args (inc i)) env ctx)))
(when (not (nil? val)) (when (not (nil? val))
(append! parts (str ":" (keyword-name arg))) (append! attr-parts (str ":" (keyword-name arg)))
(if (= (type-of val) "list") (if (= (type-of val) "list")
(let ((live (filter (fn (v) (not (nil? v))) val))) (let ((live (filter (fn (v) (not (nil? v))) val)))
(if (empty? live) (if (empty? live)
(append! parts "nil") (append! attr-parts "nil")
(let ((items (map serialize live))) (let ((items (map serialize live)))
(if (some (fn (v) (sx-expr? v)) live) (if (some (fn (v) (sx-expr? v)) live)
(append! parts (str "(<> " (join " " items) ")")) (append! attr-parts (str "(<> " (join " " items) ")"))
(append! parts (str "(list " (join " " items) ")")))))) (append! attr-parts (str "(list " (join " " items) ")"))))))
(append! parts (serialize val)))) (append! attr-parts (serialize val))))
(set! skip true) (set! skip true)
(set! i (inc i))) (set! i (inc i)))
(let ((result (async-aser arg env ctx))) (let ((result (async-aser arg env ctx)))
(when (not (nil? result)) (when (not (nil? result))
(if (spread? result) (if (= (type-of result) "list")
;; Spread child — merge attrs as keyword args into parent element
(for-each (for-each
(fn (k) (fn (item)
(let ((v (dict-get (spread-attrs result) k))) (when (not (nil? item))
(append! parts (str ":" k)) (append! child-parts (serialize item))))
(append! parts (serialize v)))) result)
(keys (spread-attrs result))) (append! child-parts (serialize result))))
(if (= (type-of result) "list")
(for-each
(fn (item)
(when (not (nil? item))
(append! parts (serialize item))))
result)
(append! parts (serialize result)))))
(set! i (inc i)))))) (set! i (inc i))))))
args) args)
;; Collect emitted spread attrs — after explicit attrs, before children
(for-each
(fn (spread-dict)
(for-each
(fn (k)
(let ((v (dict-get spread-dict k)))
(append! attr-parts (str ":" k))
(append! attr-parts (serialize v))))
(keys spread-dict)))
(emitted "element-attrs"))
(provide-pop! "element-attrs")
(when token (svg-context-reset! token)) (when token (svg-context-reset! token))
(make-sx-expr (str "(" (join " " parts) ")"))))) (let ((parts (concat (list name) attr-parts child-parts)))
(make-sx-expr (str "(" (join " " parts) ")"))))))
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------

View File

@@ -44,8 +44,8 @@
;; Pre-rendered DOM node → pass through ;; Pre-rendered DOM node → pass through
"dom-node" expr "dom-node" expr
;; Spread → pass through (parent element handles it) ;; Spread → emit attrs to nearest element provider, pass through for reactive-spread
"spread" expr "spread" (do (emit! "element-attrs" (spread-attrs expr)) expr)
;; Dict → empty ;; Dict → empty
"dict" (create-fragment) "dict" (create-fragment)
@@ -180,6 +180,9 @@
:else ns)) :else ns))
(el (dom-create-element tag new-ns))) (el (dom-create-element tag new-ns)))
;; Provide scope for spread emit! — deeply nested spreads emit here
(provide-push! "element-attrs" nil)
;; Process args: keywords → attrs, others → children ;; Process args: keywords → attrs, others → children
(reduce (reduce
(fn (state arg) (fn (state arg)
@@ -236,28 +239,8 @@
;; Reactive spread: track signal deps, update attrs on change ;; Reactive spread: track signal deps, update attrs on change
(and (spread? child) *island-scope*) (and (spread? child) *island-scope*)
(reactive-spread el (fn () (render-to-dom arg env new-ns))) (reactive-spread el (fn () (render-to-dom arg env new-ns)))
;; Static spread: one-shot merge attrs onto parent element ;; Static spread: already emitted via provide, skip
(spread? child) (spread? child) nil
(for-each
(fn ((key :as string))
(let ((val (dict-get (spread-attrs child) key)))
(if (= key "class")
;; Class: append to existing
(let ((existing (dom-get-attr el "class")))
(dom-set-attr el "class"
(if (and existing (not (= existing "")))
(str existing " " val)
val)))
(if (= key "style")
;; Style: append with semicolon
(let ((existing (dom-get-attr el "style")))
(dom-set-attr el "style"
(if (and existing (not (= existing "")))
(str existing ";" val)
val)))
;; Other attrs: overwrite
(dom-set-attr el key (str val))))))
(keys (spread-attrs child)))
;; Normal child: append to element ;; Normal child: append to element
:else :else
(dom-append el child)))) (dom-append el child))))
@@ -265,6 +248,29 @@
(dict "i" 0 "skip" false) (dict "i" 0 "skip" false)
args) args)
;; Collect emitted spread attrs and merge onto DOM element
(for-each
(fn (spread-dict)
(for-each
(fn ((key :as string))
(let ((val (dict-get spread-dict key)))
(if (= key "class")
(let ((existing (dom-get-attr el "class")))
(dom-set-attr el "class"
(if (and existing (not (= existing "")))
(str existing " " val)
val)))
(if (= key "style")
(let ((existing (dom-get-attr el "style")))
(dom-set-attr el "style"
(if (and existing (not (= existing "")))
(str existing ";" val)
val)))
(dom-set-attr el key (str val))))))
(keys spread-dict)))
(emitted "element-attrs"))
(provide-pop! "element-attrs")
el))) el)))

View File

@@ -30,8 +30,8 @@
"keyword" (escape-html (keyword-name expr)) "keyword" (escape-html (keyword-name expr))
;; Raw HTML passthrough ;; Raw HTML passthrough
"raw-html" (raw-html-content expr) "raw-html" (raw-html-content expr)
;; Spread — pass through as-is (parent element will merge attrs) ;; Spread — emit attrs to nearest element provider
"spread" expr "spread" (do (emit! "element-attrs" (spread-attrs expr)) "")
;; Everything else — evaluate first ;; Everything else — evaluate first
:else (render-value-to-html (trampoline (eval-expr expr env)) env)))) :else (render-value-to-html (trampoline (eval-expr expr env)) env))))
@@ -44,7 +44,7 @@
"boolean" (if val "true" "false") "boolean" (if val "true" "false")
"list" (render-list-to-html val env) "list" (render-list-to-html val env)
"raw-html" (raw-html-content val) "raw-html" (raw-html-content val)
"spread" val "spread" (do (emit! "element-attrs" (spread-attrs val)) "")
:else (escape-html (str val))))) :else (escape-html (str val)))))
@@ -73,16 +73,14 @@
"" ""
(let ((head (first expr))) (let ((head (first expr)))
(if (not (= (type-of head) "symbol")) (if (not (= (type-of head) "symbol"))
;; Data list — render each item (spreads filtered — no parent element) ;; Data list — render each item
(join "" (filter (fn (x) (not (spread? x))) (join "" (map (fn (x) (render-value-to-html x env)) expr))
(map (fn (x) (render-value-to-html x env)) expr)))
(let ((name (symbol-name head)) (let ((name (symbol-name head))
(args (rest expr))) (args (rest expr)))
(cond (cond
;; Fragment (spreads filtered — no parent element) ;; Fragment
(= name "<>") (= name "<>")
(join "" (filter (fn (x) (not (spread? x))) (join "" (map (fn (x) (render-to-html x env)) args))
(map (fn (x) (render-to-html x env)) args)))
;; Raw HTML passthrough ;; Raw HTML passthrough
(= name "raw!") (= name "raw!")
@@ -152,15 +150,14 @@
(render-to-html (nth expr 3) env) (render-to-html (nth expr 3) env)
""))) "")))
;; when — single body: pass through (spread propagates). Multi: join strings. ;; when — single body: pass through. Multi: join strings.
(= name "when") (= name "when")
(if (not (trampoline (eval-expr (nth expr 1) env))) (if (not (trampoline (eval-expr (nth expr 1) env)))
"" ""
(if (= (len expr) 3) (if (= (len expr) 3)
(render-to-html (nth expr 2) env) (render-to-html (nth expr 2) env)
(let ((results (map (fn (i) (render-to-html (nth expr i) env)) (join "" (map (fn (i) (render-to-html (nth expr i) env))
(range 2 (len expr))))) (range 2 (len expr))))))
(join "" (filter (fn (r) (not (spread? r))) results)))))
;; cond ;; cond
(= name "cond") (= name "cond")
@@ -178,64 +175,59 @@
(let ((local (process-bindings (nth expr 1) env))) (let ((local (process-bindings (nth expr 1) env)))
(if (= (len expr) 3) (if (= (len expr) 3)
(render-to-html (nth expr 2) local) (render-to-html (nth expr 2) local)
(let ((results (map (fn (i) (render-to-html (nth expr i) local)) (join "" (map (fn (i) (render-to-html (nth expr i) local))
(range 2 (len expr))))) (range 2 (len expr))))))
(join "" (filter (fn (r) (not (spread? r))) results)))))
;; begin / do — single body: pass through. Multi: join strings. ;; begin / do — single body: pass through. Multi: join strings.
(or (= name "begin") (= name "do")) (or (= name "begin") (= name "do"))
(if (= (len expr) 2) (if (= (len expr) 2)
(render-to-html (nth expr 1) env) (render-to-html (nth expr 1) env)
(let ((results (map (fn (i) (render-to-html (nth expr i) env)) (join "" (map (fn (i) (render-to-html (nth expr i) env))
(range 1 (len expr))))) (range 1 (len expr)))))
(join "" (filter (fn (r) (not (spread? r))) results))))
;; Definition forms — eval for side effects ;; Definition forms — eval for side effects
(definition-form? name) (definition-form? name)
(do (trampoline (eval-expr expr env)) "") (do (trampoline (eval-expr expr env)) "")
;; map — spreads filtered (no parent element in list context) ;; map
(= name "map") (= name "map")
(let ((f (trampoline (eval-expr (nth expr 1) env))) (let ((f (trampoline (eval-expr (nth expr 1) env)))
(coll (trampoline (eval-expr (nth expr 2) env)))) (coll (trampoline (eval-expr (nth expr 2) env))))
(join "" (join ""
(filter (fn (r) (not (spread? r))) (map
(map (fn (item)
(fn (item) (if (lambda? f)
(if (lambda? f) (render-lambda-html f (list item) env)
(render-lambda-html f (list item) env) (render-to-html (apply f (list item)) env)))
(render-to-html (apply f (list item)) env))) coll)))
coll))))
;; map-indexed — spreads filtered ;; map-indexed
(= name "map-indexed") (= name "map-indexed")
(let ((f (trampoline (eval-expr (nth expr 1) env))) (let ((f (trampoline (eval-expr (nth expr 1) env)))
(coll (trampoline (eval-expr (nth expr 2) env)))) (coll (trampoline (eval-expr (nth expr 2) env))))
(join "" (join ""
(filter (fn (r) (not (spread? r))) (map-indexed
(map-indexed (fn (i item)
(fn (i item) (if (lambda? f)
(if (lambda? f) (render-lambda-html f (list i item) env)
(render-lambda-html f (list i item) env) (render-to-html (apply f (list i item)) env)))
(render-to-html (apply f (list i item)) env))) coll)))
coll))))
;; filter — evaluate fully then render ;; filter — evaluate fully then render
(= name "filter") (= name "filter")
(render-to-html (trampoline (eval-expr expr env)) env) (render-to-html (trampoline (eval-expr expr env)) env)
;; for-each (render variant) — spreads filtered ;; for-each (render variant)
(= name "for-each") (= name "for-each")
(let ((f (trampoline (eval-expr (nth expr 1) env))) (let ((f (trampoline (eval-expr (nth expr 1) env)))
(coll (trampoline (eval-expr (nth expr 2) env)))) (coll (trampoline (eval-expr (nth expr 2) env))))
(join "" (join ""
(filter (fn (r) (not (spread? r))) (map
(map (fn (item)
(fn (item) (if (lambda? f)
(if (lambda? f) (render-lambda-html f (list item) env)
(render-lambda-html f (list item) env) (render-to-html (apply f (list item)) env)))
(render-to-html (apply f (list item)) env))) coll)))
coll))))
;; provide — render-time dynamic scope ;; provide — render-time dynamic scope
(= name "provide") (= name "provide")
@@ -246,9 +238,8 @@
(provide-push! prov-name prov-val) (provide-push! prov-name prov-val)
(let ((result (if (= body-count 1) (let ((result (if (= body-count 1)
(render-to-html (nth expr body-start) env) (render-to-html (nth expr body-start) env)
(join "" (filter (fn (r) (not (spread? r))) (join "" (map (fn (i) (render-to-html (nth expr i) env))
(map (fn (i) (render-to-html (nth expr i) env)) (range body-start (+ body-start body-count)))))))
(range body-start (+ body-start body-count))))))))
(provide-pop! prov-name) (provide-pop! prov-name)
result)) result))
@@ -307,17 +298,9 @@
(env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil))) (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil)))
(component-params comp)) (component-params comp))
;; If component accepts children, pre-render them to raw HTML ;; If component accepts children, pre-render them to raw HTML
;; Spread values are filtered out (no parent element to merge onto)
(when (component-has-children? comp) (when (component-has-children? comp)
(let ((parts (list))) (env-set! local "children"
(for-each (make-raw-html (join "" (map (fn (c) (render-to-html c env)) children)))))
(fn (c)
(let ((r (render-to-html c env)))
(when (not (spread? r))
(append! parts r))))
children)
(env-set! local "children"
(make-raw-html (join "" parts)))))
(render-to-html (component-body comp) local))))) (render-to-html (component-body comp) local)))))
@@ -329,18 +312,17 @@
(is-void (contains? VOID_ELEMENTS tag))) (is-void (contains? VOID_ELEMENTS tag)))
(if is-void (if is-void
(str "<" tag (render-attrs attrs) " />") (str "<" tag (render-attrs attrs) " />")
;; Render children, collecting spreads and content separately ;; Provide scope for spread emit!
(let ((content-parts (list))) (do
(for-each (provide-push! "element-attrs" nil)
(fn (c) (let ((content (join "" (map (fn (c) (render-to-html c env)) children))))
(let ((result (render-to-html c env))) (for-each
(if (spread? result) (fn (spread-dict) (merge-spread-attrs attrs spread-dict))
(merge-spread-attrs attrs (spread-attrs result)) (emitted "element-attrs"))
(append! content-parts result)))) (provide-pop! "element-attrs")
children) (str "<" tag (render-attrs attrs) ">"
(str "<" tag (render-attrs attrs) ">" content
(join "" content-parts) "</" tag ">")))))))
"</" tag ">"))))))
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
@@ -375,19 +357,17 @@
(assoc state "i" (inc (get state "i")))))))) (assoc state "i" (inc (get state "i"))))))))
(dict "i" 0 "skip" false) (dict "i" 0 "skip" false)
args) args)
;; Render children, handling spreads ;; Provide scope for spread emit!
(let ((lake-attrs (dict "data-sx-lake" (or lake-id ""))) (let ((lake-attrs (dict "data-sx-lake" (or lake-id ""))))
(content-parts (list))) (provide-push! "element-attrs" nil)
(for-each (let ((content (join "" (map (fn (c) (render-to-html c env)) children))))
(fn (c) (for-each
(let ((result (render-to-html c env))) (fn (spread-dict) (merge-spread-attrs lake-attrs spread-dict))
(if (spread? result) (emitted "element-attrs"))
(merge-spread-attrs lake-attrs (spread-attrs result)) (provide-pop! "element-attrs")
(append! content-parts result)))) (str "<" lake-tag (render-attrs lake-attrs) ">"
children) content
(str "<" lake-tag (render-attrs lake-attrs) ">" "</" lake-tag ">"))))))
(join "" content-parts)
"</" lake-tag ">")))))
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
@@ -425,19 +405,17 @@
(assoc state "i" (inc (get state "i")))))))) (assoc state "i" (inc (get state "i"))))))))
(dict "i" 0 "skip" false) (dict "i" 0 "skip" false)
args) args)
;; Render children, handling spreads ;; Provide scope for spread emit!
(let ((marsh-attrs (dict "data-sx-marsh" (or marsh-id ""))) (let ((marsh-attrs (dict "data-sx-marsh" (or marsh-id ""))))
(content-parts (list))) (provide-push! "element-attrs" nil)
(for-each (let ((content (join "" (map (fn (c) (render-to-html c env)) children))))
(fn (c) (for-each
(let ((result (render-to-html c env))) (fn (spread-dict) (merge-spread-attrs marsh-attrs spread-dict))
(if (spread? result) (emitted "element-attrs"))
(merge-spread-attrs marsh-attrs (spread-attrs result)) (provide-pop! "element-attrs")
(append! content-parts result)))) (str "<" marsh-tag (render-attrs marsh-attrs) ">"
children) content
(str "<" marsh-tag (render-attrs marsh-attrs) ">" "</" marsh-tag ">"))))))
(join "" content-parts)
"</" marsh-tag ">")))))
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------
@@ -487,17 +465,9 @@
(component-params island)) (component-params island))
;; If island accepts children, pre-render them to raw HTML ;; If island accepts children, pre-render them to raw HTML
;; Spread values filtered out (no parent element)
(when (component-has-children? island) (when (component-has-children? island)
(let ((parts (list))) (env-set! local "children"
(for-each (make-raw-html (join "" (map (fn (c) (render-to-html c env)) children)))))
(fn (c)
(let ((r (render-to-html c env)))
(when (not (spread? r))
(append! parts r))))
children)
(env-set! local "children"
(make-raw-html (join "" parts)))))
;; Render the island body as HTML ;; Render the island body as HTML
(let ((body-html (render-to-html (component-body island) local)) (let ((body-html (render-to-html (component-body island) local))

View File

@@ -25,33 +25,38 @@
;; Evaluate for SX wire format — serialize rendering forms, ;; Evaluate for SX wire format — serialize rendering forms,
;; evaluate control flow and function calls. ;; evaluate control flow and function calls.
(set-render-active! true) (set-render-active! true)
(case (type-of expr) (let ((result
"number" expr (case (type-of expr)
"string" expr "number" expr
"boolean" expr "string" expr
"nil" nil "boolean" expr
"nil" nil
"symbol" "symbol"
(let ((name (symbol-name expr))) (let ((name (symbol-name expr)))
(cond (cond
(env-has? env name) (env-get env name) (env-has? env name) (env-get env name)
(primitive? name) (get-primitive name) (primitive? name) (get-primitive name)
(= name "true") true (= name "true") true
(= name "false") false (= name "false") false
(= name "nil") nil (= name "nil") nil
:else (error (str "Undefined symbol: " name)))) :else (error (str "Undefined symbol: " name))))
"keyword" (keyword-name expr) "keyword" (keyword-name expr)
"list" "list"
(if (empty? expr) (if (empty? expr)
(list) (list)
(aser-list expr env)) (aser-list expr env))
;; Spread — pass through for client rendering ;; Spread — emit attrs to nearest element provider
"spread" expr "spread" (do (emit! "element-attrs" (spread-attrs expr)) nil)
:else expr))) :else expr)))
;; Catch spread values from function calls and symbol lookups
(if (spread? result)
(do (emit! "element-attrs" (spread-attrs result)) nil)
result))))
(define aser-list :effects [render] (define aser-list :effects [render]
@@ -110,7 +115,6 @@
(fn ((children :as list) (env :as dict)) (fn ((children :as list) (env :as dict))
;; Serialize (<> child1 child2 ...) to sx source string ;; Serialize (<> child1 child2 ...) to sx source string
;; Must flatten list results (e.g. from map/filter) to avoid nested parens ;; Must flatten list results (e.g. from map/filter) to avoid nested parens
;; Spreads are filtered — fragments have no parent element to merge into
(let ((parts (list))) (let ((parts (list)))
(for-each (for-each
(fn (c) (fn (c)
@@ -118,10 +122,10 @@
(if (= (type-of result) "list") (if (= (type-of result) "list")
(for-each (for-each
(fn (item) (fn (item)
(when (and (not (nil? item)) (not (spread? item))) (when (not (nil? item))
(append! parts (serialize item)))) (append! parts (serialize item))))
result) result)
(when (and (not (nil? result)) (not (spread? result))) (when (not (nil? result))
(append! parts (serialize result)))))) (append! parts (serialize result))))))
children) children)
(if (empty? parts) (if (empty? parts)
@@ -134,9 +138,13 @@
;; Serialize (name :key val child ...) — evaluate args but keep as sx ;; Serialize (name :key val child ...) — evaluate args but keep as sx
;; Uses for-each + mutable state (not reduce) so bootstrapper emits for-loops ;; Uses for-each + mutable state (not reduce) so bootstrapper emits for-loops
;; that can contain nested for-each for list flattening. ;; that can contain nested for-each for list flattening.
(let ((parts (list name)) ;; Separate attrs and children so emitted spread attrs go before children.
(let ((attr-parts (list))
(child-parts (list))
(skip false) (skip false)
(i 0)) (i 0))
;; Provide scope for spread emit!
(provide-push! "element-attrs" nil)
(for-each (for-each
(fn (arg) (fn (arg)
(if skip (if skip
@@ -146,30 +154,34 @@
(< (inc i) (len args))) (< (inc i) (len args)))
(let ((val (aser (nth args (inc i)) env))) (let ((val (aser (nth args (inc i)) env)))
(when (not (nil? val)) (when (not (nil? val))
(append! parts (str ":" (keyword-name arg))) (append! attr-parts (str ":" (keyword-name arg)))
(append! parts (serialize val))) (append! attr-parts (serialize val)))
(set! skip true) (set! skip true)
(set! i (inc i))) (set! i (inc i)))
(let ((val (aser arg env))) (let ((val (aser arg env)))
(when (not (nil? val)) (when (not (nil? val))
(if (spread? val) (if (= (type-of val) "list")
;; Spread child — merge attrs as keyword args into parent element
(for-each (for-each
(fn (k) (fn (item)
(let ((v (dict-get (spread-attrs val) k))) (when (not (nil? item))
(append! parts (str ":" k)) (append! child-parts (serialize item))))
(append! parts (serialize v)))) val)
(keys (spread-attrs val))) (append! child-parts (serialize val))))
(if (= (type-of val) "list")
(for-each
(fn (item)
(when (not (nil? item))
(append! parts (serialize item))))
val)
(append! parts (serialize val)))))
(set! i (inc i)))))) (set! i (inc i))))))
args) args)
(str "(" (join " " parts) ")")))) ;; Collect emitted spread attrs — goes after explicit attrs, before children
(for-each
(fn (spread-dict)
(for-each
(fn (k)
(let ((v (dict-get spread-dict k)))
(append! attr-parts (str ":" k))
(append! attr-parts (serialize v))))
(keys spread-dict)))
(emitted "element-attrs"))
(provide-pop! "element-attrs")
(let ((parts (concat (list name) attr-parts child-parts)))
(str "(" (join " " parts) ")")))))
;; -------------------------------------------------------------------------- ;; --------------------------------------------------------------------------

View File

@@ -1203,8 +1203,6 @@ PLATFORM_JS_PRE = '''
function sxEmit(name, value) { function sxEmit(name, value) {
if (_provideStacks[name] && _provideStacks[name].length) { if (_provideStacks[name] && _provideStacks[name].length) {
_provideStacks[name][_provideStacks[name].length - 1].emitted.push(value); _provideStacks[name][_provideStacks[name].length - 1].emitted.push(value);
} else {
throw new Error("No provider for emit!: " + name);
} }
return NIL; return NIL;
} }

View File

@@ -126,11 +126,9 @@ def sx_context(name, *default):
def sx_emit(name, value): def sx_emit(name, value):
"""Append value to nearest enclosing provider's accumulator. Error if no provider.""" """Append value to nearest enclosing provider's accumulator. No-op if no provider."""
if name in _provide_stacks and _provide_stacks[name]: if name in _provide_stacks and _provide_stacks[name]:
_provide_stacks[name][-1]["emitted"].append(value) _provide_stacks[name][-1]["emitted"].append(value)
else:
raise RuntimeError(f"No provider for emit!: {name}")
return NIL return NIL

View File

@@ -1,5 +1,3 @@
# WARNING: special-forms.sx declares forms not in eval.sx: reset, shift
# WARNING: eval.sx dispatches forms not in special-forms.sx: form?, provide
""" """
sx_ref.py -- Generated from reference SX evaluator specification. sx_ref.py -- Generated from reference SX evaluator specification.
@@ -87,11 +85,9 @@ def sx_context(name, *default):
def sx_emit(name, value): def sx_emit(name, value):
"""Append value to nearest enclosing provider's accumulator. Error if no provider.""" """Append value to nearest enclosing provider's accumulator. No-op if no provider."""
if name in _provide_stacks and _provide_stacks[name]: if name in _provide_stacks and _provide_stacks[name]:
_provide_stacks[name][-1]["emitted"].append(value) _provide_stacks[name][-1]["emitted"].append(value)
else:
raise RuntimeError(f"No provider for emit!: {name}")
return NIL return NIL
@@ -2225,7 +2221,8 @@ def render_to_html(expr, env):
elif _match == 'raw-html': elif _match == 'raw-html':
return raw_html_content(expr) return raw_html_content(expr)
elif _match == 'spread': elif _match == 'spread':
return expr sx_emit('element-attrs', spread_attrs(expr))
return ''
else: else:
return render_value_to_html(trampoline(eval_expr(expr, env)), env) return render_value_to_html(trampoline(eval_expr(expr, env)), env)
@@ -2248,7 +2245,8 @@ def render_value_to_html(val, env):
elif _match == 'raw-html': elif _match == 'raw-html':
return raw_html_content(val) return raw_html_content(val)
elif _match == 'spread': elif _match == 'spread':
return val sx_emit('element-attrs', spread_attrs(val))
return ''
else: else:
return escape_html(sx_str(val)) return escape_html(sx_str(val))
@@ -2266,12 +2264,12 @@ def render_list_to_html(expr, env):
else: else:
head = first(expr) head = first(expr)
if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))): if sx_truthy((not sx_truthy((type_of(head) == 'symbol')))):
return join('', filter(lambda x: (not sx_truthy(is_spread(x))), map(lambda x: render_value_to_html(x, env), expr))) return join('', map(lambda x: render_value_to_html(x, env), expr))
else: else:
name = symbol_name(head) name = symbol_name(head)
args = rest(expr) args = rest(expr)
if sx_truthy((name == '<>')): if sx_truthy((name == '<>')):
return join('', filter(lambda x: (not sx_truthy(is_spread(x))), map(lambda x: render_to_html(x, env), args))) return join('', map(lambda x: render_to_html(x, env), args))
elif sx_truthy((name == 'raw!')): elif sx_truthy((name == 'raw!')):
return join('', map(lambda x: sx_str(trampoline(eval_expr(x, env))), args)) return join('', map(lambda x: sx_str(trampoline(eval_expr(x, env))), args))
elif sx_truthy((name == 'lake')): elif sx_truthy((name == 'lake')):
@@ -2315,8 +2313,7 @@ def dispatch_html_form(name, expr, env):
if sx_truthy((len(expr) == 3)): if sx_truthy((len(expr) == 3)):
return render_to_html(nth(expr, 2), env) return render_to_html(nth(expr, 2), env)
else: else:
results = map(lambda i: render_to_html(nth(expr, i), env), range(2, len(expr))) return join('', map(lambda i: render_to_html(nth(expr, i), env), range(2, len(expr))))
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), results))
elif sx_truthy((name == 'cond')): elif sx_truthy((name == 'cond')):
branch = eval_cond(rest(expr), env) branch = eval_cond(rest(expr), env)
if sx_truthy(branch): if sx_truthy(branch):
@@ -2330,38 +2327,36 @@ def dispatch_html_form(name, expr, env):
if sx_truthy((len(expr) == 3)): if sx_truthy((len(expr) == 3)):
return render_to_html(nth(expr, 2), local) return render_to_html(nth(expr, 2), local)
else: else:
results = map(lambda i: render_to_html(nth(expr, i), local), range(2, len(expr))) return join('', map(lambda i: render_to_html(nth(expr, i), local), range(2, len(expr))))
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), results))
elif sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))): elif sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))):
if sx_truthy((len(expr) == 2)): if sx_truthy((len(expr) == 2)):
return render_to_html(nth(expr, 1), env) return render_to_html(nth(expr, 1), env)
else: else:
results = map(lambda i: render_to_html(nth(expr, i), env), range(1, len(expr))) return join('', map(lambda i: render_to_html(nth(expr, i), env), range(1, len(expr))))
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), results))
elif sx_truthy(is_definition_form(name)): elif sx_truthy(is_definition_form(name)):
trampoline(eval_expr(expr, env)) trampoline(eval_expr(expr, env))
return '' return ''
elif sx_truthy((name == 'map')): elif sx_truthy((name == 'map')):
f = trampoline(eval_expr(nth(expr, 1), env)) f = trampoline(eval_expr(nth(expr, 1), env))
coll = trampoline(eval_expr(nth(expr, 2), env)) coll = trampoline(eval_expr(nth(expr, 2), env))
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), map(lambda item: (render_lambda_html(f, [item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [item]), env)), coll))) return join('', map(lambda item: (render_lambda_html(f, [item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [item]), env)), coll))
elif sx_truthy((name == 'map-indexed')): elif sx_truthy((name == 'map-indexed')):
f = trampoline(eval_expr(nth(expr, 1), env)) f = trampoline(eval_expr(nth(expr, 1), env))
coll = trampoline(eval_expr(nth(expr, 2), env)) coll = trampoline(eval_expr(nth(expr, 2), env))
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), map_indexed(lambda i, item: (render_lambda_html(f, [i, item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [i, item]), env)), coll))) return join('', map_indexed(lambda i, item: (render_lambda_html(f, [i, item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [i, item]), env)), coll))
elif sx_truthy((name == 'filter')): elif sx_truthy((name == 'filter')):
return render_to_html(trampoline(eval_expr(expr, env)), env) return render_to_html(trampoline(eval_expr(expr, env)), env)
elif sx_truthy((name == 'for-each')): elif sx_truthy((name == 'for-each')):
f = trampoline(eval_expr(nth(expr, 1), env)) f = trampoline(eval_expr(nth(expr, 1), env))
coll = trampoline(eval_expr(nth(expr, 2), env)) coll = trampoline(eval_expr(nth(expr, 2), env))
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), map(lambda item: (render_lambda_html(f, [item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [item]), env)), coll))) return join('', map(lambda item: (render_lambda_html(f, [item], env) if sx_truthy(is_lambda(f)) else render_to_html(apply(f, [item]), env)), coll))
elif sx_truthy((name == 'provide')): elif sx_truthy((name == 'provide')):
prov_name = trampoline(eval_expr(nth(expr, 1), env)) prov_name = trampoline(eval_expr(nth(expr, 1), env))
prov_val = trampoline(eval_expr(nth(expr, 2), env)) prov_val = trampoline(eval_expr(nth(expr, 2), env))
body_start = 3 body_start = 3
body_count = (len(expr) - 3) body_count = (len(expr) - 3)
provide_push(prov_name, prov_val) provide_push(prov_name, prov_val)
result = (render_to_html(nth(expr, body_start), env) if sx_truthy((body_count == 1)) else join('', filter(lambda r: (not sx_truthy(is_spread(r))), map(lambda i: render_to_html(nth(expr, i), env), range(body_start, (body_start + body_count)))))) result = (render_to_html(nth(expr, body_start), env) if sx_truthy((body_count == 1)) else join('', map(lambda i: render_to_html(nth(expr, i), env), range(body_start, (body_start + body_count)))))
provide_pop(prov_name) provide_pop(prov_name)
return result return result
else: else:
@@ -2382,12 +2377,7 @@ def render_html_component(comp, args, env):
for p in component_params(comp): for p in component_params(comp):
local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL) local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)
if sx_truthy(component_has_children(comp)): if sx_truthy(component_has_children(comp)):
parts = [] local['children'] = make_raw_html(join('', map(lambda c: render_to_html(c, env), children)))
for c in children:
r = render_to_html(c, env)
if sx_truthy((not sx_truthy(is_spread(r)))):
parts.append(r)
local['children'] = make_raw_html(join('', parts))
return render_to_html(component_body(comp), local) return render_to_html(component_body(comp), local)
# render-html-element # render-html-element
@@ -2399,14 +2389,12 @@ def render_html_element(tag, args, env):
if sx_truthy(is_void): if sx_truthy(is_void):
return sx_str('<', tag, render_attrs(attrs), ' />') return sx_str('<', tag, render_attrs(attrs), ' />')
else: else:
content_parts = [] provide_push('element-attrs', NIL)
for c in children: content = join('', map(lambda c: render_to_html(c, env), children))
result = render_to_html(c, env) for spread_dict in sx_emitted('element-attrs'):
if sx_truthy(is_spread(result)): merge_spread_attrs(attrs, spread_dict)
merge_spread_attrs(attrs, spread_attrs(result)) provide_pop('element-attrs')
else: return sx_str('<', tag, render_attrs(attrs), '>', content, '</', tag, '>')
content_parts.append(result)
return sx_str('<', tag, render_attrs(attrs), '>', join('', content_parts), '</', tag, '>')
# render-html-lake # render-html-lake
def render_html_lake(args, env): def render_html_lake(args, env):
@@ -2416,14 +2404,12 @@ def render_html_lake(args, env):
children = [] children = []
reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda kname: (lambda kval: _sx_begin((_sx_cell_set(_cells, 'lake_id', kval) if sx_truthy((kname == 'id')) else (_sx_cell_set(_cells, 'lake_tag', kval) if sx_truthy((kname == 'tag')) else NIL)), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))))(keyword_name(arg)) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args) reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda kname: (lambda kval: _sx_begin((_sx_cell_set(_cells, 'lake_id', kval) if sx_truthy((kname == 'id')) else (_sx_cell_set(_cells, 'lake_tag', kval) if sx_truthy((kname == 'tag')) else NIL)), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))))(keyword_name(arg)) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args)
lake_attrs = {'data-sx-lake': (_cells['lake_id'] if sx_truthy(_cells['lake_id']) else '')} lake_attrs = {'data-sx-lake': (_cells['lake_id'] if sx_truthy(_cells['lake_id']) else '')}
content_parts = [] provide_push('element-attrs', NIL)
for c in children: content = join('', map(lambda c: render_to_html(c, env), children))
result = render_to_html(c, env) for spread_dict in sx_emitted('element-attrs'):
if sx_truthy(is_spread(result)): merge_spread_attrs(lake_attrs, spread_dict)
merge_spread_attrs(lake_attrs, spread_attrs(result)) provide_pop('element-attrs')
else: return sx_str('<', _cells['lake_tag'], render_attrs(lake_attrs), '>', content, '</', _cells['lake_tag'], '>')
content_parts.append(result)
return sx_str('<', _cells['lake_tag'], render_attrs(lake_attrs), '>', join('', content_parts), '</', _cells['lake_tag'], '>')
# render-html-marsh # render-html-marsh
def render_html_marsh(args, env): def render_html_marsh(args, env):
@@ -2433,14 +2419,12 @@ def render_html_marsh(args, env):
children = [] children = []
reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda kname: (lambda kval: _sx_begin((_sx_cell_set(_cells, 'marsh_id', kval) if sx_truthy((kname == 'id')) else (_sx_cell_set(_cells, 'marsh_tag', kval) if sx_truthy((kname == 'tag')) else (NIL if sx_truthy((kname == 'transform')) else NIL))), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))))(keyword_name(arg)) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args) reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda kname: (lambda kval: _sx_begin((_sx_cell_set(_cells, 'marsh_id', kval) if sx_truthy((kname == 'id')) else (_sx_cell_set(_cells, 'marsh_tag', kval) if sx_truthy((kname == 'tag')) else (NIL if sx_truthy((kname == 'transform')) else NIL))), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))))(keyword_name(arg)) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args)
marsh_attrs = {'data-sx-marsh': (_cells['marsh_id'] if sx_truthy(_cells['marsh_id']) else '')} marsh_attrs = {'data-sx-marsh': (_cells['marsh_id'] if sx_truthy(_cells['marsh_id']) else '')}
content_parts = [] provide_push('element-attrs', NIL)
for c in children: content = join('', map(lambda c: render_to_html(c, env), children))
result = render_to_html(c, env) for spread_dict in sx_emitted('element-attrs'):
if sx_truthy(is_spread(result)): merge_spread_attrs(marsh_attrs, spread_dict)
merge_spread_attrs(marsh_attrs, spread_attrs(result)) provide_pop('element-attrs')
else: return sx_str('<', _cells['marsh_tag'], render_attrs(marsh_attrs), '>', content, '</', _cells['marsh_tag'], '>')
content_parts.append(result)
return sx_str('<', _cells['marsh_tag'], render_attrs(marsh_attrs), '>', join('', content_parts), '</', _cells['marsh_tag'], '>')
# render-html-island # render-html-island
def render_html_island(island, args, env): def render_html_island(island, args, env):
@@ -2452,12 +2436,7 @@ def render_html_island(island, args, env):
for p in component_params(island): for p in component_params(island):
local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL) local[p] = (dict_get(kwargs, p) if sx_truthy(dict_has(kwargs, p)) else NIL)
if sx_truthy(component_has_children(island)): if sx_truthy(component_has_children(island)):
parts = [] local['children'] = make_raw_html(join('', map(lambda c: render_to_html(c, env), children)))
for c in children:
r = render_to_html(c, env)
if sx_truthy((not sx_truthy(is_spread(r)))):
parts.append(r)
local['children'] = make_raw_html(join('', parts))
body_html = render_to_html(component_body(island), local) body_html = render_to_html(component_body(island), local)
state_sx = serialize_island_state(kwargs) state_sx = serialize_island_state(kwargs)
return sx_str('<span data-sx-island="', escape_attr(island_name), '"', (sx_str(' data-sx-state="', escape_attr(state_sx), '"') if sx_truthy(state_sx) else ''), '>', body_html, '</span>') return sx_str('<span data-sx-island="', escape_attr(island_name), '"', (sx_str(' data-sx-state="', escape_attr(state_sx), '"') if sx_truthy(state_sx) else ''), '>', body_html, '</span>')
@@ -2483,40 +2462,12 @@ def render_to_sx(expr, env):
# aser # aser
def aser(expr, env): def aser(expr, env):
set_render_active_b(True) set_render_active_b(True)
_match = type_of(expr) result = _sx_case(type_of(expr), [('number', lambda: expr), ('string', lambda: expr), ('boolean', lambda: expr), ('nil', lambda: NIL), ('symbol', lambda: (lambda name: (env_get(env, name) if sx_truthy(env_has(env, name)) else (get_primitive(name) if sx_truthy(is_primitive(name)) else (True if sx_truthy((name == 'true')) else (False if sx_truthy((name == 'false')) else (NIL if sx_truthy((name == 'nil')) else error(sx_str('Undefined symbol: ', name))))))))(symbol_name(expr))), ('keyword', lambda: keyword_name(expr)), ('list', lambda: ([] if sx_truthy(empty_p(expr)) else aser_list(expr, env))), ('spread', lambda: _sx_begin(sx_emit('element-attrs', spread_attrs(expr)), NIL)), (None, lambda: expr)])
if _match == 'number': if sx_truthy(is_spread(result)):
return expr sx_emit('element-attrs', spread_attrs(result))
elif _match == 'string':
return expr
elif _match == 'boolean':
return expr
elif _match == 'nil':
return NIL return NIL
elif _match == 'symbol':
name = symbol_name(expr)
if sx_truthy(env_has(env, name)):
return env_get(env, name)
elif sx_truthy(is_primitive(name)):
return get_primitive(name)
elif sx_truthy((name == 'true')):
return True
elif sx_truthy((name == 'false')):
return False
elif sx_truthy((name == 'nil')):
return NIL
else:
return error(sx_str('Undefined symbol: ', name))
elif _match == 'keyword':
return keyword_name(expr)
elif _match == 'list':
if sx_truthy(empty_p(expr)):
return []
else:
return aser_list(expr, env)
elif _match == 'spread':
return expr
else: else:
return expr return result
# aser-list # aser-list
def aser_list(expr, env): def aser_list(expr, env):
@@ -2561,10 +2512,10 @@ def aser_fragment(children, env):
result = aser(c, env) result = aser(c, env)
if sx_truthy((type_of(result) == 'list')): if sx_truthy((type_of(result) == 'list')):
for item in result: for item in result:
if sx_truthy(((not sx_truthy(is_nil(item))) if not sx_truthy((not sx_truthy(is_nil(item)))) else (not sx_truthy(is_spread(item))))): if sx_truthy((not sx_truthy(is_nil(item)))):
parts.append(serialize(item)) parts.append(serialize(item))
else: else:
if sx_truthy(((not sx_truthy(is_nil(result))) if not sx_truthy((not sx_truthy(is_nil(result)))) else (not sx_truthy(is_spread(result))))): if sx_truthy((not sx_truthy(is_nil(result)))):
parts.append(serialize(result)) parts.append(serialize(result))
if sx_truthy(empty_p(parts)): if sx_truthy(empty_p(parts)):
return '' return ''
@@ -2574,9 +2525,11 @@ def aser_fragment(children, env):
# aser-call # aser-call
def aser_call(name, args, env): def aser_call(name, args, env):
_cells = {} _cells = {}
parts = [name] attr_parts = []
child_parts = []
_cells['skip'] = False _cells['skip'] = False
_cells['i'] = 0 _cells['i'] = 0
provide_push('element-attrs', NIL)
for arg in args: for arg in args:
if sx_truthy(_cells['skip']): if sx_truthy(_cells['skip']):
_cells['skip'] = False _cells['skip'] = False
@@ -2585,26 +2538,27 @@ def aser_call(name, args, env):
if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((_cells['i'] + 1) < len(args)))): if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((_cells['i'] + 1) < len(args)))):
val = aser(nth(args, (_cells['i'] + 1)), env) val = aser(nth(args, (_cells['i'] + 1)), env)
if sx_truthy((not sx_truthy(is_nil(val)))): if sx_truthy((not sx_truthy(is_nil(val)))):
parts.append(sx_str(':', keyword_name(arg))) attr_parts.append(sx_str(':', keyword_name(arg)))
parts.append(serialize(val)) attr_parts.append(serialize(val))
_cells['skip'] = True _cells['skip'] = True
_cells['i'] = (_cells['i'] + 1) _cells['i'] = (_cells['i'] + 1)
else: else:
val = aser(arg, env) val = aser(arg, env)
if sx_truthy((not sx_truthy(is_nil(val)))): if sx_truthy((not sx_truthy(is_nil(val)))):
if sx_truthy(is_spread(val)): if sx_truthy((type_of(val) == 'list')):
for k in keys(spread_attrs(val)): for item in val:
v = dict_get(spread_attrs(val), k) if sx_truthy((not sx_truthy(is_nil(item)))):
parts.append(sx_str(':', k)) child_parts.append(serialize(item))
parts.append(serialize(v))
else: else:
if sx_truthy((type_of(val) == 'list')): child_parts.append(serialize(val))
for item in val:
if sx_truthy((not sx_truthy(is_nil(item)))):
parts.append(serialize(item))
else:
parts.append(serialize(val))
_cells['i'] = (_cells['i'] + 1) _cells['i'] = (_cells['i'] + 1)
for spread_dict in sx_emitted('element-attrs'):
for k in keys(spread_dict):
v = dict_get(spread_dict, k)
attr_parts.append(sx_str(':', k))
attr_parts.append(serialize(v))
provide_pop('element-attrs')
parts = concat([name], attr_parts, child_parts)
return sx_str('(', join(' ', parts), ')') return sx_str('(', join(' ', parts), ')')
# SPECIAL_FORM_NAMES # SPECIAL_FORM_NAMES
@@ -3659,7 +3613,8 @@ async def async_render(expr, env, ctx):
elif _match == 'raw-html': elif _match == 'raw-html':
return raw_html_content(expr) return raw_html_content(expr)
elif _match == 'spread': elif _match == 'spread':
return expr sx_emit('element-attrs', spread_attrs(expr))
return ''
elif _match == 'symbol': elif _match == 'symbol':
val = (await async_eval(expr, env, ctx)) val = (await async_eval(expr, env, ctx))
return (await async_render(val, env, ctx)) return (await async_render(val, env, ctx))
@@ -3691,7 +3646,7 @@ async def async_render_list(expr, env, ctx):
elif sx_truthy((name == 'raw!')): elif sx_truthy((name == 'raw!')):
return (await async_render_raw(args, env, ctx)) return (await async_render_raw(args, env, ctx))
elif sx_truthy((name == '<>')): elif sx_truthy((name == '<>')):
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), (await async_map_render(args, env, ctx)))) return join('', (await async_map_render(args, env, ctx)))
elif sx_truthy(starts_with_p(name, 'html:')): elif sx_truthy(starts_with_p(name, 'html:')):
return (await async_render_element(slice(name, 5), args, env, ctx)) return (await async_render_element(slice(name, 5), args, env, ctx))
elif sx_truthy(async_render_form_p(name)): elif sx_truthy(async_render_form_p(name)):
@@ -3746,12 +3701,12 @@ async def async_render_element(tag, args, env, ctx):
else: else:
token = (svg_context_set(True) if sx_truthy(((tag == 'svg') if sx_truthy((tag == 'svg')) else (tag == 'math'))) else NIL) token = (svg_context_set(True) if sx_truthy(((tag == 'svg') if sx_truthy((tag == 'svg')) else (tag == 'math'))) else NIL)
content_parts = [] content_parts = []
provide_push('element-attrs', NIL)
for c in children: for c in children:
result = (await async_render(c, env, ctx)) content_parts.append((await async_render(c, env, ctx)))
if sx_truthy(is_spread(result)): for spread_dict in sx_emitted('element-attrs'):
merge_spread_attrs(attrs, spread_attrs(result)) merge_spread_attrs(attrs, spread_dict)
else: provide_pop('element-attrs')
content_parts.append(result)
if sx_truthy(token): if sx_truthy(token):
svg_context_reset(token) svg_context_reset(token)
return sx_str('<', tag, render_attrs(attrs), '>', join('', content_parts), '</', tag, '>') return sx_str('<', tag, render_attrs(attrs), '>', join('', content_parts), '</', tag, '>')
@@ -3787,9 +3742,7 @@ async def async_render_component(comp, args, env, ctx):
if sx_truthy(component_has_children(comp)): if sx_truthy(component_has_children(comp)):
parts = [] parts = []
for c in children: for c in children:
r = (await async_render(c, env, ctx)) parts.append((await async_render(c, env, ctx)))
if sx_truthy((not sx_truthy(is_spread(r)))):
parts.append(r)
local['children'] = make_raw_html(join('', parts)) local['children'] = make_raw_html(join('', parts))
return (await async_render(component_body(comp), local, ctx)) return (await async_render(component_body(comp), local, ctx))
@@ -3805,9 +3758,7 @@ async def async_render_island(island, args, env, ctx):
if sx_truthy(component_has_children(island)): if sx_truthy(component_has_children(island)):
parts = [] parts = []
for c in children: for c in children:
r = (await async_render(c, env, ctx)) parts.append((await async_render(c, env, ctx)))
if sx_truthy((not sx_truthy(is_spread(r)))):
parts.append(r)
local['children'] = make_raw_html(join('', parts)) local['children'] = make_raw_html(join('', parts))
body_html = (await async_render(component_body(island), local, ctx)) body_html = (await async_render(component_body(island), local, ctx))
state_json = serialize_island_state(kwargs) state_json = serialize_island_state(kwargs)
@@ -3871,8 +3822,7 @@ async def dispatch_async_render_form(name, expr, env, ctx):
if sx_truthy((len(expr) == 3)): if sx_truthy((len(expr) == 3)):
return (await async_render(nth(expr, 2), env, ctx)) return (await async_render(nth(expr, 2), env, ctx))
else: else:
results = (await async_map_render(slice(expr, 2), env, ctx)) return join('', (await async_map_render(slice(expr, 2), env, ctx)))
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), results))
elif sx_truthy((name == 'cond')): elif sx_truthy((name == 'cond')):
clauses = rest(expr) clauses = rest(expr)
if sx_truthy(cond_scheme_p(clauses)): if sx_truthy(cond_scheme_p(clauses)):
@@ -3886,38 +3836,36 @@ async def dispatch_async_render_form(name, expr, env, ctx):
if sx_truthy((len(expr) == 3)): if sx_truthy((len(expr) == 3)):
return (await async_render(nth(expr, 2), local, ctx)) return (await async_render(nth(expr, 2), local, ctx))
else: else:
results = (await async_map_render(slice(expr, 2), local, ctx)) return join('', (await async_map_render(slice(expr, 2), local, ctx)))
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), results))
elif sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))): elif sx_truthy(((name == 'begin') if sx_truthy((name == 'begin')) else (name == 'do'))):
if sx_truthy((len(expr) == 2)): if sx_truthy((len(expr) == 2)):
return (await async_render(nth(expr, 1), env, ctx)) return (await async_render(nth(expr, 1), env, ctx))
else: else:
results = (await async_map_render(rest(expr), env, ctx)) return join('', (await async_map_render(rest(expr), env, ctx)))
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), results))
elif sx_truthy(is_definition_form(name)): elif sx_truthy(is_definition_form(name)):
(await async_eval(expr, env, ctx)) (await async_eval(expr, env, ctx))
return '' return ''
elif sx_truthy((name == 'map')): elif sx_truthy((name == 'map')):
f = (await async_eval(nth(expr, 1), env, ctx)) f = (await async_eval(nth(expr, 1), env, ctx))
coll = (await async_eval(nth(expr, 2), env, ctx)) coll = (await async_eval(nth(expr, 2), env, ctx))
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), (await async_map_fn_render(f, coll, env, ctx)))) return join('', (await async_map_fn_render(f, coll, env, ctx)))
elif sx_truthy((name == 'map-indexed')): elif sx_truthy((name == 'map-indexed')):
f = (await async_eval(nth(expr, 1), env, ctx)) f = (await async_eval(nth(expr, 1), env, ctx))
coll = (await async_eval(nth(expr, 2), env, ctx)) coll = (await async_eval(nth(expr, 2), env, ctx))
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), (await async_map_indexed_fn_render(f, coll, env, ctx)))) return join('', (await async_map_indexed_fn_render(f, coll, env, ctx)))
elif sx_truthy((name == 'filter')): elif sx_truthy((name == 'filter')):
return (await async_render((await async_eval(expr, env, ctx)), env, ctx)) return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
elif sx_truthy((name == 'for-each')): elif sx_truthy((name == 'for-each')):
f = (await async_eval(nth(expr, 1), env, ctx)) f = (await async_eval(nth(expr, 1), env, ctx))
coll = (await async_eval(nth(expr, 2), env, ctx)) coll = (await async_eval(nth(expr, 2), env, ctx))
return join('', filter(lambda r: (not sx_truthy(is_spread(r))), (await async_map_fn_render(f, coll, env, ctx)))) return join('', (await async_map_fn_render(f, coll, env, ctx)))
elif sx_truthy((name == 'provide')): elif sx_truthy((name == 'provide')):
prov_name = (await async_eval(nth(expr, 1), env, ctx)) prov_name = (await async_eval(nth(expr, 1), env, ctx))
prov_val = (await async_eval(nth(expr, 2), env, ctx)) prov_val = (await async_eval(nth(expr, 2), env, ctx))
body_start = 3 body_start = 3
body_count = (len(expr) - 3) body_count = (len(expr) - 3)
provide_push(prov_name, prov_val) provide_push(prov_name, prov_val)
result = ((await async_render(nth(expr, body_start), env, ctx)) if sx_truthy((body_count == 1)) else (lambda results: join('', filter(lambda r: (not sx_truthy(is_spread(r))), results)))((await async_map_render(slice(expr, body_start), env, ctx)))) result = ((await async_render(nth(expr, body_start), env, ctx)) if sx_truthy((body_count == 1)) else join('', (await async_map_render(slice(expr, body_start), env, ctx))))
provide_pop(prov_name) provide_pop(prov_name)
return result return result
else: else:
@@ -4019,42 +3967,35 @@ async def async_invoke(f, *args):
# async-aser # async-aser
async def async_aser(expr, env, ctx): async def async_aser(expr, env, ctx):
_match = type_of(expr) t = type_of(expr)
if _match == 'number': result = NIL
return expr if sx_truthy((t == 'number')):
elif _match == 'string': result = expr
return expr elif sx_truthy((t == 'string')):
elif _match == 'boolean': result = expr
return expr elif sx_truthy((t == 'boolean')):
elif _match == 'nil': result = expr
return NIL elif sx_truthy((t == 'nil')):
elif _match == 'symbol': result = NIL
elif sx_truthy((t == 'symbol')):
name = symbol_name(expr) name = symbol_name(expr)
if sx_truthy(env_has(env, name)): result = (env_get(env, name) if sx_truthy(env_has(env, name)) else (get_primitive(name) if sx_truthy(is_primitive(name)) else (True if sx_truthy((name == 'true')) else (False if sx_truthy((name == 'false')) else (NIL if sx_truthy((name == 'nil')) else error(sx_str('Undefined symbol: ', name)))))))
return env_get(env, name) elif sx_truthy((t == 'keyword')):
elif sx_truthy(is_primitive(name)): result = keyword_name(expr)
return get_primitive(name) elif sx_truthy((t == 'dict')):
elif sx_truthy((name == 'true')): result = (await async_aser_dict(expr, env, ctx))
return True elif sx_truthy((t == 'spread')):
elif sx_truthy((name == 'false')): sx_emit('element-attrs', spread_attrs(expr))
return False result = NIL
elif sx_truthy((name == 'nil')): elif sx_truthy((t == 'list')):
return NIL result = ([] if sx_truthy(empty_p(expr)) else (await async_aser_list(expr, env, ctx)))
else:
return error(sx_str('Undefined symbol: ', name))
elif _match == 'keyword':
return keyword_name(expr)
elif _match == 'dict':
return (await async_aser_dict(expr, env, ctx))
elif _match == 'spread':
return expr
elif _match == 'list':
if sx_truthy(empty_p(expr)):
return []
else:
return (await async_aser_list(expr, env, ctx))
else: else:
return expr result = expr
if sx_truthy(is_spread(result)):
sx_emit('element-attrs', spread_attrs(result))
return NIL
else:
return result
# async-aser-dict # async-aser-dict
async def async_aser_dict(expr, env, ctx): async def async_aser_dict(expr, env, ctx):
@@ -4148,10 +4089,10 @@ async def async_aser_fragment(children, env, ctx):
result = (await async_aser(c, env, ctx)) result = (await async_aser(c, env, ctx))
if sx_truthy((type_of(result) == 'list')): if sx_truthy((type_of(result) == 'list')):
for item in result: for item in result:
if sx_truthy(((not sx_truthy(is_nil(item))) if not sx_truthy((not sx_truthy(is_nil(item)))) else (not sx_truthy(is_spread(item))))): if sx_truthy((not sx_truthy(is_nil(item)))):
parts.append(serialize(item)) parts.append(serialize(item))
else: else:
if sx_truthy(((not sx_truthy(is_nil(result))) if not sx_truthy((not sx_truthy(is_nil(result)))) else (not sx_truthy(is_spread(result))))): if sx_truthy((not sx_truthy(is_nil(result)))):
parts.append(serialize(result)) parts.append(serialize(result))
if sx_truthy(empty_p(parts)): if sx_truthy(empty_p(parts)):
return make_sx_expr('') return make_sx_expr('')
@@ -4204,9 +4145,11 @@ async def async_parse_aser_kw_args(args, kwargs, children, env, ctx):
async def async_aser_call(name, args, env, ctx): async def async_aser_call(name, args, env, ctx):
_cells = {} _cells = {}
token = (svg_context_set(True) if sx_truthy(((name == 'svg') if sx_truthy((name == 'svg')) else (name == 'math'))) else NIL) token = (svg_context_set(True) if sx_truthy(((name == 'svg') if sx_truthy((name == 'svg')) else (name == 'math'))) else NIL)
parts = [name] attr_parts = []
child_parts = []
_cells['skip'] = False _cells['skip'] = False
_cells['i'] = 0 _cells['i'] = 0
provide_push('element-attrs', NIL)
for arg in args: for arg in args:
if sx_truthy(_cells['skip']): if sx_truthy(_cells['skip']):
_cells['skip'] = False _cells['skip'] = False
@@ -4215,39 +4158,40 @@ async def async_aser_call(name, args, env, ctx):
if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((_cells['i'] + 1) < len(args)))): if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((_cells['i'] + 1) < len(args)))):
val = (await async_aser(nth(args, (_cells['i'] + 1)), env, ctx)) val = (await async_aser(nth(args, (_cells['i'] + 1)), env, ctx))
if sx_truthy((not sx_truthy(is_nil(val)))): if sx_truthy((not sx_truthy(is_nil(val)))):
parts.append(sx_str(':', keyword_name(arg))) attr_parts.append(sx_str(':', keyword_name(arg)))
if sx_truthy((type_of(val) == 'list')): if sx_truthy((type_of(val) == 'list')):
live = filter(lambda v: (not sx_truthy(is_nil(v))), val) live = filter(lambda v: (not sx_truthy(is_nil(v))), val)
if sx_truthy(empty_p(live)): if sx_truthy(empty_p(live)):
parts.append('nil') attr_parts.append('nil')
else: else:
items = map(serialize, live) items = map(serialize, live)
if sx_truthy(some(lambda v: is_sx_expr(v), live)): if sx_truthy(some(lambda v: is_sx_expr(v), live)):
parts.append(sx_str('(<> ', join(' ', items), ')')) attr_parts.append(sx_str('(<> ', join(' ', items), ')'))
else: else:
parts.append(sx_str('(list ', join(' ', items), ')')) attr_parts.append(sx_str('(list ', join(' ', items), ')'))
else: else:
parts.append(serialize(val)) attr_parts.append(serialize(val))
_cells['skip'] = True _cells['skip'] = True
_cells['i'] = (_cells['i'] + 1) _cells['i'] = (_cells['i'] + 1)
else: else:
result = (await async_aser(arg, env, ctx)) result = (await async_aser(arg, env, ctx))
if sx_truthy((not sx_truthy(is_nil(result)))): if sx_truthy((not sx_truthy(is_nil(result)))):
if sx_truthy(is_spread(result)): if sx_truthy((type_of(result) == 'list')):
for k in keys(spread_attrs(result)): for item in result:
v = dict_get(spread_attrs(result), k) if sx_truthy((not sx_truthy(is_nil(item)))):
parts.append(sx_str(':', k)) child_parts.append(serialize(item))
parts.append(serialize(v))
else: else:
if sx_truthy((type_of(result) == 'list')): child_parts.append(serialize(result))
for item in result:
if sx_truthy((not sx_truthy(is_nil(item)))):
parts.append(serialize(item))
else:
parts.append(serialize(result))
_cells['i'] = (_cells['i'] + 1) _cells['i'] = (_cells['i'] + 1)
for spread_dict in sx_emitted('element-attrs'):
for k in keys(spread_dict):
v = dict_get(spread_dict, k)
attr_parts.append(sx_str(':', k))
attr_parts.append(serialize(v))
provide_pop('element-attrs')
if sx_truthy(token): if sx_truthy(token):
svg_context_reset(token) svg_context_reset(token)
parts = concat([name], attr_parts, child_parts)
return make_sx_expr(sx_str('(', join(' ', parts), ')')) return make_sx_expr(sx_str('(', join(' ', parts), ')'))
# ASYNC_ASER_FORM_NAMES # ASYNC_ASER_FORM_NAMES

View File

@@ -285,6 +285,19 @@
(assert-equal "(div :class \"card\" :style \"color:red\" \"hello\")" (assert-equal "(div :class \"card\" :style \"color:red\" \"hello\")"
(render-sx "(div (make-spread {:class \"card\"}) (make-spread {:style \"color:red\"}) \"hello\")"))) (render-sx "(div (make-spread {:class \"card\"}) (make-spread {:style \"color:red\"}) \"hello\")")))
(deftest "spread in fragment is filtered" (deftest "spread in fragment is silently dropped"
(assert-equal "(<> \"hello\")" (assert-equal "(<> \"hello\")"
(render-sx "(<> (make-spread {:class \"card\"}) \"hello\")")))) (render-sx "(<> (make-spread {:class \"card\"}) \"hello\")")))
(deftest "stored spread in let binding"
(assert-equal "(div :class \"card\" \"hello\")"
(render-sx "(let ((card (make-spread {:class \"card\"})))
(div card \"hello\"))")))
(deftest "spread in nested element"
(assert-equal "(div (span :class \"inner\" \"hi\"))"
(render-sx "(div (span (make-spread {:class \"inner\"}) \"hi\"))")))
(deftest "spread in non-element context silently drops"
(assert-equal "hello"
(render-sx "(do (make-spread {:class \"card\"}) \"hello\")"))))