Replace invoke with cek-call in reactive island primitives
All signal operations (computed, effect, batch, etc.) now dispatch function calls through cek-call, which routes SX lambdas via cek-run and native callables via apply. This replaces the invoke shim. Key changes: - cek.sx: add cek-call (defined before reactive-shift-deref), replace invoke in subscriber disposal and ReactiveResetFrame handler - signals.sx: replace all 11 invoke calls with cek-call - js.sx: fix octal escape in js-quote-string (char-from-code 0) - platform_js.py: fix JS append to match Python (list concat semantics), add Continuation type guard in PLATFORM_CEK_JS, add scheduleIdle safety check, module ordering (cek before signals) - platform_py.py: fix ident-char regex (remove [ ] from valid chars), module ordering (cek before signals) - run_js_sx.py: emit PLATFORM_CEK_JS before transpiled spec files - page-functions.sx: add cek and provide page functions for SX URLs Co-Authored-By: Claude Opus 4.6 (1M context) <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-14T01:23:35Z";
|
||||
var SX_VERSION = "2026-03-14T10:06:04Z";
|
||||
|
||||
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
||||
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
||||
@@ -414,7 +414,7 @@
|
||||
PRIMITIVES["rest"] = function(c) { if (c && typeof c.slice !== "function") { console.error("[sx-debug] rest called on non-sliceable:", typeof c, c, new Error().stack); return []; } return c ? c.slice(1) : []; };
|
||||
PRIMITIVES["nth"] = function(c, n) { return c && n >= 0 && n < c.length ? c[n] : NIL; };
|
||||
PRIMITIVES["cons"] = function(x, c) { return [x].concat(c || []); };
|
||||
PRIMITIVES["append"] = function(c, x) { return (c || []).concat([x]); };
|
||||
PRIMITIVES["append"] = function(c, x) { return (c || []).concat(Array.isArray(x) ? x : [x]); };
|
||||
PRIMITIVES["append!"] = function(arr, x) { arr.push(x); return arr; };
|
||||
PRIMITIVES["chunk-every"] = function(c, n) {
|
||||
var r = []; for (var i = 0; i < c.length; i += n) r.push(c.slice(i, i + n)); return r;
|
||||
@@ -744,10 +744,10 @@
|
||||
// =========================================================================
|
||||
// Character classification derived from the grammar:
|
||||
// ident-start → [a-zA-Z_~*+\-><=/!?&]
|
||||
// ident-char → ident-start + [0-9.:\/\[\]#,]
|
||||
// ident-char → ident-start + [0-9.:\/\#,]
|
||||
|
||||
var _identStartRe = /[a-zA-Z_~*+\-><=/!?&]/;
|
||||
var _identCharRe = /[a-zA-Z0-9_~*+\-><=/!?.:&/\[\]#,]/;
|
||||
var _identCharRe = /[a-zA-Z0-9_~*+\-><=/!?.:&/#,]/;
|
||||
|
||||
function isIdentStart(ch) { return _identStartRe.test(ch); }
|
||||
function isIdentChar(ch) { return _identCharRe.test(ch); }
|
||||
@@ -759,6 +759,35 @@
|
||||
var charFromCode = PRIMITIVES["char-from-code"];
|
||||
|
||||
|
||||
// =========================================================================
|
||||
// Platform: CEK module — explicit CEK machine
|
||||
// =========================================================================
|
||||
|
||||
// Continuation type (needed by CEK even without the tree-walk shift/reset extension)
|
||||
if (typeof Continuation === "undefined") {
|
||||
function Continuation(fn) { this.fn = fn; }
|
||||
Continuation.prototype._continuation = true;
|
||||
Continuation.prototype.call = function(value) { return this.fn(value !== undefined ? value : NIL); };
|
||||
PRIMITIVES["continuation?"] = function(x) { return x != null && x._continuation === true; };
|
||||
}
|
||||
|
||||
// Standalone aliases for primitives used by cek.sx / frames.sx
|
||||
var inc = PRIMITIVES["inc"];
|
||||
var dec = PRIMITIVES["dec"];
|
||||
var zip_pairs = PRIMITIVES["zip-pairs"];
|
||||
|
||||
var continuation_p = PRIMITIVES["continuation?"];
|
||||
|
||||
function makeCekContinuation(captured, restKont) {
|
||||
var c = new Continuation(function(v) { return v !== undefined ? v : NIL; });
|
||||
c._cek_data = {"captured": captured, "rest-kont": restKont};
|
||||
return c;
|
||||
}
|
||||
function continuationData(c) {
|
||||
return (c && c._cek_data) ? c._cek_data : {};
|
||||
}
|
||||
|
||||
|
||||
// === Transpiled from eval ===
|
||||
|
||||
// trampoline
|
||||
@@ -1473,7 +1502,8 @@ if (isSxTruthy((pos >= lenSrc))) { return error("Unexpected end of input"); } el
|
||||
if (isSxTruthy((ch == "("))) { pos = (pos + 1);
|
||||
return readList(")"); } else if (isSxTruthy((ch == "["))) { pos = (pos + 1);
|
||||
return readList("]"); } else if (isSxTruthy((ch == "{"))) { pos = (pos + 1);
|
||||
return readMap(); } else if (isSxTruthy((ch == "\""))) { return readString(); } else if (isSxTruthy((ch == ":"))) { return readKeyword(); } else if (isSxTruthy((ch == "`"))) { pos = (pos + 1);
|
||||
return readMap(); } else if (isSxTruthy((ch == "\""))) { return readString(); } else if (isSxTruthy((ch == ":"))) { return readKeyword(); } else if (isSxTruthy((ch == "'"))) { pos = (pos + 1);
|
||||
return [makeSymbol("quote"), readExpr()]; } else if (isSxTruthy((ch == "`"))) { pos = (pos + 1);
|
||||
return [makeSymbol("quasiquote"), readExpr()]; } else if (isSxTruthy((ch == ","))) { pos = (pos + 1);
|
||||
if (isSxTruthy((isSxTruthy((pos < lenSrc)) && (nth(source, pos) == "@")))) { pos = (pos + 1);
|
||||
return [makeSymbol("splice-unquote"), readExpr()]; } else { return [makeSymbol("unquote"), readExpr()]; } } else if (isSxTruthy((ch == "#"))) { pos = (pos + 1);
|
||||
@@ -4385,224 +4415,6 @@ return scan(kont, []); };
|
||||
})(); };
|
||||
|
||||
|
||||
// === Transpiled from signals (reactive signal runtime) ===
|
||||
|
||||
// make-signal
|
||||
var makeSignal = function(value) { return {["__signal"]: true, ["value"]: value, ["subscribers"]: [], ["deps"]: []}; };
|
||||
|
||||
// signal?
|
||||
var isSignal = function(x) { return (isSxTruthy(isDict(x)) && dictHas(x, "__signal")); };
|
||||
|
||||
// signal-value
|
||||
var signalValue = function(s) { return get(s, "value"); };
|
||||
|
||||
// signal-set-value!
|
||||
var signalSetValue = function(s, v) { return dictSet(s, "value", v); };
|
||||
|
||||
// signal-subscribers
|
||||
var signalSubscribers = function(s) { return get(s, "subscribers"); };
|
||||
|
||||
// signal-add-sub!
|
||||
var signalAddSub = function(s, f) { return (isSxTruthy(!isSxTruthy(contains(get(s, "subscribers"), f))) ? append_b(get(s, "subscribers"), f) : NIL); };
|
||||
|
||||
// signal-remove-sub!
|
||||
var signalRemoveSub = function(s, f) { return dictSet(s, "subscribers", filter(function(sub) { return !isSxTruthy(isIdentical(sub, f)); }, get(s, "subscribers"))); };
|
||||
|
||||
// signal-deps
|
||||
var signalDeps = function(s) { return get(s, "deps"); };
|
||||
|
||||
// signal-set-deps!
|
||||
var signalSetDeps = function(s, deps) { return dictSet(s, "deps", deps); };
|
||||
|
||||
// signal
|
||||
var signal = function(initialValue) { return makeSignal(initialValue); };
|
||||
|
||||
// deref
|
||||
var deref = function(s) { return (isSxTruthy(!isSxTruthy(isSignal(s))) ? s : (function() {
|
||||
var ctx = sxContext("sx-reactive", NIL);
|
||||
if (isSxTruthy(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);
|
||||
})()); };
|
||||
|
||||
// reset!
|
||||
var reset_b = function(s, value) { return (isSxTruthy(isSignal(s)) ? (function() {
|
||||
var old = signalValue(s);
|
||||
return (isSxTruthy(!isSxTruthy(isIdentical(old, value))) ? (signalSetValue(s, value), notifySubscribers(s)) : NIL);
|
||||
})() : NIL); };
|
||||
|
||||
// swap!
|
||||
var swap_b = function(s, f) { var args = Array.prototype.slice.call(arguments, 2); return (isSxTruthy(isSignal(s)) ? (function() {
|
||||
var old = signalValue(s);
|
||||
var newVal = apply(f, cons(old, args));
|
||||
return (isSxTruthy(!isSxTruthy(isIdentical(old, newVal))) ? (signalSetValue(s, newVal), notifySubscribers(s)) : NIL);
|
||||
})() : NIL); };
|
||||
|
||||
// computed
|
||||
var computed = function(computeFn) { return (function() {
|
||||
var s = makeSignal(NIL);
|
||||
var deps = [];
|
||||
var computeCtx = NIL;
|
||||
return (function() {
|
||||
var recompute = function() { { var _c = signalDeps(s); for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, recompute); } }
|
||||
signalSetDeps(s, []);
|
||||
return (function() {
|
||||
var ctx = {["deps"]: [], ["notify"]: recompute};
|
||||
scopePush("sx-reactive", ctx);
|
||||
return (function() {
|
||||
var newVal = invoke(computeFn);
|
||||
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); });
|
||||
return s;
|
||||
})();
|
||||
})(); };
|
||||
|
||||
// effect
|
||||
var effect = function(effectFn) { return (function() {
|
||||
var deps = [];
|
||||
var disposed = false;
|
||||
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 = {["deps"]: [], ["notify"]: runEffect};
|
||||
scopePush("sx-reactive", ctx);
|
||||
return (function() {
|
||||
var result = invoke(effectFn);
|
||||
scopePop("sx-reactive");
|
||||
deps = get(ctx, "deps");
|
||||
return (isSxTruthy(isCallable(result)) ? (cleanupFn = result) : NIL);
|
||||
})();
|
||||
})()) : NIL); };
|
||||
runEffect();
|
||||
return (function() {
|
||||
var disposeFn = function() { disposed = true;
|
||||
if (isSxTruthy(cleanupFn)) {
|
||||
invoke(cleanupFn);
|
||||
}
|
||||
{ var _c = deps; for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, runEffect); } }
|
||||
return (deps = []); };
|
||||
registerInScope(disposeFn);
|
||||
return disposeFn;
|
||||
})();
|
||||
})();
|
||||
})(); };
|
||||
|
||||
// *batch-depth*
|
||||
var _batchDepth = 0;
|
||||
|
||||
// *batch-queue*
|
||||
var _batchQueue = [];
|
||||
|
||||
// batch
|
||||
var batch = function(thunk) { _batchDepth = (_batchDepth + 1);
|
||||
invoke(thunk);
|
||||
_batchDepth = (_batchDepth - 1);
|
||||
return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
||||
var queue = _batchQueue;
|
||||
_batchQueue = [];
|
||||
return (function() {
|
||||
var seen = [];
|
||||
var pending = [];
|
||||
{ var _c = queue; for (var _i = 0; _i < _c.length; _i++) { var s = _c[_i]; { var _c = signalSubscribers(s); for (var _i = 0; _i < _c.length; _i++) { var sub = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(seen, sub)))) {
|
||||
seen.push(sub);
|
||||
pending.push(sub);
|
||||
} } } } }
|
||||
return forEach(function(sub) { return sub(); }, pending);
|
||||
})();
|
||||
})() : NIL); };
|
||||
|
||||
// notify-subscribers
|
||||
var notifySubscribers = function(s) { return (isSxTruthy((_batchDepth > 0)) ? (isSxTruthy(!isSxTruthy(contains(_batchQueue, s))) ? append_b(_batchQueue, s) : NIL) : flushSubscribers(s)); };
|
||||
|
||||
// flush-subscribers
|
||||
var flushSubscribers = function(s) { return forEach(function(sub) { return sub(); }, signalSubscribers(s)); };
|
||||
|
||||
// dispose-computed
|
||||
var disposeComputed = function(s) { return (isSxTruthy(isSignal(s)) ? (forEach(function(dep) { return signalRemoveSub(dep, NIL); }, signalDeps(s)), signalSetDeps(s, [])) : NIL); };
|
||||
|
||||
// with-island-scope
|
||||
var withIslandScope = function(scopeFn, bodyFn) { scopePush("sx-island-scope", scopeFn);
|
||||
return (function() {
|
||||
var result = bodyFn();
|
||||
scopePop("sx-island-scope");
|
||||
return result;
|
||||
})(); };
|
||||
|
||||
// register-in-scope
|
||||
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() {
|
||||
var disposers = [];
|
||||
withIslandScope(function(d) { return append_b(disposers, d); }, bodyFn);
|
||||
return domSetData(marshEl, "sx-marsh-disposers", disposers);
|
||||
})(); };
|
||||
|
||||
// dispose-marsh-scope
|
||||
var disposeMarshScope = function(marshEl) { return (function() {
|
||||
var disposers = domGetData(marshEl, "sx-marsh-disposers");
|
||||
return (isSxTruthy(disposers) ? (forEach(function(d) { return invoke(d); }, disposers), domSetData(marshEl, "sx-marsh-disposers", NIL)) : NIL);
|
||||
})(); };
|
||||
|
||||
// *store-registry*
|
||||
var _storeRegistry = {};
|
||||
|
||||
// def-store
|
||||
var defStore = function(name, initFn) { return (function() {
|
||||
var registry = _storeRegistry;
|
||||
if (isSxTruthy(!isSxTruthy(dictHas(registry, name)))) {
|
||||
_storeRegistry = assoc(registry, name, invoke(initFn));
|
||||
}
|
||||
return get(_storeRegistry, name);
|
||||
})(); };
|
||||
|
||||
// use-store
|
||||
var useStore = function(name) { return (isSxTruthy(dictHas(_storeRegistry, name)) ? get(_storeRegistry, name) : error((String("Store not found: ") + String(name) + String(". Call (def-store ...) before (use-store ...).")))); };
|
||||
|
||||
// clear-stores
|
||||
var clearStores = function() { return (_storeRegistry = {}); };
|
||||
|
||||
// emit-event
|
||||
var emitEvent = function(el, eventName, detail) { return domDispatch(el, eventName, detail); };
|
||||
|
||||
// on-event
|
||||
var onEvent = function(el, eventName, handler) { return domListen(el, eventName, handler); };
|
||||
|
||||
// bridge-event
|
||||
var bridgeEvent = function(el, eventName, targetSignal, transformFn) { return effect(function() { return (function() {
|
||||
var remove = domListen(el, eventName, function(e) { return (function() {
|
||||
var detail = eventDetail(e);
|
||||
var newVal = (isSxTruthy(transformFn) ? invoke(transformFn, detail) : detail);
|
||||
return reset_b(targetSignal, newVal);
|
||||
})(); });
|
||||
return remove;
|
||||
})(); }); };
|
||||
|
||||
// resource
|
||||
var resource = function(fetchFn) { return (function() {
|
||||
var state = signal({["loading"]: true, ["data"]: NIL, ["error"]: NIL});
|
||||
promiseThen(invoke(fetchFn), function(data) { return reset_b(state, {["loading"]: false, ["data"]: data, ["error"]: NIL}); }, function(err) { return reset_b(state, {["loading"]: false, ["data"]: NIL, ["error"]: err}); });
|
||||
return state;
|
||||
})(); };
|
||||
|
||||
|
||||
// === Transpiled from cek (explicit CEK machine evaluator) ===
|
||||
|
||||
// cek-run
|
||||
@@ -4743,6 +4555,12 @@ return (function() {
|
||||
// step-sf-deref
|
||||
var stepSfDeref = function(args, env, kont) { return makeCekState(first(args), env, kontPush(makeDerefFrame(env), kont)); };
|
||||
|
||||
// cek-call
|
||||
var cekCall = function(f, args) { return (function() {
|
||||
var a = (isSxTruthy(isNil(args)) ? [] : args);
|
||||
return (isSxTruthy(isNil(f)) ? NIL : (isSxTruthy(isLambda(f)) ? cekRun(continueWithCall(f, a, {}, a, [])) : (isSxTruthy(isCallable(f)) ? apply(f, a) : NIL)));
|
||||
})(); };
|
||||
|
||||
// reactive-shift-deref
|
||||
var reactiveShiftDeref = function(sig, env, kont) { return (function() {
|
||||
var scanResult = kontCaptureToReactiveReset(kont);
|
||||
@@ -4753,7 +4571,7 @@ return (function() {
|
||||
return (function() {
|
||||
var subDisposers = [];
|
||||
return (function() {
|
||||
var subscriber = function() { { var _c = subDisposers; for (var _i = 0; _i < _c.length; _i++) { var d = _c[_i]; invoke(d); } }
|
||||
var subscriber = function() { { var _c = subDisposers; for (var _i = 0; _i < _c.length; _i++) { var d = _c[_i]; cekCall(d, NIL); } }
|
||||
subDisposers = [];
|
||||
return (function() {
|
||||
var newReset = makeReactiveResetFrame(env, updateFn, false);
|
||||
@@ -4762,7 +4580,7 @@ return (function() {
|
||||
})(); };
|
||||
signalAddSub(sig, subscriber);
|
||||
registerInScope(function() { signalRemoveSub(sig, subscriber);
|
||||
return forEach(function(d) { return invoke(d); }, subDisposers); });
|
||||
return forEach(function(d) { return cekCall(d, NIL); }, subDisposers); });
|
||||
return (function() {
|
||||
var initialKont = concat(capturedFrames, [resetFrame], remainingKont);
|
||||
return makeCekValue(signalValue(sig), env, initialKont);
|
||||
@@ -4926,7 +4744,7 @@ return forEach(function(d) { return invoke(d); }, subDisposers); });
|
||||
var updateFn = get(frame, "update-fn");
|
||||
var first_p = get(frame, "first-render");
|
||||
if (isSxTruthy((isSxTruthy(updateFn) && !isSxTruthy(first_p)))) {
|
||||
invoke(updateFn, value);
|
||||
cekCall(updateFn, [value]);
|
||||
}
|
||||
return makeCekValue(value, env, restK);
|
||||
})() : (isSxTruthy((ft == "scope")) ? (function() {
|
||||
@@ -4980,6 +4798,224 @@ return forEach(function(d) { return invoke(d); }, subDisposers); });
|
||||
var trampolineCek = function(val) { return (isSxTruthy(isThunk(val)) ? evalExprCek(thunkExpr(val), thunkEnv(val)) : val); };
|
||||
|
||||
|
||||
// === Transpiled from signals (reactive signal runtime) ===
|
||||
|
||||
// make-signal
|
||||
var makeSignal = function(value) { return {["__signal"]: true, ["value"]: value, ["subscribers"]: [], ["deps"]: []}; };
|
||||
|
||||
// signal?
|
||||
var isSignal = function(x) { return (isSxTruthy(isDict(x)) && dictHas(x, "__signal")); };
|
||||
|
||||
// signal-value
|
||||
var signalValue = function(s) { return get(s, "value"); };
|
||||
|
||||
// signal-set-value!
|
||||
var signalSetValue = function(s, v) { return dictSet(s, "value", v); };
|
||||
|
||||
// signal-subscribers
|
||||
var signalSubscribers = function(s) { return get(s, "subscribers"); };
|
||||
|
||||
// signal-add-sub!
|
||||
var signalAddSub = function(s, f) { return (isSxTruthy(!isSxTruthy(contains(get(s, "subscribers"), f))) ? append_b(get(s, "subscribers"), f) : NIL); };
|
||||
|
||||
// signal-remove-sub!
|
||||
var signalRemoveSub = function(s, f) { return dictSet(s, "subscribers", filter(function(sub) { return !isSxTruthy(isIdentical(sub, f)); }, get(s, "subscribers"))); };
|
||||
|
||||
// signal-deps
|
||||
var signalDeps = function(s) { return get(s, "deps"); };
|
||||
|
||||
// signal-set-deps!
|
||||
var signalSetDeps = function(s, deps) { return dictSet(s, "deps", deps); };
|
||||
|
||||
// signal
|
||||
var signal = function(initialValue) { return makeSignal(initialValue); };
|
||||
|
||||
// deref
|
||||
var deref = function(s) { return (isSxTruthy(!isSxTruthy(isSignal(s))) ? s : (function() {
|
||||
var ctx = sxContext("sx-reactive", NIL);
|
||||
if (isSxTruthy(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);
|
||||
})()); };
|
||||
|
||||
// reset!
|
||||
var reset_b = function(s, value) { return (isSxTruthy(isSignal(s)) ? (function() {
|
||||
var old = signalValue(s);
|
||||
return (isSxTruthy(!isSxTruthy(isIdentical(old, value))) ? (signalSetValue(s, value), notifySubscribers(s)) : NIL);
|
||||
})() : NIL); };
|
||||
|
||||
// swap!
|
||||
var swap_b = function(s, f) { var args = Array.prototype.slice.call(arguments, 2); return (isSxTruthy(isSignal(s)) ? (function() {
|
||||
var old = signalValue(s);
|
||||
var newVal = apply(f, cons(old, args));
|
||||
return (isSxTruthy(!isSxTruthy(isIdentical(old, newVal))) ? (signalSetValue(s, newVal), notifySubscribers(s)) : NIL);
|
||||
})() : NIL); };
|
||||
|
||||
// computed
|
||||
var computed = function(computeFn) { return (function() {
|
||||
var s = makeSignal(NIL);
|
||||
var deps = [];
|
||||
var computeCtx = NIL;
|
||||
return (function() {
|
||||
var recompute = function() { { var _c = signalDeps(s); for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, recompute); } }
|
||||
signalSetDeps(s, []);
|
||||
return (function() {
|
||||
var ctx = {["deps"]: [], ["notify"]: recompute};
|
||||
scopePush("sx-reactive", ctx);
|
||||
return (function() {
|
||||
var newVal = cekCall(computeFn, NIL);
|
||||
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); });
|
||||
return s;
|
||||
})();
|
||||
})(); };
|
||||
|
||||
// effect
|
||||
var effect = function(effectFn) { return (function() {
|
||||
var deps = [];
|
||||
var disposed = false;
|
||||
var cleanupFn = NIL;
|
||||
return (function() {
|
||||
var runEffect = function() { return (isSxTruthy(!isSxTruthy(disposed)) ? ((isSxTruthy(cleanupFn) ? cekCall(cleanupFn, NIL) : NIL), forEach(function(dep) { return signalRemoveSub(dep, runEffect); }, deps), (deps = []), (function() {
|
||||
var ctx = {["deps"]: [], ["notify"]: runEffect};
|
||||
scopePush("sx-reactive", ctx);
|
||||
return (function() {
|
||||
var result = cekCall(effectFn, NIL);
|
||||
scopePop("sx-reactive");
|
||||
deps = get(ctx, "deps");
|
||||
return (isSxTruthy(isCallable(result)) ? (cleanupFn = result) : NIL);
|
||||
})();
|
||||
})()) : NIL); };
|
||||
runEffect();
|
||||
return (function() {
|
||||
var disposeFn = function() { disposed = true;
|
||||
if (isSxTruthy(cleanupFn)) {
|
||||
cekCall(cleanupFn, NIL);
|
||||
}
|
||||
{ var _c = deps; for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, runEffect); } }
|
||||
return (deps = []); };
|
||||
registerInScope(disposeFn);
|
||||
return disposeFn;
|
||||
})();
|
||||
})();
|
||||
})(); };
|
||||
|
||||
// *batch-depth*
|
||||
var _batchDepth = 0;
|
||||
|
||||
// *batch-queue*
|
||||
var _batchQueue = [];
|
||||
|
||||
// batch
|
||||
var batch = function(thunk) { _batchDepth = (_batchDepth + 1);
|
||||
cekCall(thunk, NIL);
|
||||
_batchDepth = (_batchDepth - 1);
|
||||
return (isSxTruthy((_batchDepth == 0)) ? (function() {
|
||||
var queue = _batchQueue;
|
||||
_batchQueue = [];
|
||||
return (function() {
|
||||
var seen = [];
|
||||
var pending = [];
|
||||
{ var _c = queue; for (var _i = 0; _i < _c.length; _i++) { var s = _c[_i]; { var _c = signalSubscribers(s); for (var _i = 0; _i < _c.length; _i++) { var sub = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(seen, sub)))) {
|
||||
seen.push(sub);
|
||||
pending.push(sub);
|
||||
} } } } }
|
||||
return forEach(function(sub) { return sub(); }, pending);
|
||||
})();
|
||||
})() : NIL); };
|
||||
|
||||
// notify-subscribers
|
||||
var notifySubscribers = function(s) { return (isSxTruthy((_batchDepth > 0)) ? (isSxTruthy(!isSxTruthy(contains(_batchQueue, s))) ? append_b(_batchQueue, s) : NIL) : flushSubscribers(s)); };
|
||||
|
||||
// flush-subscribers
|
||||
var flushSubscribers = function(s) { return forEach(function(sub) { return sub(); }, signalSubscribers(s)); };
|
||||
|
||||
// dispose-computed
|
||||
var disposeComputed = function(s) { return (isSxTruthy(isSignal(s)) ? (forEach(function(dep) { return signalRemoveSub(dep, NIL); }, signalDeps(s)), signalSetDeps(s, [])) : NIL); };
|
||||
|
||||
// with-island-scope
|
||||
var withIslandScope = function(scopeFn, bodyFn) { scopePush("sx-island-scope", scopeFn);
|
||||
return (function() {
|
||||
var result = bodyFn();
|
||||
scopePop("sx-island-scope");
|
||||
return result;
|
||||
})(); };
|
||||
|
||||
// register-in-scope
|
||||
var registerInScope = function(disposable) { return (function() {
|
||||
var collector = sxContext("sx-island-scope", NIL);
|
||||
return (isSxTruthy(collector) ? cekCall(collector, [disposable]) : NIL);
|
||||
})(); };
|
||||
|
||||
// with-marsh-scope
|
||||
var withMarshScope = function(marshEl, bodyFn) { return (function() {
|
||||
var disposers = [];
|
||||
withIslandScope(function(d) { return append_b(disposers, d); }, bodyFn);
|
||||
return domSetData(marshEl, "sx-marsh-disposers", disposers);
|
||||
})(); };
|
||||
|
||||
// dispose-marsh-scope
|
||||
var disposeMarshScope = function(marshEl) { return (function() {
|
||||
var disposers = domGetData(marshEl, "sx-marsh-disposers");
|
||||
return (isSxTruthy(disposers) ? (forEach(function(d) { return cekCall(d, NIL); }, disposers), domSetData(marshEl, "sx-marsh-disposers", NIL)) : NIL);
|
||||
})(); };
|
||||
|
||||
// *store-registry*
|
||||
var _storeRegistry = {};
|
||||
|
||||
// def-store
|
||||
var defStore = function(name, initFn) { return (function() {
|
||||
var registry = _storeRegistry;
|
||||
if (isSxTruthy(!isSxTruthy(dictHas(registry, name)))) {
|
||||
_storeRegistry = assoc(registry, name, cekCall(initFn, NIL));
|
||||
}
|
||||
return get(_storeRegistry, name);
|
||||
})(); };
|
||||
|
||||
// use-store
|
||||
var useStore = function(name) { return (isSxTruthy(dictHas(_storeRegistry, name)) ? get(_storeRegistry, name) : error((String("Store not found: ") + String(name) + String(". Call (def-store ...) before (use-store ...).")))); };
|
||||
|
||||
// clear-stores
|
||||
var clearStores = function() { return (_storeRegistry = {}); };
|
||||
|
||||
// emit-event
|
||||
var emitEvent = function(el, eventName, detail) { return domDispatch(el, eventName, detail); };
|
||||
|
||||
// on-event
|
||||
var onEvent = function(el, eventName, handler) { return domListen(el, eventName, handler); };
|
||||
|
||||
// bridge-event
|
||||
var bridgeEvent = function(el, eventName, targetSignal, transformFn) { return effect(function() { return (function() {
|
||||
var remove = domListen(el, eventName, function(e) { return (function() {
|
||||
var detail = eventDetail(e);
|
||||
var newVal = (isSxTruthy(transformFn) ? cekCall(transformFn, [detail]) : detail);
|
||||
return reset_b(targetSignal, newVal);
|
||||
})(); });
|
||||
return remove;
|
||||
})(); }); };
|
||||
|
||||
// resource
|
||||
var resource = function(fetchFn) { return (function() {
|
||||
var state = signal({["loading"]: true, ["data"]: NIL, ["error"]: NIL});
|
||||
promiseThen(cekCall(fetchFn, NIL), function(data) { return reset_b(state, {["loading"]: false, ["data"]: data, ["error"]: NIL}); }, function(err) { return reset_b(state, {["loading"]: false, ["data"]: NIL, ["error"]: err}); });
|
||||
return state;
|
||||
})(); };
|
||||
|
||||
|
||||
// =========================================================================
|
||||
// Platform interface — DOM adapter (browser-only)
|
||||
// =========================================================================
|
||||
@@ -5787,6 +5823,10 @@ return forEach(function(d) { return invoke(d); }, subDisposers); });
|
||||
}
|
||||
function scheduleIdle(fn) {
|
||||
var cb = _wrapSxFn(fn);
|
||||
if (typeof cb !== "function") {
|
||||
console.error("[sx-ref] scheduleIdle: callback not callable, fn type:", typeof fn, "fn:", fn, "_lambda:", fn && fn._lambda);
|
||||
return;
|
||||
}
|
||||
if (typeof requestIdleCallback !== "undefined") requestIdleCallback(cb);
|
||||
else setTimeout(cb, 0);
|
||||
}
|
||||
@@ -5876,8 +5916,12 @@ return forEach(function(d) { return invoke(d); }, subDisposers); });
|
||||
e.preventDefault();
|
||||
// Re-read href from element at click time (not closed-over value)
|
||||
var liveHref = el.getAttribute("href") || _href;
|
||||
console.log("[sx-debug] bindBoostLink click:", liveHref, "el:", el.tagName, el.textContent.slice(0,30));
|
||||
executeRequest(el, { method: "GET", url: liveHref }).then(function() {
|
||||
console.log("[sx-debug] boost fetch OK, pushState:", liveHref);
|
||||
try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {}
|
||||
}).catch(function(err) {
|
||||
console.error("[sx-debug] boost fetch ERROR:", err);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -5902,21 +5946,25 @@ return forEach(function(d) { return invoke(d); }, subDisposers); });
|
||||
// Re-read href from element at click time (not closed-over value)
|
||||
var liveHref = link.getAttribute("href") || _href;
|
||||
var pathname = urlPathname(liveHref);
|
||||
console.log("[sx-debug] bindClientRouteClick:", pathname, "el:", link.tagName, link.textContent.slice(0,30));
|
||||
// Find target selector: sx-boost ancestor, explicit sx-target, or #main-panel
|
||||
var boostEl = link.closest("[sx-boost]");
|
||||
var targetSel = boostEl ? boostEl.getAttribute("sx-boost") : null;
|
||||
if (!targetSel || targetSel === "true") {
|
||||
targetSel = link.getAttribute("sx-target") || "#main-panel";
|
||||
}
|
||||
console.log("[sx-debug] targetSel:", targetSel, "trying client route...");
|
||||
if (tryClientRoute(pathname, targetSel)) {
|
||||
console.log("[sx-debug] client route SUCCESS, pushState:", liveHref);
|
||||
try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {}
|
||||
if (typeof window !== "undefined") window.scrollTo(0, 0);
|
||||
} else {
|
||||
logInfo("sx:route server " + pathname);
|
||||
console.log("[sx-debug] client route FAILED, server fetch:", liveHref);
|
||||
executeRequest(link, { method: "GET", url: liveHref }).then(function() {
|
||||
console.log("[sx-debug] server fetch OK, pushState:", liveHref);
|
||||
try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {}
|
||||
}).catch(function(err) {
|
||||
logWarn("sx:route server fetch error: " + (err && err.message ? err.message : err));
|
||||
console.error("[sx-debug] server fetch ERROR:", err);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -6290,27 +6338,6 @@ return forEach(function(d) { return invoke(d); }, subDisposers); });
|
||||
|
||||
|
||||
|
||||
// =========================================================================
|
||||
// Platform: CEK module — explicit CEK machine
|
||||
// =========================================================================
|
||||
|
||||
// Standalone aliases for primitives used by cek.sx / frames.sx
|
||||
var inc = PRIMITIVES["inc"];
|
||||
var dec = PRIMITIVES["dec"];
|
||||
var zip_pairs = PRIMITIVES["zip-pairs"];
|
||||
|
||||
var continuation_p = PRIMITIVES["continuation?"];
|
||||
|
||||
function makeCekContinuation(captured, restKont) {
|
||||
var c = new Continuation(function(v) { return v !== undefined ? v : NIL; });
|
||||
c._cek_data = {"captured": captured, "rest-kont": restKont};
|
||||
return c;
|
||||
}
|
||||
function continuationData(c) {
|
||||
return (c && c._cek_data) ? c._cek_data : {};
|
||||
}
|
||||
|
||||
|
||||
// =========================================================================
|
||||
// Post-transpilation fixups
|
||||
// =========================================================================
|
||||
|
||||
Reference in New Issue
Block a user