Add provide/context/emit!/emitted — render-time dynamic scope
Four new primitives for scoped downward value passing and upward accumulation through the render tree. Specced in .sx, bootstrapped to Python and JS across all adapters (eval, html, sx, dom, async). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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-13T02:18:19Z";
|
var SX_VERSION = "2026-03-13T02:54:01Z";
|
||||||
|
|
||||||
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); }
|
||||||
@@ -87,6 +87,7 @@
|
|||||||
SxSpread.prototype._spread = true;
|
SxSpread.prototype._spread = true;
|
||||||
|
|
||||||
var _collectBuckets = {};
|
var _collectBuckets = {};
|
||||||
|
var _provideStacks = {};
|
||||||
|
|
||||||
function isSym(x) { return x != null && x._sym === true; }
|
function isSym(x) { return x != null && x._sym === true; }
|
||||||
function isKw(x) { return x != null && x._kw === true; }
|
function isKw(x) { return x != null && x._kw === true; }
|
||||||
@@ -162,6 +163,35 @@
|
|||||||
if (_collectBuckets[bucket]) _collectBuckets[bucket] = [];
|
if (_collectBuckets[bucket]) _collectBuckets[bucket] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function providePush(name, value) {
|
||||||
|
if (!_provideStacks[name]) _provideStacks[name] = [];
|
||||||
|
_provideStacks[name].push({value: value !== undefined ? value : NIL, emitted: []});
|
||||||
|
}
|
||||||
|
function providePop(name) {
|
||||||
|
if (_provideStacks[name] && _provideStacks[name].length) _provideStacks[name].pop();
|
||||||
|
}
|
||||||
|
function sxContext(name) {
|
||||||
|
if (_provideStacks[name] && _provideStacks[name].length) {
|
||||||
|
return _provideStacks[name][_provideStacks[name].length - 1].value;
|
||||||
|
}
|
||||||
|
if (arguments.length > 1) return arguments[1];
|
||||||
|
throw new Error("No provider for: " + name);
|
||||||
|
}
|
||||||
|
function sxEmit(name, value) {
|
||||||
|
if (_provideStacks[name] && _provideStacks[name].length) {
|
||||||
|
_provideStacks[name][_provideStacks[name].length - 1].emitted.push(value);
|
||||||
|
} else {
|
||||||
|
throw new Error("No provider for emit!: " + name);
|
||||||
|
}
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
function sxEmitted(name) {
|
||||||
|
if (_provideStacks[name] && _provideStacks[name].length) {
|
||||||
|
return _provideStacks[name][_provideStacks[name].length - 1].emitted.slice();
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
function lambdaParams(f) { return f.params; }
|
function lambdaParams(f) { return f.params; }
|
||||||
function lambdaBody(f) { return f.body; }
|
function lambdaBody(f) { return f.body; }
|
||||||
function lambdaClosure(f) { return f.closure; }
|
function lambdaClosure(f) { return f.closure; }
|
||||||
@@ -495,6 +525,12 @@
|
|||||||
PRIMITIVES["collect!"] = sxCollect;
|
PRIMITIVES["collect!"] = sxCollect;
|
||||||
PRIMITIVES["collected"] = sxCollected;
|
PRIMITIVES["collected"] = sxCollected;
|
||||||
PRIMITIVES["clear-collected!"] = sxClearCollected;
|
PRIMITIVES["clear-collected!"] = sxClearCollected;
|
||||||
|
// provide/context/emit! — render-time dynamic scope
|
||||||
|
PRIMITIVES["provide-push!"] = providePush;
|
||||||
|
PRIMITIVES["provide-pop!"] = providePop;
|
||||||
|
PRIMITIVES["context"] = sxContext;
|
||||||
|
PRIMITIVES["emit!"] = sxEmit;
|
||||||
|
PRIMITIVES["emitted"] = sxEmitted;
|
||||||
|
|
||||||
|
|
||||||
function isPrimitive(name) { return name in PRIMITIVES; }
|
function isPrimitive(name) { return name in PRIMITIVES; }
|
||||||
@@ -760,10 +796,10 @@
|
|||||||
var args = rest(expr);
|
var args = rest(expr);
|
||||||
return (isSxTruthy(!isSxTruthy(sxOr((typeOf(head) == "symbol"), (typeOf(head) == "lambda"), (typeOf(head) == "list")))) ? map(function(x) { return trampoline(evalExpr(x, env)); }, expr) : (isSxTruthy((typeOf(head) == "symbol")) ? (function() {
|
return (isSxTruthy(!isSxTruthy(sxOr((typeOf(head) == "symbol"), (typeOf(head) == "lambda"), (typeOf(head) == "list")))) ? map(function(x) { return trampoline(evalExpr(x, env)); }, expr) : (isSxTruthy((typeOf(head) == "symbol")) ? (function() {
|
||||||
var name = symbolName(head);
|
var name = symbolName(head);
|
||||||
return (isSxTruthy((name == "if")) ? sfIf(args, env) : (isSxTruthy((name == "when")) ? sfWhen(args, env) : (isSxTruthy((name == "cond")) ? sfCond(args, env) : (isSxTruthy((name == "case")) ? sfCase(args, env) : (isSxTruthy((name == "and")) ? sfAnd(args, env) : (isSxTruthy((name == "or")) ? sfOr(args, env) : (isSxTruthy((name == "let")) ? sfLet(args, env) : (isSxTruthy((name == "let*")) ? sfLet(args, env) : (isSxTruthy((name == "letrec")) ? sfLetrec(args, env) : (isSxTruthy((name == "lambda")) ? sfLambda(args, env) : (isSxTruthy((name == "fn")) ? sfLambda(args, env) : (isSxTruthy((name == "define")) ? sfDefine(args, env) : (isSxTruthy((name == "defcomp")) ? sfDefcomp(args, env) : (isSxTruthy((name == "defisland")) ? sfDefisland(args, env) : (isSxTruthy((name == "defmacro")) ? sfDefmacro(args, env) : (isSxTruthy((name == "defstyle")) ? sfDefstyle(args, env) : (isSxTruthy((name == "defhandler")) ? sfDefhandler(args, env) : (isSxTruthy((name == "defpage")) ? sfDefpage(args, env) : (isSxTruthy((name == "defquery")) ? sfDefquery(args, env) : (isSxTruthy((name == "defaction")) ? sfDefaction(args, env) : (isSxTruthy((name == "deftype")) ? sfDeftype(args, env) : (isSxTruthy((name == "defeffect")) ? sfDefeffect(args, env) : (isSxTruthy((name == "begin")) ? sfBegin(args, env) : (isSxTruthy((name == "do")) ? sfBegin(args, env) : (isSxTruthy((name == "quote")) ? sfQuote(args, env) : (isSxTruthy((name == "quasiquote")) ? sfQuasiquote(args, env) : (isSxTruthy((name == "->")) ? sfThreadFirst(args, env) : (isSxTruthy((name == "set!")) ? sfSetBang(args, env) : (isSxTruthy((name == "reset")) ? sfReset(args, env) : (isSxTruthy((name == "shift")) ? sfShift(args, env) : (isSxTruthy((name == "dynamic-wind")) ? sfDynamicWind(args, env) : (isSxTruthy((name == "map")) ? hoMap(args, env) : (isSxTruthy((name == "map-indexed")) ? hoMapIndexed(args, env) : (isSxTruthy((name == "filter")) ? hoFilter(args, env) : (isSxTruthy((name == "reduce")) ? hoReduce(args, env) : (isSxTruthy((name == "some")) ? hoSome(args, env) : (isSxTruthy((name == "every?")) ? hoEvery(args, env) : (isSxTruthy((name == "for-each")) ? hoForEach(args, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? (function() {
|
return (isSxTruthy((name == "if")) ? sfIf(args, env) : (isSxTruthy((name == "when")) ? sfWhen(args, env) : (isSxTruthy((name == "cond")) ? sfCond(args, env) : (isSxTruthy((name == "case")) ? sfCase(args, env) : (isSxTruthy((name == "and")) ? sfAnd(args, env) : (isSxTruthy((name == "or")) ? sfOr(args, env) : (isSxTruthy((name == "let")) ? sfLet(args, env) : (isSxTruthy((name == "let*")) ? sfLet(args, env) : (isSxTruthy((name == "letrec")) ? sfLetrec(args, env) : (isSxTruthy((name == "lambda")) ? sfLambda(args, env) : (isSxTruthy((name == "fn")) ? sfLambda(args, env) : (isSxTruthy((name == "define")) ? sfDefine(args, env) : (isSxTruthy((name == "defcomp")) ? sfDefcomp(args, env) : (isSxTruthy((name == "defisland")) ? sfDefisland(args, env) : (isSxTruthy((name == "defmacro")) ? sfDefmacro(args, env) : (isSxTruthy((name == "defstyle")) ? sfDefstyle(args, env) : (isSxTruthy((name == "defhandler")) ? sfDefhandler(args, env) : (isSxTruthy((name == "defpage")) ? sfDefpage(args, env) : (isSxTruthy((name == "defquery")) ? sfDefquery(args, env) : (isSxTruthy((name == "defaction")) ? sfDefaction(args, env) : (isSxTruthy((name == "deftype")) ? sfDeftype(args, env) : (isSxTruthy((name == "defeffect")) ? sfDefeffect(args, env) : (isSxTruthy((name == "begin")) ? sfBegin(args, env) : (isSxTruthy((name == "do")) ? sfBegin(args, env) : (isSxTruthy((name == "quote")) ? sfQuote(args, env) : (isSxTruthy((name == "quasiquote")) ? sfQuasiquote(args, env) : (isSxTruthy((name == "->")) ? sfThreadFirst(args, env) : (isSxTruthy((name == "set!")) ? sfSetBang(args, env) : (isSxTruthy((name == "reset")) ? sfReset(args, env) : (isSxTruthy((name == "shift")) ? sfShift(args, env) : (isSxTruthy((name == "dynamic-wind")) ? sfDynamicWind(args, env) : (isSxTruthy((name == "provide")) ? sfProvide(args, env) : (isSxTruthy((name == "map")) ? hoMap(args, env) : (isSxTruthy((name == "map-indexed")) ? hoMapIndexed(args, env) : (isSxTruthy((name == "filter")) ? hoFilter(args, env) : (isSxTruthy((name == "reduce")) ? hoReduce(args, env) : (isSxTruthy((name == "some")) ? hoSome(args, env) : (isSxTruthy((name == "every?")) ? hoEvery(args, env) : (isSxTruthy((name == "for-each")) ? hoForEach(args, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? (function() {
|
||||||
var mac = envGet(env, name);
|
var mac = envGet(env, name);
|
||||||
return makeThunk(expandMacro(mac, args, env), env);
|
return makeThunk(expandMacro(mac, args, env), env);
|
||||||
})() : (isSxTruthy((isSxTruthy(renderActiveP()) && isRenderExpr(expr))) ? renderExpr(expr, env) : evalCall(head, args, env)))))))))))))))))))))))))))))))))))))))));
|
})() : (isSxTruthy((isSxTruthy(renderActiveP()) && isRenderExpr(expr))) ? renderExpr(expr, env) : evalCall(head, args, env))))))))))))))))))))))))))))))))))))))))));
|
||||||
})() : evalCall(head, args, env)));
|
})() : evalCall(head, args, env)));
|
||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
@@ -1170,6 +1206,18 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai
|
|||||||
})();
|
})();
|
||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
|
// sf-provide
|
||||||
|
var sfProvide = function(args, env) { return (function() {
|
||||||
|
var name = trampoline(evalExpr(first(args), env));
|
||||||
|
var val = trampoline(evalExpr(nth(args, 1), env));
|
||||||
|
var bodyExprs = slice(args, 2);
|
||||||
|
var result = NIL;
|
||||||
|
providePush(name, val);
|
||||||
|
{ var _c = bodyExprs; for (var _i = 0; _i < _c.length; _i++) { var e = _c[_i]; result = trampoline(evalExpr(e, env)); } }
|
||||||
|
providePop(name);
|
||||||
|
return result;
|
||||||
|
})(); };
|
||||||
|
|
||||||
// expand-macro
|
// expand-macro
|
||||||
var expandMacro = function(mac, rawArgs, env) { return (function() {
|
var expandMacro = function(mac, rawArgs, env) { return (function() {
|
||||||
var local = envMerge(macroClosure(mac), env);
|
var local = envMerge(macroClosure(mac), env);
|
||||||
@@ -1468,7 +1516,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m =
|
|||||||
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 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"];
|
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"];
|
||||||
|
|
||||||
// render-html-form?
|
// render-html-form?
|
||||||
var isRenderHtmlForm = function(name) { return contains(RENDER_HTML_FORMS, name); };
|
var isRenderHtmlForm = function(name) { return contains(RENDER_HTML_FORMS, name); };
|
||||||
@@ -1517,7 +1565,18 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m =
|
|||||||
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("", filter(function(r) { return !isSxTruthy(isSpread(r)); }, map(function(item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [item], env) : renderToHtml(apply(f, [item]), env)); }, coll)));
|
||||||
})() : renderValueToHtml(trampoline(evalExpr(expr, env)), env)))))))))))); };
|
})() : (isSxTruthy((name == "provide")) ? (function() {
|
||||||
|
var provName = trampoline(evalExpr(nth(expr, 1), env));
|
||||||
|
var provVal = trampoline(evalExpr(nth(expr, 2), env));
|
||||||
|
var bodyStart = 3;
|
||||||
|
var bodyCount = (len(expr) - 3);
|
||||||
|
providePush(provName, provVal);
|
||||||
|
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))))));
|
||||||
|
providePop(provName);
|
||||||
|
return result;
|
||||||
|
})();
|
||||||
|
})() : renderValueToHtml(trampoline(evalExpr(expr, env)), env))))))))))))); };
|
||||||
|
|
||||||
// render-lambda-html
|
// render-lambda-html
|
||||||
var renderLambdaHtml = function(f, args, env) { return (function() {
|
var renderLambdaHtml = function(f, args, env) { return (function() {
|
||||||
@@ -1722,7 +1781,7 @@ return (function() { var _m = typeOf(expr); if (_m == "number") return expr; if
|
|||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
// SPECIAL_FORM_NAMES
|
// SPECIAL_FORM_NAMES
|
||||||
var SPECIAL_FORM_NAMES = ["if", "when", "cond", "case", "and", "or", "let", "let*", "lambda", "fn", "define", "defcomp", "defmacro", "defstyle", "defhandler", "defpage", "defquery", "defaction", "defrelation", "begin", "do", "quote", "quasiquote", "->", "set!", "letrec", "dynamic-wind", "defisland", "deftype", "defeffect"];
|
var SPECIAL_FORM_NAMES = ["if", "when", "cond", "case", "and", "or", "let", "let*", "lambda", "fn", "define", "defcomp", "defmacro", "defstyle", "defhandler", "defpage", "defquery", "defaction", "defrelation", "begin", "do", "quote", "quasiquote", "->", "set!", "letrec", "dynamic-wind", "defisland", "deftype", "defeffect", "provide"];
|
||||||
|
|
||||||
// HO_FORM_NAMES
|
// HO_FORM_NAMES
|
||||||
var HO_FORM_NAMES = ["map", "map-indexed", "filter", "reduce", "some", "every?", "for-each"];
|
var HO_FORM_NAMES = ["map", "map-indexed", "filter", "reduce", "some", "every?", "for-each"];
|
||||||
@@ -1793,7 +1852,15 @@ return result; }, args);
|
|||||||
return append_b(results, aser(lambdaBody(f), local));
|
return append_b(results, aser(lambdaBody(f), local));
|
||||||
})() : invoke(f, item)); } }
|
})() : invoke(f, item)); } }
|
||||||
return (isSxTruthy(isEmpty(results)) ? NIL : results);
|
return (isSxTruthy(isEmpty(results)) ? NIL : results);
|
||||||
})() : (isSxTruthy((name == "defisland")) ? (trampoline(evalExpr(expr, env)), serialize(expr)) : (isSxTruthy(sxOr((name == "define"), (name == "defcomp"), (name == "defmacro"), (name == "defstyle"), (name == "defhandler"), (name == "defpage"), (name == "defquery"), (name == "defaction"), (name == "defrelation"), (name == "deftype"), (name == "defeffect"))) ? (trampoline(evalExpr(expr, env)), NIL) : trampoline(evalExpr(expr, env)))))))))))))));
|
})() : (isSxTruthy((name == "defisland")) ? (trampoline(evalExpr(expr, env)), serialize(expr)) : (isSxTruthy(sxOr((name == "define"), (name == "defcomp"), (name == "defmacro"), (name == "defstyle"), (name == "defhandler"), (name == "defpage"), (name == "defquery"), (name == "defaction"), (name == "defrelation"), (name == "deftype"), (name == "defeffect"))) ? (trampoline(evalExpr(expr, env)), NIL) : (isSxTruthy((name == "provide")) ? (function() {
|
||||||
|
var provName = trampoline(evalExpr(first(args), env));
|
||||||
|
var provVal = trampoline(evalExpr(nth(args, 1), env));
|
||||||
|
var result = NIL;
|
||||||
|
providePush(provName, provVal);
|
||||||
|
{ var _c = slice(args, 2); for (var _i = 0; _i < _c.length; _i++) { var body = _c[_i]; result = aser(body, env); } }
|
||||||
|
providePop(provName);
|
||||||
|
return result;
|
||||||
|
})() : trampoline(evalExpr(expr, env))))))))))))))));
|
||||||
})(); };
|
})(); };
|
||||||
|
|
||||||
// eval-case-aser
|
// eval-case-aser
|
||||||
@@ -1926,7 +1993,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
|
|||||||
var renderDomUnknownComponent = function(name) { return error((String("Unknown component: ") + String(name))); };
|
var renderDomUnknownComponent = function(name) { return error((String("Unknown component: ") + String(name))); };
|
||||||
|
|
||||||
// RENDER_DOM_FORMS
|
// RENDER_DOM_FORMS
|
||||||
var RENDER_DOM_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "defhandler", "map", "map-indexed", "filter", "for-each", "portal", "error-boundary"];
|
var RENDER_DOM_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "defhandler", "map", "map-indexed", "filter", "for-each", "portal", "error-boundary", "provide"];
|
||||||
|
|
||||||
// render-dom-form?
|
// render-dom-form?
|
||||||
var isRenderDomForm = function(name) { return contains(RENDER_DOM_FORMS, name); };
|
var isRenderDomForm = function(name) { return contains(RENDER_DOM_FORMS, name); };
|
||||||
@@ -2060,7 +2127,15 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
|
|||||||
return domAppend(frag, val);
|
return domAppend(frag, val);
|
||||||
})(); } }
|
})(); } }
|
||||||
return frag;
|
return frag;
|
||||||
})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns)))))))))))))); };
|
})() : (isSxTruthy((name == "provide")) ? (function() {
|
||||||
|
var provName = trampoline(evalExpr(nth(expr, 1), env));
|
||||||
|
var provVal = trampoline(evalExpr(nth(expr, 2), env));
|
||||||
|
var frag = createFragment();
|
||||||
|
providePush(provName, provVal);
|
||||||
|
{ var _c = range(3, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; domAppend(frag, renderToDom(nth(expr, i), env, ns)); } }
|
||||||
|
providePop(provName);
|
||||||
|
return frag;
|
||||||
|
})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns))))))))))))))); };
|
||||||
|
|
||||||
// render-lambda-dom
|
// render-lambda-dom
|
||||||
var renderLambdaDom = function(f, args, env, ns) { return (function() {
|
var renderLambdaDom = function(f, args, env, ns) { return (function() {
|
||||||
@@ -6453,6 +6528,11 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
|||||||
collect: sxCollect,
|
collect: sxCollect,
|
||||||
collected: sxCollected,
|
collected: sxCollected,
|
||||||
clearCollected: sxClearCollected,
|
clearCollected: sxClearCollected,
|
||||||
|
providePush: providePush,
|
||||||
|
providePop: providePop,
|
||||||
|
context: sxContext,
|
||||||
|
emit: sxEmit,
|
||||||
|
emitted: sxEmitted,
|
||||||
_version: "ref-2.0 (boot+dom+engine+html+orchestration+parser+sx, bootstrap-compiled)"
|
_version: "ref-2.0 (boot+dom+engine+html+orchestration+parser+sx, bootstrap-compiled)"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -341,7 +341,7 @@
|
|||||||
(list "if" "when" "cond" "case" "let" "let*" "begin" "do"
|
(list "if" "when" "cond" "case" "let" "let*" "begin" "do"
|
||||||
"define" "defcomp" "defisland" "defmacro" "defstyle" "defhandler"
|
"define" "defcomp" "defisland" "defmacro" "defstyle" "defhandler"
|
||||||
"deftype" "defeffect"
|
"deftype" "defeffect"
|
||||||
"map" "map-indexed" "filter" "for-each"))
|
"map" "map-indexed" "filter" "for-each" "provide"))
|
||||||
|
|
||||||
(define async-render-form? :effects []
|
(define async-render-form? :effects []
|
||||||
(fn ((name :as string))
|
(fn ((name :as string))
|
||||||
@@ -434,6 +434,20 @@
|
|||||||
(filter (fn (r) (not (spread? r)))
|
(filter (fn (r) (not (spread? r)))
|
||||||
(async-map-fn-render f coll env ctx))))
|
(async-map-fn-render f coll env ctx))))
|
||||||
|
|
||||||
|
;; provide — render-time dynamic scope
|
||||||
|
(= name "provide")
|
||||||
|
(let ((prov-name (async-eval (nth expr 1) env ctx))
|
||||||
|
(prov-val (async-eval (nth expr 2) env ctx))
|
||||||
|
(body-start 3)
|
||||||
|
(body-count (- (len expr) 3)))
|
||||||
|
(provide-push! prov-name prov-val)
|
||||||
|
(let ((result (if (= body-count 1)
|
||||||
|
(async-render (nth expr body-start) env ctx)
|
||||||
|
(let ((results (async-map-render (slice expr body-start) env ctx)))
|
||||||
|
(join "" (filter (fn (r) (not (spread? r))) results))))))
|
||||||
|
(provide-pop! prov-name)
|
||||||
|
result))
|
||||||
|
|
||||||
;; Fallback
|
;; Fallback
|
||||||
:else
|
:else
|
||||||
(async-render (async-eval expr env ctx) env ctx))))
|
(async-render (async-eval expr env ctx) env ctx))))
|
||||||
@@ -894,7 +908,7 @@
|
|||||||
"define" "defcomp" "defmacro" "defstyle"
|
"define" "defcomp" "defmacro" "defstyle"
|
||||||
"defhandler" "defpage" "defquery" "defaction"
|
"defhandler" "defpage" "defquery" "defaction"
|
||||||
"begin" "do" "quote" "->" "set!" "defisland"
|
"begin" "do" "quote" "->" "set!" "defisland"
|
||||||
"deftype" "defeffect"))
|
"deftype" "defeffect" "provide"))
|
||||||
|
|
||||||
(define ASYNC_ASER_HO_NAMES
|
(define ASYNC_ASER_HO_NAMES
|
||||||
(list "map" "map-indexed" "filter" "for-each"))
|
(list "map" "map-indexed" "filter" "for-each"))
|
||||||
@@ -1032,6 +1046,17 @@
|
|||||||
(= name "deftype") (= name "defeffect"))
|
(= name "deftype") (= name "defeffect"))
|
||||||
(do (async-eval expr env ctx) nil)
|
(do (async-eval expr env ctx) nil)
|
||||||
|
|
||||||
|
;; provide — render-time dynamic scope
|
||||||
|
(= name "provide")
|
||||||
|
(let ((prov-name (async-eval (first args) env ctx))
|
||||||
|
(prov-val (async-eval (nth args 1) env ctx))
|
||||||
|
(result nil))
|
||||||
|
(provide-push! prov-name prov-val)
|
||||||
|
(for-each (fn (body) (set! result (async-aser body env ctx)))
|
||||||
|
(slice args 2))
|
||||||
|
(provide-pop! prov-name)
|
||||||
|
result)
|
||||||
|
|
||||||
;; Fallback
|
;; Fallback
|
||||||
:else
|
:else
|
||||||
(async-eval expr env ctx)))))
|
(async-eval expr env ctx)))))
|
||||||
|
|||||||
@@ -359,7 +359,7 @@
|
|||||||
(list "if" "when" "cond" "case" "let" "let*" "begin" "do"
|
(list "if" "when" "cond" "case" "let" "let*" "begin" "do"
|
||||||
"define" "defcomp" "defisland" "defmacro" "defstyle" "defhandler"
|
"define" "defcomp" "defisland" "defmacro" "defstyle" "defhandler"
|
||||||
"map" "map-indexed" "filter" "for-each" "portal"
|
"map" "map-indexed" "filter" "for-each" "portal"
|
||||||
"error-boundary"))
|
"error-boundary" "provide"))
|
||||||
|
|
||||||
(define render-dom-form? :effects []
|
(define render-dom-form? :effects []
|
||||||
(fn ((name :as string))
|
(fn ((name :as string))
|
||||||
@@ -598,6 +598,19 @@
|
|||||||
coll)
|
coll)
|
||||||
frag)
|
frag)
|
||||||
|
|
||||||
|
;; provide — render-time dynamic scope
|
||||||
|
(= name "provide")
|
||||||
|
(let ((prov-name (trampoline (eval-expr (nth expr 1) env)))
|
||||||
|
(prov-val (trampoline (eval-expr (nth expr 2) env)))
|
||||||
|
(frag (create-fragment)))
|
||||||
|
(provide-push! prov-name prov-val)
|
||||||
|
(for-each
|
||||||
|
(fn (i)
|
||||||
|
(dom-append frag (render-to-dom (nth expr i) env ns)))
|
||||||
|
(range 3 (len expr)))
|
||||||
|
(provide-pop! prov-name)
|
||||||
|
frag)
|
||||||
|
|
||||||
;; Fallback
|
;; Fallback
|
||||||
:else
|
:else
|
||||||
(render-to-dom (trampoline (eval-expr expr env)) env ns))))
|
(render-to-dom (trampoline (eval-expr expr env)) env ns))))
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
(list "if" "when" "cond" "case" "let" "let*" "begin" "do"
|
(list "if" "when" "cond" "case" "let" "let*" "begin" "do"
|
||||||
"define" "defcomp" "defisland" "defmacro" "defstyle" "defhandler"
|
"define" "defcomp" "defisland" "defmacro" "defstyle" "defhandler"
|
||||||
"deftype" "defeffect"
|
"deftype" "defeffect"
|
||||||
"map" "map-indexed" "filter" "for-each"))
|
"map" "map-indexed" "filter" "for-each" "provide"))
|
||||||
|
|
||||||
(define render-html-form? :effects []
|
(define render-html-form? :effects []
|
||||||
(fn ((name :as string))
|
(fn ((name :as string))
|
||||||
@@ -237,6 +237,21 @@
|
|||||||
(render-to-html (apply f (list item)) env)))
|
(render-to-html (apply f (list item)) env)))
|
||||||
coll))))
|
coll))))
|
||||||
|
|
||||||
|
;; provide — render-time dynamic scope
|
||||||
|
(= name "provide")
|
||||||
|
(let ((prov-name (trampoline (eval-expr (nth expr 1) env)))
|
||||||
|
(prov-val (trampoline (eval-expr (nth expr 2) env)))
|
||||||
|
(body-start 3)
|
||||||
|
(body-count (- (len expr) 3)))
|
||||||
|
(provide-push! prov-name prov-val)
|
||||||
|
(let ((result (if (= body-count 1)
|
||||||
|
(render-to-html (nth expr body-start) env)
|
||||||
|
(join "" (filter (fn (r) (not (spread? r)))
|
||||||
|
(map (fn (i) (render-to-html (nth expr i) env))
|
||||||
|
(range body-start (+ body-start body-count))))))))
|
||||||
|
(provide-pop! prov-name)
|
||||||
|
result))
|
||||||
|
|
||||||
;; Fallback
|
;; Fallback
|
||||||
:else
|
:else
|
||||||
(render-value-to-html (trampoline (eval-expr expr env)) env))))
|
(render-value-to-html (trampoline (eval-expr expr env)) env))))
|
||||||
|
|||||||
@@ -174,7 +174,7 @@
|
|||||||
"defhandler" "defpage" "defquery" "defaction" "defrelation"
|
"defhandler" "defpage" "defquery" "defaction" "defrelation"
|
||||||
"begin" "do" "quote" "quasiquote"
|
"begin" "do" "quote" "quasiquote"
|
||||||
"->" "set!" "letrec" "dynamic-wind" "defisland"
|
"->" "set!" "letrec" "dynamic-wind" "defisland"
|
||||||
"deftype" "defeffect"))
|
"deftype" "defeffect" "provide"))
|
||||||
|
|
||||||
(define HO_FORM_NAMES
|
(define HO_FORM_NAMES
|
||||||
(list "map" "map-indexed" "filter" "reduce"
|
(list "map" "map-indexed" "filter" "reduce"
|
||||||
@@ -312,6 +312,17 @@
|
|||||||
(= name "deftype") (= name "defeffect"))
|
(= name "deftype") (= name "defeffect"))
|
||||||
(do (trampoline (eval-expr expr env)) nil)
|
(do (trampoline (eval-expr expr env)) nil)
|
||||||
|
|
||||||
|
;; provide — render-time dynamic scope
|
||||||
|
(= name "provide")
|
||||||
|
(let ((prov-name (trampoline (eval-expr (first args) env)))
|
||||||
|
(prov-val (trampoline (eval-expr (nth args 1) env)))
|
||||||
|
(result nil))
|
||||||
|
(provide-push! prov-name prov-val)
|
||||||
|
(for-each (fn (body) (set! result (aser body env)))
|
||||||
|
(slice args 2))
|
||||||
|
(provide-pop! prov-name)
|
||||||
|
result)
|
||||||
|
|
||||||
;; Everything else — evaluate normally
|
;; Everything else — evaluate normally
|
||||||
:else
|
:else
|
||||||
(trampoline (eval-expr expr env))))))
|
(trampoline (eval-expr expr env))))))
|
||||||
|
|||||||
@@ -293,6 +293,11 @@ class PyEmitter:
|
|||||||
"collect!": "sx_collect",
|
"collect!": "sx_collect",
|
||||||
"collected": "sx_collected",
|
"collected": "sx_collected",
|
||||||
"clear-collected!": "sx_clear_collected",
|
"clear-collected!": "sx_clear_collected",
|
||||||
|
"provide-push!": "provide_push",
|
||||||
|
"provide-pop!": "provide_pop",
|
||||||
|
"context": "sx_context",
|
||||||
|
"emit!": "sx_emit",
|
||||||
|
"emitted": "sx_emitted",
|
||||||
"is-raw-html?": "is_raw_html",
|
"is-raw-html?": "is_raw_html",
|
||||||
"async-coroutine?": "is_async_coroutine",
|
"async-coroutine?": "is_async_coroutine",
|
||||||
"async-await!": "async_await",
|
"async-await!": "async_await",
|
||||||
|
|||||||
@@ -371,3 +371,51 @@
|
|||||||
:effects [mutation]
|
:effects [mutation]
|
||||||
:doc "Clear a named render-time accumulator bucket. Used at flush points
|
:doc "Clear a named render-time accumulator bucket. Used at flush points
|
||||||
after emitting collected values (e.g. after writing a <style> tag).")
|
after emitting collected values (e.g. after writing a <style> tag).")
|
||||||
|
|
||||||
|
|
||||||
|
;; --------------------------------------------------------------------------
|
||||||
|
;; Tier 5: Dynamic scope — render-time provide/context/emit!
|
||||||
|
;;
|
||||||
|
;; `provide` is a special form (not a primitive) that creates a named scope
|
||||||
|
;; with a value and an empty accumulator. `context` reads the value from the
|
||||||
|
;; nearest enclosing provider. `emit!` appends to the accumulator, `emitted`
|
||||||
|
;; reads the accumulated values.
|
||||||
|
;;
|
||||||
|
;; The platform must implement per-name stacks. Each entry has a value and
|
||||||
|
;; an emitted list. `provide-push!`/`provide-pop!` manage the stack.
|
||||||
|
;; --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
(declare-tier :dynamic-scope :source "eval.sx")
|
||||||
|
|
||||||
|
(declare-spread-primitive "provide-push!"
|
||||||
|
:params (name value)
|
||||||
|
:returns "nil"
|
||||||
|
:effects [mutation]
|
||||||
|
:doc "Push a provider scope with name and value (platform internal).")
|
||||||
|
|
||||||
|
(declare-spread-primitive "provide-pop!"
|
||||||
|
:params (name)
|
||||||
|
:returns "nil"
|
||||||
|
:effects [mutation]
|
||||||
|
:doc "Pop the most recent provider scope for name (platform internal).")
|
||||||
|
|
||||||
|
(declare-spread-primitive "context"
|
||||||
|
:params (name &rest default)
|
||||||
|
:returns "any"
|
||||||
|
:effects []
|
||||||
|
:doc "Read value from nearest enclosing provide with matching name.
|
||||||
|
Errors if no provider and no default given.")
|
||||||
|
|
||||||
|
(declare-spread-primitive "emit!"
|
||||||
|
:params (name value)
|
||||||
|
:returns "nil"
|
||||||
|
:effects [mutation]
|
||||||
|
:doc "Append value to nearest enclosing provide's accumulator.
|
||||||
|
Errors if no matching provider. No deduplication.")
|
||||||
|
|
||||||
|
(declare-spread-primitive "emitted"
|
||||||
|
:params (name)
|
||||||
|
:returns "list"
|
||||||
|
:effects []
|
||||||
|
:doc "Return list of values emitted into nearest matching provider.
|
||||||
|
Empty list if no provider.")
|
||||||
|
|||||||
@@ -162,6 +162,7 @@
|
|||||||
(= name "reset") (sf-reset args env)
|
(= name "reset") (sf-reset args env)
|
||||||
(= name "shift") (sf-shift args env)
|
(= name "shift") (sf-shift args env)
|
||||||
(= name "dynamic-wind") (sf-dynamic-wind args env)
|
(= name "dynamic-wind") (sf-dynamic-wind args env)
|
||||||
|
(= name "provide") (sf-provide args env)
|
||||||
|
|
||||||
;; Higher-order forms
|
;; Higher-order forms
|
||||||
(= name "map") (ho-map args env)
|
(= name "map") (ho-map args env)
|
||||||
@@ -949,6 +950,25 @@
|
|||||||
result))))
|
result))))
|
||||||
|
|
||||||
|
|
||||||
|
;; --------------------------------------------------------------------------
|
||||||
|
;; 6a2. provide — render-time dynamic scope
|
||||||
|
;; --------------------------------------------------------------------------
|
||||||
|
;;
|
||||||
|
;; (provide name value body...) — push a named scope with value and empty
|
||||||
|
;; accumulator, evaluate body, pop scope. Returns last body result.
|
||||||
|
|
||||||
|
(define sf-provide
|
||||||
|
(fn ((args :as list) (env :as dict))
|
||||||
|
(let ((name (trampoline (eval-expr (first args) env)))
|
||||||
|
(val (trampoline (eval-expr (nth args 1) env)))
|
||||||
|
(body-exprs (slice args 2))
|
||||||
|
(result nil))
|
||||||
|
(provide-push! name val)
|
||||||
|
(for-each (fn (e) (set! result (trampoline (eval-expr e env)))) body-exprs)
|
||||||
|
(provide-pop! name)
|
||||||
|
result)))
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
;; 6b. Macro expansion
|
;; 6b. Macro expansion
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -527,6 +527,11 @@
|
|||||||
"collect!" "sxCollect"
|
"collect!" "sxCollect"
|
||||||
"collected" "sxCollected"
|
"collected" "sxCollected"
|
||||||
"clear-collected!" "sxClearCollected"
|
"clear-collected!" "sxClearCollected"
|
||||||
|
"provide-push!" "providePush"
|
||||||
|
"provide-pop!" "providePop"
|
||||||
|
"context" "sxContext"
|
||||||
|
"emit!" "sxEmit"
|
||||||
|
"emitted" "sxEmitted"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -868,6 +868,7 @@ PREAMBLE = '''\
|
|||||||
SxSpread.prototype._spread = true;
|
SxSpread.prototype._spread = true;
|
||||||
|
|
||||||
var _collectBuckets = {};
|
var _collectBuckets = {};
|
||||||
|
var _provideStacks = {};
|
||||||
|
|
||||||
function isSym(x) { return x != null && x._sym === true; }
|
function isSym(x) { return x != null && x._sym === true; }
|
||||||
function isKw(x) { return x != null && x._kw === true; }
|
function isKw(x) { return x != null && x._kw === true; }
|
||||||
@@ -1087,6 +1088,12 @@ PRIMITIVES_JS_MODULES: dict[str, str] = {
|
|||||||
PRIMITIVES["collect!"] = sxCollect;
|
PRIMITIVES["collect!"] = sxCollect;
|
||||||
PRIMITIVES["collected"] = sxCollected;
|
PRIMITIVES["collected"] = sxCollected;
|
||||||
PRIMITIVES["clear-collected!"] = sxClearCollected;
|
PRIMITIVES["clear-collected!"] = sxClearCollected;
|
||||||
|
// provide/context/emit! — render-time dynamic scope
|
||||||
|
PRIMITIVES["provide-push!"] = providePush;
|
||||||
|
PRIMITIVES["provide-pop!"] = providePop;
|
||||||
|
PRIMITIVES["context"] = sxContext;
|
||||||
|
PRIMITIVES["emit!"] = sxEmit;
|
||||||
|
PRIMITIVES["emitted"] = sxEmitted;
|
||||||
''',
|
''',
|
||||||
}
|
}
|
||||||
# Modules to include by default (all)
|
# Modules to include by default (all)
|
||||||
@@ -1162,6 +1169,35 @@ PLATFORM_JS_PRE = '''
|
|||||||
if (_collectBuckets[bucket]) _collectBuckets[bucket] = [];
|
if (_collectBuckets[bucket]) _collectBuckets[bucket] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function providePush(name, value) {
|
||||||
|
if (!_provideStacks[name]) _provideStacks[name] = [];
|
||||||
|
_provideStacks[name].push({value: value !== undefined ? value : NIL, emitted: []});
|
||||||
|
}
|
||||||
|
function providePop(name) {
|
||||||
|
if (_provideStacks[name] && _provideStacks[name].length) _provideStacks[name].pop();
|
||||||
|
}
|
||||||
|
function sxContext(name) {
|
||||||
|
if (_provideStacks[name] && _provideStacks[name].length) {
|
||||||
|
return _provideStacks[name][_provideStacks[name].length - 1].value;
|
||||||
|
}
|
||||||
|
if (arguments.length > 1) return arguments[1];
|
||||||
|
throw new Error("No provider for: " + name);
|
||||||
|
}
|
||||||
|
function sxEmit(name, value) {
|
||||||
|
if (_provideStacks[name] && _provideStacks[name].length) {
|
||||||
|
_provideStacks[name][_provideStacks[name].length - 1].emitted.push(value);
|
||||||
|
} else {
|
||||||
|
throw new Error("No provider for emit!: " + name);
|
||||||
|
}
|
||||||
|
return NIL;
|
||||||
|
}
|
||||||
|
function sxEmitted(name) {
|
||||||
|
if (_provideStacks[name] && _provideStacks[name].length) {
|
||||||
|
return _provideStacks[name][_provideStacks[name].length - 1].emitted.slice();
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
function lambdaParams(f) { return f.params; }
|
function lambdaParams(f) { return f.params; }
|
||||||
function lambdaBody(f) { return f.body; }
|
function lambdaBody(f) { return f.body; }
|
||||||
function lambdaClosure(f) { return f.closure; }
|
function lambdaClosure(f) { return f.closure; }
|
||||||
@@ -3192,6 +3228,11 @@ def public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_boot, has
|
|||||||
api_lines.append(' collect: sxCollect,')
|
api_lines.append(' collect: sxCollect,')
|
||||||
api_lines.append(' collected: sxCollected,')
|
api_lines.append(' collected: sxCollected,')
|
||||||
api_lines.append(' clearCollected: sxClearCollected,')
|
api_lines.append(' clearCollected: sxClearCollected,')
|
||||||
|
api_lines.append(' providePush: providePush,')
|
||||||
|
api_lines.append(' providePop: providePop,')
|
||||||
|
api_lines.append(' context: sxContext,')
|
||||||
|
api_lines.append(' emit: sxEmit,')
|
||||||
|
api_lines.append(' emitted: sxEmitted,')
|
||||||
api_lines.append(f' _version: "{version}"')
|
api_lines.append(f' _version: "{version}"')
|
||||||
api_lines.append(' };')
|
api_lines.append(' };')
|
||||||
api_lines.append('')
|
api_lines.append('')
|
||||||
|
|||||||
@@ -101,6 +101,46 @@ def _collect_reset():
|
|||||||
_collect_buckets = {}
|
_collect_buckets = {}
|
||||||
|
|
||||||
|
|
||||||
|
# Render-time dynamic scope stacks (provide/context/emit!)
|
||||||
|
_provide_stacks: dict[str, list[dict]] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def provide_push(name, value=None):
|
||||||
|
"""Push a provider scope with name, value, and empty emitted list."""
|
||||||
|
_provide_stacks.setdefault(name, []).append({"value": value, "emitted": []})
|
||||||
|
|
||||||
|
|
||||||
|
def provide_pop(name):
|
||||||
|
"""Pop the most recent provider scope for name."""
|
||||||
|
if name in _provide_stacks and _provide_stacks[name]:
|
||||||
|
_provide_stacks[name].pop()
|
||||||
|
|
||||||
|
|
||||||
|
def sx_context(name, *default):
|
||||||
|
"""Read value from nearest enclosing provider. Error if no provider and no default."""
|
||||||
|
if name in _provide_stacks and _provide_stacks[name]:
|
||||||
|
return _provide_stacks[name][-1]["value"]
|
||||||
|
if default:
|
||||||
|
return default[0]
|
||||||
|
raise RuntimeError(f"No provider for: {name}")
|
||||||
|
|
||||||
|
|
||||||
|
def sx_emit(name, value):
|
||||||
|
"""Append value to nearest enclosing provider's accumulator. Error if no provider."""
|
||||||
|
if name in _provide_stacks and _provide_stacks[name]:
|
||||||
|
_provide_stacks[name][-1]["emitted"].append(value)
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f"No provider for emit!: {name}")
|
||||||
|
return NIL
|
||||||
|
|
||||||
|
|
||||||
|
def sx_emitted(name):
|
||||||
|
"""Return list of values emitted into nearest matching provider."""
|
||||||
|
if name in _provide_stacks and _provide_stacks[name]:
|
||||||
|
return list(_provide_stacks[name][-1]["emitted"])
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
def sx_truthy(x):
|
def sx_truthy(x):
|
||||||
"""SX truthiness: everything is truthy except False, None, and NIL."""
|
"""SX truthiness: everything is truthy except False, None, and NIL."""
|
||||||
if x is False:
|
if x is False:
|
||||||
@@ -942,6 +982,12 @@ PRIMITIVES["spread-attrs"] = spread_attrs
|
|||||||
PRIMITIVES["collect!"] = sx_collect
|
PRIMITIVES["collect!"] = sx_collect
|
||||||
PRIMITIVES["collected"] = sx_collected
|
PRIMITIVES["collected"] = sx_collected
|
||||||
PRIMITIVES["clear-collected!"] = sx_clear_collected
|
PRIMITIVES["clear-collected!"] = sx_clear_collected
|
||||||
|
# provide/context/emit! — render-time dynamic scope
|
||||||
|
PRIMITIVES["provide-push!"] = provide_push
|
||||||
|
PRIMITIVES["provide-pop!"] = provide_pop
|
||||||
|
PRIMITIVES["context"] = sx_context
|
||||||
|
PRIMITIVES["emit!"] = sx_emit
|
||||||
|
PRIMITIVES["emitted"] = sx_emitted
|
||||||
''',
|
''',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -252,6 +252,11 @@
|
|||||||
"collect!" "sx_collect"
|
"collect!" "sx_collect"
|
||||||
"collected" "sx_collected"
|
"collected" "sx_collected"
|
||||||
"clear-collected!" "sx_clear_collected"
|
"clear-collected!" "sx_clear_collected"
|
||||||
|
"provide-push!" "provide_push"
|
||||||
|
"provide-pop!" "provide_pop"
|
||||||
|
"context" "sx_context"
|
||||||
|
"emit!" "sx_emit"
|
||||||
|
"emitted" "sx_emitted"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -269,6 +269,13 @@
|
|||||||
;; (collected bucket) → list
|
;; (collected bucket) → list
|
||||||
;; (clear-collected! bucket) → void
|
;; (clear-collected! bucket) → void
|
||||||
;;
|
;;
|
||||||
|
;; Dynamic scope (provide/context/emit!):
|
||||||
|
;; (provide-push! name val) → void
|
||||||
|
;; (provide-pop! name) → void
|
||||||
|
;; (context name &rest def) → value from nearest provider
|
||||||
|
;; (emit! name value) → void (append to provider accumulator)
|
||||||
|
;; (emitted name) → list of emitted values
|
||||||
|
;;
|
||||||
;; From parser.sx:
|
;; From parser.sx:
|
||||||
;; (sx-serialize val) → SX source string (aliased as serialize above)
|
;; (sx-serialize val) → SX source string (aliased as serialize above)
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -60,6 +60,46 @@ def _collect_reset():
|
|||||||
_collect_buckets = {}
|
_collect_buckets = {}
|
||||||
|
|
||||||
|
|
||||||
|
# Render-time dynamic scope stacks (provide/context/emit!)
|
||||||
|
_provide_stacks: dict[str, list[dict]] = {}
|
||||||
|
|
||||||
|
|
||||||
|
def provide_push(name, value=None):
|
||||||
|
"""Push a provider scope with name, value, and empty emitted list."""
|
||||||
|
_provide_stacks.setdefault(name, []).append({"value": value, "emitted": []})
|
||||||
|
|
||||||
|
|
||||||
|
def provide_pop(name):
|
||||||
|
"""Pop the most recent provider scope for name."""
|
||||||
|
if name in _provide_stacks and _provide_stacks[name]:
|
||||||
|
_provide_stacks[name].pop()
|
||||||
|
|
||||||
|
|
||||||
|
def sx_context(name, *default):
|
||||||
|
"""Read value from nearest enclosing provider. Error if no provider and no default."""
|
||||||
|
if name in _provide_stacks and _provide_stacks[name]:
|
||||||
|
return _provide_stacks[name][-1]["value"]
|
||||||
|
if default:
|
||||||
|
return default[0]
|
||||||
|
raise RuntimeError(f"No provider for: {name}")
|
||||||
|
|
||||||
|
|
||||||
|
def sx_emit(name, value):
|
||||||
|
"""Append value to nearest enclosing provider's accumulator. Error if no provider."""
|
||||||
|
if name in _provide_stacks and _provide_stacks[name]:
|
||||||
|
_provide_stacks[name][-1]["emitted"].append(value)
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f"No provider for emit!: {name}")
|
||||||
|
return NIL
|
||||||
|
|
||||||
|
|
||||||
|
def sx_emitted(name):
|
||||||
|
"""Return list of values emitted into nearest matching provider."""
|
||||||
|
if name in _provide_stacks and _provide_stacks[name]:
|
||||||
|
return list(_provide_stacks[name][-1]["emitted"])
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
def sx_truthy(x):
|
def sx_truthy(x):
|
||||||
"""SX truthiness: everything is truthy except False, None, and NIL."""
|
"""SX truthiness: everything is truthy except False, None, and NIL."""
|
||||||
if x is False:
|
if x is False:
|
||||||
@@ -905,6 +945,12 @@ PRIMITIVES["spread-attrs"] = spread_attrs
|
|||||||
PRIMITIVES["collect!"] = sx_collect
|
PRIMITIVES["collect!"] = sx_collect
|
||||||
PRIMITIVES["collected"] = sx_collected
|
PRIMITIVES["collected"] = sx_collected
|
||||||
PRIMITIVES["clear-collected!"] = sx_clear_collected
|
PRIMITIVES["clear-collected!"] = sx_clear_collected
|
||||||
|
# provide/context/emit! — render-time dynamic scope
|
||||||
|
PRIMITIVES["provide-push!"] = provide_push
|
||||||
|
PRIMITIVES["provide-pop!"] = provide_pop
|
||||||
|
PRIMITIVES["context"] = sx_context
|
||||||
|
PRIMITIVES["emit!"] = sx_emit
|
||||||
|
PRIMITIVES["emitted"] = sx_emitted
|
||||||
|
|
||||||
|
|
||||||
def is_primitive(name):
|
def is_primitive(name):
|
||||||
@@ -1349,6 +1395,8 @@ def eval_list(expr, env):
|
|||||||
return sf_shift(args, env)
|
return sf_shift(args, env)
|
||||||
elif sx_truthy((name == 'dynamic-wind')):
|
elif sx_truthy((name == 'dynamic-wind')):
|
||||||
return sf_dynamic_wind(args, env)
|
return sf_dynamic_wind(args, env)
|
||||||
|
elif sx_truthy((name == 'provide')):
|
||||||
|
return sf_provide(args, env)
|
||||||
elif sx_truthy((name == 'map')):
|
elif sx_truthy((name == 'map')):
|
||||||
return ho_map(args, env)
|
return ho_map(args, env)
|
||||||
elif sx_truthy((name == 'map-indexed')):
|
elif sx_truthy((name == 'map-indexed')):
|
||||||
@@ -1840,6 +1888,19 @@ def sf_dynamic_wind(args, env):
|
|||||||
call_thunk(after, env)
|
call_thunk(after, env)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
# sf-provide
|
||||||
|
def sf_provide(args, env):
|
||||||
|
_cells = {}
|
||||||
|
name = trampoline(eval_expr(first(args), env))
|
||||||
|
val = trampoline(eval_expr(nth(args, 1), env))
|
||||||
|
body_exprs = slice(args, 2)
|
||||||
|
_cells['result'] = NIL
|
||||||
|
provide_push(name, val)
|
||||||
|
for e in body_exprs:
|
||||||
|
_cells['result'] = trampoline(eval_expr(e, env))
|
||||||
|
provide_pop(name)
|
||||||
|
return _cells['result']
|
||||||
|
|
||||||
# expand-macro
|
# expand-macro
|
||||||
def expand_macro(mac, raw_args, env):
|
def expand_macro(mac, raw_args, env):
|
||||||
local = env_merge(macro_closure(mac), env)
|
local = env_merge(macro_closure(mac), env)
|
||||||
@@ -2189,7 +2250,7 @@ def render_value_to_html(val, env):
|
|||||||
return escape_html(sx_str(val))
|
return escape_html(sx_str(val))
|
||||||
|
|
||||||
# RENDER_HTML_FORMS
|
# RENDER_HTML_FORMS
|
||||||
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']
|
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']
|
||||||
|
|
||||||
# render-html-form?
|
# render-html-form?
|
||||||
def is_render_html_form(name):
|
def is_render_html_form(name):
|
||||||
@@ -2291,6 +2352,15 @@ def dispatch_html_form(name, expr, env):
|
|||||||
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('', 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)))
|
||||||
|
elif sx_truthy((name == 'provide')):
|
||||||
|
prov_name = trampoline(eval_expr(nth(expr, 1), env))
|
||||||
|
prov_val = trampoline(eval_expr(nth(expr, 2), env))
|
||||||
|
body_start = 3
|
||||||
|
body_count = (len(expr) - 3)
|
||||||
|
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))))))
|
||||||
|
provide_pop(prov_name)
|
||||||
|
return result
|
||||||
else:
|
else:
|
||||||
return render_value_to_html(trampoline(eval_expr(expr, env)), env)
|
return render_value_to_html(trampoline(eval_expr(expr, env)), env)
|
||||||
|
|
||||||
@@ -2529,7 +2599,7 @@ def aser_call(name, args, env):
|
|||||||
return sx_str('(', join(' ', parts), ')')
|
return sx_str('(', join(' ', parts), ')')
|
||||||
|
|
||||||
# SPECIAL_FORM_NAMES
|
# SPECIAL_FORM_NAMES
|
||||||
SPECIAL_FORM_NAMES = ['if', 'when', 'cond', 'case', 'and', 'or', 'let', 'let*', 'lambda', 'fn', 'define', 'defcomp', 'defmacro', 'defstyle', 'defhandler', 'defpage', 'defquery', 'defaction', 'defrelation', 'begin', 'do', 'quote', 'quasiquote', '->', 'set!', 'letrec', 'dynamic-wind', 'defisland', 'deftype', 'defeffect']
|
SPECIAL_FORM_NAMES = ['if', 'when', 'cond', 'case', 'and', 'or', 'let', 'let*', 'lambda', 'fn', 'define', 'defcomp', 'defmacro', 'defstyle', 'defhandler', 'defpage', 'defquery', 'defaction', 'defrelation', 'begin', 'do', 'quote', 'quasiquote', '->', 'set!', 'letrec', 'dynamic-wind', 'defisland', 'deftype', 'defeffect', 'provide']
|
||||||
|
|
||||||
# HO_FORM_NAMES
|
# HO_FORM_NAMES
|
||||||
HO_FORM_NAMES = ['map', 'map-indexed', 'filter', 'reduce', 'some', 'every?', 'for-each']
|
HO_FORM_NAMES = ['map', 'map-indexed', 'filter', 'reduce', 'some', 'every?', 'for-each']
|
||||||
@@ -2626,6 +2696,15 @@ def aser_special(name, expr, env):
|
|||||||
elif sx_truthy(((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else ((name == 'defhandler') if sx_truthy((name == 'defhandler')) else ((name == 'defpage') if sx_truthy((name == 'defpage')) else ((name == 'defquery') if sx_truthy((name == 'defquery')) else ((name == 'defaction') if sx_truthy((name == 'defaction')) else ((name == 'defrelation') if sx_truthy((name == 'defrelation')) else ((name == 'deftype') if sx_truthy((name == 'deftype')) else (name == 'defeffect')))))))))))):
|
elif sx_truthy(((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else ((name == 'defhandler') if sx_truthy((name == 'defhandler')) else ((name == 'defpage') if sx_truthy((name == 'defpage')) else ((name == 'defquery') if sx_truthy((name == 'defquery')) else ((name == 'defaction') if sx_truthy((name == 'defaction')) else ((name == 'defrelation') if sx_truthy((name == 'defrelation')) else ((name == 'deftype') if sx_truthy((name == 'deftype')) else (name == 'defeffect')))))))))))):
|
||||||
trampoline(eval_expr(expr, env))
|
trampoline(eval_expr(expr, env))
|
||||||
return NIL
|
return NIL
|
||||||
|
elif sx_truthy((name == 'provide')):
|
||||||
|
prov_name = trampoline(eval_expr(first(args), env))
|
||||||
|
prov_val = trampoline(eval_expr(nth(args, 1), env))
|
||||||
|
_cells['result'] = NIL
|
||||||
|
provide_push(prov_name, prov_val)
|
||||||
|
for body in slice(args, 2):
|
||||||
|
_cells['result'] = aser(body, env)
|
||||||
|
provide_pop(prov_name)
|
||||||
|
return _cells['result']
|
||||||
else:
|
else:
|
||||||
return trampoline(eval_expr(expr, env))
|
return trampoline(eval_expr(expr, env))
|
||||||
|
|
||||||
@@ -3759,7 +3838,7 @@ async def async_map_render(exprs, env, ctx):
|
|||||||
return results
|
return results
|
||||||
|
|
||||||
# ASYNC_RENDER_FORMS
|
# ASYNC_RENDER_FORMS
|
||||||
ASYNC_RENDER_FORMS = ['if', 'when', 'cond', 'case', 'let', 'let*', 'begin', 'do', 'define', 'defcomp', 'defisland', 'defmacro', 'defstyle', 'defhandler', 'deftype', 'defeffect', 'map', 'map-indexed', 'filter', 'for-each']
|
ASYNC_RENDER_FORMS = ['if', 'when', 'cond', 'case', 'let', 'let*', 'begin', 'do', 'define', 'defcomp', 'defisland', 'defmacro', 'defstyle', 'defhandler', 'deftype', 'defeffect', 'map', 'map-indexed', 'filter', 'for-each', 'provide']
|
||||||
|
|
||||||
# async-render-form?
|
# async-render-form?
|
||||||
def async_render_form_p(name):
|
def async_render_form_p(name):
|
||||||
@@ -3823,6 +3902,15 @@ async def dispatch_async_render_form(name, expr, env, ctx):
|
|||||||
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('', filter(lambda r: (not sx_truthy(is_spread(r))), (await async_map_fn_render(f, coll, env, ctx))))
|
||||||
|
elif sx_truthy((name == 'provide')):
|
||||||
|
prov_name = (await async_eval(nth(expr, 1), env, ctx))
|
||||||
|
prov_val = (await async_eval(nth(expr, 2), env, ctx))
|
||||||
|
body_start = 3
|
||||||
|
body_count = (len(expr) - 3)
|
||||||
|
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))))
|
||||||
|
provide_pop(prov_name)
|
||||||
|
return result
|
||||||
else:
|
else:
|
||||||
return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
|
return (await async_render((await async_eval(expr, env, ctx)), env, ctx))
|
||||||
|
|
||||||
@@ -4148,7 +4236,7 @@ async def async_aser_call(name, args, env, ctx):
|
|||||||
return make_sx_expr(sx_str('(', join(' ', parts), ')'))
|
return make_sx_expr(sx_str('(', join(' ', parts), ')'))
|
||||||
|
|
||||||
# ASYNC_ASER_FORM_NAMES
|
# ASYNC_ASER_FORM_NAMES
|
||||||
ASYNC_ASER_FORM_NAMES = ['if', 'when', 'cond', 'case', 'and', 'or', 'let', 'let*', 'lambda', 'fn', 'define', 'defcomp', 'defmacro', 'defstyle', 'defhandler', 'defpage', 'defquery', 'defaction', 'begin', 'do', 'quote', '->', 'set!', 'defisland', 'deftype', 'defeffect']
|
ASYNC_ASER_FORM_NAMES = ['if', 'when', 'cond', 'case', 'and', 'or', 'let', 'let*', 'lambda', 'fn', 'define', 'defcomp', 'defmacro', 'defstyle', 'defhandler', 'defpage', 'defquery', 'defaction', 'begin', 'do', 'quote', '->', 'set!', 'defisland', 'deftype', 'defeffect', 'provide']
|
||||||
|
|
||||||
# ASYNC_ASER_HO_NAMES
|
# ASYNC_ASER_HO_NAMES
|
||||||
ASYNC_ASER_HO_NAMES = ['map', 'map-indexed', 'filter', 'for-each']
|
ASYNC_ASER_HO_NAMES = ['map', 'map-indexed', 'filter', 'for-each']
|
||||||
@@ -4242,6 +4330,15 @@ async def dispatch_async_aser_form(name, expr, env, ctx):
|
|||||||
elif sx_truthy(((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else ((name == 'defhandler') if sx_truthy((name == 'defhandler')) else ((name == 'defpage') if sx_truthy((name == 'defpage')) else ((name == 'defquery') if sx_truthy((name == 'defquery')) else ((name == 'defaction') if sx_truthy((name == 'defaction')) else ((name == 'deftype') if sx_truthy((name == 'deftype')) else (name == 'defeffect'))))))))))):
|
elif sx_truthy(((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else ((name == 'defhandler') if sx_truthy((name == 'defhandler')) else ((name == 'defpage') if sx_truthy((name == 'defpage')) else ((name == 'defquery') if sx_truthy((name == 'defquery')) else ((name == 'defaction') if sx_truthy((name == 'defaction')) else ((name == 'deftype') if sx_truthy((name == 'deftype')) else (name == 'defeffect'))))))))))):
|
||||||
(await async_eval(expr, env, ctx))
|
(await async_eval(expr, env, ctx))
|
||||||
return NIL
|
return NIL
|
||||||
|
elif sx_truthy((name == 'provide')):
|
||||||
|
prov_name = (await async_eval(first(args), env, ctx))
|
||||||
|
prov_val = (await async_eval(nth(args, 1), env, ctx))
|
||||||
|
_cells['result'] = NIL
|
||||||
|
provide_push(prov_name, prov_val)
|
||||||
|
for body in slice(args, 2):
|
||||||
|
_cells['result'] = (await async_aser(body, env, ctx))
|
||||||
|
provide_pop(prov_name)
|
||||||
|
return _cells['result']
|
||||||
else:
|
else:
|
||||||
return (await async_eval(expr, env, ctx))
|
return (await async_eval(expr, env, ctx))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user