;; --------------------------------------------------------------------------- ;; 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"))))) ;; --------------------------------------------------------------------------- ;; CEK stepper: interactive stepping debugger ;; --------------------------------------------------------------------------- (defisland ~geography/cek/demo-stepper (&key initial-expr) (let ((source (signal (or initial-expr "(+ 1 (* 2 3))"))) (state (signal nil)) (steps (signal 0)) (history (signal (list))) (error-msg (signal nil))) ;; Parse and create initial CEK state (define start-eval (fn () (reset! error-msg nil) (reset! history (list)) (reset! steps 0) (let ((parsed (sx-parse (deref source)))) (if (empty? parsed) (reset! error-msg "Parse error: empty expression") (reset! state (make-cek-state (first parsed) (make-env) (list))))))) ;; Single step (define do-step (fn () (when (and (deref state) (not (cek-terminal? (deref state)))) (let ((prev (deref state))) (swap! history (fn (h) (append h (list prev)))) (swap! steps inc) (reset! state (cek-step prev)))))) ;; Run to completion (define do-run (fn () (when (deref state) (let run-loop ((n 0)) (when (and (not (cek-terminal? (deref state))) (< n 200)) (do-step) (run-loop (+ n 1))))))) ;; Reset (define do-reset (fn () (reset! state nil) (reset! steps 0) (reset! history (list)) (reset! error-msg nil))) ;; Format control for display (define fmt-control (fn (s) (if (nil? s) "\u2014" (let ((c (get s "control"))) (if (nil? c) "\u2014" (sx-serialize c)))))) ;; Format value (define fmt-value (fn (s) (if (nil? s) "\u2014" (let ((v (get s "value"))) (cond (nil? v) "nil" (callable? v) (str "\u03bb:" (or (lambda-name v) "fn")) :else (sx-serialize v)))))) ;; Format kont (define fmt-kont (fn (s) (if (nil? s) "\u2014" (let ((k (get s "kont"))) (if (empty? k) "[]" (str "[" (join " " (map (fn (f) (get f "type")) k)) "]")))))) ;; Initialize on first render (start-eval) (div :class "space-y-4" ;; Input (div :class "flex gap-2 items-end" (div :class "flex-1" (label :class "text-xs text-stone-400 block mb-1" "Expression") (input :type "text" :bind source :class "w-full px-3 py-1.5 rounded border border-stone-300 font-mono text-sm focus:outline-none focus:border-violet-400" :on-change (fn (e) (start-eval)))) (div :class "flex gap-1" (button :on-click (fn (e) (start-eval)) :class "px-3 py-1.5 rounded bg-stone-200 text-stone-700 text-sm hover:bg-stone-300" "Reset") (button :on-click (fn (e) (do-step)) :class "px-3 py-1.5 rounded bg-violet-500 text-white text-sm hover:bg-violet-600" "Step") (button :on-click (fn (e) (do-run)) :class "px-3 py-1.5 rounded bg-violet-700 text-white text-sm hover:bg-violet-800" "Run"))) ;; Error (when (deref error-msg) (div :class "text-red-600 text-sm" (deref error-msg))) ;; Current state (when (deref state) (div :class "rounded border border-stone-200 bg-white p-3 font-mono text-sm space-y-1" (div :class "flex gap-4" (span :class "text-stone-400 w-16" "Step") (span :class "font-bold" (deref steps))) (div :class "flex gap-4" (span :class "text-stone-400 w-16" "Phase") (span :class (str "font-bold " (if (= (get (deref state) "phase") "eval") "text-blue-600" "text-green-600")) (get (deref state) "phase"))) (div :class "flex gap-4" (span :class "text-violet-500 w-16" "C") (span (fmt-control (deref state)))) (div :class "flex gap-4" (span :class "text-amber-600 w-16" "V") (span (fmt-value (deref state)))) (div :class "flex gap-4" (span :class "text-emerald-600 w-16" "K") (span (fmt-kont (deref state)))) (when (cek-terminal? (deref state)) (div :class "mt-2 pt-2 border-t border-stone-200 text-stone-800 font-bold" (str "Result: " (sx-serialize (cek-value (deref state)))))))) ;; Step history (when (not (empty? (deref history))) (div :class "rounded border border-stone-100 bg-stone-50 p-2" (div :class "text-xs text-stone-400 mb-1" "History") (div :class "space-y-0.5 font-mono text-xs max-h-48 overflow-y-auto" (map-indexed (fn (i s) (div :class "flex gap-2 text-stone-500" (span :class "text-stone-300 w-6 text-right" (+ i 1)) (span :class (if (= (get s "phase") "eval") "text-blue-400" "text-green-400") (get s "phase")) (span :class "text-violet-400 truncate" (fmt-control s)) (span :class "text-amber-400" (fmt-value s)) (span :class "text-emerald-400" (fmt-kont s)))) (deref history)))))))) ;; --------------------------------------------------------------------------- ;; Render stepper: watch a component render itself, tag by tag ;; ;; Walks the SX AST depth-first. At each step, renders ONE subtree ;; via render-to-html and appends to the accumulating output. ;; The preview pane shows partial HTML building up. ;; --------------------------------------------------------------------------- (defisland ~geography/cek/demo-render-stepper (&key initial-expr) (let ((source (signal (or initial-expr "(div :class \"p-4 rounded border border-violet-200 bg-violet-50\"\n (h2 :class \"font-bold text-violet-800\" \"Hello from CEK\")\n (p :class \"text-stone-600\" (str \"2+3 = \" (+ 2 3))))"))) (steps (signal (list))) (preview (signal "")) (step-idx (signal 0)) (error-msg (signal nil))) (div :class "space-y-4" (div (label :class "text-xs text-stone-400 block mb-1" "Component expression") (textarea :bind source :rows 4 :class "w-full px-3 py-2 rounded border border-stone-300 font-mono text-xs focus:outline-none focus:border-violet-400")) (div :class "flex gap-1" (button :on-click (fn (e) ;; Parse and build step list (reset! error-msg nil) (reset! preview "") (reset! step-idx 0) (let ((parsed (sx-parse (deref source)))) (if (empty? parsed) (do (reset! error-msg "Parse error") (reset! steps (list))) (let ((expr (first parsed))) ;; If root is an HTML tag, split into per-child steps (if (and (list? expr) (not (empty? expr)) (= (type-of (first expr)) "symbol") (is-html-tag? (symbol-name (first expr)))) ;; Split: open tag, each child, close tag (let ((tag (symbol-name (first expr))) (args (rest expr)) (children (list)) (attrs (list)) (in-kw false) (result (list))) (for-each (fn (a) (cond (= (type-of a) "keyword") (do (set! in-kw true) (append! attrs a)) in-kw (do (set! in-kw false) (append! attrs a)) :else (do (set! in-kw false) (append! children a)))) args) ;; Build attr string (let ((attr-parts (list))) (let loop ((i 0)) (when (< i (len attrs)) (append! attr-parts (str " " (keyword-name (nth attrs i)) "=\"" (nth attrs (+ i 1)) "\"")) (loop (+ i 2)))) (let ((attr-str (join "" attr-parts)) (open-html (str "<" tag attr-str ">"))) ;; Step 1: open tag (append! result {"label" (str "<" tag " ...>") "html" open-html}) ;; Steps 2..N-1: each child rendered (for-each (fn (child) (let ((child-html (render-to-html child (make-env))) (child-sx (sx-serialize child))) (append! result {"label" child-sx "html" child-html}))) children) ;; Step N: close tag (append! result {"label" (str "") "html" (str "")}) (reset! steps result)))) ;; Not a tag — single step (let ((html (render-to-html expr (make-env)))) (reset! steps (list {"label" (sx-serialize expr) "html" html})))))))) :class "px-3 py-1.5 rounded bg-stone-200 text-stone-700 text-sm hover:bg-stone-300" "Parse") (button :on-click (fn (e) ;; Step: add next step's HTML to preview (when (< (deref step-idx) (len (deref steps))) (let ((step (nth (deref steps) (deref step-idx)))) (swap! preview (fn (p) (str p (get step "html")))) (swap! step-idx inc)))) :class "px-3 py-1.5 rounded bg-violet-500 text-white text-sm hover:bg-violet-600" "Step") (button :on-click (fn (e) ;; Run all remaining steps (let loop () (when (< (deref step-idx) (len (deref steps))) (let ((step (nth (deref steps) (deref step-idx)))) (swap! preview (fn (p) (str p (get step "html")))) (swap! step-idx inc) (loop))))) :class "px-3 py-1.5 rounded bg-violet-700 text-white text-sm hover:bg-violet-800" "Run")) (when (deref error-msg) (div :class "text-red-600 text-sm" (deref error-msg))) ;; Two-pane (div :class "grid grid-cols-1 md:grid-cols-2 gap-4" ;; Left: step list (div :class "rounded border border-stone-200 bg-white p-3 min-h-24" (div :class "text-xs text-stone-400 mb-2" (str (deref step-idx) " / " (len (deref steps)) " steps" (if (and (> (len (deref steps)) 0) (= (deref step-idx) (len (deref steps)))) " \u2014 complete" ""))) (div :class "space-y-0.5 font-mono text-xs max-h-64 overflow-y-auto" (map-indexed (fn (i step) (div :class (str "flex gap-2 px-1 rounded " (if (= i (deref step-idx)) "bg-violet-100" "") (if (< i (deref step-idx)) " text-stone-400" " text-stone-600")) (span :class "text-stone-300 w-4 text-right" (+ i 1)) (span :class "truncate" (let ((lbl (get step "label"))) (if (> (len lbl) 60) (str (slice lbl 0 57) "...") lbl))))) (deref steps)))) ;; Right: live preview (div :class "rounded border border-stone-200 bg-white p-3 min-h-24" (div :class "text-xs text-stone-400 mb-2" "Live preview") (if (empty? (deref preview)) (div :class "text-stone-300 text-sm italic" "Click Parse then Step...") (raw! (deref preview)))))))) ;; --------------------------------------------------------------------------- ;; 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") " and " (code "frames.sx") " — pure s-expressions, bootstrapped to both JavaScript and Python.")) (~docs/section :title "Stepper" :id "stepper" (p "The CEK machine is pure data\u2192data. Each step takes a state dict and returns a new one. " "Type an expression, click Step to advance one CEK transition.") (~geography/cek/demo-stepper :initial-expr "(let ((x 10)) (+ x (* 2 3)))") (~docs/code :code (highlight (component-source "~geography/cek/demo-stepper") "lisp"))) (~docs/section :title "Render stepper" :id "render-stepper" (p "Watch a component render itself. The CEK evaluates the expression — " "when it encounters " (code "(div ...)") ", the render adapter produces HTML in one step. " "Click Run to see the rendered output appear in the preview.") (~geography/cek/demo-render-stepper) (~docs/code :code (highlight (component-source "~geography/cek/demo-render-stepper") "lisp"))) (~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 (component-source "~geography/cek/demo-counter") "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 (component-source "~geography/cek/demo-chain") "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 (component-source "~geography/cek/demo-reactive-attr") "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 (component-source "~geography/cek/demo-stopwatch") "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 (component-source "~geography/cek/demo-batch") "lisp")))))