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:
@@ -112,10 +112,9 @@ def compile_ref_to_js(
|
||||
spec_mod_set.add("deps")
|
||||
if "page-helpers" in SPEC_MODULES:
|
||||
spec_mod_set.add("page-helpers")
|
||||
# CEK needed for reactive rendering (deref-as-shift)
|
||||
if "dom" in adapter_set:
|
||||
spec_mod_set.add("cek")
|
||||
spec_mod_set.add("frames")
|
||||
# CEK is the canonical evaluator — always included
|
||||
spec_mod_set.add("cek")
|
||||
spec_mod_set.add("frames")
|
||||
# cek module requires frames
|
||||
if "cek" in spec_mod_set:
|
||||
spec_mod_set.add("frames")
|
||||
@@ -238,8 +237,11 @@ def compile_ref_to_js(
|
||||
for name in ("dom", "engine", "orchestration", "boot"):
|
||||
if name in adapter_set and name in adapter_platform:
|
||||
parts.append(adapter_platform[name])
|
||||
if has_continuations:
|
||||
parts.append(CONTINUATIONS_JS)
|
||||
# CONTINUATIONS_JS is the tree-walk shift/reset extension.
|
||||
# With CEK as sole evaluator, continuations are handled natively by
|
||||
# cek.sx (step-sf-reset, step-sf-shift). Skip the tree-walk extension.
|
||||
# if has_continuations:
|
||||
# parts.append(CONTINUATIONS_JS)
|
||||
if has_dom:
|
||||
parts.append(ASYNC_IO_JS)
|
||||
parts.append(public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_boot, has_parser, adapter_label, has_deps, has_router, has_signals, has_page_helpers, has_cek))
|
||||
|
||||
@@ -1504,18 +1504,15 @@ PLATFORM_CEK_JS = '''
|
||||
// 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
|
||||
@@ -1543,6 +1540,20 @@ CEK_FIXUPS_JS = '''
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -20,7 +20,7 @@
|
||||
:class "hidden md:flex md:flex-col max-w-xs md:h-full md:min-h-0 mr-3"
|
||||
(when aside aside))
|
||||
(section :id "main-panel"
|
||||
:class "flex-1 md:h-full md:min-h-0 overflow-y-auto overscroll-contain js-grid-viewport"
|
||||
:class "flex-1 md:h-full md:min-h-0 md:overflow-y-auto md:overscroll-contain js-grid-viewport"
|
||||
(when content content)
|
||||
(div :class "pb-8")))))))
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
(div :id "root-menu" :sx-swap-oob "outerHTML" :class "md:hidden"
|
||||
(when menu menu))
|
||||
(section :id "main-panel"
|
||||
:class "flex-1 md:h-full md:min-h-0 overflow-y-auto overscroll-contain js-grid-viewport"
|
||||
:class "flex-1 md:h-full md:min-h-0 md:overflow-y-auto md:overscroll-contain js-grid-viewport"
|
||||
(when content content))))
|
||||
|
||||
(defcomp ~shared:layout/hamburger ()
|
||||
|
||||
46
spec/cek.sx
46
spec/cek.sx
@@ -466,11 +466,13 @@
|
||||
(define step-eval-call
|
||||
(fn (head args env kont)
|
||||
;; First evaluate the head, then evaluate args left-to-right
|
||||
(make-cek-state
|
||||
head env
|
||||
(kont-push
|
||||
(make-arg-frame nil (list) args env args)
|
||||
kont))))
|
||||
;; Preserve head name for strict mode type checking
|
||||
(let ((hname (if (= (type-of head) "symbol") (symbol-name head) nil)))
|
||||
(make-cek-state
|
||||
head env
|
||||
(kont-push
|
||||
(make-arg-frame nil (list) args env args hname)
|
||||
kont)))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
@@ -760,28 +762,36 @@
|
||||
(evaled (get frame "evaled"))
|
||||
(remaining (get frame "remaining"))
|
||||
(fenv (get frame "env"))
|
||||
(raw-args (get frame "raw-args")))
|
||||
(raw-args (get frame "raw-args"))
|
||||
(hname (get frame "head-name")))
|
||||
(if (nil? f)
|
||||
;; Head just evaluated — value is the function
|
||||
(if (empty? remaining)
|
||||
;; No args — call immediately
|
||||
(continue-with-call value (list) fenv raw-args rest-k)
|
||||
;; Start evaluating args
|
||||
(make-cek-state
|
||||
(first remaining) fenv
|
||||
(kont-push
|
||||
(make-arg-frame value (list) (rest remaining) fenv raw-args)
|
||||
rest-k)))
|
||||
(do
|
||||
;; Strict mode: check arg types for named primitives
|
||||
(when (and *strict* hname)
|
||||
(strict-check-args hname (list)))
|
||||
(if (empty? remaining)
|
||||
;; No args — call immediately
|
||||
(continue-with-call value (list) fenv raw-args rest-k)
|
||||
;; Start evaluating args
|
||||
(make-cek-state
|
||||
(first remaining) fenv
|
||||
(kont-push
|
||||
(make-arg-frame value (list) (rest remaining) fenv raw-args hname)
|
||||
rest-k))))
|
||||
;; An arg was evaluated — accumulate
|
||||
(let ((new-evaled (append evaled (list value))))
|
||||
(if (empty? remaining)
|
||||
;; All args evaluated — call
|
||||
(continue-with-call f new-evaled fenv raw-args rest-k)
|
||||
;; All args evaluated — strict check then call
|
||||
(do
|
||||
(when (and *strict* hname)
|
||||
(strict-check-args hname new-evaled))
|
||||
(continue-with-call f new-evaled fenv raw-args rest-k))
|
||||
;; Next arg
|
||||
(make-cek-state
|
||||
(first remaining) fenv
|
||||
(kont-push
|
||||
(make-arg-frame f new-evaled (rest remaining) fenv raw-args)
|
||||
(make-arg-frame f new-evaled (rest remaining) fenv raw-args hname)
|
||||
rest-k))))))
|
||||
|
||||
;; --- DictFrame: value evaluated ---
|
||||
|
||||
@@ -88,9 +88,9 @@
|
||||
;; f = function value (already evaluated), evaled = already evaluated args
|
||||
;; remaining = remaining arg expressions
|
||||
(define make-arg-frame
|
||||
(fn (f evaled remaining env raw-args)
|
||||
(fn (f evaled remaining env raw-args head-name)
|
||||
{:type "arg" :f f :evaled evaled :remaining remaining :env env
|
||||
:raw-args raw-args}))
|
||||
:raw-args raw-args :head-name (or head-name nil)}))
|
||||
|
||||
;; CallFrame: about to call with fully evaluated args
|
||||
(define make-call-frame
|
||||
|
||||
Reference in New Issue
Block a user