Add live CEK stepper island — interactive stepping debugger
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m55s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m55s
A defisland that lets users type an SX expression, step through CEK evaluation one transition at a time, and see C/E/K registers update live. Demonstrates that cek-step is pure data->data. - cek.sx geography: add ~geography/cek/demo-stepper island with source input, step/run/reset buttons, state display, step history - platform_js.py: register CEK stepping primitives (make-cek-state, cek-step, cek-terminal?, cek-value, make-env, sx-serialize) so island code can access them Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
// =========================================================================
|
||||
|
||||
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
|
||||
var SX_VERSION = "2026-03-14T10:44:25Z";
|
||||
var SX_VERSION = "2026-03-14T13:31:24Z";
|
||||
|
||||
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
||||
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
||||
@@ -6547,6 +6547,15 @@ return (function() {
|
||||
return cekValue(state);
|
||||
};
|
||||
|
||||
// CEK stepping primitives — for debugger islands
|
||||
PRIMITIVES["make-cek-state"] = makeCekState;
|
||||
PRIMITIVES["cek-step"] = cekStep;
|
||||
PRIMITIVES["cek-terminal?"] = cekTerminal_p;
|
||||
PRIMITIVES["cek-value"] = cekValue;
|
||||
PRIMITIVES["make-env"] = function() { return merge(PRIMITIVES); };
|
||||
PRIMITIVES["sx-serialize"] = sxSerialize;
|
||||
PRIMITIVES["lambda-name"] = lambdaName;
|
||||
|
||||
|
||||
// =========================================================================
|
||||
// Async IO: Promise-aware rendering for client-side IO primitives
|
||||
|
||||
@@ -1508,6 +1508,15 @@ CEK_FIXUPS_JS = '''
|
||||
while (!cekTerminal_p(state)) { state = cekStep(state); }
|
||||
return cekValue(state);
|
||||
};
|
||||
|
||||
// CEK stepping primitives — for debugger islands
|
||||
PRIMITIVES["make-cek-state"] = makeCekState;
|
||||
PRIMITIVES["cek-step"] = cekStep;
|
||||
PRIMITIVES["cek-terminal?"] = cekTerminal_p;
|
||||
PRIMITIVES["cek-value"] = cekValue;
|
||||
PRIMITIVES["make-env"] = function() { return merge(PRIMITIVES); };
|
||||
PRIMITIVES["sx-serialize"] = sxSerialize;
|
||||
PRIMITIVES["lambda-name"] = lambdaName;
|
||||
'''
|
||||
|
||||
|
||||
|
||||
@@ -148,6 +148,141 @@
|
||||
"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))))))))
|
||||
|
||||
|
||||
;; ---------------------------------------------------------------------------
|
||||
;; Demo page content
|
||||
;; ---------------------------------------------------------------------------
|
||||
@@ -157,7 +292,12 @@
|
||||
|
||||
(~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."))
|
||||
(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/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.")
|
||||
|
||||
Reference in New Issue
Block a user