Phase 1: CEK is now the sole evaluator on JavaScript

- Override evalExpr/trampoline in CEK_FIXUPS_JS to route through
  cekRun (matching what Python already does)
- Always include frames+cek in JS builds (not just when DOM present)
- Remove CONTINUATIONS_JS extension (CEK handles shift/reset natively)
- Remove Continuation constructor guard (always define it)
- Add strict-mode type checking to CEK call path via head-name
  propagation through ArgFrame

Standard build: 746/747 passing (1 dotimes macro edge case)
Full build: 858/870 passing (6 continuation edge cases, 5 deftype
issues, 1 dotimes — all pre-existing CEK behavioral differences)

The tree-walk eval-expr, eval-list, eval-call, and all sf-*/ho-*
forms in eval.sx are now dead code — never reached at runtime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-15 12:49:08 +00:00
parent ebb3445667
commit 293af75821
7 changed files with 94 additions and 56 deletions

View File

@@ -14,7 +14,7 @@
// =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-03-15T12:23:29Z";
var SX_VERSION = "2026-03-15T12:48:42Z";
function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -775,18 +775,15 @@
// Platform: CEK module — explicit CEK machine
// =========================================================================
// Continuation type (needed by CEK even without the tree-walk shift/reset extension)
// Continuations must be callable as JS functions so isCallable/apply work.
if (typeof Continuation === "undefined") {
function Continuation(fn) {
var c = function(value) { return fn(value !== undefined ? value : NIL); };
c.fn = fn;
c._continuation = true;
c.call = function(value) { return fn(value !== undefined ? value : NIL); };
return c;
}
// Continuation type — callable as JS function so isCallable/apply work.
// CEK is the canonical evaluator; continuations are always available.
function Continuation(fn) {
var c = function(value) { return fn(value !== undefined ? value : NIL); };
c.fn = fn;
c._continuation = true;
c.call = function(value) { return fn(value !== undefined ? value : NIL); };
return c;
}
// Always register the predicate (may be overridden by continuations extension)
PRIMITIVES["continuation?"] = function(x) { return x != null && x._continuation === true; };
// Standalone aliases for primitives used by cek.sx / frames.sx
@@ -4275,7 +4272,7 @@ PRIMITIVES["make-define-frame"] = makeDefineFrame;
PRIMITIVES["make-set-frame"] = makeSetFrame;
// make-arg-frame
var makeArgFrame = function(f, evaled, remaining, env, rawArgs) { return {"type": "arg", "f": f, "evaled": evaled, "remaining": remaining, "env": env, "raw-args": rawArgs}; };
var makeArgFrame = function(f, evaled, remaining, env, rawArgs, headName) { return {"type": "arg", "f": f, "evaled": evaled, "remaining": remaining, "env": env, "raw-args": rawArgs, "head-name": sxOr(headName, NIL)}; };
PRIMITIVES["make-arg-frame"] = makeArgFrame;
// make-call-frame
@@ -5057,7 +5054,10 @@ return forEach(function(d) { return cekCall(d, NIL); }, subDisposers); });
PRIMITIVES["reactive-shift-deref"] = reactiveShiftDeref;
// step-eval-call
var stepEvalCall = function(head, args, env, kont) { return makeCekState(head, env, kontPush(makeArgFrame(NIL, [], args, env, args), kont)); };
var stepEvalCall = function(head, args, env, kont) { return (function() {
var hname = (isSxTruthy((typeOf(head) == "symbol")) ? symbolName(head) : NIL);
return makeCekState(head, env, kontPush(makeArgFrame(NIL, [], args, env, args, hname), kont));
})(); };
PRIMITIVES["step-eval-call"] = stepEvalCall;
// step-ho-map
@@ -5222,9 +5222,10 @@ PRIMITIVES["step-ho-for-each"] = stepHoForEach;
var remaining = get(frame, "remaining");
var fenv = get(frame, "env");
var rawArgs = get(frame, "raw-args");
return (isSxTruthy(isNil(f)) ? (isSxTruthy(isEmpty(remaining)) ? continueWithCall(value, [], fenv, rawArgs, restK) : makeCekState(first(remaining), fenv, kontPush(makeArgFrame(value, [], rest(remaining), fenv, rawArgs), restK))) : (function() {
var hname = get(frame, "head-name");
return (isSxTruthy(isNil(f)) ? ((isSxTruthy((isSxTruthy(_strict_) && hname)) ? strictCheckArgs(hname, []) : NIL), (isSxTruthy(isEmpty(remaining)) ? continueWithCall(value, [], fenv, rawArgs, restK) : makeCekState(first(remaining), fenv, kontPush(makeArgFrame(value, [], rest(remaining), fenv, rawArgs, hname), restK)))) : (function() {
var newEvaled = append(evaled, [value]);
return (isSxTruthy(isEmpty(remaining)) ? continueWithCall(f, newEvaled, fenv, rawArgs, restK) : makeCekState(first(remaining), fenv, kontPush(makeArgFrame(f, newEvaled, rest(remaining), fenv, rawArgs), restK)));
return (isSxTruthy(isEmpty(remaining)) ? ((isSxTruthy((isSxTruthy(_strict_) && hname)) ? strictCheckArgs(hname, newEvaled) : NIL), continueWithCall(f, newEvaled, fenv, rawArgs, restK)) : makeCekState(first(remaining), fenv, kontPush(makeArgFrame(f, newEvaled, rest(remaining), fenv, rawArgs, hname), restK)));
})());
})() : (isSxTruthy((ft == "dict")) ? (function() {
var remaining = get(frame, "remaining");
@@ -5848,6 +5849,20 @@ PRIMITIVES["resource"] = resource;
return cekValue(state);
};
// CEK is the canonical evaluator — override evalExpr to use it.
// The tree-walk evaluator (evalExpr from eval.sx) is superseded.
var _treeWalkEvalExpr = evalExpr;
evalExpr = function(expr, env) {
return cekRun(makeCekState(expr, env, []));
};
// CEK never produces thunks — trampoline resolves any legacy thunks
var _treeWalkTrampoline = trampoline;
trampoline = function(val) {
if (isThunk(val)) return evalExpr(thunkExpr(val), thunkEnv(val));
return val;
};
// Platform functions — defined in platform_js.py, not in .sx spec files.
// Spec defines self-register via js-emit-define; these are the platform interface.
PRIMITIVES["type-of"] = typeOf;