CEK-native higher-order forms: map, filter, reduce, some, every?, for-each
Some checks are pending
Build and Deploy / build-and-deploy (push) Has started running

Higher-order forms now step element-by-element through the CEK machine
using dedicated frames instead of delegating to tree-walk ho-map etc.
Each callback invocation goes through continue-with-call, so deref-as-shift
works inside map/filter/reduce callbacks in reactive island contexts.

- cek.sx: rewrite step-ho-* to use CEK frames, add frame handlers in
  step-continue for map, filter, reduce, for-each, some, every
- frames.sx: add SomeFrame, EveryFrame, MapIndexedFrame
- test-cek-reactive.sx: add 10 tests for CEK-native HO forms

89 tests pass (20 signal + 43 CEK + 26 CEK reactive).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-14 10:45:36 +00:00
parent d0a5ce1070
commit 2211655060
5 changed files with 451 additions and 27 deletions

View File

@@ -168,11 +168,11 @@
;; 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 "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") (make-cek-value (ho-some args env) env kont)
(= name "every?") (make-cek-value (ho-every args env) 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)
;; Macro expansion
@@ -477,23 +477,74 @@
;; 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.
(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)))
(let ((f (trampoline (eval-expr (first args) env)))
(coll (trampoline (eval-expr (nth args 1) env))))
(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))))))
(define step-ho-map-indexed
(fn (args env kont)
(let ((f (trampoline (eval-expr (first args) env)))
(coll (trampoline (eval-expr (nth args 1) env))))
(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))))))
(define step-ho-filter
(fn (args env kont)
(make-cek-value (ho-filter args env) env kont)))
(let ((f (trampoline (eval-expr (first args) env)))
(coll (trampoline (eval-expr (nth args 1) env))))
(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))))))
(define step-ho-reduce
(fn (args env kont)
(make-cek-value (ho-reduce args env) env kont)))
(let ((f (trampoline (eval-expr (first args) env)))
(init (trampoline (eval-expr (nth args 1) env)))
(coll (trampoline (eval-expr (nth args 2) env))))
(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))))))
(define step-ho-some
(fn (args env kont)
(let ((f (trampoline (eval-expr (first args) env)))
(coll (trampoline (eval-expr (nth args 1) env))))
(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))))))
(define step-ho-every
(fn (args env kont)
(let ((f (trampoline (eval-expr (first args) env)))
(coll (trampoline (eval-expr (nth args 1) env))))
(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))))))
(define step-ho-for-each
(fn (args env kont)
(make-cek-value (ho-for-each args env) env kont)))
(let ((f (trampoline (eval-expr (first args) env)))
(coll (trampoline (eval-expr (nth args 1) env))))
(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))))))
;; --------------------------------------------------------------------------
@@ -809,6 +860,84 @@
(make-scope-frame name (rest remaining) fenv)
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))))))))