Replace invoke with cek-call in reactive island primitives

All signal operations (computed, effect, batch, etc.) now dispatch
function calls through cek-call, which routes SX lambdas via cek-run
and native callables via apply. This replaces the invoke shim.

Key changes:
- cek.sx: add cek-call (defined before reactive-shift-deref), replace
  invoke in subscriber disposal and ReactiveResetFrame handler
- signals.sx: replace all 11 invoke calls with cek-call
- js.sx: fix octal escape in js-quote-string (char-from-code 0)
- platform_js.py: fix JS append to match Python (list concat semantics),
  add Continuation type guard in PLATFORM_CEK_JS, add scheduleIdle
  safety check, module ordering (cek before signals)
- platform_py.py: fix ident-char regex (remove [ ] from valid chars),
  module ordering (cek before signals)
- run_js_sx.py: emit PLATFORM_CEK_JS before transpiled spec files
- page-functions.sx: add cek and provide page functions for SX URLs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-14 10:11:48 +00:00
parent 30d9d4aa4c
commit 455e48df07
20 changed files with 911 additions and 600 deletions

View File

@@ -407,6 +407,16 @@
(first args) env
(kont-push (make-deref-frame env) kont))))
;; cek-call — call a function via CEK (replaces invoke)
(define cek-call
(fn (f args)
(let ((a (if (nil? args) (list) args)))
(cond
(nil? f) nil
(lambda? f) (cek-run (continue-with-call f a (dict) a (list)))
(callable? f) (apply f a)
: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.
@@ -422,7 +432,7 @@
(let ((subscriber
(fn ()
;; Dispose previous nested subscribers
(for-each (fn (d) (invoke d)) sub-disposers)
(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))
@@ -440,7 +450,7 @@
(register-in-scope
(fn ()
(signal-remove-sub! sig subscriber)
(for-each (fn (d) (invoke d)) sub-disposers)))
(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
@@ -782,7 +792,7 @@
(first? (get frame "first-render")))
;; On re-render (not first), call update-fn with new value
(when (and update-fn (not first?))
(invoke update-fn value))
(cek-call update-fn (list value)))
(make-cek-value value env rest-k))
;; --- ScopeFrame: body result ---