Files
rose-ash/shared/sx/ref/cek.sx
giles 1765216335 Implement explicit CEK machine, continuations, effect signatures, fix dynamic-wind and inspect shadowing
Three-phase foundations implementation:

Phase A — Activate dormant shift/reset continuations with 24 SX-native tests
covering basic semantics, predicates, stored continuations, nested reset,
scope interaction, and TCO.

Phase B — Bridge compile-time effect system to runtime: boundary_parser extracts
46 effect annotations, platform provides populate_effect_annotations() and
check_component_effects() for static analysis. 6 new type tests.

Phase C — Explicit CEK machine (frames.sx + cek.sx): evaluation state as data
({control, env, kont, phase, value}), 21 frame types, two-phase step function
(step-eval/step-continue), native shift/reset via frame capture. Bootstrapper
integration: --spec-modules cek transpiles to Python with iterative cek_run.
43 interpreted + 49 transpiled tests passing.

Bug fixes:
- inspect() shadowed by `import inspect` in PLATFORM_ASYNC_PY — renamed to
  `import inspect as _inspect`
- dynamic-wind missing platform functions (call_thunk, push_wind!, pop_wind!) —
  added with try/finally error safety via dynamic_wind_call

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 22:14:55 +00:00

813 lines
34 KiB
Plaintext

;; ==========================================================================
;; cek.sx — Explicit CEK machine evaluator
;;
;; Replaces the implicit CEK (tree-walk + trampoline) with explicit
;; C/E/K data structures. Each evaluation step is a pure function from
;; state to state. Enables stepping, serialization, migration.
;;
;; The CEK uses the frame types defined in frames.sx.
;; eval-expr remains as the public API — it creates a CEK state and runs.
;;
;; Requires: frames.sx loaded first.
;; ==========================================================================
;; --------------------------------------------------------------------------
;; 1. Run loop — drive the CEK machine to completion
;; --------------------------------------------------------------------------
(define cek-run
(fn (state)
;; Drive the CEK machine until terminal state.
;; Returns the final value.
(if (cek-terminal? state)
(cek-value state)
(cek-run (cek-step state)))))
;; --------------------------------------------------------------------------
;; 2. Step function — single CEK step
;; --------------------------------------------------------------------------
(define cek-step
(fn (state)
(if (= (cek-phase state) "eval")
(step-eval state)
(step-continue state))))
;; --------------------------------------------------------------------------
;; 3. step-eval — Control is an expression, dispatch on type
;; --------------------------------------------------------------------------
(define step-eval
(fn (state)
(let ((expr (cek-control state))
(env (cek-env state))
(kont (cek-kont state)))
(case (type-of expr)
;; --- Literals: immediate value ---
"number" (make-cek-value expr env kont)
"string" (make-cek-value expr env kont)
"boolean" (make-cek-value expr env kont)
"nil" (make-cek-value nil env kont)
;; --- Symbol lookup ---
"symbol"
(let ((name (symbol-name expr)))
(let ((val (cond
(env-has? env name) (env-get env name)
(primitive? name) (get-primitive name)
(= name "true") true
(= name "false") false
(= name "nil") nil
:else (error (str "Undefined symbol: " name)))))
(make-cek-value val env kont)))
;; --- Keyword → string ---
"keyword" (make-cek-value (keyword-name expr) env kont)
;; --- Dict literal: evaluate values ---
"dict"
(let ((ks (keys expr)))
(if (empty? ks)
(make-cek-value (dict) env kont)
;; Build entry pairs from dict, evaluate first value
(let ((first-key (first ks))
(remaining-entries (list)))
(for-each (fn (k) (append! remaining-entries (list k (get expr k))))
(rest ks))
(make-cek-state
(get expr first-key)
env
(kont-push
(make-dict-frame
remaining-entries
(list (list first-key)) ;; results: list of (key) waiting for val
env)
kont)))))
;; --- List = call or special form ---
"list"
(if (empty? expr)
(make-cek-value (list) env kont)
(step-eval-list expr env kont))
;; --- Anything else passes through ---
:else (make-cek-value expr env kont)))))
;; --------------------------------------------------------------------------
;; 4. step-eval-list — Dispatch on list head
;; --------------------------------------------------------------------------
(define step-eval-list
(fn (expr env kont)
(let ((head (first expr))
(args (rest expr)))
;; If head isn't symbol/lambda/list → treat as data list
(if (not (or (= (type-of head) "symbol")
(= (type-of head) "lambda")
(= (type-of head) "list")))
;; Evaluate as data list — evaluate each element
(if (empty? expr)
(make-cek-value (list) env kont)
(make-cek-state
(first expr) env
(kont-push (make-map-frame nil (rest expr) (list) env) kont)))
;; Head is symbol — check special forms
(if (= (type-of head) "symbol")
(let ((name (symbol-name head)))
(cond
;; --- Special forms → push appropriate frame ---
(= name "if") (step-sf-if args env kont)
(= name "when") (step-sf-when args env kont)
(= name "cond") (step-sf-cond args env kont)
(= name "case") (step-sf-case args env kont)
(= name "and") (step-sf-and args env kont)
(= name "or") (step-sf-or args env kont)
(= name "let") (step-sf-let args env kont)
(= name "let*") (step-sf-let args env kont)
(= name "lambda") (step-sf-lambda args env kont)
(= name "fn") (step-sf-lambda args env kont)
(= name "define") (step-sf-define args env kont)
(= name "defcomp") (make-cek-value (sf-defcomp args env) env kont)
(= name "defisland") (make-cek-value (sf-defisland args env) env kont)
(= name "defmacro") (make-cek-value (sf-defmacro args env) env kont)
(= name "defstyle") (make-cek-value (sf-defstyle args env) env kont)
(= name "defhandler") (make-cek-value (sf-defhandler args env) env kont)
(= name "defpage") (make-cek-value (sf-defpage args env) env kont)
(= name "defquery") (make-cek-value (sf-defquery args env) env kont)
(= name "defaction") (make-cek-value (sf-defaction args env) env kont)
(= name "deftype") (make-cek-value (sf-deftype args env) env kont)
(= name "defeffect") (make-cek-value (sf-defeffect args env) env kont)
(= name "begin") (step-sf-begin args env kont)
(= name "do") (step-sf-begin args env kont)
(= name "quote") (make-cek-value (if (empty? args) nil (first args)) env kont)
(= name "quasiquote") (make-cek-value (qq-expand (first args) env) env kont)
(= name "->") (step-sf-thread-first args env kont)
(= name "set!") (step-sf-set! args env kont)
(= name "letrec") (make-cek-value (sf-letrec args env) env kont)
;; Continuations — native in CEK
(= name "reset") (step-sf-reset args env kont)
(= name "shift") (step-sf-shift args env kont)
;; Scoped effects
(= name "scope") (step-sf-scope args env kont)
(= name "provide") (step-sf-provide args env kont)
;; Dynamic wind
(= name "dynamic-wind") (make-cek-value (sf-dynamic-wind args env) env kont)
;; Higher-order forms
(= name "map") (step-ho-map args env kont)
(= name "map-indexed") (make-cek-value (ho-map-indexed args env) env kont)
(= name "filter") (step-ho-filter args env kont)
(= name "reduce") (step-ho-reduce args env kont)
(= name "some") (make-cek-value (ho-some args env) env kont)
(= name "every?") (make-cek-value (ho-every args env) env kont)
(= name "for-each") (step-ho-for-each args env kont)
;; Macro expansion
(and (env-has? env name) (macro? (env-get env name)))
(let ((mac (env-get env name)))
(make-cek-state (expand-macro mac args env) env kont))
;; Render expression
(and (render-active?) (is-render-expr? expr))
(make-cek-value (render-expr expr env) env kont)
;; Fall through to function call
:else (step-eval-call head args env kont)))
;; Head is lambda or list — function call
(step-eval-call head args env kont))))))
;; --------------------------------------------------------------------------
;; 5. Special form step handlers
;; --------------------------------------------------------------------------
;; if: evaluate condition, push IfFrame
(define step-sf-if
(fn (args env kont)
(make-cek-state
(first args) env
(kont-push
(make-if-frame (nth args 1)
(if (> (len args) 2) (nth args 2) nil)
env)
kont))))
;; when: evaluate condition, push WhenFrame
(define step-sf-when
(fn (args env kont)
(make-cek-state
(first args) env
(kont-push (make-when-frame (rest args) env) kont))))
;; begin/do: evaluate first expr, push BeginFrame for rest
(define step-sf-begin
(fn (args env kont)
(if (empty? args)
(make-cek-value nil env kont)
(if (= (len args) 1)
(make-cek-state (first args) env kont)
(make-cek-state
(first args) env
(kont-push (make-begin-frame (rest args) env) kont))))))
;; let: start evaluating bindings
(define step-sf-let
(fn (args env kont)
;; Detect named let
(if (= (type-of (first args)) "symbol")
;; Named let — delegate to existing handler (complex desugaring)
(make-cek-value (sf-named-let args env) env kont)
(let ((bindings (first args))
(body (rest args))
(local (env-extend env)))
;; Parse first binding
(if (empty? bindings)
;; No bindings — evaluate body
(step-sf-begin body local kont)
;; Start evaluating first binding value
(let ((first-binding (if (and (= (type-of (first bindings)) "list")
(= (len (first bindings)) 2))
;; Scheme-style: ((name val) ...)
(first bindings)
;; Clojure-style: (name val ...) → synthesize pair
(list (first bindings) (nth bindings 1))))
(rest-bindings (if (and (= (type-of (first bindings)) "list")
(= (len (first bindings)) 2))
(rest bindings)
;; Clojure-style: skip 2 elements
(let ((pairs (list)))
(reduce
(fn (acc i)
(append! pairs (list (nth bindings (* i 2))
(nth bindings (inc (* i 2))))))
nil
(range 1 (/ (len bindings) 2)))
pairs))))
(let ((vname (if (= (type-of (first first-binding)) "symbol")
(symbol-name (first first-binding))
(first first-binding))))
(make-cek-state
(nth first-binding 1) local
(kont-push
(make-let-frame vname rest-bindings body local)
kont)))))))))
;; define: evaluate value expression
(define step-sf-define
(fn (args env kont)
(let ((name-sym (first args))
(has-effects (and (>= (len args) 4)
(= (type-of (nth args 1)) "keyword")
(= (keyword-name (nth args 1)) "effects")))
(val-idx (if (and (>= (len args) 4)
(= (type-of (nth args 1)) "keyword")
(= (keyword-name (nth args 1)) "effects"))
3 1))
(effect-list (if (and (>= (len args) 4)
(= (type-of (nth args 1)) "keyword")
(= (keyword-name (nth args 1)) "effects"))
(nth args 2) nil)))
(make-cek-state
(nth args val-idx) env
(kont-push
(make-define-frame (symbol-name name-sym) env has-effects effect-list)
kont)))))
;; set!: evaluate value
(define step-sf-set!
(fn (args env kont)
(make-cek-state
(nth args 1) env
(kont-push (make-set-frame (symbol-name (first args)) env) kont))))
;; and: evaluate first, push AndFrame
(define step-sf-and
(fn (args env kont)
(if (empty? args)
(make-cek-value true env kont)
(make-cek-state
(first args) env
(kont-push (make-and-frame (rest args) env) kont)))))
;; or: evaluate first, push OrFrame
(define step-sf-or
(fn (args env kont)
(if (empty? args)
(make-cek-value false env kont)
(make-cek-state
(first args) env
(kont-push (make-or-frame (rest args) env) kont)))))
;; cond: evaluate first test, push CondFrame
(define step-sf-cond
(fn (args env kont)
(let ((scheme? (cond-scheme? args)))
(if scheme?
;; Scheme-style: ((test body) ...)
(if (empty? args)
(make-cek-value nil env kont)
(let ((clause (first args))
(test (first clause)))
;; Check for :else / else
(if (or (and (= (type-of test) "symbol")
(or (= (symbol-name test) "else")
(= (symbol-name test) ":else")))
(and (= (type-of test) "keyword")
(= (keyword-name test) "else")))
(make-cek-state (nth clause 1) env kont)
(make-cek-state
test env
(kont-push (make-cond-frame args env true) kont)))))
;; Clojure-style: test body test body ...
(if (< (len args) 2)
(make-cek-value nil env kont)
(let ((test (first args)))
(if (or (and (= (type-of test) "keyword") (= (keyword-name test) "else"))
(and (= (type-of test) "symbol")
(or (= (symbol-name test) "else")
(= (symbol-name test) ":else"))))
(make-cek-state (nth args 1) env kont)
(make-cek-state
test env
(kont-push (make-cond-frame args env false) kont)))))))))
;; case: evaluate match value
(define step-sf-case
(fn (args env kont)
(make-cek-state
(first args) env
(kont-push (make-case-frame nil (rest args) env) kont))))
;; thread-first: evaluate initial value
(define step-sf-thread-first
(fn (args env kont)
(make-cek-state
(first args) env
(kont-push (make-thread-frame (rest args) env) kont))))
;; lambda/fn: immediate — create lambda value
(define step-sf-lambda
(fn (args env kont)
(make-cek-value (sf-lambda args env) env kont)))
;; scope: evaluate name, then push ScopeFrame
(define step-sf-scope
(fn (args env kont)
;; Delegate to existing sf-scope for now — scope involves mutation
(make-cek-value (sf-scope args env) env kont)))
;; provide: delegate to existing handler
(define step-sf-provide
(fn (args env kont)
(make-cek-value (sf-provide args env) env kont)))
;; reset: push ResetFrame, evaluate body
(define step-sf-reset
(fn (args env kont)
(make-cek-state
(first args) env
(kont-push (make-reset-frame env) kont))))
;; shift: capture frames to nearest reset
(define step-sf-shift
(fn (args env kont)
(let ((k-name (symbol-name (first args)))
(body (nth args 1))
(captured-result (kont-capture-to-reset kont))
(captured (first captured-result))
(rest-kont (nth captured-result 1)))
;; Store captured frames as a dict on the continuation value.
;; When the continuation is invoked, continue-with-call detects
;; the cek-frames key and restores them.
(let ((k (make-cek-continuation captured rest-kont)))
;; Evaluate shift body with k bound, continuation goes to rest-kont
(let ((shift-env (env-extend env)))
(env-set! shift-env k-name k)
(make-cek-state body shift-env rest-kont))))))
;; --------------------------------------------------------------------------
;; 6. Function call step handler
;; --------------------------------------------------------------------------
(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))))
;; --------------------------------------------------------------------------
;; 7. Higher-order form step handlers
;; --------------------------------------------------------------------------
(define step-ho-map
(fn (args env kont)
;; Evaluate function, then collection
;; For now, delegate to existing ho-map (it's a tight loop)
(make-cek-value (ho-map args env) env kont)))
(define step-ho-filter
(fn (args env kont)
(make-cek-value (ho-filter args env) env kont)))
(define step-ho-reduce
(fn (args env kont)
(make-cek-value (ho-reduce args env) env kont)))
(define step-ho-for-each
(fn (args env kont)
(make-cek-value (ho-for-each args env) env kont)))
;; --------------------------------------------------------------------------
;; 8. step-continue — Value produced, dispatch on top frame
;; --------------------------------------------------------------------------
(define step-continue
(fn (state)
(let ((value (cek-value state))
(env (cek-env state))
(kont (cek-kont state)))
(if (kont-empty? kont)
state ;; Terminal — return as-is
(let ((frame (kont-top kont))
(rest-k (kont-pop kont))
(ft (frame-type frame)))
(cond
;; --- IfFrame: condition evaluated ---
(= ft "if")
(if (and value (not (nil? value)))
(make-cek-state (get frame "then") (get frame "env") rest-k)
(if (nil? (get frame "else"))
(make-cek-value nil env rest-k)
(make-cek-state (get frame "else") (get frame "env") rest-k)))
;; --- WhenFrame: condition evaluated ---
(= ft "when")
(if (and value (not (nil? value)))
(let ((body (get frame "body"))
(fenv (get frame "env")))
(if (empty? body)
(make-cek-value nil fenv rest-k)
(if (= (len body) 1)
(make-cek-state (first body) fenv rest-k)
(make-cek-state
(first body) fenv
(kont-push (make-begin-frame (rest body) fenv) rest-k)))))
(make-cek-value nil env rest-k))
;; --- BeginFrame: expression evaluated, continue with next ---
(= ft "begin")
(let ((remaining (get frame "remaining"))
(fenv (get frame "env")))
(if (empty? remaining)
(make-cek-value value fenv rest-k)
(if (= (len remaining) 1)
(make-cek-state (first remaining) fenv rest-k)
(make-cek-state
(first remaining) fenv
(kont-push (make-begin-frame (rest remaining) fenv) rest-k)))))
;; --- LetFrame: binding value evaluated ---
(= ft "let")
(let ((name (get frame "name"))
(remaining (get frame "remaining"))
(body (get frame "body"))
(local (get frame "env")))
;; Bind the value
(env-set! local name value)
;; More bindings?
(if (empty? remaining)
;; All bindings done — evaluate body
(step-sf-begin body local rest-k)
;; Next binding
(let ((next-binding (first remaining))
(vname (if (= (type-of (first next-binding)) "symbol")
(symbol-name (first next-binding))
(first next-binding))))
(make-cek-state
(nth next-binding 1) local
(kont-push
(make-let-frame vname (rest remaining) body local)
rest-k)))))
;; --- DefineFrame: value evaluated ---
(= ft "define")
(let ((name (get frame "name"))
(fenv (get frame "env"))
(has-effects (get frame "has-effects"))
(effect-list (get frame "effect-list")))
(when (and (lambda? value) (nil? (lambda-name value)))
(set-lambda-name! value name))
(env-set! fenv name value)
;; Effect annotation
(when has-effects
(let ((effect-names (if (= (type-of effect-list) "list")
(map (fn (e) (if (= (type-of e) "symbol")
(symbol-name e) (str e)))
effect-list)
(list (str effect-list))))
(effect-anns (if (env-has? fenv "*effect-annotations*")
(env-get fenv "*effect-annotations*")
(dict))))
(dict-set! effect-anns name effect-names)
(env-set! fenv "*effect-annotations*" effect-anns)))
(make-cek-value value fenv rest-k))
;; --- SetFrame: value evaluated ---
(= ft "set")
(let ((name (get frame "name"))
(fenv (get frame "env")))
(env-set! fenv name value)
(make-cek-value value env rest-k))
;; --- AndFrame: value evaluated ---
(= ft "and")
(if (not value)
(make-cek-value value env rest-k)
(let ((remaining (get frame "remaining")))
(if (empty? remaining)
(make-cek-value value env rest-k)
(make-cek-state
(first remaining) (get frame "env")
(if (= (len remaining) 1)
rest-k
(kont-push (make-and-frame (rest remaining) (get frame "env")) rest-k))))))
;; --- OrFrame: value evaluated ---
(= ft "or")
(if value
(make-cek-value value env rest-k)
(let ((remaining (get frame "remaining")))
(if (empty? remaining)
(make-cek-value false env rest-k)
(make-cek-state
(first remaining) (get frame "env")
(if (= (len remaining) 1)
rest-k
(kont-push (make-or-frame (rest remaining) (get frame "env")) rest-k))))))
;; --- CondFrame: test evaluated ---
(= ft "cond")
(let ((remaining (get frame "remaining"))
(fenv (get frame "env"))
(scheme? (get frame "scheme")))
(if scheme?
;; Scheme-style: test truthy → evaluate body
(if value
(make-cek-state (nth (first remaining) 1) fenv rest-k)
;; Next clause
(let ((next-clauses (rest remaining)))
(if (empty? next-clauses)
(make-cek-value nil fenv rest-k)
(let ((next-clause (first next-clauses))
(next-test (first next-clause)))
(if (or (and (= (type-of next-test) "symbol")
(or (= (symbol-name next-test) "else")
(= (symbol-name next-test) ":else")))
(and (= (type-of next-test) "keyword")
(= (keyword-name next-test) "else")))
(make-cek-state (nth next-clause 1) fenv rest-k)
(make-cek-state
next-test fenv
(kont-push (make-cond-frame next-clauses fenv true) rest-k)))))))
;; Clojure-style
(if value
(make-cek-state (nth remaining 1) fenv rest-k)
(let ((next (slice remaining 2)))
(if (< (len next) 2)
(make-cek-value nil fenv rest-k)
(let ((next-test (first next)))
(if (or (and (= (type-of next-test) "keyword") (= (keyword-name next-test) "else"))
(and (= (type-of next-test) "symbol")
(or (= (symbol-name next-test) "else")
(= (symbol-name next-test) ":else"))))
(make-cek-state (nth next 1) fenv rest-k)
(make-cek-state
next-test fenv
(kont-push (make-cond-frame next fenv false) rest-k)))))))))
;; --- CaseFrame ---
(= ft "case")
(let ((match-val (get frame "match-val"))
(remaining (get frame "remaining"))
(fenv (get frame "env")))
(if (nil? match-val)
;; First step: match-val just evaluated
(sf-case-step-loop value remaining fenv rest-k)
;; Subsequent: test clause evaluated
(sf-case-step-loop match-val remaining fenv rest-k)))
;; --- ThreadFirstFrame ---
(= ft "thread")
(let ((remaining (get frame "remaining"))
(fenv (get frame "env")))
(if (empty? remaining)
(make-cek-value value fenv rest-k)
;; Apply next form to value
(let ((form (first remaining))
(rest-forms (rest remaining)))
(let ((result (if (= (type-of form) "list")
(let ((f (trampoline (eval-expr (first form) fenv)))
(rargs (map (fn (a) (trampoline (eval-expr a fenv))) (rest form)))
(all-args (cons value rargs)))
(cond
(and (callable? f) (not (lambda? f))) (apply f all-args)
(lambda? f) (trampoline (call-lambda f all-args fenv))
:else (error (str "-> form not callable: " (inspect f)))))
(let ((f (trampoline (eval-expr form fenv))))
(cond
(and (callable? f) (not (lambda? f))) (f value)
(lambda? f) (trampoline (call-lambda f (list value) fenv))
:else (error (str "-> form not callable: " (inspect f))))))))
(if (empty? rest-forms)
(make-cek-value result fenv rest-k)
(make-cek-value result fenv
(kont-push (make-thread-frame rest-forms fenv) rest-k)))))))
;; --- ArgFrame: head or arg evaluated ---
(= ft "arg")
(let ((f (get frame "f"))
(evaled (get frame "evaled"))
(remaining (get frame "remaining"))
(fenv (get frame "env"))
(raw-args (get frame "raw-args")))
(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)))
;; 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)
;; Next arg
(make-cek-state
(first remaining) fenv
(kont-push
(make-arg-frame f new-evaled (rest remaining) fenv raw-args)
rest-k))))))
;; --- DictFrame: value evaluated ---
(= ft "dict")
(let ((remaining (get frame "remaining"))
(results (get frame "results"))
(fenv (get frame "env")))
;; Last result entry is (key) — append value to make (key val)
(let ((last-result (last results))
(completed (append (slice results 0 (dec (len results)))
(list (list (first last-result) value)))))
(if (empty? remaining)
;; All done — build dict
(let ((d (dict)))
(for-each
(fn (pair) (dict-set! d (first pair) (nth pair 1)))
completed)
(make-cek-value d fenv rest-k))
;; Next entry
(let ((next-entry (first remaining)))
(make-cek-state
(nth next-entry 1) fenv
(kont-push
(make-dict-frame
(rest remaining)
(append completed (list (list (first next-entry))))
fenv)
rest-k))))))
;; --- ResetFrame: body evaluated normally (no shift) ---
(= ft "reset")
(make-cek-value value env rest-k)
;; --- ScopeFrame: body result ---
(= ft "scope")
(let ((name (get frame "name"))
(remaining (get frame "remaining"))
(fenv (get frame "env")))
(if (empty? remaining)
(do (scope-pop! name)
(make-cek-value value fenv rest-k))
(make-cek-state
(first remaining) fenv
(kont-push
(make-scope-frame name (rest remaining) fenv)
rest-k))))
:else (error (str "Unknown frame type: " ft))))))))
;; --------------------------------------------------------------------------
;; 9. Helper: continue with function call
;; --------------------------------------------------------------------------
(define continue-with-call
(fn (f args env raw-args kont)
(cond
;; Continuation — restore captured frames and inject value
(continuation? f)
(let ((arg (if (empty? args) nil (first args)))
(cont-data (continuation-data f)))
(let ((captured (get cont-data "captured"))
(rest-k (get cont-data "rest-kont")))
(make-cek-value arg env (concat captured rest-k))))
;; Native callable
(and (callable? f) (not (lambda? f)) (not (component? f)) (not (island? f)))
(make-cek-value (apply f args) env kont)
;; Lambda — bind params, evaluate body
(lambda? f)
(let ((params (lambda-params f))
(local (env-merge (lambda-closure f) env)))
(if (> (len args) (len params))
(error (str (or (lambda-name f) "lambda")
" expects " (len params) " args, got " (len args)))
(do
(for-each
(fn (pair) (env-set! local (first pair) (nth pair 1)))
(zip params args))
(for-each
(fn (p) (env-set! local p nil))
(slice params (len args)))
(make-cek-state (lambda-body f) local kont))))
;; Component — parse kwargs, bind, evaluate body
(or (component? f) (island? f))
(let ((parsed (parse-keyword-args raw-args env))
(kwargs (first parsed))
(children (nth parsed 1))
(local (env-merge (component-closure f) env)))
(for-each
(fn (p) (env-set! local p (or (dict-get kwargs p) nil)))
(component-params f))
(when (component-has-children? f)
(env-set! local "children" children))
(make-cek-state (component-body f) local kont))
:else (error (str "Not callable: " (inspect f))))))
;; --------------------------------------------------------------------------
;; 10. Case step loop helper
;; --------------------------------------------------------------------------
(define sf-case-step-loop
(fn (match-val clauses env kont)
(if (< (len clauses) 2)
(make-cek-value nil env kont)
(let ((test (first clauses))
(body (nth clauses 1)))
(if (or (and (= (type-of test) "keyword") (= (keyword-name test) "else"))
(and (= (type-of test) "symbol")
(or (= (symbol-name test) "else")
(= (symbol-name test) ":else"))))
(make-cek-state body env kont)
;; Evaluate test expression
(let ((test-val (trampoline (eval-expr test env))))
(if (= match-val test-val)
(make-cek-state body env kont)
(sf-case-step-loop match-val (slice clauses 2) env kont))))))))
;; --------------------------------------------------------------------------
;; 11. Compatibility wrapper — eval-expr-cek
;; --------------------------------------------------------------------------
;;
;; Drop-in replacement for eval-expr. Creates a CEK state and runs.
;; All downstream code (adapters, services) works unchanged.
(define eval-expr-cek
(fn (expr env)
(cek-run (make-cek-state expr env (list)))))
(define trampoline-cek
(fn (val)
;; In CEK mode, thunks are not produced — values are immediate.
;; But for compatibility, resolve any remaining thunks.
(if (thunk? val)
(eval-expr-cek (thunk-expr val) (thunk-env val))
val)))