Implement deref-as-shift: ReactiveResetFrame, DerefFrame, continuation capture
frames.sx: ReactiveResetFrame + DerefFrame constructors, kont-capture-to-reactive-reset, has-reactive-reset-frame?. cek.sx: deref as CEK special form, step-sf-deref pushes DerefFrame, reactive-shift-deref captures continuation as signal subscriber, ReactiveResetFrame in step-continue calls update-fn on re-render. adapter-dom.sx: cek-reactive-text/cek-reactive-attr using cek-run with ReactiveResetFrame for implicit DOM bindings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1107,6 +1107,48 @@
|
|||||||
(reset! sig (dom-get-prop el "value"))))))))
|
(reset! sig (dom-get-prop el "value"))))))))
|
||||||
|
|
||||||
|
|
||||||
|
;; --------------------------------------------------------------------------
|
||||||
|
;; CEK-based reactive rendering (opt-in, deref-as-shift)
|
||||||
|
;; --------------------------------------------------------------------------
|
||||||
|
;;
|
||||||
|
;; When enabled, (deref sig) inside a reactive-reset boundary performs
|
||||||
|
;; continuation capture: "the rest of this expression" becomes the subscriber.
|
||||||
|
;; No explicit effect() wrapping needed for text/attr bindings.
|
||||||
|
|
||||||
|
(define *use-cek-reactive* true)
|
||||||
|
(define enable-cek-reactive! (fn () (set! *use-cek-reactive* true)))
|
||||||
|
|
||||||
|
;; cek-reactive-text — create a text node bound via continuation capture
|
||||||
|
(define cek-reactive-text :effects [render mutation]
|
||||||
|
(fn (expr env)
|
||||||
|
(let ((node (create-text-node ""))
|
||||||
|
(update-fn (fn (val)
|
||||||
|
(dom-set-text-content node (str val)))))
|
||||||
|
(let ((initial (cek-run
|
||||||
|
(make-cek-state expr env
|
||||||
|
(list (make-reactive-reset-frame env update-fn true))))))
|
||||||
|
(dom-set-text-content node (str initial))
|
||||||
|
node))))
|
||||||
|
|
||||||
|
;; cek-reactive-attr — bind an attribute via continuation capture
|
||||||
|
(define cek-reactive-attr :effects [render mutation]
|
||||||
|
(fn (el attr-name expr env)
|
||||||
|
(let ((update-fn (fn (val)
|
||||||
|
(cond
|
||||||
|
(or (nil? val) (= val false)) (dom-remove-attr el attr-name)
|
||||||
|
(= val true) (dom-set-attr el attr-name "")
|
||||||
|
:else (dom-set-attr el attr-name (str val))))))
|
||||||
|
;; Mark for morph protection
|
||||||
|
(let ((existing (or (dom-get-attr el "data-sx-reactive-attrs") ""))
|
||||||
|
(updated (if (empty? existing) attr-name (str existing "," attr-name))))
|
||||||
|
(dom-set-attr el "data-sx-reactive-attrs" updated))
|
||||||
|
;; Initial render via CEK with ReactiveResetFrame
|
||||||
|
(let ((initial (cek-run
|
||||||
|
(make-cek-state expr env
|
||||||
|
(list (make-reactive-reset-frame env update-fn true))))))
|
||||||
|
(invoke update-fn initial)))))
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
;; render-dom-portal — render children into a remote target element
|
;; render-dom-portal — render children into a remote target element
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -156,6 +156,9 @@
|
|||||||
(= name "reset") (step-sf-reset args env kont)
|
(= name "reset") (step-sf-reset args env kont)
|
||||||
(= name "shift") (step-sf-shift 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
|
;; Scoped effects
|
||||||
(= name "scope") (step-sf-scope args env kont)
|
(= name "scope") (step-sf-scope args env kont)
|
||||||
(= name "provide") (step-sf-provide args env kont)
|
(= name "provide") (step-sf-provide args env kont)
|
||||||
@@ -397,6 +400,55 @@
|
|||||||
(make-cek-state body shift-env rest-kont))))))
|
(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))))
|
||||||
|
|
||||||
|
;; 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) (invoke d)) 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) (invoke d)) 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
|
;; 6. Function call step handler
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
@@ -702,6 +754,37 @@
|
|||||||
(= ft "reset")
|
(= ft "reset")
|
||||||
(make-cek-value value env rest-k)
|
(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?))
|
||||||
|
(invoke update-fn value))
|
||||||
|
(make-cek-value value env rest-k))
|
||||||
|
|
||||||
;; --- ScopeFrame: body result ---
|
;; --- ScopeFrame: body result ---
|
||||||
(= ft "scope")
|
(= ft "scope")
|
||||||
(let ((name (get frame "name"))
|
(let ((name (get frame "name"))
|
||||||
|
|||||||
@@ -166,6 +166,18 @@
|
|||||||
{:type "dynamic-wind" :phase phase
|
{:type "dynamic-wind" :phase phase
|
||||||
:body-thunk body-thunk :after-thunk after-thunk :env env}))
|
: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}))
|
||||||
|
|
||||||
|
|
||||||
;; --------------------------------------------------------------------------
|
;; --------------------------------------------------------------------------
|
||||||
;; 3. Frame accessors
|
;; 3. Frame accessors
|
||||||
@@ -202,12 +214,35 @@
|
|||||||
;; Returns (captured-frames remaining-kont).
|
;; Returns (captured-frames remaining-kont).
|
||||||
;; captured-frames: frames from top up to (not including) ResetFrame.
|
;; captured-frames: frames from top up to (not including) ResetFrame.
|
||||||
;; remaining-kont: frames after ResetFrame.
|
;; remaining-kont: frames after ResetFrame.
|
||||||
|
;; Stops at either "reset" or "reactive-reset" frames.
|
||||||
(define scan
|
(define scan
|
||||||
(fn (k captured)
|
(fn (k captured)
|
||||||
(if (empty? k)
|
(if (empty? k)
|
||||||
(error "shift without enclosing reset")
|
(error "shift without enclosing reset")
|
||||||
(let ((frame (first k)))
|
(let ((frame (first k)))
|
||||||
(if (= (frame-type frame) "reset")
|
(if (or (= (frame-type frame) "reset")
|
||||||
|
(= (frame-type frame) "reactive-reset"))
|
||||||
(list captured (rest k))
|
(list captured (rest k))
|
||||||
(scan (rest k) (append captured (list frame))))))))
|
(scan (rest k) (append captured (list frame))))))))
|
||||||
(scan kont (list))))
|
(scan kont (list))))
|
||||||
|
|
||||||
|
;; 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))))
|
||||||
|
|||||||
Reference in New Issue
Block a user