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:
2026-03-13 02:58:21 +00:00
parent 41097eeef9
commit ea2b71cfa3
14 changed files with 436 additions and 18 deletions

View File

@@ -868,6 +868,7 @@ PREAMBLE = '''\
SxSpread.prototype._spread = true;
var _collectBuckets = {};
var _provideStacks = {};
function isSym(x) { return x != null && x._sym === true; }
function isKw(x) { return x != null && x._kw === true; }
@@ -1087,6 +1088,12 @@ PRIMITIVES_JS_MODULES: dict[str, str] = {
PRIMITIVES["collect!"] = sxCollect;
PRIMITIVES["collected"] = sxCollected;
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)
@@ -1162,6 +1169,35 @@ PLATFORM_JS_PRE = '''
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 lambdaBody(f) { return f.body; }
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(' collected: sxCollected,')
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(' };')
api_lines.append('')