Frame-based dynamic scope: 870/870 — all tests passing
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 13m34s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 13m34s
provide/context and scope/emit!/emitted now use CEK continuation frames instead of an imperative global stack. Scope state is part of the continuation — captured by shift, restored by k invocation. New frame types: - ProvideFrame: holds name + value, consumed when body completes - ScopeAccFrame: holds name + mutable emitted list New CEK special forms: - context: walks kont for nearest ProvideFrame, returns value - emit!: walks kont for nearest ScopeAccFrame, appends to emitted - emitted: walks kont for nearest ScopeAccFrame, returns list Kont walkers: kont-find-provide, kont-find-scope-acc This fixes the last 2 test failures: - provide survives resume: scope captured by shift, restored by k - scope and emit across shift: accumulator preserved in continuation JS Full: 870/870 (100%) JS Standard: 747/747 (100%) Python: 679/679 (100%) 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 NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
|
||||||
var SX_VERSION = "2026-03-15T14:20:13Z";
|
var SX_VERSION = "2026-03-15T14:39:41Z";
|
||||||
|
|
||||||
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); }
|
||||||
@@ -913,6 +913,14 @@ PRIMITIVES["make-every-frame"] = makeEveryFrame;
|
|||||||
var makeScopeFrame = function(name, remaining, env) { return {"type": "scope", "name": name, "remaining": remaining, "env": env}; };
|
var makeScopeFrame = function(name, remaining, env) { return {"type": "scope", "name": name, "remaining": remaining, "env": env}; };
|
||||||
PRIMITIVES["make-scope-frame"] = makeScopeFrame;
|
PRIMITIVES["make-scope-frame"] = makeScopeFrame;
|
||||||
|
|
||||||
|
// make-provide-frame
|
||||||
|
var makeProvideFrame = function(name, value, remaining, env) { return {"type": "provide", "name": name, "value": value, "remaining": remaining, "env": env}; };
|
||||||
|
PRIMITIVES["make-provide-frame"] = makeProvideFrame;
|
||||||
|
|
||||||
|
// make-scope-acc-frame
|
||||||
|
var makeScopeAccFrame = function(name, value, remaining, env) { return {"type": "scope-acc", "name": name, "value": sxOr(value, NIL), "emitted": [], "remaining": remaining, "env": env}; };
|
||||||
|
PRIMITIVES["make-scope-acc-frame"] = makeScopeAccFrame;
|
||||||
|
|
||||||
// make-reset-frame
|
// make-reset-frame
|
||||||
var makeResetFrame = function(env) { return {"type": "reset", "env": env}; };
|
var makeResetFrame = function(env) { return {"type": "reset", "env": env}; };
|
||||||
PRIMITIVES["make-reset-frame"] = makeResetFrame;
|
PRIMITIVES["make-reset-frame"] = makeResetFrame;
|
||||||
@@ -974,6 +982,20 @@ PRIMITIVES["scan"] = scan;
|
|||||||
return scan(kont, []); };
|
return scan(kont, []); };
|
||||||
PRIMITIVES["kont-capture-to-reset"] = kontCaptureToReset;
|
PRIMITIVES["kont-capture-to-reset"] = kontCaptureToReset;
|
||||||
|
|
||||||
|
// kont-find-provide
|
||||||
|
var kontFindProvide = function(kont, name) { return (isSxTruthy(isEmpty(kont)) ? NIL : (function() {
|
||||||
|
var frame = first(kont);
|
||||||
|
return (isSxTruthy((isSxTruthy((frameType(frame) == "provide")) && (get(frame, "name") == name))) ? frame : kontFindProvide(rest(kont), name));
|
||||||
|
})()); };
|
||||||
|
PRIMITIVES["kont-find-provide"] = kontFindProvide;
|
||||||
|
|
||||||
|
// kont-find-scope-acc
|
||||||
|
var kontFindScopeAcc = function(kont, name) { return (isSxTruthy(isEmpty(kont)) ? NIL : (function() {
|
||||||
|
var frame = first(kont);
|
||||||
|
return (isSxTruthy((isSxTruthy((frameType(frame) == "scope-acc")) && (get(frame, "name") == name))) ? frame : kontFindScopeAcc(rest(kont), name));
|
||||||
|
})()); };
|
||||||
|
PRIMITIVES["kont-find-scope-acc"] = kontFindScopeAcc;
|
||||||
|
|
||||||
// has-reactive-reset-frame?
|
// has-reactive-reset-frame?
|
||||||
var hasReactiveResetFrame_p = function(kont) { return (isSxTruthy(isEmpty(kont)) ? false : (isSxTruthy((frameType(first(kont)) == "reactive-reset")) ? true : hasReactiveResetFrame_p(rest(kont)))); };
|
var hasReactiveResetFrame_p = function(kont) { return (isSxTruthy(isEmpty(kont)) ? false : (isSxTruthy((frameType(first(kont)) == "reactive-reset")) ? true : hasReactiveResetFrame_p(rest(kont)))); };
|
||||||
PRIMITIVES["has-reactive-reset-frame?"] = hasReactiveResetFrame_p;
|
PRIMITIVES["has-reactive-reset-frame?"] = hasReactiveResetFrame_p;
|
||||||
@@ -1403,10 +1425,10 @@ PRIMITIVES["step-eval"] = stepEval;
|
|||||||
var args = rest(expr);
|
var args = rest(expr);
|
||||||
return (isSxTruthy(!isSxTruthy(sxOr((typeOf(head) == "symbol"), (typeOf(head) == "lambda"), (typeOf(head) == "list")))) ? (isSxTruthy(isEmpty(expr)) ? makeCekValue([], env, kont) : makeCekState(first(expr), env, kontPush(makeMapFrame(NIL, rest(expr), [], env), kont))) : (isSxTruthy((typeOf(head) == "symbol")) ? (function() {
|
return (isSxTruthy(!isSxTruthy(sxOr((typeOf(head) == "symbol"), (typeOf(head) == "lambda"), (typeOf(head) == "list")))) ? (isSxTruthy(isEmpty(expr)) ? makeCekValue([], env, kont) : makeCekState(first(expr), env, kontPush(makeMapFrame(NIL, rest(expr), [], env), kont))) : (isSxTruthy((typeOf(head) == "symbol")) ? (function() {
|
||||||
var name = symbolName(head);
|
var name = symbolName(head);
|
||||||
return (isSxTruthy((name == "if")) ? stepSfIf(args, env, kont) : (isSxTruthy((name == "when")) ? stepSfWhen(args, env, kont) : (isSxTruthy((name == "cond")) ? stepSfCond(args, env, kont) : (isSxTruthy((name == "case")) ? stepSfCase(args, env, kont) : (isSxTruthy((name == "and")) ? stepSfAnd(args, env, kont) : (isSxTruthy((name == "or")) ? stepSfOr(args, env, kont) : (isSxTruthy((name == "let")) ? stepSfLet(args, env, kont) : (isSxTruthy((name == "let*")) ? stepSfLet(args, env, kont) : (isSxTruthy((name == "lambda")) ? stepSfLambda(args, env, kont) : (isSxTruthy((name == "fn")) ? stepSfLambda(args, env, kont) : (isSxTruthy((name == "define")) ? stepSfDefine(args, env, kont) : (isSxTruthy((name == "defcomp")) ? makeCekValue(sfDefcomp(args, env), env, kont) : (isSxTruthy((name == "defisland")) ? makeCekValue(sfDefisland(args, env), env, kont) : (isSxTruthy((name == "defmacro")) ? makeCekValue(sfDefmacro(args, env), env, kont) : (isSxTruthy((name == "defstyle")) ? makeCekValue(sfDefstyle(args, env), env, kont) : (isSxTruthy((name == "defhandler")) ? makeCekValue(sfDefhandler(args, env), env, kont) : (isSxTruthy((name == "defpage")) ? makeCekValue(sfDefpage(args, env), env, kont) : (isSxTruthy((name == "defquery")) ? makeCekValue(sfDefquery(args, env), env, kont) : (isSxTruthy((name == "defaction")) ? makeCekValue(sfDefaction(args, env), env, kont) : (isSxTruthy((name == "deftype")) ? makeCekValue(sfDeftype(args, env), env, kont) : (isSxTruthy((name == "defeffect")) ? makeCekValue(sfDefeffect(args, env), env, kont) : (isSxTruthy((name == "begin")) ? stepSfBegin(args, env, kont) : (isSxTruthy((name == "do")) ? stepSfBegin(args, env, kont) : (isSxTruthy((name == "quote")) ? makeCekValue((isSxTruthy(isEmpty(args)) ? NIL : first(args)), env, kont) : (isSxTruthy((name == "quasiquote")) ? makeCekValue(qqExpand(first(args), env), env, kont) : (isSxTruthy((name == "->")) ? stepSfThreadFirst(args, env, kont) : (isSxTruthy((name == "set!")) ? stepSfSet(args, env, kont) : (isSxTruthy((name == "letrec")) ? makeCekValue(sfLetrec(args, env), env, kont) : (isSxTruthy((name == "reset")) ? stepSfReset(args, env, kont) : (isSxTruthy((name == "shift")) ? stepSfShift(args, env, kont) : (isSxTruthy((name == "deref")) ? stepSfDeref(args, env, kont) : (isSxTruthy((name == "scope")) ? stepSfScope(args, env, kont) : (isSxTruthy((name == "provide")) ? stepSfProvide(args, env, kont) : (isSxTruthy((name == "dynamic-wind")) ? makeCekValue(sfDynamicWind(args, env), env, kont) : (isSxTruthy((name == "map")) ? stepHoMap(args, env, kont) : (isSxTruthy((name == "map-indexed")) ? stepHoMapIndexed(args, env, kont) : (isSxTruthy((name == "filter")) ? stepHoFilter(args, env, kont) : (isSxTruthy((name == "reduce")) ? stepHoReduce(args, env, kont) : (isSxTruthy((name == "some")) ? stepHoSome(args, env, kont) : (isSxTruthy((name == "every?")) ? stepHoEvery(args, env, kont) : (isSxTruthy((name == "for-each")) ? stepHoForEach(args, env, kont) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? (function() {
|
return (isSxTruthy((name == "if")) ? stepSfIf(args, env, kont) : (isSxTruthy((name == "when")) ? stepSfWhen(args, env, kont) : (isSxTruthy((name == "cond")) ? stepSfCond(args, env, kont) : (isSxTruthy((name == "case")) ? stepSfCase(args, env, kont) : (isSxTruthy((name == "and")) ? stepSfAnd(args, env, kont) : (isSxTruthy((name == "or")) ? stepSfOr(args, env, kont) : (isSxTruthy((name == "let")) ? stepSfLet(args, env, kont) : (isSxTruthy((name == "let*")) ? stepSfLet(args, env, kont) : (isSxTruthy((name == "lambda")) ? stepSfLambda(args, env, kont) : (isSxTruthy((name == "fn")) ? stepSfLambda(args, env, kont) : (isSxTruthy((name == "define")) ? stepSfDefine(args, env, kont) : (isSxTruthy((name == "defcomp")) ? makeCekValue(sfDefcomp(args, env), env, kont) : (isSxTruthy((name == "defisland")) ? makeCekValue(sfDefisland(args, env), env, kont) : (isSxTruthy((name == "defmacro")) ? makeCekValue(sfDefmacro(args, env), env, kont) : (isSxTruthy((name == "defstyle")) ? makeCekValue(sfDefstyle(args, env), env, kont) : (isSxTruthy((name == "defhandler")) ? makeCekValue(sfDefhandler(args, env), env, kont) : (isSxTruthy((name == "defpage")) ? makeCekValue(sfDefpage(args, env), env, kont) : (isSxTruthy((name == "defquery")) ? makeCekValue(sfDefquery(args, env), env, kont) : (isSxTruthy((name == "defaction")) ? makeCekValue(sfDefaction(args, env), env, kont) : (isSxTruthy((name == "deftype")) ? makeCekValue(sfDeftype(args, env), env, kont) : (isSxTruthy((name == "defeffect")) ? makeCekValue(sfDefeffect(args, env), env, kont) : (isSxTruthy((name == "begin")) ? stepSfBegin(args, env, kont) : (isSxTruthy((name == "do")) ? stepSfBegin(args, env, kont) : (isSxTruthy((name == "quote")) ? makeCekValue((isSxTruthy(isEmpty(args)) ? NIL : first(args)), env, kont) : (isSxTruthy((name == "quasiquote")) ? makeCekValue(qqExpand(first(args), env), env, kont) : (isSxTruthy((name == "->")) ? stepSfThreadFirst(args, env, kont) : (isSxTruthy((name == "set!")) ? stepSfSet(args, env, kont) : (isSxTruthy((name == "letrec")) ? makeCekValue(sfLetrec(args, env), env, kont) : (isSxTruthy((name == "reset")) ? stepSfReset(args, env, kont) : (isSxTruthy((name == "shift")) ? stepSfShift(args, env, kont) : (isSxTruthy((name == "deref")) ? stepSfDeref(args, env, kont) : (isSxTruthy((name == "scope")) ? stepSfScope(args, env, kont) : (isSxTruthy((name == "provide")) ? stepSfProvide(args, env, kont) : (isSxTruthy((name == "context")) ? stepSfContext(args, env, kont) : (isSxTruthy((name == "emit!")) ? stepSfEmit(args, env, kont) : (isSxTruthy((name == "emitted")) ? stepSfEmitted(args, env, kont) : (isSxTruthy((name == "dynamic-wind")) ? makeCekValue(sfDynamicWind(args, env), env, kont) : (isSxTruthy((name == "map")) ? stepHoMap(args, env, kont) : (isSxTruthy((name == "map-indexed")) ? stepHoMapIndexed(args, env, kont) : (isSxTruthy((name == "filter")) ? stepHoFilter(args, env, kont) : (isSxTruthy((name == "reduce")) ? stepHoReduce(args, env, kont) : (isSxTruthy((name == "some")) ? stepHoSome(args, env, kont) : (isSxTruthy((name == "every?")) ? stepHoEvery(args, env, kont) : (isSxTruthy((name == "for-each")) ? stepHoForEach(args, env, kont) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? (function() {
|
||||||
var mac = envGet(env, name);
|
var mac = envGet(env, name);
|
||||||
return makeCekState(expandMacro(mac, args, env), env, kont);
|
return makeCekState(expandMacro(mac, args, env), env, kont);
|
||||||
})() : (isSxTruthy((isSxTruthy(renderActiveP()) && isRenderExpr(expr))) ? makeCekValue(renderExpr(expr, env), env, kont) : stepEvalCall(head, args, env, kont))))))))))))))))))))))))))))))))))))))))))));
|
})() : (isSxTruthy((isSxTruthy(renderActiveP()) && isRenderExpr(expr))) ? makeCekValue(renderExpr(expr, env), env, kont) : stepEvalCall(head, args, env, kont)))))))))))))))))))))))))))))))))))))))))))))));
|
||||||
})() : stepEvalCall(head, args, env, kont)));
|
})() : stepEvalCall(head, args, env, kont)));
|
||||||
})(); };
|
})(); };
|
||||||
PRIMITIVES["step-eval-list"] = stepEvalList;
|
PRIMITIVES["step-eval-list"] = stepEvalList;
|
||||||
@@ -1492,13 +1514,51 @@ PRIMITIVES["step-sf-thread-first"] = stepSfThreadFirst;
|
|||||||
PRIMITIVES["step-sf-lambda"] = stepSfLambda;
|
PRIMITIVES["step-sf-lambda"] = stepSfLambda;
|
||||||
|
|
||||||
// step-sf-scope
|
// step-sf-scope
|
||||||
var stepSfScope = function(args, env, kont) { return makeCekValue(sfScope(args, env), env, kont); };
|
var stepSfScope = function(args, env, kont) { return (function() {
|
||||||
|
var name = trampoline(evalExpr(first(args), env));
|
||||||
|
var restArgs = slice(args, 1);
|
||||||
|
var val = NIL;
|
||||||
|
var body = NIL;
|
||||||
|
(isSxTruthy((isSxTruthy((len(restArgs) >= 2)) && isSxTruthy((typeOf(first(restArgs)) == "keyword")) && (keywordName(first(restArgs)) == "value"))) ? ((val = trampoline(evalExpr(nth(restArgs, 1), env))), (body = slice(restArgs, 2))) : (body = restArgs));
|
||||||
|
return (isSxTruthy(isEmpty(body)) ? makeCekValue(NIL, env, kont) : (isSxTruthy((len(body) == 1)) ? makeCekState(first(body), env, kontPush(makeScopeAccFrame(name, val, [], env), kont)) : makeCekState(first(body), env, kontPush(makeScopeAccFrame(name, val, rest(body), env), kont))));
|
||||||
|
})(); };
|
||||||
PRIMITIVES["step-sf-scope"] = stepSfScope;
|
PRIMITIVES["step-sf-scope"] = stepSfScope;
|
||||||
|
|
||||||
// step-sf-provide
|
// step-sf-provide
|
||||||
var stepSfProvide = function(args, env, kont) { return makeCekValue(sfProvide(args, env), env, kont); };
|
var stepSfProvide = function(args, env, kont) { return (function() {
|
||||||
|
var name = trampoline(evalExpr(first(args), env));
|
||||||
|
var val = trampoline(evalExpr(nth(args, 1), env));
|
||||||
|
var body = slice(args, 2);
|
||||||
|
return (isSxTruthy(isEmpty(body)) ? makeCekValue(NIL, env, kont) : (isSxTruthy((len(body) == 1)) ? makeCekState(first(body), env, kontPush(makeProvideFrame(name, val, [], env), kont)) : makeCekState(first(body), env, kontPush(makeProvideFrame(name, val, rest(body), env), kont))));
|
||||||
|
})(); };
|
||||||
PRIMITIVES["step-sf-provide"] = stepSfProvide;
|
PRIMITIVES["step-sf-provide"] = stepSfProvide;
|
||||||
|
|
||||||
|
// step-sf-context
|
||||||
|
var stepSfContext = function(args, env, kont) { return (function() {
|
||||||
|
var name = trampoline(evalExpr(first(args), env));
|
||||||
|
var defaultVal = (isSxTruthy((len(args) >= 2)) ? trampoline(evalExpr(nth(args, 1), env)) : NIL);
|
||||||
|
var frame = kontFindProvide(kont, name);
|
||||||
|
return (isSxTruthy(frame) ? makeCekValue(get(frame, "value"), env, kont) : (isSxTruthy((len(args) >= 2)) ? makeCekValue(defaultVal, env, kont) : error((String("No provider for: ") + String(name)))));
|
||||||
|
})(); };
|
||||||
|
PRIMITIVES["step-sf-context"] = stepSfContext;
|
||||||
|
|
||||||
|
// step-sf-emit
|
||||||
|
var stepSfEmit = function(args, env, kont) { return (function() {
|
||||||
|
var name = trampoline(evalExpr(first(args), env));
|
||||||
|
var val = trampoline(evalExpr(nth(args, 1), env));
|
||||||
|
var frame = kontFindScopeAcc(kont, name);
|
||||||
|
return (isSxTruthy(frame) ? (append_b(get(frame, "emitted"), val), makeCekValue(NIL, env, kont)) : error((String("No scope for emit!: ") + String(name))));
|
||||||
|
})(); };
|
||||||
|
PRIMITIVES["step-sf-emit"] = stepSfEmit;
|
||||||
|
|
||||||
|
// step-sf-emitted
|
||||||
|
var stepSfEmitted = function(args, env, kont) { return (function() {
|
||||||
|
var name = trampoline(evalExpr(first(args), env));
|
||||||
|
var frame = kontFindScopeAcc(kont, name);
|
||||||
|
return (isSxTruthy(frame) ? makeCekValue(get(frame, "emitted"), env, kont) : error((String("No scope for emitted: ") + String(name))));
|
||||||
|
})(); };
|
||||||
|
PRIMITIVES["step-sf-emitted"] = stepSfEmitted;
|
||||||
|
|
||||||
// step-sf-reset
|
// step-sf-reset
|
||||||
var stepSfReset = function(args, env, kont) { return makeCekState(first(args), env, kontPush(makeResetFrame(env), kont)); };
|
var stepSfReset = function(args, env, kont) { return makeCekState(first(args), env, kontPush(makeResetFrame(env), kont)); };
|
||||||
PRIMITIVES["step-sf-reset"] = stepSfReset;
|
PRIMITIVES["step-sf-reset"] = stepSfReset;
|
||||||
@@ -1780,6 +1840,18 @@ PRIMITIVES["step-ho-for-each"] = stepHoForEach;
|
|||||||
var remaining = get(frame, "remaining");
|
var remaining = get(frame, "remaining");
|
||||||
var fenv = get(frame, "env");
|
var fenv = get(frame, "env");
|
||||||
return (isSxTruthy(isEmpty(remaining)) ? (scopePop(name), makeCekValue(value, fenv, restK)) : makeCekState(first(remaining), fenv, kontPush(makeScopeFrame(name, rest(remaining), fenv), restK)));
|
return (isSxTruthy(isEmpty(remaining)) ? (scopePop(name), makeCekValue(value, fenv, restK)) : makeCekState(first(remaining), fenv, kontPush(makeScopeFrame(name, rest(remaining), fenv), restK)));
|
||||||
|
})() : (isSxTruthy((ft == "provide")) ? (function() {
|
||||||
|
var remaining = get(frame, "remaining");
|
||||||
|
var fenv = get(frame, "env");
|
||||||
|
return (isSxTruthy(isEmpty(remaining)) ? makeCekValue(value, fenv, restK) : makeCekState(first(remaining), fenv, kontPush(makeProvideFrame(get(frame, "name"), get(frame, "value"), rest(remaining), fenv), restK)));
|
||||||
|
})() : (isSxTruthy((ft == "scope-acc")) ? (function() {
|
||||||
|
var remaining = get(frame, "remaining");
|
||||||
|
var fenv = get(frame, "env");
|
||||||
|
return (isSxTruthy(isEmpty(remaining)) ? makeCekValue(value, fenv, restK) : makeCekState(first(remaining), fenv, kontPush((function() {
|
||||||
|
var newFrame = makeScopeAccFrame(get(frame, "name"), get(frame, "value"), rest(remaining), fenv);
|
||||||
|
newFrame["emitted"] = get(frame, "emitted");
|
||||||
|
return newFrame;
|
||||||
|
})(), restK)));
|
||||||
})() : (isSxTruthy((ft == "map")) ? (function() {
|
})() : (isSxTruthy((ft == "map")) ? (function() {
|
||||||
var f = get(frame, "f");
|
var f = get(frame, "f");
|
||||||
var remaining = get(frame, "remaining");
|
var remaining = get(frame, "remaining");
|
||||||
@@ -1824,7 +1896,7 @@ PRIMITIVES["step-ho-for-each"] = stepHoForEach;
|
|||||||
var remaining = get(frame, "remaining");
|
var remaining = get(frame, "remaining");
|
||||||
var fenv = get(frame, "env");
|
var fenv = get(frame, "env");
|
||||||
return (isSxTruthy(!isSxTruthy(value)) ? makeCekValue(false, fenv, restK) : (isSxTruthy(isEmpty(remaining)) ? makeCekValue(true, fenv, restK) : continueWithCall(f, [first(remaining)], fenv, [], kontPush(makeEveryFrame(f, rest(remaining), fenv), restK))));
|
return (isSxTruthy(!isSxTruthy(value)) ? makeCekValue(false, fenv, restK) : (isSxTruthy(isEmpty(remaining)) ? makeCekValue(true, fenv, restK) : continueWithCall(f, [first(remaining)], fenv, [], kontPush(makeEveryFrame(f, rest(remaining), fenv), restK))));
|
||||||
})() : error((String("Unknown frame type: ") + String(ft)))))))))))))))))))))))))));
|
})() : error((String("Unknown frame type: ") + String(ft)))))))))))))))))))))))))))));
|
||||||
})());
|
})());
|
||||||
})(); };
|
})(); };
|
||||||
PRIMITIVES["step-continue"] = stepContinue;
|
PRIMITIVES["step-continue"] = stepContinue;
|
||||||
|
|||||||
@@ -152,11 +152,22 @@
|
|||||||
(fn (f remaining env)
|
(fn (f remaining env)
|
||||||
{:type "every" :f f :remaining remaining :env env}))
|
{:type "every" :f f :remaining remaining :env env}))
|
||||||
|
|
||||||
;; ScopeFrame: scope-pop! when frame pops
|
;; ScopeFrame: remaining body expressions for scope special form
|
||||||
(define make-scope-frame
|
(define make-scope-frame
|
||||||
(fn (name remaining env)
|
(fn (name remaining env)
|
||||||
{:type "scope" :name name :remaining remaining :env env}))
|
{:type "scope" :name name :remaining remaining :env env}))
|
||||||
|
|
||||||
|
;; ProvideFrame: dynamic variable binding (context reads this from kont)
|
||||||
|
(define make-provide-frame
|
||||||
|
(fn (name value remaining env)
|
||||||
|
{:type "provide" :name name :value value :remaining remaining :env env}))
|
||||||
|
|
||||||
|
;; ScopeAccFrame: accumulator scope (emit! appends, emitted reads)
|
||||||
|
(define make-scope-acc-frame
|
||||||
|
(fn (name value remaining env)
|
||||||
|
{:type "scope-acc" :name name :value (or value nil)
|
||||||
|
:emitted (list) :remaining remaining :env env}))
|
||||||
|
|
||||||
;; ResetFrame: delimiter for shift/reset continuations
|
;; ResetFrame: delimiter for shift/reset continuations
|
||||||
(define make-reset-frame
|
(define make-reset-frame
|
||||||
(fn (env)
|
(fn (env)
|
||||||
@@ -253,6 +264,26 @@
|
|||||||
(scan (rest k) (append captured (list frame))))))))
|
(scan (rest k) (append captured (list frame))))))))
|
||||||
(scan kont (list))))
|
(scan kont (list))))
|
||||||
|
|
||||||
|
;; Walk kont for nearest ProvideFrame with matching name
|
||||||
|
(define kont-find-provide
|
||||||
|
(fn (kont name)
|
||||||
|
(if (empty? kont) nil
|
||||||
|
(let ((frame (first kont)))
|
||||||
|
(if (and (= (frame-type frame) "provide")
|
||||||
|
(= (get frame "name") name))
|
||||||
|
frame
|
||||||
|
(kont-find-provide (rest kont) name))))))
|
||||||
|
|
||||||
|
;; Walk kont for nearest ScopeAccFrame with matching name
|
||||||
|
(define kont-find-scope-acc
|
||||||
|
(fn (kont name)
|
||||||
|
(if (empty? kont) nil
|
||||||
|
(let ((frame (first kont)))
|
||||||
|
(if (and (= (frame-type frame) "scope-acc")
|
||||||
|
(= (get frame "name") name))
|
||||||
|
frame
|
||||||
|
(kont-find-scope-acc (rest kont) name))))))
|
||||||
|
|
||||||
;; Check if a ReactiveResetFrame exists anywhere in the continuation
|
;; Check if a ReactiveResetFrame exists anywhere in the continuation
|
||||||
(define has-reactive-reset-frame?
|
(define has-reactive-reset-frame?
|
||||||
(fn (kont)
|
(fn (kont)
|
||||||
@@ -1254,9 +1285,12 @@
|
|||||||
;; Reactive deref-as-shift
|
;; Reactive deref-as-shift
|
||||||
(= name "deref") (step-sf-deref args env kont)
|
(= name "deref") (step-sf-deref args env kont)
|
||||||
|
|
||||||
;; Scoped effects
|
;; Scoped effects — frame-based dynamic scope
|
||||||
(= name "scope") (step-sf-scope args env kont)
|
(= name "scope") (step-sf-scope args env kont)
|
||||||
(= name "provide") (step-sf-provide args env kont)
|
(= name "provide") (step-sf-provide args env kont)
|
||||||
|
(= name "context") (step-sf-context args env kont)
|
||||||
|
(= name "emit!") (step-sf-emit args env kont)
|
||||||
|
(= name "emitted") (step-sf-emitted args env kont)
|
||||||
|
|
||||||
;; Dynamic wind
|
;; Dynamic wind
|
||||||
(= name "dynamic-wind") (make-cek-value (sf-dynamic-wind args env) env kont)
|
(= name "dynamic-wind") (make-cek-value (sf-dynamic-wind args env) env kont)
|
||||||
@@ -1460,15 +1494,83 @@
|
|||||||
(make-cek-value (sf-lambda args env) env kont)))
|
(make-cek-value (sf-lambda args env) env kont)))
|
||||||
|
|
||||||
;; scope: evaluate name, then push ScopeFrame
|
;; scope: evaluate name, then push ScopeFrame
|
||||||
|
;; scope: push ScopeAccFrame, evaluate body. emit!/emitted walk kont.
|
||||||
|
;; (scope name body...) or (scope name :value v body...)
|
||||||
(define step-sf-scope
|
(define step-sf-scope
|
||||||
(fn (args env kont)
|
(fn (args env kont)
|
||||||
;; Delegate to existing sf-scope for now — scope involves mutation
|
(let ((name (trampoline (eval-expr (first args) env)))
|
||||||
(make-cek-value (sf-scope args env) env kont)))
|
(rest-args (slice args 1))
|
||||||
|
(val nil)
|
||||||
|
(body nil))
|
||||||
|
;; Check for :value keyword
|
||||||
|
(if (and (>= (len rest-args) 2)
|
||||||
|
(= (type-of (first rest-args)) "keyword")
|
||||||
|
(= (keyword-name (first rest-args)) "value"))
|
||||||
|
(do (set! val (trampoline (eval-expr (nth rest-args 1) env)))
|
||||||
|
(set! body (slice rest-args 2)))
|
||||||
|
(set! body rest-args))
|
||||||
|
;; Push ScopeAccFrame and start evaluating body
|
||||||
|
(if (empty? body)
|
||||||
|
(make-cek-value nil env kont)
|
||||||
|
(if (= (len body) 1)
|
||||||
|
(make-cek-state (first body) env
|
||||||
|
(kont-push (make-scope-acc-frame name val (list) env) kont))
|
||||||
|
(make-cek-state (first body) env
|
||||||
|
(kont-push
|
||||||
|
(make-scope-acc-frame name val (rest body) env)
|
||||||
|
kont)))))))
|
||||||
|
|
||||||
;; provide: delegate to existing handler
|
;; provide: push ProvideFrame, evaluate body. context walks kont to read.
|
||||||
|
;; (provide name value body...)
|
||||||
(define step-sf-provide
|
(define step-sf-provide
|
||||||
(fn (args env kont)
|
(fn (args env kont)
|
||||||
(make-cek-value (sf-provide args env) env kont)))
|
(let ((name (trampoline (eval-expr (first args) env)))
|
||||||
|
(val (trampoline (eval-expr (nth args 1) env)))
|
||||||
|
(body (slice args 2)))
|
||||||
|
;; Push ProvideFrame and start evaluating body
|
||||||
|
(if (empty? body)
|
||||||
|
(make-cek-value nil env kont)
|
||||||
|
(if (= (len body) 1)
|
||||||
|
(make-cek-state (first body) env
|
||||||
|
(kont-push (make-provide-frame name val (list) env) kont))
|
||||||
|
(make-cek-state (first body) env
|
||||||
|
(kont-push
|
||||||
|
(make-provide-frame name val (rest body) env)
|
||||||
|
kont)))))))
|
||||||
|
|
||||||
|
;; context: walk kont for nearest ProvideFrame with matching name
|
||||||
|
(define step-sf-context
|
||||||
|
(fn (args env kont)
|
||||||
|
(let ((name (trampoline (eval-expr (first args) env)))
|
||||||
|
(default-val (if (>= (len args) 2)
|
||||||
|
(trampoline (eval-expr (nth args 1) env))
|
||||||
|
nil))
|
||||||
|
(frame (kont-find-provide kont name)))
|
||||||
|
(if frame
|
||||||
|
(make-cek-value (get frame "value") env kont)
|
||||||
|
(if (>= (len args) 2)
|
||||||
|
(make-cek-value default-val env kont)
|
||||||
|
(error (str "No provider for: " name)))))))
|
||||||
|
|
||||||
|
;; emit!: walk kont for nearest ScopeAccFrame, append value
|
||||||
|
(define step-sf-emit
|
||||||
|
(fn (args env kont)
|
||||||
|
(let ((name (trampoline (eval-expr (first args) env)))
|
||||||
|
(val (trampoline (eval-expr (nth args 1) env)))
|
||||||
|
(frame (kont-find-scope-acc kont name)))
|
||||||
|
(if frame
|
||||||
|
(do (append! (get frame "emitted") val)
|
||||||
|
(make-cek-value nil env kont))
|
||||||
|
(error (str "No scope for emit!: " name))))))
|
||||||
|
|
||||||
|
;; emitted: walk kont for nearest ScopeAccFrame, return accumulated list
|
||||||
|
(define step-sf-emitted
|
||||||
|
(fn (args env kont)
|
||||||
|
(let ((name (trampoline (eval-expr (first args) env)))
|
||||||
|
(frame (kont-find-scope-acc kont name)))
|
||||||
|
(if frame
|
||||||
|
(make-cek-value (get frame "emitted") env kont)
|
||||||
|
(error (str "No scope for emitted: " name))))))
|
||||||
|
|
||||||
;; reset: push ResetFrame, evaluate body
|
;; reset: push ResetFrame, evaluate body
|
||||||
(define step-sf-reset
|
(define step-sf-reset
|
||||||
@@ -2013,6 +2115,41 @@
|
|||||||
(make-scope-frame name (rest remaining) fenv)
|
(make-scope-frame name (rest remaining) fenv)
|
||||||
rest-k))))
|
rest-k))))
|
||||||
|
|
||||||
|
;; --- ProvideFrame: body expression evaluated ---
|
||||||
|
(= ft "provide")
|
||||||
|
(let ((remaining (get frame "remaining"))
|
||||||
|
(fenv (get frame "env")))
|
||||||
|
(if (empty? remaining)
|
||||||
|
;; Body done — return value, frame consumed
|
||||||
|
(make-cek-value value fenv rest-k)
|
||||||
|
;; More body expressions — keep frame on kont
|
||||||
|
(make-cek-state
|
||||||
|
(first remaining) fenv
|
||||||
|
(kont-push
|
||||||
|
(make-provide-frame
|
||||||
|
(get frame "name") (get frame "value")
|
||||||
|
(rest remaining) fenv)
|
||||||
|
rest-k))))
|
||||||
|
|
||||||
|
;; --- ScopeAccFrame: body expression evaluated ---
|
||||||
|
(= ft "scope-acc")
|
||||||
|
(let ((remaining (get frame "remaining"))
|
||||||
|
(fenv (get frame "env")))
|
||||||
|
(if (empty? remaining)
|
||||||
|
;; Body done — return value, frame consumed
|
||||||
|
(make-cek-value value fenv rest-k)
|
||||||
|
;; More body expressions — carry emitted list forward
|
||||||
|
(make-cek-state
|
||||||
|
(first remaining) fenv
|
||||||
|
(kont-push
|
||||||
|
(let ((new-frame (make-scope-acc-frame
|
||||||
|
(get frame "name") (get frame "value")
|
||||||
|
(rest remaining) fenv)))
|
||||||
|
;; Preserve accumulated emitted from current frame
|
||||||
|
(dict-set! new-frame "emitted" (get frame "emitted"))
|
||||||
|
new-frame)
|
||||||
|
rest-k))))
|
||||||
|
|
||||||
;; --- MapFrame: callback result for map/map-indexed ---
|
;; --- MapFrame: callback result for map/map-indexed ---
|
||||||
(= ft "map")
|
(= ft "map")
|
||||||
(let ((f (get frame "f"))
|
(let ((f (get frame "f"))
|
||||||
|
|||||||
@@ -491,6 +491,78 @@
|
|||||||
(td :class "px-3 py-2 text-stone-600" "Full compiled pipeline. JIT compilation, cached WASM functions, near-native rendering speed."))))))
|
(td :class "px-3 py-2 text-stone-600" "Full compiled pipeline. JIT compilation, cached WASM functions, near-native rendering speed."))))))
|
||||||
|
|
||||||
|
|
||||||
|
;; -----------------------------------------------------------------------
|
||||||
|
;; WASM responses — htmx with compiled components
|
||||||
|
;; -----------------------------------------------------------------------
|
||||||
|
|
||||||
|
(~docs/section :title "WASM Responses" :id "wasm-responses"
|
||||||
|
|
||||||
|
(p "The server doesn't have to send HTML fragments for HTMX-style swaps. "
|
||||||
|
"It can send compiled WASM functions that produce DOM directly, "
|
||||||
|
"or \u2014 when the client already has the components cached \u2014 "
|
||||||
|
"just the data as gzipped bytecode.")
|
||||||
|
|
||||||
|
(h4 :class "font-semibold mt-4 mb-2" "Three response tiers")
|
||||||
|
|
||||||
|
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
|
||||||
|
(table :class "w-full text-left text-sm"
|
||||||
|
(thead (tr :class "border-b border-stone-200 bg-stone-100"
|
||||||
|
(th :class "px-3 py-2 font-medium text-stone-600" "Client cache state")
|
||||||
|
(th :class "px-3 py-2 font-medium text-stone-600" "Server sends")
|
||||||
|
(th :class "px-3 py-2 font-medium text-stone-600" "Client does")))
|
||||||
|
(tbody
|
||||||
|
(tr :class "border-b border-stone-100"
|
||||||
|
(td :class "px-3 py-2 text-stone-700" "Nothing cached")
|
||||||
|
(td :class "px-3 py-2 text-stone-700" "HTML (fallback)")
|
||||||
|
(td :class "px-3 py-2 text-stone-600" "Parse HTML, morph DOM"))
|
||||||
|
(tr :class "border-b border-stone-100"
|
||||||
|
(td :class "px-3 py-2 text-stone-700" "Compiler cached")
|
||||||
|
(td :class "px-3 py-2 text-stone-700" "SX source")
|
||||||
|
(td :class "px-3 py-2 text-stone-600" "Compile, execute, morph DOM"))
|
||||||
|
(tr :class "border-b border-stone-100"
|
||||||
|
(td :class "px-3 py-2 text-stone-700" "Components cached")
|
||||||
|
(td :class "px-3 py-2 text-stone-700" "Bytecode (data only)")
|
||||||
|
(td :class "px-3 py-2 text-stone-600" "Call cached WASM function with args"))
|
||||||
|
(tr
|
||||||
|
(td :class "px-3 py-2 text-stone-700" "Components cached + gzip")
|
||||||
|
(td :class "px-3 py-2 text-stone-700" "Gzipped bytecode")
|
||||||
|
(td :class "px-3 py-2 text-stone-600" "Decompress, call cached function")))))
|
||||||
|
|
||||||
|
(h4 :class "font-semibold mt-4 mb-2" "Why bytecode is tiny")
|
||||||
|
(p "The bytecode doesn't repeat what the client already knows. "
|
||||||
|
"Component CID references are 4-byte pointers to already-cached compiled functions. "
|
||||||
|
"Tag names, class strings, attribute keys \u2014 all baked into the compiled component. "
|
||||||
|
"The only payload is the data that differs between instances.")
|
||||||
|
|
||||||
|
(~docs/code :code (highlight ";; HTML response: ~450 bytes\n<div class=\"card\"><h2>Hello</h2><p class=\"text-stone-600\">World</p></div>\n\n;; SX source response: ~60 bytes\n(~card :title \"Hello\" :body \"World\")\n\n;; Bytecode response: ~18 bytes\n[CALL_CID bafyrei..card] [STR \"Hello\"] [STR \"World\"]\n\n;; Gzipped bytecode: ~12 bytes" "text"))
|
||||||
|
|
||||||
|
(p "For a list of 50 cards:")
|
||||||
|
|
||||||
|
(~docs/code :code (highlight "HTML: 50\u00d7 <div class=\"card\"><h2>...</h2>...</div> ~22 KB\nSX source: 50\u00d7 (~card :title \"...\" :body \"...\") ~3 KB\nBytecode: [CALL_CID] + 50\u00d7 [STR, STR] ~800 bytes\nGzipped bytecode: ~400 bytes" "text"))
|
||||||
|
|
||||||
|
(p "The markup structure is in the compiled component. "
|
||||||
|
"The class strings are in the compiled component. "
|
||||||
|
"The only thing on the wire is the data that differs between instances. "
|
||||||
|
"Gzip crushes the repetitive framing to almost nothing.")
|
||||||
|
|
||||||
|
(h4 :class "font-semibold mt-4 mb-2" "Content negotiation")
|
||||||
|
(p "The client advertises what it has via request headers:")
|
||||||
|
|
||||||
|
(~docs/code :code (highlight ";; Client tells server what's cached\nAccept: application/sx-bytecode, text/sx, text/html\nX-Sx-Cached-Components: bafyrei..card, bafyrei..header, bafyrei..nav\n\n;; Server picks the smallest response:\n;; - Client has ~card cached? Send bytecode (data only)\n;; - Client has compiler but not ~card? Send SX source\n;; - Client has nothing? Send HTML" "text"))
|
||||||
|
|
||||||
|
(p "The server and client negotiate the optimal response format automatically. "
|
||||||
|
"First visit is HTML (full progressive enhancement). "
|
||||||
|
"Subsequent navigations are gzipped bytecode \u2014 "
|
||||||
|
"just the data, a few hundred bytes, instant render via cached compiled components.")
|
||||||
|
|
||||||
|
(h4 :class "font-semibold mt-4 mb-2" "The server becomes a data API")
|
||||||
|
(p "At the bytecode tier, the server is no longer rendering views. "
|
||||||
|
"It's serving data \u2014 structured arguments for compiled client-side functions. "
|
||||||
|
"The rendering logic is cached on the client as compiled WASM. "
|
||||||
|
"This inverts the traditional server-rendered model without sacrificing "
|
||||||
|
"progressive enhancement \u2014 the HTML fallback is always available."))
|
||||||
|
|
||||||
|
|
||||||
;; -----------------------------------------------------------------------
|
;; -----------------------------------------------------------------------
|
||||||
;; How this changes existing plans
|
;; How this changes existing plans
|
||||||
;; -----------------------------------------------------------------------
|
;; -----------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user