Collapse reactive islands into scopes: replace TrackingContext and *island-scope* with scope-push!/scope-pop!/context
Reactive tracking (deref/computed/effect dep discovery) and island lifecycle now use the general scoped effects system instead of parallel infrastructure. Two scope names: "sx-reactive" for tracking context, "sx-island-scope" for island disposable collection. Eliminates ~98 net lines: _TrackingContext class, 7 tracking context platform functions (Python + JS), *island-scope* global, and corresponding RENAME_MAP entries. All 20 signal tests pass (17 original + 3 new scope integration tests), plus CEK/continuation/type tests clean. 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 SX_VERSION = "2026-03-13T19:15:06Z";
|
||||
var SX_VERSION = "2026-03-13T22:49:51Z";
|
||||
|
||||
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
||||
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
||||
@@ -61,13 +61,6 @@
|
||||
}
|
||||
SxSignal.prototype._signal = true;
|
||||
|
||||
function TrackingCtx(notifyFn) {
|
||||
this.notifyFn = notifyFn;
|
||||
this.deps = [];
|
||||
}
|
||||
|
||||
var _trackingContext = null;
|
||||
|
||||
function Macro(params, restParam, body, closure, name) {
|
||||
this.params = params;
|
||||
this.restParam = restParam;
|
||||
@@ -243,12 +236,6 @@
|
||||
function signalRemoveSub(s, fn) { var i = s.subscribers.indexOf(fn); if (i >= 0) s.subscribers.splice(i, 1); }
|
||||
function signalDeps(s) { return s.deps.slice(); }
|
||||
function signalSetDeps(s, deps) { s.deps = Array.isArray(deps) ? deps.slice() : []; }
|
||||
function setTrackingContext(ctx) { _trackingContext = ctx; }
|
||||
function getTrackingContext() { return _trackingContext || NIL; }
|
||||
function makeTrackingContext(notifyFn) { return new TrackingCtx(notifyFn); }
|
||||
function trackingContextDeps(ctx) { return ctx ? ctx.deps : []; }
|
||||
function trackingContextAddDep(ctx, s) { if (ctx && ctx.deps.indexOf(s) < 0) ctx.deps.push(s); }
|
||||
function trackingContextNotifyFn(ctx) { return ctx ? ctx.notifyFn : NIL; }
|
||||
|
||||
// invoke — call any callable (native fn or SX lambda) with args.
|
||||
// Transpiled code emits direct calls f(args) which fail on SX lambdas
|
||||
@@ -1208,14 +1195,7 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai
|
||||
var before = trampoline(evalExpr(first(args), env));
|
||||
var body = trampoline(evalExpr(nth(args, 1), env));
|
||||
var after = trampoline(evalExpr(nth(args, 2), env));
|
||||
callThunk(before, env);
|
||||
pushWind(before, after);
|
||||
return (function() {
|
||||
var result = callThunk(body, env);
|
||||
popWind();
|
||||
callThunk(after, env);
|
||||
return result;
|
||||
})();
|
||||
return dynamicWindCall(before, body, after, env);
|
||||
})(); };
|
||||
|
||||
// sf-scope
|
||||
@@ -1936,7 +1916,7 @@ return result; }, args);
|
||||
|
||||
// render-to-dom
|
||||
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 (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)))); })(); };
|
||||
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(sxContext("sx-island-scope", NIL)) ? reactiveText(expr) : createTextNode((String(deref(expr))))) : createTextNode((String(expr)))); })(); };
|
||||
|
||||
// render-dom-list
|
||||
var renderDomList = function(expr, env, ns) { return (function() {
|
||||
@@ -1947,7 +1927,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
|
||||
return (isSxTruthy((name == "raw!")) ? renderDomRaw(args, env) : (isSxTruthy((name == "<>")) ? renderDomFragment(args, env, ns) : (isSxTruthy((name == "lake")) ? renderDomLake(args, env, ns) : (isSxTruthy((name == "marsh")) ? renderDomMarsh(args, env, ns) : (isSxTruthy(startsWith(name, "html:")) ? renderDomElement(slice(name, 5), args, env, ns) : (isSxTruthy(isRenderDomForm(name)) ? (isSxTruthy((isSxTruthy(contains(HTML_TAGS, name)) && sxOr((isSxTruthy((len(args) > 0)) && (typeOf(first(args)) == "keyword")), ns))) ? renderDomElement(name, args, env, ns) : dispatchRenderForm(name, expr, env, ns)) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? renderToDom(expandMacro(envGet(env, name), args, env), env, ns) : (isSxTruthy(contains(HTML_TAGS, name)) ? renderDomElement(name, args, env, ns) : (isSxTruthy((isSxTruthy(startsWith(name, "~")) && isSxTruthy(envHas(env, name)) && isIsland(envGet(env, name)))) ? renderDomIsland(envGet(env, name), args, env, ns) : (isSxTruthy(startsWith(name, "~")) ? (function() {
|
||||
var comp = envGet(env, name);
|
||||
return (isSxTruthy(isComponent(comp)) ? renderDomComponent(comp, args, env, ns) : renderDomUnknownComponent(name));
|
||||
})() : (isSxTruthy((isSxTruthy((indexOf_(name, "-") > 0)) && isSxTruthy((len(args) > 0)) && (typeOf(first(args)) == "keyword"))) ? renderDomElement(name, args, env, ns) : (isSxTruthy(ns) ? renderDomElement(name, args, env, ns) : (isSxTruthy((isSxTruthy((name == "deref")) && _islandScope)) ? (function() {
|
||||
})() : (isSxTruthy((isSxTruthy((indexOf_(name, "-") > 0)) && isSxTruthy((len(args) > 0)) && (typeOf(first(args)) == "keyword"))) ? renderDomElement(name, args, env, ns) : (isSxTruthy(ns) ? renderDomElement(name, args, env, ns) : (isSxTruthy((isSxTruthy((name == "deref")) && sxContext("sx-island-scope", NIL))) ? (function() {
|
||||
var sigOrVal = trampoline(evalExpr(first(args), env));
|
||||
return (isSxTruthy(isSignal(sigOrVal)) ? reactiveText(sigOrVal) : createTextNode((String(deref(sigOrVal)))));
|
||||
})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns))))))))))))));
|
||||
@@ -1983,14 +1963,14 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
|
||||
})() : (isSxTruthy((attrName == "key")) ? (function() {
|
||||
var attrVal = trampoline(evalExpr(attrExpr, env));
|
||||
return domSetAttr(el, "key", (String(attrVal)));
|
||||
})() : (isSxTruthy(_islandScope) ? reactiveAttr(el, attrName, function() { return trampoline(evalExpr(attrExpr, env)); }) : (function() {
|
||||
})() : (isSxTruthy(sxContext("sx-island-scope", NIL)) ? reactiveAttr(el, attrName, function() { return trampoline(evalExpr(attrExpr, env)); }) : (function() {
|
||||
var attrVal = trampoline(evalExpr(attrExpr, env));
|
||||
return (isSxTruthy(sxOr(isNil(attrVal), (attrVal == false))) ? NIL : (isSxTruthy(contains(BOOLEAN_ATTRS, attrName)) ? (isSxTruthy(attrVal) ? domSetAttr(el, attrName, "") : NIL) : (isSxTruthy((attrVal == true)) ? domSetAttr(el, attrName, "") : domSetAttr(el, attrName, (String(attrVal))))));
|
||||
})())))));
|
||||
return assoc(state, "skip", true, "i", (get(state, "i") + 1));
|
||||
})() : ((isSxTruthy(!isSxTruthy(contains(VOID_ELEMENTS, tag))) ? (function() {
|
||||
var child = renderToDom(arg, env, newNs);
|
||||
return (isSxTruthy((isSxTruthy(isSpread(child)) && _islandScope)) ? reactiveSpread(el, function() { return renderToDom(arg, env, newNs); }) : (isSxTruthy(isSpread(child)) ? NIL : domAppend(el, child)));
|
||||
return (isSxTruthy((isSxTruthy(isSpread(child)) && sxContext("sx-island-scope", NIL))) ? reactiveSpread(el, function() { return renderToDom(arg, env, newNs); }) : (isSxTruthy(isSpread(child)) ? NIL : domAppend(el, child)));
|
||||
})() : 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() {
|
||||
@@ -2066,7 +2046,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
|
||||
var isRenderDomForm = function(name) { return contains(RENDER_DOM_FORMS, name); };
|
||||
|
||||
// dispatch-render-form
|
||||
var dispatchRenderForm = function(name, expr, env, ns) { return (isSxTruthy((name == "if")) ? (isSxTruthy(_islandScope) ? (function() {
|
||||
var dispatchRenderForm = function(name, expr, env, ns) { return (isSxTruthy((name == "if")) ? (isSxTruthy(sxContext("sx-island-scope", NIL)) ? (function() {
|
||||
var marker = createComment("r-if");
|
||||
var currentNodes = [];
|
||||
var initialResult = NIL;
|
||||
@@ -2089,7 +2069,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
|
||||
})() : (function() {
|
||||
var condVal = trampoline(evalExpr(nth(expr, 1), env));
|
||||
return (isSxTruthy(condVal) ? renderToDom(nth(expr, 2), env, ns) : (isSxTruthy((len(expr) > 3)) ? renderToDom(nth(expr, 3), env, ns) : createFragment()));
|
||||
})()) : (isSxTruthy((name == "when")) ? (isSxTruthy(_islandScope) ? (function() {
|
||||
})()) : (isSxTruthy((name == "when")) ? (isSxTruthy(sxContext("sx-island-scope", NIL)) ? (function() {
|
||||
var marker = createComment("r-when");
|
||||
var currentNodes = [];
|
||||
var initialResult = NIL;
|
||||
@@ -2116,7 +2096,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
|
||||
var frag = createFragment();
|
||||
{ var _c = range(2, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; domAppend(frag, renderToDom(nth(expr, i), env, ns)); } }
|
||||
return frag;
|
||||
})())) : (isSxTruthy((name == "cond")) ? (isSxTruthy(_islandScope) ? (function() {
|
||||
})())) : (isSxTruthy((name == "cond")) ? (isSxTruthy(sxContext("sx-island-scope", NIL)) ? (function() {
|
||||
var marker = createComment("r-cond");
|
||||
var currentNodes = [];
|
||||
var initialResult = NIL;
|
||||
@@ -2162,7 +2142,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
|
||||
return frag;
|
||||
})()) : (isSxTruthy(isDefinitionForm(name)) ? (trampoline(evalExpr(expr, env)), createFragment()) : (isSxTruthy((name == "map")) ? (function() {
|
||||
var collExpr = nth(expr, 2);
|
||||
return (isSxTruthy((isSxTruthy(_islandScope) && isSxTruthy((typeOf(collExpr) == "list")) && isSxTruthy((len(collExpr) > 1)) && isSxTruthy((typeOf(first(collExpr)) == "symbol")) && (symbolName(first(collExpr)) == "deref"))) ? (function() {
|
||||
return (isSxTruthy((isSxTruthy(sxContext("sx-island-scope", NIL)) && isSxTruthy((typeOf(collExpr) == "list")) && isSxTruthy((len(collExpr) > 1)) && isSxTruthy((typeOf(first(collExpr)) == "symbol")) && (symbolName(first(collExpr)) == "deref"))) ? (function() {
|
||||
var f = trampoline(evalExpr(nth(expr, 1), env));
|
||||
var sig = trampoline(evalExpr(nth(collExpr, 1), env));
|
||||
return (isSxTruthy(isSignal(sig)) ? reactiveList(f, sig, env, ns) : (function() {
|
||||
@@ -2490,15 +2470,13 @@ return (isSxTruthy(testFn()) ? (function() {
|
||||
domSetAttr(container, "data-sx-boundary", "true");
|
||||
effect(function() { deref(retryVersion);
|
||||
domSetProp(container, "innerHTML", "");
|
||||
return (function() {
|
||||
var savedScope = _islandScope;
|
||||
_islandScope = NIL;
|
||||
return tryCatch(function() { (function() {
|
||||
scopePush("sx-island-scope", NIL);
|
||||
return tryCatch(function() { (function() {
|
||||
var frag = createFragment();
|
||||
{ var _c = bodyExprs; for (var _i = 0; _i < _c.length; _i++) { var child = _c[_i]; domAppend(frag, renderToDom(child, env, ns)); } }
|
||||
return domAppend(container, frag);
|
||||
})();
|
||||
return (_islandScope = savedScope); }, function(err) { _islandScope = savedScope;
|
||||
return scopePop("sx-island-scope"); }, function(err) { scopePop("sx-island-scope");
|
||||
return (function() {
|
||||
var fallbackFn = trampoline(evalExpr(fallbackExpr, env));
|
||||
var retryFn = function() { return swap_b(retryVersion, function(n) { return (n + 1); }); };
|
||||
@@ -2506,8 +2484,7 @@ return (function() {
|
||||
var fallbackDom = (isSxTruthy(isLambda(fallbackFn)) ? renderLambdaDom(fallbackFn, [err, retryFn], env, ns) : renderToDom(apply(fallbackFn, [err, retryFn]), env, ns));
|
||||
return domAppend(container, fallbackDom);
|
||||
})();
|
||||
})(); });
|
||||
})(); });
|
||||
})(); }); });
|
||||
return container;
|
||||
})(); };
|
||||
|
||||
@@ -4272,10 +4249,13 @@ callExpr.push(dictGet(kwargs, k)); } }
|
||||
|
||||
// deref
|
||||
var deref = function(s) { return (isSxTruthy(!isSxTruthy(isSignal(s))) ? s : (function() {
|
||||
var ctx = getTrackingContext();
|
||||
var ctx = sxContext("sx-reactive", NIL);
|
||||
if (isSxTruthy(ctx)) {
|
||||
trackingContextAddDep(ctx, s);
|
||||
signalAddSub(s, trackingContextNotifyFn(ctx));
|
||||
(function() {
|
||||
var depList = get(ctx, "deps");
|
||||
var notifyFn = get(ctx, "notify");
|
||||
return (isSxTruthy(!isSxTruthy(contains(depList, s))) ? (append_b(depList, s), signalAddSub(s, notifyFn)) : NIL);
|
||||
})();
|
||||
}
|
||||
return signalValue(s);
|
||||
})()); };
|
||||
@@ -4302,21 +4282,18 @@ callExpr.push(dictGet(kwargs, k)); } }
|
||||
var recompute = function() { { var _c = signalDeps(s); for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, recompute); } }
|
||||
signalSetDeps(s, []);
|
||||
return (function() {
|
||||
var ctx = makeTrackingContext(recompute);
|
||||
return (function() {
|
||||
var prev = getTrackingContext();
|
||||
setTrackingContext(ctx);
|
||||
var ctx = {["deps"]: [], ["notify"]: recompute};
|
||||
scopePush("sx-reactive", ctx);
|
||||
return (function() {
|
||||
var newVal = invoke(computeFn);
|
||||
setTrackingContext(prev);
|
||||
signalSetDeps(s, trackingContextDeps(ctx));
|
||||
scopePop("sx-reactive");
|
||||
signalSetDeps(s, get(ctx, "deps"));
|
||||
return (function() {
|
||||
var old = signalValue(s);
|
||||
signalSetValue(s, newVal);
|
||||
return (isSxTruthy(!isSxTruthy(isIdentical(old, newVal))) ? notifySubscribers(s) : NIL);
|
||||
})();
|
||||
})();
|
||||
})();
|
||||
})(); };
|
||||
recompute();
|
||||
registerInScope(function() { return disposeComputed(s); });
|
||||
@@ -4331,17 +4308,14 @@ return (function() {
|
||||
var cleanupFn = NIL;
|
||||
return (function() {
|
||||
var runEffect = function() { return (isSxTruthy(!isSxTruthy(disposed)) ? ((isSxTruthy(cleanupFn) ? invoke(cleanupFn) : NIL), forEach(function(dep) { return signalRemoveSub(dep, runEffect); }, deps), (deps = []), (function() {
|
||||
var ctx = makeTrackingContext(runEffect);
|
||||
return (function() {
|
||||
var prev = getTrackingContext();
|
||||
setTrackingContext(ctx);
|
||||
var ctx = {["deps"]: [], ["notify"]: runEffect};
|
||||
scopePush("sx-reactive", ctx);
|
||||
return (function() {
|
||||
var result = invoke(effectFn);
|
||||
setTrackingContext(prev);
|
||||
deps = trackingContextDeps(ctx);
|
||||
scopePop("sx-reactive");
|
||||
deps = get(ctx, "deps");
|
||||
return (isSxTruthy(isCallable(result)) ? (cleanupFn = result) : NIL);
|
||||
})();
|
||||
})();
|
||||
})()) : NIL); };
|
||||
runEffect();
|
||||
return (function() {
|
||||
@@ -4390,22 +4364,19 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
||||
// dispose-computed
|
||||
var disposeComputed = function(s) { return (isSxTruthy(isSignal(s)) ? (forEach(function(dep) { return signalRemoveSub(dep, NIL); }, signalDeps(s)), signalSetDeps(s, [])) : NIL); };
|
||||
|
||||
// *island-scope*
|
||||
var _islandScope = NIL;
|
||||
|
||||
// with-island-scope
|
||||
var withIslandScope = function(scopeFn, bodyFn) { return (function() {
|
||||
var prev = _islandScope;
|
||||
_islandScope = scopeFn;
|
||||
return (function() {
|
||||
var withIslandScope = function(scopeFn, bodyFn) { scopePush("sx-island-scope", scopeFn);
|
||||
return (function() {
|
||||
var result = bodyFn();
|
||||
_islandScope = prev;
|
||||
scopePop("sx-island-scope");
|
||||
return result;
|
||||
})();
|
||||
})(); };
|
||||
|
||||
// register-in-scope
|
||||
var registerInScope = function(disposable) { return (isSxTruthy(_islandScope) ? _islandScope(disposable) : NIL); };
|
||||
var registerInScope = function(disposable) { return (function() {
|
||||
var collector = sxContext("sx-island-scope", NIL);
|
||||
return (isSxTruthy(collector) ? invoke(collector, disposable) : NIL);
|
||||
})(); };
|
||||
|
||||
// with-marsh-scope
|
||||
var withMarshScope = function(marshEl, bodyFn) { return (function() {
|
||||
|
||||
Reference in New Issue
Block a user