Add CEK Machine section under Geography with live island demos

geography/cek.sx: overview page (three registers, deref-as-shift
explanation) + demo page with 5 live islands (counter, computed chain,
reactive attrs, stopwatch effect+cleanup, batch coalescing). Nav entry,
router routes, defpage definitions. CEK exports (cekRun, makeCekState,
makeReactiveResetFrame, evalExpr) added to Sx public API via
platform_js.py.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-14 01:37:16 +00:00
parent 203f9a49a1
commit f96506024e
7 changed files with 246 additions and 86 deletions

195
sx/sx/geography/cek.sx Normal file
View File

@@ -0,0 +1,195 @@
;; ---------------------------------------------------------------------------
;; CEK Machine — Geography section
;; ---------------------------------------------------------------------------
;; ---------------------------------------------------------------------------
;; Island demos
;; ---------------------------------------------------------------------------
;; Counter: signal + deref in text position
(defisland ~geography/cek/demo-counter (&key initial)
(let ((count (signal (or initial 0)))
(doubled (computed (fn () (* 2 (deref count))))))
(div :class "rounded-lg border border-stone-200 p-4 space-y-2"
(div :class "flex items-center gap-3"
(button :class "px-3 py-1 rounded bg-violet-600 text-white text-sm"
:on-click (fn (e) (swap! count dec)) "-")
(span :class "text-2xl font-bold text-violet-700 min-w-[3ch] text-center"
(deref count))
(button :class "px-3 py-1 rounded bg-violet-600 text-white text-sm"
:on-click (fn (e) (swap! count inc)) "+"))
(p :class "text-sm text-stone-500"
(str "doubled: " (deref doubled))))))
;; Computed chain: base -> doubled -> quadrupled
(defisland ~geography/cek/demo-chain ()
(let ((base (signal 1))
(doubled (computed (fn () (* (deref base) 2))))
(quadrupled (computed (fn () (* (deref doubled) 2)))))
(div :class "rounded-lg border border-stone-200 p-4 space-y-2"
(div :class "flex items-center gap-3"
(button :class "px-3 py-1 rounded bg-violet-600 text-white text-sm"
:on-click (fn (e) (swap! base dec)) "-")
(span :class "text-2xl font-bold text-violet-700 min-w-[3ch] text-center"
(deref base))
(button :class "px-3 py-1 rounded bg-violet-600 text-white text-sm"
:on-click (fn (e) (swap! base inc)) "+"))
(p :class "text-sm text-stone-500"
(str "doubled: " (deref doubled) " | quadrupled: " (deref quadrupled))))))
;; Reactive attribute: (deref sig) in :class position
(defisland ~geography/cek/demo-reactive-attr ()
(let ((danger (signal false)))
(div :class "rounded-lg border border-stone-200 p-4 space-y-3"
(button :class "px-3 py-1 rounded bg-violet-600 text-white text-sm"
:on-click (fn (e) (swap! danger not))
(if (deref danger) "Safe mode" "Danger mode"))
(div :class (str "p-3 rounded font-medium transition-colors "
(if (deref danger)
"bg-red-100 text-red-800"
"bg-green-100 text-green-800"))
(if (deref danger)
"DANGER: reactive class binding via CEK"
"SAFE: reactive class binding via CEK")))))
;; Stopwatch: effect + cleanup
(defisland ~geography/cek/demo-stopwatch ()
(let ((running (signal false))
(elapsed (signal 0))
(time-text (create-text-node "0.0s"))
(btn-text (create-text-node "Start")))
(effect (fn ()
(when (deref running)
(let ((id (set-interval (fn () (swap! elapsed inc)) 100)))
(fn () (clear-interval id))))))
(effect (fn ()
(let ((e (deref elapsed)))
(dom-set-text-content time-text
(str (floor (/ e 10)) "." (mod e 10) "s")))))
(effect (fn ()
(dom-set-text-content btn-text
(if (deref running) "Stop" "Start"))))
(div :class "rounded-lg border border-stone-200 p-4"
(div :class "flex items-center gap-3"
(span :class "text-2xl font-bold text-violet-700 font-mono min-w-[5ch]" time-text)
(button :class "px-3 py-1 rounded bg-violet-600 text-white text-sm"
:on-click (fn (e) (swap! running not)) btn-text)
(button :class "px-3 py-1 rounded bg-stone-400 text-white text-sm"
:on-click (fn (e) (reset! running false) (reset! elapsed 0)) "Reset")))))
;; Batch: two signals, one notification
(defisland ~geography/cek/demo-batch ()
(let ((first-sig (signal 0))
(second-sig (signal 0))
(renders (signal 0)))
(effect (fn ()
(deref first-sig) (deref second-sig)
(swap! renders inc)))
(div :class "rounded-lg border border-stone-200 p-4 space-y-2"
(div :class "flex items-center gap-4 text-sm"
(span (str "first: " (deref first-sig)))
(span (str "second: " (deref second-sig)))
(span :class "px-2 py-0.5 rounded bg-green-100 text-green-800 text-xs font-semibold"
(str "renders: " (deref renders))))
(div :class "flex items-center gap-2"
(button :class "px-3 py-1 rounded bg-violet-600 text-white text-sm"
:on-click (fn (e)
(batch (fn ()
(swap! first-sig inc)
(swap! second-sig inc))))
"Batch +1")
(button :class "px-3 py-1 rounded bg-stone-400 text-white text-sm"
:on-click (fn (e)
(swap! first-sig inc)
(swap! second-sig inc))
"No-batch +1")))))
;; ---------------------------------------------------------------------------
;; Overview page content
;; ---------------------------------------------------------------------------
(defcomp ~geography/cek/cek-content ()
(~docs/page :title "CEK Machine"
(~docs/section :title "Three registers" :id "registers"
(p "The CEK machine makes evaluation explicit. Every step is a pure function from state to state:")
(ul :class "space-y-1 text-stone-600 list-disc pl-5"
(li (strong "C") "ontrol — the expression being evaluated")
(li (strong "E") "nvironment — the bindings in scope")
(li (strong "K") "ontinuation — what to do with the result"))
(p "The tree-walk evaluator uses the same three things, but hides them in the call stack. The CEK makes them " (em "data") " — inspectable, serializable, capturable."))
(~docs/section :title "Why it matters" :id "why"
(p "Making the continuation explicit enables:")
(ul :class "space-y-1 text-stone-600 list-disc pl-5"
(li (strong "Stepping") " — pause evaluation, inspect state, resume")
(li (strong "Serialization") " — save a computation mid-flight, restore later")
(li (strong "Delimited continuations") " — " (code "shift") "/" (code "reset") " capture \"the rest of this expression\" as a value")
(li (strong "Deref-as-shift") " — " (code "(deref sig)") " inside a reactive boundary captures the continuation as the subscriber")))
(~docs/section :title "Default evaluator" :id "default"
(p "CEK is the default evaluator on both client (JS) and server (Python). Every " (code "eval-expr") " call goes through " (code "cek-run") ". The tree-walk evaluator is preserved as " (code "_tree_walk_eval_expr") " for test runners that interpret " (code ".sx") " files.")
(p "The CEK is defined in two spec files:")
(ul :class "space-y-1 text-stone-600 list-disc pl-5"
(li (code "frames.sx") " — frame types (IfFrame, ArgFrame, ResetFrame, ReactiveResetFrame, ...)")
(li (code "cek.sx") " — step function, run loop, special form handlers, continuation operations")))
(~docs/section :title "Deref as shift" :id "deref-as-shift"
(p "The reactive payoff. When " (code "(deref sig)") " encounters a signal inside a " (code "reactive-reset") " boundary:")
(ol :class "space-y-1 text-stone-600 list-decimal pl-5"
(li (strong "Shift") " — capture all frames between here and the reactive-reset")
(li (strong "Subscribe") " — register the captured continuation as a signal subscriber")
(li (strong "Return") " — flow the current signal value through the rest of the expression"))
(p "When the signal changes, the captured continuation is re-invoked with the new value. The " (code "update-fn") " on the ReactiveResetFrame mutates the DOM. No explicit " (code "effect()") " wrapping needed.")
(~docs/code :code (highlight
";; User writes:\n(div :class (str \"count-\" (deref counter))\n (str \"Value: \" (deref counter)))\n\n;; CEK sees (deref counter) → signal? → reactive-reset on stack?\n;; Yes: capture (str \"count-\" [HOLE]) as continuation\n;; Register as subscriber. Return current value.\n;; When counter changes: re-invoke continuation → update DOM."
"lisp")))))
;; ---------------------------------------------------------------------------
;; Demo page content
;; ---------------------------------------------------------------------------
(defcomp ~geography/cek/cek-demo-content ()
(~docs/page :title "CEK Demo"
(~docs/section :title "What this demonstrates" :id "what"
(p "These are " (strong "live islands") " evaluated by the CEK machine. Every " (code "eval-expr") " goes through " (code "cek-run") ". Every " (code "(deref sig)") " in an island creates a reactive DOM binding via continuation frames.")
(p "The CEK machine is defined in " (code "cek.sx") " (160 lines) and " (code "frames.sx") " (100 lines) — pure s-expressions, bootstrapped to both JavaScript and Python."))
(~docs/section :title "1. Counter" :id "demo-counter"
(p (code "(deref count)") " in text position creates a reactive text node. " (code "(deref doubled)") " is a computed that updates when count changes.")
(~geography/cek/demo-counter :initial 0)
(~docs/code :code (highlight
"(defisland ~demo-counter (&key initial)\n (let ((count (signal (or initial 0)))\n (doubled (computed (fn () (* 2 (deref count))))))\n (div\n (button :on-click (fn (e) (swap! count dec)) \"-\")\n (span (deref count))\n (button :on-click (fn (e) (swap! count inc)) \"+\")\n (p (str \"doubled: \" (deref doubled))))))"
"lisp")))
(~docs/section :title "2. Computed chain" :id "demo-chain"
(p "Three levels of computed: base -> doubled -> quadrupled. Change base, all propagate.")
(~geography/cek/demo-chain)
(~docs/code :code (highlight
"(let ((base (signal 1))\n (doubled (computed (fn () (* (deref base) 2))))\n (quadrupled (computed (fn () (* (deref doubled) 2)))))\n (span (deref base))\n (p (str \"doubled: \" (deref doubled)\n \" | quadrupled: \" (deref quadrupled))))"
"lisp")))
(~docs/section :title "3. Reactive attributes" :id "demo-attr"
(p (code "(deref sig)") " in " (code ":class") " position. The CEK evaluates the " (code "str") " expression, and when the signal changes, the continuation re-evaluates and updates the attribute.")
(~geography/cek/demo-reactive-attr)
(~docs/code :code (highlight
"(div :class (str \"p-3 rounded font-medium \"\n (if (deref danger)\n \"bg-red-100 text-red-800\"\n \"bg-green-100 text-green-800\"))\n (if (deref danger) \"DANGER\" \"SAFE\"))"
"lisp")))
(~docs/section :title "4. Effect + cleanup" :id "demo-stopwatch"
(p "Effects still work through CEK. This stopwatch uses " (code "effect") " with cleanup — toggling the signal clears the interval.")
(~geography/cek/demo-stopwatch)
(~docs/code :code (highlight
"(effect (fn ()\n (when (deref running)\n (let ((id (set-interval (fn () (swap! elapsed inc)) 100)))\n (fn () (clear-interval id))))))"
"lisp")))
(~docs/section :title "5. Batch coalescing" :id "demo-batch"
(p "Two signals updated in " (code "batch") " — one notification cycle. Compare render counts between batch and no-batch.")
(~geography/cek/demo-batch)
(~docs/code :code (highlight
"(batch (fn ()\n (swap! first-sig inc)\n (swap! second-sig inc)))\n;; One render pass, not two."
"lisp")))))

View File

@@ -166,6 +166,12 @@
(dict :label "Optimistic" :href "/sx/(geography.(isomorphism.optimistic))")
(dict :label "Offline" :href "/sx/(geography.(isomorphism.offline))")))
(define cek-nav-items (list
(dict :label "Overview" :href "/sx/(geography.(cek))"
:summary "The CEK machine — explicit evaluator with Control, Environment, Kontinuation. Three registers, pure step function.")
(dict :label "Demo" :href "/sx/(geography.(cek.demo))"
:summary "Live islands evaluated by the CEK machine. Counter, computed chains, reactive attributes — all through explicit continuation frames.")))
(define plans-nav-items (list
(dict :label "Status" :href "/sx/(etc.(plan.status))"
:summary "Audit of all plans — what's done, what's in progress, and what remains.")
@@ -384,7 +390,8 @@
:summary "Child-to-parent communication across render boundaries — spread, collect!, reactive-spread, built on scopes."}
{:label "Marshes" :href "/sx/(geography.(marshes))"
:summary "Where reactivity and hypermedia interpenetrate — server writes to signals, reactive transforms reshape server content, client state modifies how hypermedia is interpreted."}
{:label "Isomorphism" :href "/sx/(geography.(isomorphism))" :children isomorphism-nav-items})}
{:label "Isomorphism" :href "/sx/(geography.(isomorphism))" :children isomorphism-nav-items}
{:label "CEK Machine" :href "/sx/(geography.(cek))" :children cek-nav-items})}
{:label "Language" :href "/sx/(language)"
:children (list
{:label "Docs" :href "/sx/(language.(doc))" :children docs-nav-items}

View File

@@ -650,6 +650,25 @@
:layout :sx-docs
:content (~layouts/doc :path "/sx/(geography.(marshes))" (~reactive-islands/marshes/reactive-islands-marshes-content)))
;; ---------------------------------------------------------------------------
;; CEK Machine section (under Geography)
;; ---------------------------------------------------------------------------
(defpage cek-index
:path "/geography/cek/"
:auth :public
:layout :sx-docs
:content (~layouts/doc :path "/sx/(geography.(cek))" (~geography/cek/cek-content)))
(defpage cek-page
:path "/geography/cek/<slug>"
:auth :public
:layout :sx-docs
:content (~layouts/doc :path (str "/sx/(geography.(cek." slug "))")
(case slug
"demo" (~geography/cek/cek-demo-content)
:else (~geography/cek/cek-content))))
;; ---------------------------------------------------------------------------
;; Bootstrapped page helpers demo
;; ---------------------------------------------------------------------------

View File

@@ -267,6 +267,8 @@ _REDIRECT_PATTERNS = [
lambda m: f"/sx/(geography.(reactive.{m.group(1)}))"),
(re.compile(r"^/geography/isomorphism/(.+?)/?$"),
lambda m: f"/sx/(geography.(isomorphism.{m.group(1)}))"),
(re.compile(r"^/geography/cek/(.+?)/?$"),
lambda m: f"/sx/(geography.(cek.{m.group(1)}))"),
(re.compile(r"^/geography/spreads/?$"),
"/sx/(geography.(spreads))"),
(re.compile(r"^/geography/marshes/?$"),
@@ -290,6 +292,7 @@ _REDIRECT_PATTERNS = [
(re.compile(r"^/geography/hypermedia/?$"), "/sx/(geography.(hypermedia))"),
(re.compile(r"^/geography/reactive/?$"), "/sx/(geography.(reactive))"),
(re.compile(r"^/geography/isomorphism/?$"), "/sx/(geography.(isomorphism))"),
(re.compile(r"^/geography/cek/?$"), "/sx/(geography.(cek))"),
(re.compile(r"^/geography/?$"), "/sx/(geography)"),
(re.compile(r"^/applications/cssx/?$"), "/sx/(applications.(cssx))"),
(re.compile(r"^/applications/protocols/?$"), "/sx/(applications.(protocol))"),