JIT allowlist (sx_server.ml): - Replace try-every-lambda strategy with StringSet allowlist. Only functions in the list get JIT compiled (compiler, parser, pure transforms). Render functions that need dynamic scope skip JIT entirely — no retry overhead, no silent fallbacks. - Add (jit-allow name) command for dynamic expansion from Python bridge. - JIT failures log once with "[jit] DISABLED fn — reason" then go silent. Standalone --test mode (sx_server.ml): - New --test flag loads full env (spec + adapters + compiler + signals), supports --eval and --load flags. Quick kernel testing without Docker. Example: dune exec bin/sx_server.exe -- --test --eval '(len HTML_TAGS)' Integration tests (integration_tests.ml): - New binary exercising the full rendering pipeline: loads spec + adapters into a server-like env, renders HTML via both native and SX adapter paths. - 26 tests: HTML tags, special forms (when/if/let), letrec with side effects, component rendering, eval-expr with HTML tag functions. - Would have caught the "Undefined symbol: div/lake/init" issues from the previous commit immediately without Docker. VM cleanup (sx_vm.ml): - Remove temporary debug logging (insn counter, call_closure counter, VmClosure depth tracking) added during debugging. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2317 lines
95 KiB
Plaintext
2317 lines
95 KiB
Plaintext
;; ==========================================================================
|
|
;; evaluator.sx — The SX evaluator specification
|
|
;;
|
|
;; This is the canonical, single-file specification of SX evaluation.
|
|
;; All evaluation goes through the CEK machine (explicit control,
|
|
;; environment, and continuation). There is no tree-walk interpreter.
|
|
;;
|
|
;; Structure:
|
|
;; Part 1: CEK frames — state and continuation frame constructors
|
|
;; Part 2: Evaluation utilities — lambda/component call, keyword arg
|
|
;; parsing, macro expansion, quasiquote, definition forms
|
|
;; Part 3: CEK machine — step function, frame dispatch, call dispatch
|
|
;;
|
|
;; The evaluator is written in a restricted subset of SX that bootstrap
|
|
;; compilers (JS, Python) can transpile to native code.
|
|
;;
|
|
;; Platform interface (must be provided by each host):
|
|
;; See Part 2 section headers for type constructors, env operations,
|
|
;; and rendering primitives.
|
|
;; ==========================================================================
|
|
|
|
|
|
;; **************************************************************************
|
|
;; Part 1: CEK Frames — state, continuation, and frame constructors
|
|
;; **************************************************************************
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 1. CEK State constructors
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define make-cek-state
|
|
(fn (control env kont)
|
|
{:control control :env env :kont kont :phase "eval" :value nil}))
|
|
|
|
(define make-cek-value
|
|
(fn (value env kont)
|
|
{:control nil :env env :kont kont :phase "continue" :value value}))
|
|
|
|
(define cek-terminal?
|
|
(fn (state)
|
|
(and (= (get state "phase") "continue")
|
|
(empty? (get state "kont")))))
|
|
|
|
(define cek-control (fn (s) (get s "control")))
|
|
(define cek-env (fn (s) (get s "env")))
|
|
(define cek-kont (fn (s) (get s "kont")))
|
|
(define cek-phase (fn (s) (get s "phase")))
|
|
(define cek-value (fn (s) (get s "value")))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 2. Frame constructors
|
|
;; --------------------------------------------------------------------------
|
|
;; Each frame type is a dict with a "type" key and frame-specific data.
|
|
|
|
;; IfFrame: waiting for condition value
|
|
;; After condition evaluates, choose then or else branch
|
|
(define make-if-frame
|
|
(fn (then-expr else-expr env)
|
|
{:type "if" :then then-expr :else else-expr :env env}))
|
|
|
|
;; WhenFrame: waiting for condition value
|
|
;; If truthy, evaluate body exprs sequentially
|
|
(define make-when-frame
|
|
(fn (body-exprs env)
|
|
{:type "when" :body body-exprs :env env}))
|
|
|
|
;; BeginFrame: sequential evaluation
|
|
;; Remaining expressions to evaluate after current one
|
|
(define make-begin-frame
|
|
(fn (remaining env)
|
|
{:type "begin" :remaining remaining :env env}))
|
|
|
|
;; LetFrame: binding evaluation in progress
|
|
;; name = current binding name, remaining = remaining (name val) pairs
|
|
;; body = body expressions to evaluate after all bindings
|
|
(define make-let-frame
|
|
(fn (name remaining body local)
|
|
{:type "let" :name name :remaining remaining :body body :env local}))
|
|
|
|
;; DefineFrame: waiting for value to bind
|
|
(define make-define-frame
|
|
(fn (name env has-effects effect-list)
|
|
{:type "define" :name name :env env
|
|
:has-effects has-effects :effect-list effect-list}))
|
|
|
|
;; SetFrame: waiting for value to assign
|
|
(define make-set-frame
|
|
(fn (name env)
|
|
{:type "set" :name name :env env}))
|
|
|
|
;; ArgFrame: evaluating function arguments
|
|
;; f = function value (already evaluated), evaled = already evaluated args
|
|
;; remaining = remaining arg expressions
|
|
(define make-arg-frame
|
|
(fn (f evaled remaining env raw-args head-name)
|
|
{:type "arg" :f f :evaled evaled :remaining remaining :env env
|
|
:raw-args raw-args :head-name (or head-name nil)}))
|
|
|
|
;; CallFrame: about to call with fully evaluated args
|
|
(define make-call-frame
|
|
(fn (f args env)
|
|
{:type "call" :f f :args args :env env}))
|
|
|
|
;; CondFrame: evaluating cond clauses
|
|
(define make-cond-frame
|
|
(fn (remaining env scheme?)
|
|
{:type "cond" :remaining remaining :env env :scheme scheme?}))
|
|
|
|
;; CaseFrame: evaluating case clauses
|
|
(define make-case-frame
|
|
(fn (match-val remaining env)
|
|
{:type "case" :match-val match-val :remaining remaining :env env}))
|
|
|
|
;; ThreadFirstFrame: pipe threading
|
|
(define make-thread-frame
|
|
(fn (remaining env)
|
|
{:type "thread" :remaining remaining :env env}))
|
|
|
|
;; MapFrame: higher-order map/map-indexed in progress
|
|
(define make-map-frame
|
|
(fn (f remaining results env)
|
|
{:type "map" :f f :remaining remaining :results results :env env :indexed false}))
|
|
|
|
(define make-map-indexed-frame
|
|
(fn (f remaining results env)
|
|
{:type "map" :f f :remaining remaining :results results :env env :indexed true}))
|
|
|
|
;; FilterFrame: higher-order filter in progress
|
|
(define make-filter-frame
|
|
(fn (f remaining results current-item env)
|
|
{:type "filter" :f f :remaining remaining :results results
|
|
:current-item current-item :env env}))
|
|
|
|
;; ReduceFrame: higher-order reduce in progress
|
|
(define make-reduce-frame
|
|
(fn (f remaining env)
|
|
{:type "reduce" :f f :remaining remaining :env env}))
|
|
|
|
;; ForEachFrame: higher-order for-each in progress
|
|
(define make-for-each-frame
|
|
(fn (f remaining env)
|
|
{:type "for-each" :f f :remaining remaining :env env}))
|
|
|
|
;; SomeFrame: higher-order some (short-circuit on first truthy)
|
|
(define make-some-frame
|
|
(fn (f remaining env)
|
|
{:type "some" :f f :remaining remaining :env env}))
|
|
|
|
;; EveryFrame: higher-order every? (short-circuit on first falsy)
|
|
(define make-every-frame
|
|
(fn (f remaining env)
|
|
{:type "every" :f f :remaining remaining :env env}))
|
|
|
|
;; ScopeFrame: remaining body expressions for scope special form
|
|
(define make-scope-frame
|
|
(fn (name remaining 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
|
|
(define make-reset-frame
|
|
(fn (env)
|
|
{:type "reset" :env env}))
|
|
|
|
;; DictFrame: evaluating dict values
|
|
(define make-dict-frame
|
|
(fn (remaining results env)
|
|
{:type "dict" :remaining remaining :results results :env env}))
|
|
|
|
;; AndFrame: short-circuit and
|
|
(define make-and-frame
|
|
(fn (remaining env)
|
|
{:type "and" :remaining remaining :env env}))
|
|
|
|
;; OrFrame: short-circuit or
|
|
(define make-or-frame
|
|
(fn (remaining env)
|
|
{:type "or" :remaining remaining :env env}))
|
|
|
|
;; QuasiquoteFrame (not a real frame — QQ is handled specially)
|
|
|
|
;; DynamicWindFrame: phases of dynamic-wind
|
|
(define make-dynamic-wind-frame
|
|
(fn (phase body-thunk after-thunk env)
|
|
{:type "dynamic-wind" :phase phase
|
|
:body-thunk body-thunk :after-thunk after-thunk :env env}))
|
|
|
|
;; ReactiveResetFrame: delimiter for reactive deref-as-shift
|
|
;; Carries an update-fn that gets called with new values on re-render.
|
|
(define make-reactive-reset-frame
|
|
(fn (env update-fn first-render?)
|
|
{:type "reactive-reset" :env env :update-fn update-fn
|
|
:first-render first-render?}))
|
|
|
|
;; DerefFrame: awaiting evaluation of deref's argument
|
|
(define make-deref-frame
|
|
(fn (env)
|
|
{:type "deref" :env env}))
|
|
|
|
;; HoSetupFrame: staged evaluation of higher-order form arguments
|
|
;; ho-type is "map", "filter", "reduce", etc.
|
|
;; Evaluates args one at a time, then dispatches to the iteration frame.
|
|
(define make-ho-setup-frame
|
|
(fn (ho-type remaining-args evaled-args env)
|
|
{:type "ho-setup" :ho-type ho-type :remaining remaining-args
|
|
:evaled evaled-args :env env}))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 3. Frame accessors
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define frame-type (fn (f) (get f "type")))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 4. Continuation operations
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define kont-push
|
|
(fn (frame kont) (cons frame kont)))
|
|
|
|
(define kont-top
|
|
(fn (kont) (first kont)))
|
|
|
|
(define kont-pop
|
|
(fn (kont) (rest kont)))
|
|
|
|
(define kont-empty?
|
|
(fn (kont) (empty? kont)))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 5. CEK shift/reset support
|
|
;; --------------------------------------------------------------------------
|
|
;; shift captures all frames up to the nearest ResetFrame.
|
|
;; reset pushes a ResetFrame.
|
|
|
|
(define kont-capture-to-reset
|
|
(fn (kont)
|
|
;; Returns (captured-frames remaining-kont).
|
|
;; captured-frames: frames from top up to (not including) ResetFrame.
|
|
;; remaining-kont: frames after ResetFrame.
|
|
;; Stops at either "reset" or "reactive-reset" frames.
|
|
(define scan
|
|
(fn (k captured)
|
|
(if (empty? k)
|
|
(error "shift without enclosing reset")
|
|
(let ((frame (first k)))
|
|
(if (or (= (frame-type frame) "reset")
|
|
(= (frame-type frame) "reactive-reset"))
|
|
(list captured (rest k))
|
|
(scan (rest k) (append captured (list frame))))))))
|
|
(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
|
|
(define has-reactive-reset-frame?
|
|
(fn (kont)
|
|
(if (empty? kont) false
|
|
(if (= (frame-type (first kont)) "reactive-reset") true
|
|
(has-reactive-reset-frame? (rest kont))))))
|
|
|
|
;; Capture frames up to nearest ReactiveResetFrame.
|
|
;; Returns (captured-frames, reset-frame, remaining-kont).
|
|
(define kont-capture-to-reactive-reset
|
|
(fn (kont)
|
|
(define scan
|
|
(fn (k captured)
|
|
(if (empty? k)
|
|
(error "reactive deref without enclosing reactive-reset")
|
|
(let ((frame (first k)))
|
|
(if (= (frame-type frame) "reactive-reset")
|
|
(list captured frame (rest k))
|
|
(scan (rest k) (append captured (list frame))))))))
|
|
(scan kont (list))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Extension points — custom special forms and render dispatch
|
|
;; --------------------------------------------------------------------------
|
|
;;
|
|
;; Extensions (web forms, type system, etc.) register handlers here.
|
|
;; The evaluator calls these from step-eval-list after core forms.
|
|
|
|
(define *custom-special-forms* (dict))
|
|
|
|
(define register-special-form!
|
|
(fn ((name :as string) handler)
|
|
(dict-set! *custom-special-forms* name handler)))
|
|
|
|
;; Render dispatch — installed by web adapters, nil when no renderer active.
|
|
;; *render-check*: (expr env) → boolean — should this expression be rendered?
|
|
;; *render-fn*: (expr env) → value — render and return result
|
|
(define *render-check* nil)
|
|
(define *render-fn* nil)
|
|
|
|
|
|
;; **************************************************************************
|
|
;; Part 2: Evaluation Utilities
|
|
;; **************************************************************************
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 1. Types
|
|
;; --------------------------------------------------------------------------
|
|
;;
|
|
;; The evaluator operates on these value types:
|
|
;;
|
|
;; number — integer or float
|
|
;; string — double-quoted text
|
|
;; boolean — true / false
|
|
;; nil — singleton null
|
|
;; symbol — unquoted identifier (e.g. div, ~card, map)
|
|
;; keyword — colon-prefixed key (e.g. :class, :id)
|
|
;; list — ordered sequence (also used as code)
|
|
;; dict — string-keyed hash map
|
|
;; lambda — closure: {params, body, closure-env, name?}
|
|
;; macro — AST transformer: {params, rest-param, body, closure-env}
|
|
;; component — UI component: {name, params, has-children, body, closure-env}
|
|
;; island — reactive component: like component but with island flag
|
|
;; thunk — deferred eval for TCO: {expr, env}
|
|
;;
|
|
;; Each target must provide:
|
|
;; (type-of x) → one of the strings above
|
|
;; (make-lambda ...) → platform Lambda value
|
|
;; (make-component ..) → platform Component value
|
|
;; (make-island ...) → platform Island value (component + island flag)
|
|
;; (make-macro ...) → platform Macro value
|
|
;; (make-thunk ...) → platform Thunk value
|
|
;;
|
|
;; These are declared in platform.sx and implemented per target.
|
|
;; --------------------------------------------------------------------------
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 2. Trampoline — tail-call optimization
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define trampoline
|
|
(fn ((val :as any))
|
|
;; Iteratively resolve thunks until we get an actual value.
|
|
;; Each target implements thunk? and thunk-expr/thunk-env.
|
|
(let ((result val))
|
|
(do
|
|
;; Loop while result is a thunk
|
|
;; Note: this is pseudo-iteration — bootstrap compilers convert
|
|
;; this tail-recursive form to a while loop.
|
|
(if (thunk? result)
|
|
(trampoline (eval-expr (thunk-expr result) (thunk-env result)))
|
|
result)))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 2b. Strict mode — runtime type checking for primitive calls
|
|
;; --------------------------------------------------------------------------
|
|
;;
|
|
;; When *strict* is true, primitive calls check arg types before dispatch.
|
|
;; The primitive param type registry maps name → {positional [[name type]...],
|
|
;; rest-type type-or-nil}. Stored in *prim-param-types* in the env.
|
|
;;
|
|
;; Strict mode is off by default. Hosts can enable it at startup via:
|
|
;; (set-strict! true)
|
|
;; (set-prim-param-types! types-dict)
|
|
|
|
(define *strict* false)
|
|
|
|
(define set-strict!
|
|
(fn (val)
|
|
(set! *strict* val)))
|
|
|
|
(define *prim-param-types* nil)
|
|
|
|
(define set-prim-param-types!
|
|
(fn (types)
|
|
(set! *prim-param-types* types)))
|
|
|
|
(define value-matches-type?
|
|
(fn (val expected-type)
|
|
;; Check if a runtime value matches a declared type string.
|
|
(cond
|
|
(= expected-type "any") true
|
|
(= expected-type "number") (number? val)
|
|
(= expected-type "string") (string? val)
|
|
(= expected-type "boolean") (boolean? val)
|
|
(= expected-type "nil") (nil? val)
|
|
(= expected-type "list") (list? val)
|
|
(= expected-type "dict") (dict? val)
|
|
(= expected-type "lambda") (lambda? val)
|
|
(= expected-type "symbol") (= (type-of val) "symbol")
|
|
(= expected-type "keyword") (= (type-of val) "keyword")
|
|
;; Nullable: "string?" means string or nil
|
|
(and (string? expected-type)
|
|
(ends-with? expected-type "?"))
|
|
(or (nil? val)
|
|
(value-matches-type? val (slice expected-type 0 (- (string-length expected-type) 1))))
|
|
:else true)))
|
|
|
|
(define strict-check-args
|
|
(fn (name args)
|
|
;; Check args against *prim-param-types* if strict mode is on.
|
|
;; Throws on type violation. No-op if *strict* is false or types not registered.
|
|
(when (and *strict* *prim-param-types*)
|
|
(let ((spec (get *prim-param-types* name)))
|
|
(when spec
|
|
(let ((positional (get spec "positional"))
|
|
(rest-type (get spec "rest-type")))
|
|
;; Check positional params
|
|
(when positional
|
|
(for-each
|
|
(fn (pair)
|
|
(let ((idx (first pair))
|
|
(param (nth pair 1))
|
|
(p-name (first param))
|
|
(p-type (nth param 1)))
|
|
(when (< idx (len args))
|
|
(let ((val (nth args idx)))
|
|
(when (not (value-matches-type? val p-type))
|
|
(error (str "Type error: " name " expected " p-type
|
|
" for param " p-name
|
|
", got " (type-of val) " (" (str val) ")")))))))
|
|
(map-indexed (fn (i p) (list i p)) positional)))
|
|
;; Check rest args
|
|
(when (and rest-type (> (len args) (len (or positional (list)))))
|
|
(for-each
|
|
(fn (pair)
|
|
(let ((idx (first pair))
|
|
(val (nth pair 1)))
|
|
(when (not (value-matches-type? val rest-type))
|
|
(error (str "Type error: " name " expected " rest-type
|
|
" for rest arg " idx
|
|
", got " (type-of val) " (" (str val) ")")))))
|
|
(map-indexed (fn (i v) (list i v))
|
|
(slice args (len (or positional (list)))))))))))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 3. Core evaluator — stub (overridden by CEK in fixups)
|
|
;; --------------------------------------------------------------------------
|
|
;;
|
|
;; eval-expr and trampoline are defined as stubs here so the transpiler
|
|
;; creates the variable declarations. The CEK fixups override them with:
|
|
;; eval-expr = (expr, env) → cek-run(make-cek-state(expr, env, []))
|
|
;; trampoline = (val) → if thunk? then eval-expr(thunk-expr, thunk-env) else val
|
|
;; All evaluation goes through the CEK machine.
|
|
|
|
;; eval-expr: forward declaration — redefined at end of file after cek-run exists.
|
|
;; This stub is needed so functions between here and Part 3 can reference eval-expr.
|
|
(define eval-expr
|
|
(fn (expr (env :as dict)) nil))
|
|
|
|
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 5. Function / lambda / component call
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define call-lambda
|
|
(fn ((f :as lambda) (args :as list) (caller-env :as dict))
|
|
(let ((params (lambda-params f))
|
|
(local (env-merge (lambda-closure f) caller-env)))
|
|
;; Too many args is an error; too few pads with nil
|
|
(if (> (len args) (len params))
|
|
(error (str (or (lambda-name f) "lambda")
|
|
" expects " (len params) " args, got " (len args)))
|
|
(do
|
|
;; Bind params — provided args first, then nil for missing
|
|
(for-each
|
|
(fn (pair) (env-bind! local (first pair) (nth pair 1)))
|
|
(zip params args))
|
|
(for-each
|
|
(fn (p) (env-bind! local p nil))
|
|
(slice params (len args)))
|
|
;; Return thunk for TCO
|
|
(make-thunk (lambda-body f) local))))))
|
|
|
|
|
|
(define call-component
|
|
(fn ((comp :as component) (raw-args :as list) (env :as dict))
|
|
;; Parse keyword args and children from unevaluated arg list
|
|
(let ((parsed (parse-keyword-args raw-args env))
|
|
(kwargs (first parsed))
|
|
(children (nth parsed 1))
|
|
(local (env-merge (component-closure comp) env)))
|
|
;; Bind keyword params
|
|
(for-each
|
|
(fn (p) (env-bind! local p (or (dict-get kwargs p) nil)))
|
|
(component-params comp))
|
|
;; Bind children if component accepts them
|
|
(when (component-has-children? comp)
|
|
(env-bind! local "children" children))
|
|
;; Return thunk — body evaluated in local env
|
|
(make-thunk (component-body comp) local))))
|
|
|
|
|
|
(define parse-keyword-args
|
|
(fn ((raw-args :as list) (env :as dict))
|
|
;; Walk args: keyword + next-val → kwargs dict, else → children list
|
|
(let ((kwargs (dict))
|
|
(children (list))
|
|
(i 0))
|
|
;; Iterative parse — bootstrap converts to while loop
|
|
(reduce
|
|
(fn (state arg)
|
|
(let ((idx (get state "i"))
|
|
(skip (get state "skip")))
|
|
(if skip
|
|
;; This arg was consumed as a keyword value
|
|
(assoc state "skip" false "i" (inc idx))
|
|
(if (and (= (type-of arg) "keyword")
|
|
(< (inc idx) (len raw-args)))
|
|
;; Keyword: evaluate next arg and store
|
|
(do
|
|
(dict-set! kwargs (keyword-name arg)
|
|
(trampoline (eval-expr (nth raw-args (inc idx)) env)))
|
|
(assoc state "skip" true "i" (inc idx)))
|
|
;; Positional: evaluate and add to children
|
|
(do
|
|
(append! children (trampoline (eval-expr arg env)))
|
|
(assoc state "i" (inc idx)))))))
|
|
(dict "i" 0 "skip" false)
|
|
raw-args)
|
|
(list kwargs children))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 6. Special forms
|
|
;; --------------------------------------------------------------------------
|
|
;; — all superseded by CEK step handlers in cek.sx
|
|
|
|
|
|
;; cond-scheme? — still needed by CEK's step-sf-cond
|
|
(define cond-scheme?
|
|
(fn ((clauses :as list))
|
|
(every? (fn (c) (and (= (type-of c) "list") (= (len c) 2)))
|
|
clauses)))
|
|
|
|
;; is-else-clause? — check if a cond/case test is an else marker
|
|
(define is-else-clause?
|
|
(fn (test)
|
|
(or (and (= (type-of test) "keyword") (= (keyword-name test) "else"))
|
|
(and (= (type-of test) "symbol")
|
|
(or (= (symbol-name test) "else")
|
|
(= (symbol-name test) ":else"))))))
|
|
|
|
|
|
;; Named let: (let name ((x 0) (y 1)) body...)
|
|
;; Desugars to a self-recursive lambda called with initial values.
|
|
;; The loop name is bound in the body so recursive calls produce TCO thunks.
|
|
(define sf-named-let
|
|
(fn ((args :as list) (env :as dict))
|
|
(let ((loop-name (symbol-name (first args)))
|
|
(bindings (nth args 1))
|
|
(body (slice args 2))
|
|
(params (list))
|
|
(inits (list)))
|
|
;; Extract param names and init expressions
|
|
(if (and (= (type-of (first bindings)) "list")
|
|
(= (len (first bindings)) 2))
|
|
;; Scheme-style: ((x 0) (y 1))
|
|
(for-each
|
|
(fn (binding)
|
|
(append! params (if (= (type-of (first binding)) "symbol")
|
|
(symbol-name (first binding))
|
|
(first binding)))
|
|
(append! inits (nth binding 1)))
|
|
bindings)
|
|
;; Clojure-style: (x 0 y 1)
|
|
(reduce
|
|
(fn (acc pair-idx)
|
|
(do
|
|
(append! params (if (= (type-of (nth bindings (* pair-idx 2))) "symbol")
|
|
(symbol-name (nth bindings (* pair-idx 2)))
|
|
(nth bindings (* pair-idx 2))))
|
|
(append! inits (nth bindings (inc (* pair-idx 2))))))
|
|
nil
|
|
(range 0 (/ (len bindings) 2))))
|
|
;; Build loop body (wrap in begin if multiple exprs)
|
|
(let ((loop-body (if (= (len body) 1) (first body)
|
|
(cons (make-symbol "begin") body)))
|
|
(loop-fn (make-lambda params loop-body env)))
|
|
;; Self-reference: loop can call itself by name
|
|
(set-lambda-name! loop-fn loop-name)
|
|
(env-bind! (lambda-closure loop-fn) loop-name loop-fn)
|
|
;; Evaluate initial values in enclosing env, then call
|
|
(let ((init-vals (map (fn (e) (trampoline (eval-expr e env))) inits)))
|
|
(call-lambda loop-fn init-vals env))))))
|
|
|
|
|
|
(define sf-lambda
|
|
(fn ((args :as list) (env :as dict))
|
|
(let ((params-expr (first args))
|
|
(body-exprs (rest args))
|
|
(body (if (= (len body-exprs) 1)
|
|
(first body-exprs)
|
|
(cons (make-symbol "begin") body-exprs)))
|
|
(param-names (map (fn (p)
|
|
(cond
|
|
(= (type-of p) "symbol")
|
|
(symbol-name p)
|
|
;; Annotated param: (name :as type) → extract name
|
|
(and (= (type-of p) "list")
|
|
(= (len p) 3)
|
|
(= (type-of (nth p 1)) "keyword")
|
|
(= (keyword-name (nth p 1)) "as"))
|
|
(symbol-name (first p))
|
|
:else p))
|
|
params-expr)))
|
|
(make-lambda param-names body env))))
|
|
|
|
|
|
(define sf-defcomp
|
|
(fn ((args :as list) (env :as dict))
|
|
;; (defcomp ~name (params) [:affinity :client|:server] body)
|
|
;; Body is always the last element. Optional keyword annotations
|
|
;; may appear between the params list and the body.
|
|
(let ((name-sym (first args))
|
|
(params-raw (nth args 1))
|
|
(body (last args))
|
|
(comp-name (strip-prefix (symbol-name name-sym) "~"))
|
|
(parsed (parse-comp-params params-raw))
|
|
(params (first parsed))
|
|
(has-children (nth parsed 1))
|
|
(param-types (nth parsed 2))
|
|
(affinity (defcomp-kwarg args "affinity" "auto")))
|
|
(let ((comp (make-component comp-name params has-children body env affinity))
|
|
(effects (defcomp-kwarg args "effects" nil)))
|
|
;; Store type annotations if any were declared
|
|
(when (and (not (nil? param-types))
|
|
(not (empty? (keys param-types))))
|
|
(component-set-param-types! comp param-types))
|
|
;; Store effect annotation if declared
|
|
(when (not (nil? effects))
|
|
(let ((effect-list (if (= (type-of effects) "list")
|
|
(map (fn (e) (if (= (type-of e) "symbol")
|
|
(symbol-name e) (str e)))
|
|
effects)
|
|
(list (str effects))))
|
|
(effect-anns (if (env-has? env "*effect-annotations*")
|
|
(env-get env "*effect-annotations*")
|
|
(dict))))
|
|
(dict-set! effect-anns (symbol-name name-sym) effect-list)
|
|
(env-bind! env "*effect-annotations*" effect-anns)))
|
|
(env-bind! env (symbol-name name-sym) comp)
|
|
comp))))
|
|
|
|
(define defcomp-kwarg
|
|
(fn ((args :as list) (key :as string) default)
|
|
;; Search for :key value between params (index 2) and body (last).
|
|
(let ((end (- (len args) 1))
|
|
(result default))
|
|
(for-each
|
|
(fn (i)
|
|
(when (and (= (type-of (nth args i)) "keyword")
|
|
(= (keyword-name (nth args i)) key)
|
|
(< (+ i 1) end))
|
|
(let ((val (nth args (+ i 1))))
|
|
(set! result (if (= (type-of val) "keyword")
|
|
(keyword-name val) val)))))
|
|
(range 2 end 1))
|
|
result)))
|
|
|
|
(define parse-comp-params
|
|
(fn ((params-expr :as list))
|
|
;; Parse (&key param1 param2 &children) → (params has-children param-types)
|
|
;; Also accepts &rest as synonym for &children.
|
|
;; Supports typed params: (name :as type) — a 3-element list where
|
|
;; the second element is the keyword :as. Unannotated params get no
|
|
;; type entry. param-types is a dict {name → type-expr} or empty dict.
|
|
(let ((params (list))
|
|
(param-types (dict))
|
|
(has-children false)
|
|
(in-key false))
|
|
(for-each
|
|
(fn (p)
|
|
(if (and (= (type-of p) "list")
|
|
(= (len p) 3)
|
|
(= (type-of (first p)) "symbol")
|
|
(= (type-of (nth p 1)) "keyword")
|
|
(= (keyword-name (nth p 1)) "as"))
|
|
;; Typed param: (name :as type)
|
|
(let ((name (symbol-name (first p)))
|
|
(ptype (nth p 2)))
|
|
;; Convert type to string if it's a symbol
|
|
(let ((type-val (if (= (type-of ptype) "symbol")
|
|
(symbol-name ptype)
|
|
ptype)))
|
|
(when (not has-children)
|
|
(append! params name)
|
|
(dict-set! param-types name type-val))))
|
|
;; Untyped param or marker
|
|
(when (= (type-of p) "symbol")
|
|
(let ((name (symbol-name p)))
|
|
(cond
|
|
(= name "&key") (set! in-key true)
|
|
(= name "&rest") (set! has-children true)
|
|
(= name "&children") (set! has-children true)
|
|
has-children nil ;; skip params after &children/&rest
|
|
in-key (append! params name)
|
|
:else (append! params name))))))
|
|
params-expr)
|
|
(list params has-children param-types))))
|
|
|
|
|
|
(define sf-defisland
|
|
(fn ((args :as list) (env :as dict))
|
|
;; (defisland ~name (params) body)
|
|
;; Like defcomp but creates an island (reactive component).
|
|
;; Islands have the same calling convention as components but
|
|
;; render with a reactive context on the client.
|
|
(let ((name-sym (first args))
|
|
(params-raw (nth args 1))
|
|
(body (last args))
|
|
(comp-name (strip-prefix (symbol-name name-sym) "~"))
|
|
(parsed (parse-comp-params params-raw))
|
|
(params (first parsed))
|
|
(has-children (nth parsed 1)))
|
|
(let ((island (make-island comp-name params has-children body env)))
|
|
(env-bind! env (symbol-name name-sym) island)
|
|
island))))
|
|
|
|
|
|
(define sf-defmacro
|
|
(fn ((args :as list) (env :as dict))
|
|
(let ((name-sym (first args))
|
|
(params-raw (nth args 1))
|
|
(body (nth args 2))
|
|
(parsed (parse-macro-params params-raw))
|
|
(params (first parsed))
|
|
(rest-param (nth parsed 1)))
|
|
(let ((mac (make-macro params rest-param body env (symbol-name name-sym))))
|
|
(env-bind! env (symbol-name name-sym) mac)
|
|
mac))))
|
|
|
|
(define parse-macro-params
|
|
(fn ((params-expr :as list))
|
|
;; Parse (a b &rest rest) → ((a b) rest)
|
|
(let ((params (list))
|
|
(rest-param nil))
|
|
(reduce
|
|
(fn (state p)
|
|
(if (and (= (type-of p) "symbol") (= (symbol-name p) "&rest"))
|
|
(assoc state "in-rest" true)
|
|
(if (get state "in-rest")
|
|
(do (set! rest-param (if (= (type-of p) "symbol")
|
|
(symbol-name p) p))
|
|
state)
|
|
(do (append! params (if (= (type-of p) "symbol")
|
|
(symbol-name p) p))
|
|
state))))
|
|
(dict "in-rest" false)
|
|
params-expr)
|
|
(list params rest-param))))
|
|
|
|
|
|
(define qq-expand
|
|
(fn (template (env :as dict))
|
|
(if (not (= (type-of template) "list"))
|
|
template
|
|
(if (empty? template)
|
|
(list)
|
|
(let ((head (first template)))
|
|
(if (and (= (type-of head) "symbol") (= (symbol-name head) "unquote"))
|
|
(trampoline (eval-expr (nth template 1) env))
|
|
;; Walk children, handling splice-unquote
|
|
(reduce
|
|
(fn (result item)
|
|
(if (and (= (type-of item) "list")
|
|
(= (len item) 2)
|
|
(= (type-of (first item)) "symbol")
|
|
(= (symbol-name (first item)) "splice-unquote"))
|
|
(let ((spliced (trampoline (eval-expr (nth item 1) env))))
|
|
(if (= (type-of spliced) "list")
|
|
(concat result spliced)
|
|
(if (nil? spliced) result (concat result (list spliced)))))
|
|
(concat result (list (qq-expand item env)))))
|
|
(list)
|
|
template)))))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 6c. letrec — mutually recursive local bindings
|
|
;; --------------------------------------------------------------------------
|
|
;;
|
|
;; (letrec ((even? (fn (n) (if (= n 0) true (odd? (- n 1)))))
|
|
;; (odd? (fn (n) (if (= n 0) false (even? (- n 1))))))
|
|
;; (even? 10))
|
|
;;
|
|
;; All bindings are first set to nil in the local env, then all values
|
|
;; are evaluated (so they can see each other's names), then lambda
|
|
;; closures are patched to include the final bindings.
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define sf-letrec
|
|
(fn ((args :as list) (env :as dict))
|
|
(let ((bindings (first args))
|
|
(body (rest args))
|
|
(local (env-extend env))
|
|
(names (list))
|
|
(val-exprs (list)))
|
|
;; First pass: bind all names to nil
|
|
(if (and (= (type-of (first bindings)) "list")
|
|
(= (len (first bindings)) 2))
|
|
;; Scheme-style
|
|
(for-each
|
|
(fn (binding)
|
|
(let ((vname (if (= (type-of (first binding)) "symbol")
|
|
(symbol-name (first binding))
|
|
(first binding))))
|
|
(append! names vname)
|
|
(append! val-exprs (nth binding 1))
|
|
(env-bind! local vname nil)))
|
|
bindings)
|
|
;; Clojure-style
|
|
(reduce
|
|
(fn (acc pair-idx)
|
|
(let ((vname (if (= (type-of (nth bindings (* pair-idx 2))) "symbol")
|
|
(symbol-name (nth bindings (* pair-idx 2)))
|
|
(nth bindings (* pair-idx 2))))
|
|
(val-expr (nth bindings (inc (* pair-idx 2)))))
|
|
(append! names vname)
|
|
(append! val-exprs val-expr)
|
|
(env-bind! local vname nil)))
|
|
nil
|
|
(range 0 (/ (len bindings) 2))))
|
|
;; Second pass: evaluate values (they can see each other's names)
|
|
(let ((values (map (fn (e) (trampoline (eval-expr e local))) val-exprs)))
|
|
;; Bind final values
|
|
(for-each
|
|
(fn (pair) (env-bind! local (first pair) (nth pair 1)))
|
|
(zip names values))
|
|
;; Patch lambda closures so they see the final bindings
|
|
(for-each
|
|
(fn (val)
|
|
(when (lambda? val)
|
|
(for-each
|
|
(fn (n) (env-bind! (lambda-closure val) n (env-get local n)))
|
|
names)))
|
|
values))
|
|
;; Evaluate body
|
|
(for-each
|
|
(fn (e) (trampoline (eval-expr e local)))
|
|
(slice body 0 (dec (len body))))
|
|
(make-thunk (last body) local))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 6d. dynamic-wind — entry/exit guards
|
|
;; --------------------------------------------------------------------------
|
|
;;
|
|
;; (dynamic-wind before-thunk body-thunk after-thunk)
|
|
;;
|
|
;; All three are zero-argument functions (thunks):
|
|
;; 1. Call before-thunk
|
|
;; 2. Call body-thunk, capture result
|
|
;; 3. Call after-thunk (always, even on error)
|
|
;; 4. Return body result
|
|
;;
|
|
;; The wind stack is maintained so that when continuations jump across
|
|
;; dynamic-wind boundaries, the correct before/after thunks fire.
|
|
;; Without active continuations, this is equivalent to try/finally.
|
|
;;
|
|
;; Platform requirements:
|
|
;; (push-wind! before after) — push wind record onto stack
|
|
;; (pop-wind!) — pop wind record from stack
|
|
;; (call-thunk f env) — call a zero-arg function
|
|
;; --------------------------------------------------------------------------
|
|
|
|
;; step-sf-letrec: sf-letrec evaluates bindings + intermediate body,
|
|
;; returns a thunk for the last body expression. Unwrap into CEK state
|
|
;; so the last expression is properly evaluated by the CEK machine.
|
|
(define step-sf-letrec
|
|
(fn (args env kont)
|
|
(let ((thk (sf-letrec args env)))
|
|
(make-cek-state (thunk-expr thk) (thunk-env thk) kont))))
|
|
|
|
(define sf-dynamic-wind
|
|
(fn ((args :as list) (env :as dict))
|
|
(let ((before (trampoline (eval-expr (first args) env)))
|
|
(body (trampoline (eval-expr (nth args 1) env)))
|
|
(after (trampoline (eval-expr (nth args 2) env))))
|
|
;; Delegate to platform — needs try/finally for error safety
|
|
(dynamic-wind-call before body after env))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 6a2. scope — unified render-time dynamic scope primitive
|
|
;; --------------------------------------------------------------------------
|
|
;;
|
|
;; (scope name body...) or (scope name :value v body...)
|
|
;; Push a named scope with optional value and empty accumulator,
|
|
;; evaluate body, pop scope. Returns last body result.
|
|
;;
|
|
;; `provide` is sugar: (provide name value body...) = (scope name :value value body...)
|
|
|
|
(define sf-scope
|
|
(fn ((args :as list) (env :as dict))
|
|
(let ((name (trampoline (eval-expr (first args) env)))
|
|
(rest (slice args 1))
|
|
(val nil)
|
|
(body-exprs nil))
|
|
;; Check for :value keyword
|
|
(if (and (>= (len rest) 2) (= (type-of (first rest)) "keyword") (= (keyword-name (first rest)) "value"))
|
|
(do (set! val (trampoline (eval-expr (nth rest 1) env)))
|
|
(set! body-exprs (slice rest 2)))
|
|
(set! body-exprs rest))
|
|
(scope-push! name val)
|
|
(let ((result nil))
|
|
(for-each (fn (e) (set! result (trampoline (eval-expr e env)))) body-exprs)
|
|
(scope-pop! name)
|
|
result))))
|
|
|
|
|
|
;; provide — sugar for scope with a value
|
|
;; (provide name value body...) → (scope name :value value body...)
|
|
|
|
(define sf-provide
|
|
(fn ((args :as list) (env :as dict))
|
|
(let ((name (trampoline (eval-expr (first args) env)))
|
|
(val (trampoline (eval-expr (nth args 1) env)))
|
|
(body-exprs (slice args 2))
|
|
(result nil))
|
|
(scope-push! name val)
|
|
(for-each (fn (e) (set! result (trampoline (eval-expr e env)))) body-exprs)
|
|
(scope-pop! name)
|
|
result)))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 6b. Macro expansion
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define expand-macro
|
|
(fn ((mac :as macro) (raw-args :as list) (env :as dict))
|
|
(let ((local (env-merge (macro-closure mac) env)))
|
|
;; Bind positional params (unevaluated)
|
|
(for-each
|
|
(fn (pair)
|
|
(env-bind! local (first pair)
|
|
(if (< (nth pair 1) (len raw-args))
|
|
(nth raw-args (nth pair 1))
|
|
nil)))
|
|
(map-indexed (fn (i p) (list p i)) (macro-params mac)))
|
|
;; Bind &rest param
|
|
(when (macro-rest-param mac)
|
|
(env-bind! local (macro-rest-param mac)
|
|
(slice raw-args (len (macro-params mac)))))
|
|
;; Evaluate body → new AST
|
|
(trampoline (eval-expr (macro-body mac) local)))))
|
|
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 8. Primitives — pure functions available in all targets
|
|
;; --------------------------------------------------------------------------
|
|
;; These are the ~80 built-in functions. Each target implements them
|
|
;; natively but they MUST have identical semantics. This section serves
|
|
;; as the specification — bootstrap compilers use it for reference.
|
|
;;
|
|
;; Primitives are NOT defined here as SX lambdas (that would be circular).
|
|
;; Instead, this is a declarative registry that bootstrap compilers read.
|
|
;; --------------------------------------------------------------------------
|
|
|
|
;; See primitives.sx for the full specification.
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 9. Platform interface — must be provided by each target
|
|
;; --------------------------------------------------------------------------
|
|
;;
|
|
;; Type inspection:
|
|
;; (type-of x) → "number" | "string" | "boolean" | "nil"
|
|
;; | "symbol" | "keyword" | "list" | "dict"
|
|
;; | "lambda" | "component" | "macro" | "thunk"
|
|
;; | "spread"
|
|
;; (symbol-name sym) → string
|
|
;; (keyword-name kw) → string
|
|
;;
|
|
;; Constructors:
|
|
;; (make-lambda params body env) → Lambda
|
|
;; (make-component name params has-children body env affinity) → Component
|
|
;; (make-macro params rest-param body env name) → Macro
|
|
;; (make-thunk expr env) → Thunk
|
|
;;
|
|
;; Accessors:
|
|
;; (lambda-params f) → list of strings
|
|
;; (lambda-body f) → expr
|
|
;; (lambda-closure f) → env
|
|
;; (lambda-name f) → string or nil
|
|
;; (set-lambda-name! f n) → void
|
|
;; (component-params c) → list of strings
|
|
;; (component-body c) → expr
|
|
;; (component-closure c) → env
|
|
;; (component-has-children? c) → boolean
|
|
;; (component-affinity c) → "auto" | "client" | "server"
|
|
;;
|
|
;; (make-island name params has-children body env) → Island
|
|
;; (island? x) → boolean
|
|
;; ;; Islands reuse component accessors: component-params, component-body, etc.
|
|
;;
|
|
;; (make-spread attrs) → Spread (attrs dict injected onto parent element)
|
|
;; (spread? x) → boolean
|
|
;; (spread-attrs s) → dict
|
|
;;
|
|
;; (macro-params m) → list of strings
|
|
;; (macro-rest-param m) → string or nil
|
|
;; (macro-body m) → expr
|
|
;; (macro-closure m) → env
|
|
;; (thunk? x) → boolean
|
|
;; (thunk-expr t) → expr
|
|
;; (thunk-env t) → env
|
|
;;
|
|
;; Predicates:
|
|
;; (callable? x) → boolean (native function or lambda)
|
|
;; (lambda? x) → boolean
|
|
;; (component? x) → boolean
|
|
;; (island? x) → boolean
|
|
;; (macro? x) → boolean
|
|
;; (primitive? name) → boolean (is name a registered primitive?)
|
|
;; (get-primitive name) → function
|
|
;;
|
|
;; Environment:
|
|
;; (env-has? env name) → boolean
|
|
;; (env-get env name) → value
|
|
;; (env-bind! env name val) → void (create binding on THIS env, no chain walk)
|
|
;; (env-set! env name val) → void (mutate existing binding, walks scope chain)
|
|
;; (env-extend env) → new env inheriting from env
|
|
;; (env-merge base overlay) → new env with overlay on top
|
|
;;
|
|
;; Mutation helpers (for parse-keyword-args):
|
|
;; (dict-set! d key val) → void
|
|
;; (dict-get d key) → value or nil
|
|
;; (append! lst val) → void (mutating append)
|
|
;;
|
|
;; Error:
|
|
;; (error msg) → raise/throw with message
|
|
;; (inspect x) → string representation for debugging
|
|
;;
|
|
;; Utility:
|
|
;; (strip-prefix s prefix) → string with prefix removed (or s unchanged)
|
|
;; (apply f args) → call f with args list
|
|
;; (zip lists...) → list of tuples
|
|
;;
|
|
;;
|
|
;; Dynamic wind (for dynamic-wind):
|
|
;; (push-wind! before after) → void (push wind record onto stack)
|
|
;; (pop-wind!) → void (pop wind record from stack)
|
|
;; (call-thunk f env) → value (call a zero-arg function)
|
|
;;
|
|
;; Extension hooks (set by web adapters, type system, etc.):
|
|
;; *custom-special-forms* — dict of name → handler fn
|
|
;; register-special-form! — (name handler) → registers custom form
|
|
;; *render-check* — nil or (expr env) → boolean
|
|
;; *render-fn* — nil or (expr env) → value
|
|
;; --------------------------------------------------------------------------
|
|
|
|
|
|
;; **************************************************************************
|
|
;; Part 3: CEK Machine — the sole evaluator
|
|
;; **************************************************************************
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 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)))))
|
|
;; Warn when a ~component symbol resolves to nil (likely missing)
|
|
(when (and (nil? val) (starts-with? name "~"))
|
|
(debug-log "Component not found:" 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 "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") (step-sf-letrec args env kont)
|
|
|
|
;; Continuations — native in CEK
|
|
(= name "reset") (step-sf-reset args env kont)
|
|
(= name "shift") (step-sf-shift args env kont)
|
|
|
|
;; Reactive deref-as-shift
|
|
(= name "deref") (step-sf-deref args env kont)
|
|
|
|
;; Scoped effects — frame-based dynamic scope
|
|
(= name "scope") (step-sf-scope 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
|
|
(= 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") (step-ho-map-indexed args env kont)
|
|
(= name "filter") (step-ho-filter args env kont)
|
|
(= name "reduce") (step-ho-reduce args env kont)
|
|
(= name "some") (step-ho-some args env kont)
|
|
(= name "every?") (step-ho-every args env kont)
|
|
(= name "for-each") (step-ho-for-each args env kont)
|
|
|
|
;; Custom special forms (registered by extensions)
|
|
(has-key? *custom-special-forms* name)
|
|
(make-cek-value
|
|
((get *custom-special-forms* name) args env)
|
|
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 dispatch (installed by web adapters)
|
|
(and *render-check* (*render-check* expr env))
|
|
(make-cek-value (*render-fn* 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 (is-else-clause? test)
|
|
(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 (is-else-clause? test)
|
|
(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
|
|
;; scope/provide/context/emit!/emitted — CEK frame-based.
|
|
;; provide/scope push proper CEK frames onto the continuation so that
|
|
;; shift/reset can capture and restore them correctly.
|
|
;; context/emit!/emitted walk the kont to find the relevant frame.
|
|
|
|
;; scope: push ScopeAccFrame, evaluate body expressions via continuation.
|
|
;; (scope name body...) or (scope name :value v body...)
|
|
(define step-sf-scope
|
|
(fn (args env kont)
|
|
(let ((name (trampoline (eval-expr (first args) env)))
|
|
(rest-args (slice args 1))
|
|
(val nil)
|
|
(body nil))
|
|
(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))
|
|
(if (empty? body)
|
|
(make-cek-value nil env kont)
|
|
(make-cek-state
|
|
(first body) env
|
|
(kont-push (make-scope-acc-frame name val (rest body) env) kont))))))
|
|
|
|
;; provide: push ProvideFrame, evaluate body expressions via continuation.
|
|
(define step-sf-provide
|
|
(fn (args env kont)
|
|
(let ((name (trampoline (eval-expr (first args) env)))
|
|
(val (trampoline (eval-expr (nth args 1) env)))
|
|
(body (slice args 2)))
|
|
(if (empty? body)
|
|
(make-cek-value nil 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)))
|
|
(make-cek-value (if (nil? frame) default-val (get frame "value")) env kont))))
|
|
|
|
;; emit!: walk kont for nearest ScopeAccFrame, append to its emitted list.
|
|
(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)))
|
|
(when frame
|
|
(dict-set! frame "emitted" (append (get frame "emitted") (list val))))
|
|
(make-cek-value nil env kont))))
|
|
|
|
;; emitted: walk kont for nearest ScopeAccFrame, return its emitted list.
|
|
(define step-sf-emitted
|
|
(fn (args env kont)
|
|
(let ((name (trampoline (eval-expr (first args) env)))
|
|
(frame (kont-find-scope-acc kont name)))
|
|
(make-cek-value (if (nil? frame) (list) (get frame "emitted")) 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-bind! shift-env k-name k)
|
|
(make-cek-state body shift-env rest-kont))))))
|
|
|
|
|
|
;; deref: evaluate argument, push DerefFrame
|
|
(define step-sf-deref
|
|
(fn (args env kont)
|
|
(make-cek-state
|
|
(first args) env
|
|
(kont-push (make-deref-frame env) kont))))
|
|
|
|
;; cek-call — call a function via CEK (replaces invoke)
|
|
;; cek-call — unified function dispatch
|
|
;; Both lambdas and native callables go through continue-with-call
|
|
;; so they interact identically with the continuation stack.
|
|
;; This is critical: replacing a native callable with an SX lambda
|
|
;; (e.g. stdlib.sx) must not change shift/reset behavior.
|
|
(define cek-call
|
|
(fn (f args)
|
|
(let ((a (if (nil? args) (list) args)))
|
|
(cond
|
|
(nil? f) nil
|
|
(or (lambda? f) (callable? f))
|
|
(cek-run (continue-with-call f a (make-env) a (list)))
|
|
:else nil))))
|
|
|
|
;; reactive-shift-deref: the heart of deref-as-shift
|
|
;; When deref encounters a signal inside a reactive-reset boundary,
|
|
;; capture the continuation up to the reactive-reset as the subscriber.
|
|
(define reactive-shift-deref
|
|
(fn (sig env kont)
|
|
(let ((scan-result (kont-capture-to-reactive-reset kont))
|
|
(captured-frames (first scan-result))
|
|
(reset-frame (nth scan-result 1))
|
|
(remaining-kont (nth scan-result 2))
|
|
(update-fn (get reset-frame "update-fn")))
|
|
;; Sub-scope for nested subscriber cleanup on re-invocation
|
|
(let ((sub-disposers (list)))
|
|
(let ((subscriber
|
|
(fn ()
|
|
;; Dispose previous nested subscribers
|
|
(for-each (fn (d) (cek-call d nil)) sub-disposers)
|
|
(set! sub-disposers (list))
|
|
;; Re-invoke: push fresh ReactiveResetFrame (first-render=false)
|
|
(let ((new-reset (make-reactive-reset-frame env update-fn false))
|
|
(new-kont (concat captured-frames
|
|
(list new-reset)
|
|
remaining-kont)))
|
|
(with-island-scope
|
|
(fn (d) (append! sub-disposers d))
|
|
(fn ()
|
|
(cek-run
|
|
(make-cek-value (signal-value sig) env new-kont))))))))
|
|
;; Register subscriber
|
|
(signal-add-sub! sig subscriber)
|
|
;; Register cleanup with island scope
|
|
(register-in-scope
|
|
(fn ()
|
|
(signal-remove-sub! sig subscriber)
|
|
(for-each (fn (d) (cek-call d nil)) sub-disposers)))
|
|
;; Initial render: value flows through captured frames + reset (first-render=true)
|
|
;; so the full expression completes normally
|
|
(let ((initial-kont (concat captured-frames
|
|
(list reset-frame)
|
|
remaining-kont)))
|
|
(make-cek-value (signal-value sig) env initial-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
|
|
;; 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)))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; 7. Higher-order form step handlers
|
|
;; --------------------------------------------------------------------------
|
|
|
|
;; CEK-native higher-order forms — each callback invocation goes through
|
|
;; continue-with-call so deref-as-shift works inside callbacks.
|
|
;; Function and collection args are evaluated via tree-walk (simple exprs),
|
|
;; then the loop is driven by CEK frames.
|
|
|
|
;; HO step handlers — push HoSetupFrame to evaluate args via CEK
|
|
;; (no nested eval-expr calls). When all args are evaluated, the
|
|
;; HoSetupFrame dispatch in step-continue sets up the iteration frame.
|
|
|
|
;; ho-form-name? — is this symbol name a higher-order special form?
|
|
(define ho-form-name?
|
|
(fn (name)
|
|
(or (= name "map") (= name "map-indexed") (= name "filter")
|
|
(= name "reduce") (= name "some") (= name "every?")
|
|
(= name "for-each"))))
|
|
|
|
;; ho-fn? — is this value usable as a HO callback?
|
|
(define ho-fn?
|
|
(fn (v) (or (callable? v) (lambda? v))))
|
|
|
|
;; ho-swap-args: normalise data-first arg order
|
|
;; 2-arg forms: (coll fn) → (fn coll)
|
|
;; 3-arg reduce: (coll fn init) → (fn init coll)
|
|
(define ho-swap-args
|
|
(fn (ho-type evaled)
|
|
(if (= ho-type "reduce")
|
|
(let ((a (first evaled))
|
|
(b (nth evaled 1)))
|
|
(if (and (not (ho-fn? a)) (ho-fn? b))
|
|
(list b (nth evaled 2) a)
|
|
evaled))
|
|
(let ((a (first evaled))
|
|
(b (nth evaled 1)))
|
|
(if (and (not (ho-fn? a)) (ho-fn? b))
|
|
(list b a)
|
|
evaled)))))
|
|
|
|
;; ho-setup-dispatch: all HO args evaluated, set up iteration
|
|
(define ho-setup-dispatch
|
|
(fn (ho-type evaled env kont)
|
|
(let ((ordered (ho-swap-args ho-type evaled)))
|
|
(let ((f (first ordered)))
|
|
(cond
|
|
(= ho-type "map")
|
|
(let ((coll (nth ordered 1)))
|
|
(if (empty? coll)
|
|
(make-cek-value (list) env kont)
|
|
(continue-with-call f (list (first coll)) env (list)
|
|
(kont-push (make-map-frame f (rest coll) (list) env) kont))))
|
|
|
|
(= ho-type "map-indexed")
|
|
(let ((coll (nth ordered 1)))
|
|
(if (empty? coll)
|
|
(make-cek-value (list) env kont)
|
|
(continue-with-call f (list 0 (first coll)) env (list)
|
|
(kont-push (make-map-indexed-frame f (rest coll) (list) env) kont))))
|
|
|
|
(= ho-type "filter")
|
|
(let ((coll (nth ordered 1)))
|
|
(if (empty? coll)
|
|
(make-cek-value (list) env kont)
|
|
(continue-with-call f (list (first coll)) env (list)
|
|
(kont-push (make-filter-frame f (rest coll) (list) (first coll) env) kont))))
|
|
|
|
(= ho-type "reduce")
|
|
(let ((init (nth ordered 1))
|
|
(coll (nth ordered 2)))
|
|
(if (empty? coll)
|
|
(make-cek-value init env kont)
|
|
(continue-with-call f (list init (first coll)) env (list)
|
|
(kont-push (make-reduce-frame f (rest coll) env) kont))))
|
|
|
|
(= ho-type "some")
|
|
(let ((coll (nth ordered 1)))
|
|
(if (empty? coll)
|
|
(make-cek-value false env kont)
|
|
(continue-with-call f (list (first coll)) env (list)
|
|
(kont-push (make-some-frame f (rest coll) env) kont))))
|
|
|
|
(= ho-type "every")
|
|
(let ((coll (nth ordered 1)))
|
|
(if (empty? coll)
|
|
(make-cek-value true env kont)
|
|
(continue-with-call f (list (first coll)) env (list)
|
|
(kont-push (make-every-frame f (rest coll) env) kont))))
|
|
|
|
(= ho-type "for-each")
|
|
(let ((coll (nth ordered 1)))
|
|
(if (empty? coll)
|
|
(make-cek-value nil env kont)
|
|
(continue-with-call f (list (first coll)) env (list)
|
|
(kont-push (make-for-each-frame f (rest coll) env) kont))))
|
|
|
|
:else (error (str "Unknown HO type: " ho-type)))))))
|
|
|
|
(define step-ho-map
|
|
(fn (args env kont)
|
|
(make-cek-state (first args) env
|
|
(kont-push (make-ho-setup-frame "map" (rest args) (list) env) kont))))
|
|
|
|
(define step-ho-map-indexed
|
|
(fn (args env kont)
|
|
(make-cek-state (first args) env
|
|
(kont-push (make-ho-setup-frame "map-indexed" (rest args) (list) env) kont))))
|
|
|
|
(define step-ho-filter
|
|
(fn (args env kont)
|
|
(make-cek-state (first args) env
|
|
(kont-push (make-ho-setup-frame "filter" (rest args) (list) env) kont))))
|
|
|
|
(define step-ho-reduce
|
|
(fn (args env kont)
|
|
(make-cek-state (first args) env
|
|
(kont-push (make-ho-setup-frame "reduce" (rest args) (list) env) kont))))
|
|
|
|
(define step-ho-some
|
|
(fn (args env kont)
|
|
(make-cek-state (first args) env
|
|
(kont-push (make-ho-setup-frame "some" (rest args) (list) env) kont))))
|
|
|
|
(define step-ho-every
|
|
(fn (args env kont)
|
|
(make-cek-state (first args) env
|
|
(kont-push (make-ho-setup-frame "every" (rest args) (list) env) kont))))
|
|
|
|
(define step-ho-for-each
|
|
(fn (args env kont)
|
|
(make-cek-state (first args) env
|
|
(kont-push (make-ho-setup-frame "for-each" (rest args) (list) 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-bind! 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-bind! 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-bind! 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 (is-else-clause? next-test)
|
|
(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 (is-else-clause? next-test)
|
|
(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))
|
|
(new-kont (if (empty? (rest remaining)) rest-k
|
|
(kont-push (make-thread-frame (rest remaining) fenv) rest-k))))
|
|
;; Check if form is a HO call like (map fn)
|
|
(if (and (= (type-of form) "list")
|
|
(not (empty? form))
|
|
(= (type-of (first form)) "symbol")
|
|
(ho-form-name? (symbol-name (first form))))
|
|
;; HO form — splice value as quoted arg, dispatch via CEK
|
|
(make-cek-state
|
|
(cons (first form) (cons (list 'quote value) (rest form)))
|
|
fenv new-kont)
|
|
;; Normal: tree-walk eval + apply
|
|
(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"))
|
|
(hname (get frame "head-name")))
|
|
(if (nil? f)
|
|
;; Head just evaluated — value is the function
|
|
(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 — 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 hname)
|
|
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))))))
|
|
|
|
;; --- HoSetupFrame: evaluating HO form arguments ---
|
|
(= ft "ho-setup")
|
|
(let ((ho-type (get frame "ho-type"))
|
|
(remaining (get frame "remaining"))
|
|
(evaled (append (get frame "evaled") (list value)))
|
|
(fenv (get frame "env")))
|
|
(if (empty? remaining)
|
|
;; All args evaluated — dispatch to iteration
|
|
(ho-setup-dispatch ho-type evaled fenv rest-k)
|
|
;; More args to evaluate
|
|
(make-cek-state
|
|
(first remaining) fenv
|
|
(kont-push
|
|
(make-ho-setup-frame ho-type (rest remaining) evaled fenv)
|
|
rest-k))))
|
|
|
|
;; --- ResetFrame: body evaluated normally (no shift) ---
|
|
(= ft "reset")
|
|
(make-cek-value value env rest-k)
|
|
|
|
;; --- DerefFrame: deref argument evaluated ---
|
|
(= ft "deref")
|
|
(let ((val value)
|
|
(fenv (get frame "env")))
|
|
(if (not (signal? val))
|
|
;; Not a signal: pass through
|
|
(make-cek-value val fenv rest-k)
|
|
;; Signal: check for ReactiveResetFrame
|
|
(if (has-reactive-reset-frame? rest-k)
|
|
;; Perform reactive shift
|
|
(reactive-shift-deref val fenv rest-k)
|
|
;; No reactive-reset: normal deref (scope-based tracking)
|
|
(do
|
|
(let ((ctx (context "sx-reactive" nil)))
|
|
(when ctx
|
|
(let ((dep-list (get ctx "deps"))
|
|
(notify-fn (get ctx "notify")))
|
|
(when (not (contains? dep-list val))
|
|
(append! dep-list val)
|
|
(signal-add-sub! val notify-fn)))))
|
|
(make-cek-value (signal-value val) fenv rest-k)))))
|
|
|
|
;; --- ReactiveResetFrame: expression completed ---
|
|
(= ft "reactive-reset")
|
|
(let ((update-fn (get frame "update-fn"))
|
|
(first? (get frame "first-render")))
|
|
;; On re-render (not first), call update-fn with new value
|
|
(when (and update-fn (not first?))
|
|
(cek-call update-fn (list value)))
|
|
(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))))
|
|
|
|
;; --- 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 ---
|
|
(= ft "map")
|
|
(let ((f (get frame "f"))
|
|
(remaining (get frame "remaining"))
|
|
(results (get frame "results"))
|
|
(indexed (get frame "indexed"))
|
|
(fenv (get frame "env")))
|
|
(let ((new-results (append results (list value))))
|
|
(if (empty? remaining)
|
|
(make-cek-value new-results fenv rest-k)
|
|
(let ((call-args (if indexed
|
|
(list (len new-results) (first remaining))
|
|
(list (first remaining))))
|
|
(next-frame (if indexed
|
|
(make-map-indexed-frame f (rest remaining) new-results fenv)
|
|
(make-map-frame f (rest remaining) new-results fenv))))
|
|
(continue-with-call f call-args fenv (list)
|
|
(kont-push next-frame rest-k))))))
|
|
|
|
;; --- FilterFrame: predicate result ---
|
|
(= ft "filter")
|
|
(let ((f (get frame "f"))
|
|
(remaining (get frame "remaining"))
|
|
(results (get frame "results"))
|
|
(current-item (get frame "current-item"))
|
|
(fenv (get frame "env")))
|
|
(let ((new-results (if value
|
|
(append results (list current-item))
|
|
results)))
|
|
(if (empty? remaining)
|
|
(make-cek-value new-results fenv rest-k)
|
|
(continue-with-call f (list (first remaining)) fenv (list)
|
|
(kont-push (make-filter-frame f (rest remaining) new-results (first remaining) fenv) rest-k)))))
|
|
|
|
;; --- ReduceFrame: accumulator step ---
|
|
(= ft "reduce")
|
|
(let ((f (get frame "f"))
|
|
(remaining (get frame "remaining"))
|
|
(fenv (get frame "env")))
|
|
(if (empty? remaining)
|
|
(make-cek-value value fenv rest-k)
|
|
(continue-with-call f (list value (first remaining)) fenv (list)
|
|
(kont-push (make-reduce-frame f (rest remaining) fenv) rest-k))))
|
|
|
|
;; --- ForEachFrame: side effect, discard result ---
|
|
(= ft "for-each")
|
|
(let ((f (get frame "f"))
|
|
(remaining (get frame "remaining"))
|
|
(fenv (get frame "env")))
|
|
(if (empty? remaining)
|
|
(make-cek-value nil fenv rest-k)
|
|
(continue-with-call f (list (first remaining)) fenv (list)
|
|
(kont-push (make-for-each-frame f (rest remaining) fenv) rest-k))))
|
|
|
|
;; --- SomeFrame: short-circuit on first truthy ---
|
|
(= ft "some")
|
|
(let ((f (get frame "f"))
|
|
(remaining (get frame "remaining"))
|
|
(fenv (get frame "env")))
|
|
(if value
|
|
(make-cek-value value fenv rest-k)
|
|
(if (empty? remaining)
|
|
(make-cek-value false fenv rest-k)
|
|
(continue-with-call f (list (first remaining)) fenv (list)
|
|
(kont-push (make-some-frame f (rest remaining) fenv) rest-k)))))
|
|
|
|
;; --- EveryFrame: short-circuit on first falsy ---
|
|
(= ft "every")
|
|
(let ((f (get frame "f"))
|
|
(remaining (get frame "remaining"))
|
|
(fenv (get frame "env")))
|
|
(if (not value)
|
|
(make-cek-value false fenv rest-k)
|
|
(if (empty? remaining)
|
|
(make-cek-value true fenv rest-k)
|
|
(continue-with-call f (list (first remaining)) fenv (list)
|
|
(kont-push (make-every-frame f (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 — run captured delimited continuation, return result to caller.
|
|
;; Multi-shot: each invocation runs captured frames to completion via nested
|
|
;; cek-run, then returns the result to the caller's kont.
|
|
(continuation? f)
|
|
(let ((arg (if (empty? args) nil (first args)))
|
|
(cont-data (continuation-data f)))
|
|
(let ((captured (get cont-data "captured")))
|
|
;; Run ONLY the captured frames (delimited by reset).
|
|
;; Empty kont after captured = the continuation terminates and returns.
|
|
(let ((result (cek-run (make-cek-value arg env captured))))
|
|
(make-cek-value result env kont))))
|
|
|
|
;; 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-bind! local (first pair) (nth pair 1)))
|
|
(zip params args))
|
|
(for-each
|
|
(fn (p) (env-bind! 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-bind! local p (or (dict-get kwargs p) nil)))
|
|
(component-params f))
|
|
(when (component-has-children? f)
|
|
(env-bind! 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 (is-else-clause? test)
|
|
(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)))
|
|
|
|
|
|
;; **************************************************************************
|
|
;; eval-expr / trampoline — canonical definitions (after cek-run is defined)
|
|
;; **************************************************************************
|
|
;;
|
|
;; These override the forward declarations from Part 2. All evaluation
|
|
;; goes through the CEK machine. The CEK fixups in the host platform
|
|
;; may further override these (e.g., to make cek-run iterative).
|
|
|
|
(define eval-expr
|
|
(fn (expr (env :as dict))
|
|
(cek-run (make-cek-state expr env (list)))))
|
|
|
|
(define trampoline
|
|
(fn (val)
|
|
(if (thunk? val)
|
|
(eval-expr (thunk-expr val) (thunk-env val))
|
|
val)))
|