4 Commits

Author SHA1 Message Date
719da7914e Multi-shot delimited continuations: 868/870 passing
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 9m5s
Continuations are now multi-shot — k can be invoked multiple times.
Each invocation runs the captured frames via nested cek-run and
returns the result to the caller's continuation.

Fix: continue-with-call runs ONLY the captured delimited frames
(not rest-kont), so the continuation terminates and returns rather
than escaping to the outer program.

Fixed 4 continuation tests:
- shift with multiple invokes: (list (k 10) (k 20)) → (11 21)
- k returned from reset: continuation callable after escaping
- invoke k multiple times: same k reusable
- k in data structure: store in list, retrieve, invoke

Remaining 2 failures: scope/provide across shift boundaries.
These need scope state tracked in frames (not imperative push/pop).

JS 747/747, Full 868/870, Python 679/679.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 14:20:31 +00:00
c6a662c980 Phase 4: Eliminate nested CEK from HO form handlers
Higher-order forms (map, filter, reduce, some, every?, for-each,
map-indexed) now evaluate their arguments via CEK frames instead
of nested trampoline(eval-expr(...)) calls.

Added HoSetupFrame — staged evaluation of HO form arguments.
When all args are evaluated, ho-setup-dispatch sets up the
iteration frame. This keeps a single linear CEK continuation
chain instead of spawning nested CEK instances.

14 nested eval-expr calls eliminated (39 → 25 remaining).
The remaining 25 are in delegate functions (sf-letrec, sf-scope,
parse-keyword-args, qq-expand, etc.) called infrequently.

All tests unchanged: JS 747/747, Full 864/870, Python 679/679.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 14:10:33 +00:00
e475222099 Merge eval.sx + frames.sx + cek.sx into single evaluator.sx
The core spec is now one file: spec/evaluator.sx (2275 lines).
Three parts:
  Part 1: CEK frames — state and continuation frame constructors
  Part 2: Evaluation utilities — call, parse, define, macro, strict
  Part 3: CEK machine — the sole evaluator

Deleted:
- spec/eval.sx (merged into evaluator.sx)
- spec/frames.sx (merged into evaluator.sx)
- spec/cek.sx (merged into evaluator.sx)
- spec/continuations.sx (dead — CEK handles shift/reset natively)

Updated bootstrappers (JS + Python) to load evaluator.sx as core.
Removed frames/cek from SPEC_MODULES (now part of core).

Bundle size: 392KB → 377KB standard, 418KB → 403KB full.
All tests unchanged: JS 747/747, Full 864/870, Python 679/679.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:43:48 +00:00
b4df216fae Phase 2: Remove dead tree-walk code from eval.sx
eval.sx: 1272 → 846 lines (-33%). sx-browser.js: 392KB → 377KB.

Deleted (superseded by CEK step handlers in cek.sx):
- eval-list: tree-walk dispatch table
- eval-call: tree-walk function dispatch
- sf-if, sf-when, sf-cond (3 variants), sf-case (2 variants)
- sf-and, sf-or, sf-let, sf-begin, sf-quote, sf-quasiquote
- sf-thread-first, sf-set!, sf-define
- ho-map, ho-filter, ho-reduce, ho-some, ho-every, ho-for-each,
  ho-map-indexed, call-fn

Kept (still called by CEK as delegates):
- sf-lambda, sf-defcomp, sf-defisland, sf-defmacro, sf-defstyle,
  sf-deftype, sf-defeffect, sf-letrec, sf-named-let
- sf-scope, sf-provide, sf-dynamic-wind
- expand-macro, qq-expand, cond-scheme?
- call-lambda, call-component, parse-keyword-args
- Strict mode, type helpers

eval-expr is now a stub overridden by CEK fixup.
All tests unchanged: JS 747/747, Full 864/870, Python 679/679.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 13:28:09 +00:00
16 changed files with 3731 additions and 4016 deletions

View File

@@ -112,16 +112,11 @@ def compile_ref_to_js(
spec_mod_set.add("deps")
if "page-helpers" in SPEC_MODULES:
spec_mod_set.add("page-helpers")
# CEK is the canonical evaluator — always included
spec_mod_set.add("cek")
spec_mod_set.add("frames")
# cek module requires frames
if "cek" in spec_mod_set:
spec_mod_set.add("frames")
# CEK is always included (part of evaluator.sx core file)
has_cek = True
has_deps = "deps" in spec_mod_set
has_router = "router" in spec_mod_set
has_page_helpers = "page-helpers" in spec_mod_set
has_cek = "cek" in spec_mod_set
# Resolve extensions
ext_set = set()
@@ -132,9 +127,10 @@ def compile_ref_to_js(
ext_set.add(e)
has_continuations = "continuations" in ext_set
# Build file list: core + adapters + spec modules
# Build file list: core evaluator + adapters + spec modules
# evaluator.sx = merged frames + eval utilities + CEK machine
sx_files = [
("eval.sx", "eval"),
("evaluator.sx", "evaluator (frames + eval + CEK)"),
("render.sx", "render (core)"),
]
for name in ("parser", "html", "sx", "dom", "engine", "orchestration", "boot"):

View File

@@ -46,14 +46,12 @@ SPEC_MODULES = {
"router": ("router.sx", "router (client-side route matching)"),
"signals": ("signals.sx", "signals (reactive signal runtime)"),
"page-helpers": ("page-helpers.sx", "page-helpers (pure data transformation helpers)"),
"frames": ("frames.sx", "frames (CEK continuation frames)"),
"cek": ("cek.sx", "cek (explicit CEK machine evaluator)"),
"types": ("types.sx", "types (gradual type system)"),
}
# Note: frames and cek are now part of evaluator.sx (always loaded as core)
# Explicit ordering for spec modules with dependencies.
# Modules listed here are emitted in this order; any not listed use alphabetical.
SPEC_MODULE_ORDER = ["deps", "frames", "page-helpers", "router", "cek", "signals", "types"]
SPEC_MODULE_ORDER = ["deps", "page-helpers", "router", "signals", "types"]
EXTENSION_NAMES = {"continuations"}

View File

@@ -1484,15 +1484,14 @@ def compile_ref_to_py(
spec_mod_set.add("page-helpers")
if "router" in SPEC_MODULES:
spec_mod_set.add("router")
# CEK is the canonical evaluator — always include
spec_mod_set.add("cek")
spec_mod_set.add("frames")
# CEK is always included (part of evaluator.sx core file)
has_cek = True
has_deps = "deps" in spec_mod_set
has_cek = "cek" in spec_mod_set
# Core files always included, then selected adapters, then spec modules
# evaluator.sx = merged frames + eval utilities + CEK machine
sx_files = [
("eval.sx", "eval"),
("evaluator.sx", "evaluator (frames + eval + CEK)"),
("forms.sx", "forms (server definition forms)"),
("render.sx", "render (core)"),
]

View File

@@ -1636,14 +1636,12 @@ SPEC_MODULES = {
"signals": ("signals.sx", "signals (reactive signal runtime)"),
"page-helpers": ("page-helpers.sx", "page-helpers (pure data transformation helpers)"),
"types": ("types.sx", "types (gradual type system)"),
"frames": ("frames.sx", "frames (CEK continuation frames)"),
"cek": ("cek.sx", "cek (explicit CEK machine evaluator)"),
}
# Note: frames and cek are now part of evaluator.sx (always loaded as core)
# Explicit ordering for spec modules with dependencies.
# Modules listed here are emitted in this order; any not listed use alphabetical.
SPEC_MODULE_ORDER = [
"deps", "engine", "frames", "page-helpers", "router", "cek", "signals", "types",
"deps", "engine", "page-helpers", "router", "signals", "types",
]
EXTENSION_NAMES = {"continuations"}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -20,7 +20,7 @@
:class "hidden md:flex md:flex-col max-w-xs md:h-full md:min-h-0 mr-3"
(when aside aside))
(section :id "main-panel"
:class "flex-1 md:h-full md:min-h-0 md:overflow-y-auto md:overscroll-contain js-grid-viewport"
:class "flex-1 md:h-full md:min-h-0 md:overflow-y-auto md:overscroll-contain overflow-x-hidden js-grid-viewport"
(when content content)
(div :class "pb-8")))))))
@@ -35,7 +35,7 @@
(div :id "root-menu" :sx-swap-oob "outerHTML" :class "md:hidden"
(when menu menu))
(section :id "main-panel"
:class "flex-1 md:h-full md:min-h-0 md:overflow-y-auto md:overscroll-contain js-grid-viewport"
:class "flex-1 md:h-full md:min-h-0 md:overflow-y-auto md:overscroll-contain overflow-x-hidden js-grid-viewport"
(when content content))))
(defcomp ~shared:layout/hamburger ()

File diff suppressed because it is too large Load Diff

View File

@@ -1,248 +0,0 @@
;; ==========================================================================
;; continuations.sx — Delimited continuations (shift/reset)
;;
;; OPTIONAL EXTENSION — not required by the core evaluator.
;; Bootstrappers include this only when the target requests it.
;;
;; Delimited continuations capture "the rest of the computation up to
;; a delimiter." They are strictly less powerful than full call/cc but
;; cover the practical use cases: suspendable rendering, cooperative
;; scheduling, linear async flows, wizard forms, and undo.
;;
;; Two new special forms:
;; (reset body) — establish a delimiter
;; (shift k body) — capture the continuation to the nearest reset
;;
;; One new type:
;; continuation — a captured delimited continuation, callable
;;
;; The captured continuation is a function of one argument. Invoking it
;; provides the value that the shift expression "returns" within the
;; delimited context, then completes the rest of the reset body.
;;
;; Continuations are composable — invoking a continuation returns a
;; value (the result of the reset body), which can be used normally.
;; This is the key difference from undelimited call/cc, where invoking
;; a continuation never returns.
;;
;; Platform requirements:
;; (make-continuation fn) — wrap a function as a continuation value
;; (continuation? x) — type predicate
;; (type-of continuation) → "continuation"
;; Continuations are callable (same dispatch as lambda).
;; ==========================================================================
;; --------------------------------------------------------------------------
;; 1. Type
;; --------------------------------------------------------------------------
;;
;; A continuation is a callable value of one argument.
;;
;; (continuation? k) → true if k is a captured continuation
;; (type-of k) → "continuation"
;; (k value) → invoke: resume the captured computation with value
;;
;; Continuations are first-class: they can be stored in variables, passed
;; as arguments, returned from functions, and put in data structures.
;;
;; Invoking a delimited continuation RETURNS a value — the result of the
;; reset body. This makes them composable:
;;
;; (+ 1 (reset (+ 10 (shift k (k 5)))))
;; ;; k is "add 10 to _ and return from reset"
;; ;; (k 5) → 15, which is returned from reset
;; ;; (+ 1 15) → 16
;;
;; --------------------------------------------------------------------------
;; --------------------------------------------------------------------------
;; 2. reset — establish a continuation delimiter
;; --------------------------------------------------------------------------
;;
;; (reset body)
;;
;; Evaluates body in the current environment. If no shift occurs during
;; evaluation of body, reset simply returns the value of body.
;;
;; If shift occurs, reset is the boundary — the continuation captured by
;; shift extends from the shift point back to (and including) this reset.
;;
;; reset is the "prompt" — it marks where the continuation stops.
;;
;; Semantics:
;; (reset expr) where expr contains no shift
;; → (eval expr env) ;; just evaluates normally
;;
;; (reset ... (shift k body) ...)
;; → captures continuation, evaluates shift's body
;; → the result of the shift body is the result of the reset
;;
;; --------------------------------------------------------------------------
(define sf-reset
(fn ((args :as list) (env :as dict))
;; Single argument: the body expression.
;; Install a continuation delimiter, then evaluate body.
;; The implementation is target-specific:
;; - In Scheme: native reset/shift
;; - In Haskell: Control.Monad.CC or delimited continuations library
;; - In Python: coroutine/generator-based (see implementation notes)
;; - In JavaScript: generator-based or CPS transform
;; - In Rust: CPS transform at compile time
(let ((body (first args)))
(eval-with-delimiter body env))))
;; --------------------------------------------------------------------------
;; 3. shift — capture the continuation to the nearest reset
;; --------------------------------------------------------------------------
;;
;; (shift k body)
;;
;; Captures the continuation from this point back to the nearest enclosing
;; reset and binds it to k. Then evaluates body in the current environment
;; extended with k. The result of body becomes the result of the enclosing
;; reset.
;;
;; k is a function of one argument. Calling (k value) resumes the captured
;; computation with value standing in for the shift expression.
;;
;; The continuation k is composable: (k value) returns a value (the result
;; of the reset body when resumed with value). This means k can be called
;; multiple times, and its result can be used in further computation.
;;
;; Examples:
;;
;; ;; Basic: shift provides a value to the surrounding computation
;; (reset (+ 1 (shift k (k 41))))
;; ;; k = "add 1 to _", (k 41) → 42, reset returns 42
;;
;; ;; Abort: shift can discard the continuation entirely
;; (reset (+ 1 (shift k "aborted")))
;; ;; k is never called, reset returns "aborted"
;;
;; ;; Multiple invocations: k can be called more than once
;; (reset (+ 1 (shift k (list (k 10) (k 20)))))
;; ;; (k 10) → 11, (k 20) → 21, reset returns (11 21)
;;
;; ;; Stored for later: k can be saved and invoked outside reset
;; (define saved nil)
;; (reset (+ 1 (shift k (set! saved k) 0)))
;; ;; reset returns 0, saved holds the continuation
;; (saved 99) ;; → 100
;;
;; --------------------------------------------------------------------------
(define sf-shift
(fn ((args :as list) (env :as dict))
;; Two arguments: the continuation variable name, and the body.
(let ((k-name (symbol-name (first args)))
(body (second args)))
;; Capture the current continuation up to the nearest reset.
;; Bind it to k-name in the environment, then evaluate body.
;; The result of body is returned to the reset.
(capture-continuation k-name body env))))
;; --------------------------------------------------------------------------
;; 4. Interaction with other features
;; --------------------------------------------------------------------------
;;
;; TCO (trampoline):
;; Continuations interact naturally with the trampoline. A shift inside
;; a tail-call position captures the continuation including the pending
;; return. The trampoline resolves thunks before the continuation is
;; delimited.
;;
;; Macros:
;; shift/reset are special forms, not macros. Macros expand before
;; evaluation, so shift inside a macro-expanded form works correctly —
;; it captures the continuation of the expanded code.
;;
;; Components:
;; shift inside a component body captures the continuation of that
;; component's render. The enclosing reset determines the delimiter.
;; This is the foundation for suspendable rendering — a component can
;; shift to suspend, and the server resumes it when data arrives.
;;
;; I/O primitives:
;; I/O primitives execute at invocation time, in whatever context
;; exists then. A continuation that captures a computation containing
;; I/O will re-execute that I/O when invoked. If the I/O requires
;; request context (e.g. current-user), invoking the continuation
;; outside a request will fail — same as calling the I/O directly.
;; This is consistent, not a restriction.
;;
;; In typed targets (Haskell, Rust), the type system can enforce that
;; continuations containing I/O are only invoked in appropriate contexts.
;; In dynamic targets (Python, JS), it fails at runtime.
;;
;; Lexical scope:
;; Continuations capture the dynamic extent (what happens next) but
;; close over the lexical environment at the point of capture. Variable
;; bindings in the continuation refer to the same environment — mutations
;; via set! are visible.
;;
;; --------------------------------------------------------------------------
;; --------------------------------------------------------------------------
;; 5. Implementation notes per target
;; --------------------------------------------------------------------------
;;
;; The bootstrapper emits target-specific continuation machinery.
;; The spec defines semantics; each target chooses representation.
;;
;; Scheme / Racket:
;; Native shift/reset. No transformation needed. The bootstrapper
;; emits (require racket/control) or equivalent.
;;
;; Haskell:
;; Control.Monad.CC provides delimited continuations in the CC monad.
;; Alternatively, the evaluator can be CPS-transformed at compile time.
;; Continuations become first-class functions naturally.
;;
;; Python:
;; Generator-based: reset creates a generator, shift yields from it.
;; The trampoline loop drives the generator. Each yield is a shift
;; point, and send() provides the resume value.
;; Alternative: greenlet-based (stackful coroutines).
;;
;; JavaScript:
;; Generator-based (function* / yield). Similar to Python.
;; Alternative: CPS transform at bootstrap time — the bootstrapper
;; rewrites the evaluator into continuation-passing style, making
;; shift/reset explicit function arguments.
;;
;; Rust:
;; CPS transform at compile time. Continuations become enum variants
;; or boxed closures. The type system ensures continuations are used
;; linearly if desired (affine types via ownership).
;;
;; --------------------------------------------------------------------------
;; --------------------------------------------------------------------------
;; 6. Platform interface — what each target must provide
;; --------------------------------------------------------------------------
;;
;; (eval-with-delimiter expr env)
;; Install a reset delimiter, evaluate expr, return result.
;; If expr calls shift, the continuation is captured up to here.
;;
;; (capture-continuation k-name body env)
;; Capture the current continuation up to the nearest delimiter.
;; Bind it to k-name in env, evaluate body, return result to delimiter.
;;
;; (make-continuation fn)
;; Wrap a native function as a continuation value.
;;
;; (continuation? x)
;; Type predicate.
;;
;; Continuations must be callable via the standard function-call
;; dispatch in eval-list (same path as lambda calls).
;;
;; --------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

2335
spec/evaluator.sx Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,262 +0,0 @@
;; ==========================================================================
;; frames.sx — CEK machine frame types
;;
;; Defines the continuation frame types used by the explicit CEK evaluator.
;; Each frame represents a "what to do next" when a sub-evaluation completes.
;;
;; A CEK state is a dict:
;; {:control expr — expression being evaluated (or nil in continue phase)
;; :env env — current environment
;; :kont list — continuation: list of frames (stack, head = top)
;; :phase "eval"|"continue"
;; :value any} — value produced (only in continue phase)
;;
;; Two-phase step function:
;; step-eval: control is expression → dispatch → push frame + new control
;; step-continue: value produced → pop frame → dispatch → new state
;;
;; Terminal state: phase = "continue" and kont is empty → value is final result.
;; ==========================================================================
;; --------------------------------------------------------------------------
;; 1. CEK State constructors
;; --------------------------------------------------------------------------
(define make-cek-state
(fn (control env kont)
{:control control :env env :kont kont :phase "eval" :value nil}))
(define make-cek-value
(fn (value env kont)
{:control nil :env env :kont kont :phase "continue" :value value}))
(define cek-terminal?
(fn (state)
(and (= (get state "phase") "continue")
(empty? (get state "kont")))))
(define cek-control (fn (s) (get s "control")))
(define cek-env (fn (s) (get s "env")))
(define cek-kont (fn (s) (get s "kont")))
(define cek-phase (fn (s) (get s "phase")))
(define cek-value (fn (s) (get s "value")))
;; --------------------------------------------------------------------------
;; 2. Frame constructors
;; --------------------------------------------------------------------------
;; Each frame type is a dict with a "type" key and frame-specific data.
;; IfFrame: waiting for condition value
;; After condition evaluates, choose then or else branch
(define make-if-frame
(fn (then-expr else-expr env)
{:type "if" :then then-expr :else else-expr :env env}))
;; WhenFrame: waiting for condition value
;; If truthy, evaluate body exprs sequentially
(define make-when-frame
(fn (body-exprs env)
{:type "when" :body body-exprs :env env}))
;; BeginFrame: sequential evaluation
;; Remaining expressions to evaluate after current one
(define make-begin-frame
(fn (remaining env)
{:type "begin" :remaining remaining :env env}))
;; LetFrame: binding evaluation in progress
;; name = current binding name, remaining = remaining (name val) pairs
;; body = body expressions to evaluate after all bindings
(define make-let-frame
(fn (name remaining body local)
{:type "let" :name name :remaining remaining :body body :env local}))
;; DefineFrame: waiting for value to bind
(define make-define-frame
(fn (name env has-effects effect-list)
{:type "define" :name name :env env
:has-effects has-effects :effect-list effect-list}))
;; SetFrame: waiting for value to assign
(define make-set-frame
(fn (name env)
{:type "set" :name name :env env}))
;; ArgFrame: evaluating function arguments
;; f = function value (already evaluated), evaled = already evaluated args
;; remaining = remaining arg expressions
(define make-arg-frame
(fn (f evaled remaining env raw-args head-name)
{:type "arg" :f f :evaled evaled :remaining remaining :env env
:raw-args raw-args :head-name (or head-name nil)}))
;; CallFrame: about to call with fully evaluated args
(define make-call-frame
(fn (f args env)
{:type "call" :f f :args args :env env}))
;; CondFrame: evaluating cond clauses
(define make-cond-frame
(fn (remaining env scheme?)
{:type "cond" :remaining remaining :env env :scheme scheme?}))
;; CaseFrame: evaluating case clauses
(define make-case-frame
(fn (match-val remaining env)
{:type "case" :match-val match-val :remaining remaining :env env}))
;; ThreadFirstFrame: pipe threading
(define make-thread-frame
(fn (remaining env)
{:type "thread" :remaining remaining :env env}))
;; MapFrame: higher-order map/map-indexed in progress
(define make-map-frame
(fn (f remaining results env)
{:type "map" :f f :remaining remaining :results results :env env :indexed false}))
(define make-map-indexed-frame
(fn (f remaining results env)
{:type "map" :f f :remaining remaining :results results :env env :indexed true}))
;; FilterFrame: higher-order filter in progress
(define make-filter-frame
(fn (f remaining results current-item env)
{:type "filter" :f f :remaining remaining :results results
:current-item current-item :env env}))
;; ReduceFrame: higher-order reduce in progress
(define make-reduce-frame
(fn (f remaining env)
{:type "reduce" :f f :remaining remaining :env env}))
;; ForEachFrame: higher-order for-each in progress
(define make-for-each-frame
(fn (f remaining env)
{:type "for-each" :f f :remaining remaining :env env}))
;; SomeFrame: higher-order some (short-circuit on first truthy)
(define make-some-frame
(fn (f remaining env)
{:type "some" :f f :remaining remaining :env env}))
;; EveryFrame: higher-order every? (short-circuit on first falsy)
(define make-every-frame
(fn (f remaining env)
{:type "every" :f f :remaining remaining :env env}))
;; ScopeFrame: scope-pop! when frame pops
(define make-scope-frame
(fn (name remaining env)
{:type "scope" :name name :remaining remaining :env env}))
;; ResetFrame: delimiter for shift/reset continuations
(define make-reset-frame
(fn (env)
{:type "reset" :env env}))
;; DictFrame: evaluating dict values
(define make-dict-frame
(fn (remaining results env)
{:type "dict" :remaining remaining :results results :env env}))
;; AndFrame: short-circuit and
(define make-and-frame
(fn (remaining env)
{:type "and" :remaining remaining :env env}))
;; OrFrame: short-circuit or
(define make-or-frame
(fn (remaining env)
{:type "or" :remaining remaining :env env}))
;; QuasiquoteFrame (not a real frame — QQ is handled specially)
;; DynamicWindFrame: phases of dynamic-wind
(define make-dynamic-wind-frame
(fn (phase body-thunk after-thunk env)
{:type "dynamic-wind" :phase phase
: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
;; --------------------------------------------------------------------------
(define frame-type (fn (f) (get f "type")))
;; --------------------------------------------------------------------------
;; 4. Continuation operations
;; --------------------------------------------------------------------------
(define kont-push
(fn (frame kont) (cons frame kont)))
(define kont-top
(fn (kont) (first kont)))
(define kont-pop
(fn (kont) (rest kont)))
(define kont-empty?
(fn (kont) (empty? kont)))
;; --------------------------------------------------------------------------
;; 5. CEK shift/reset support
;; --------------------------------------------------------------------------
;; shift captures all frames up to the nearest ResetFrame.
;; reset pushes a ResetFrame.
(define kont-capture-to-reset
(fn (kont)
;; Returns (captured-frames remaining-kont).
;; captured-frames: frames from top up to (not including) ResetFrame.
;; remaining-kont: frames after ResetFrame.
;; Stops at either "reset" or "reactive-reset" frames.
(define scan
(fn (k captured)
(if (empty? k)
(error "shift without enclosing reset")
(let ((frame (first k)))
(if (or (= (frame-type frame) "reset")
(= (frame-type frame) "reactive-reset"))
(list captured (rest k))
(scan (rest k) (append captured (list frame))))))))
(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))))

View File

@@ -70,20 +70,20 @@
(next-idx (mod (+ idx 1) count))
(prev-node (nth sibs prev-idx))
(next-node (nth sibs next-idx)))
(div :class "max-w-3xl mx-auto px-4 py-2 grid grid-cols-3 items-center"
(div :class "w-full max-w-3xl mx-auto px-4 py-2 grid grid-cols-3 items-center"
:style (str "opacity:" row-opacity ";transition:opacity 0.3s;")
(a :href (get prev-node "href")
:sx-get (get prev-node "href") :sx-target "#main-panel"
:sx-select "#main-panel" :sx-swap "outerHTML"
:sx-push-url "true"
:class "text-right"
:class "text-right min-w-0 truncate"
:style (tw "text-stone-500 text-sm")
(str " " (get prev-node "label")))
(str "\u2190 " (get prev-node "label")))
(a :href (get node "href")
:sx-get (get node "href") :sx-target "#main-panel"
:sx-select "#main-panel" :sx-swap "outerHTML"
:sx-push-url "true"
:class "text-center px-4"
:class "text-center min-w-0 truncate px-1"
:style (if is-leaf
(tw "text-violet-700 text-2xl font-bold")
(tw "text-violet-700 text-lg font-semibold"))
@@ -92,9 +92,9 @@
:sx-get (get next-node "href") :sx-target "#main-panel"
:sx-select "#main-panel" :sx-swap "outerHTML"
:sx-push-url "true"
:class "text-left"
:class "text-left min-w-0 truncate"
:style (tw "text-stone-500 text-sm")
(str (get next-node "label") " ")))))))
(str (get next-node "label") " \u2192")))))))
;; Children links — shown as clearly clickable buttons.
(defcomp ~layouts/nav-children (&key items)

View File

@@ -246,7 +246,11 @@
(dict :label "Reactive Runtime" :href "/sx/(etc.(plan.reactive-runtime))"
:summary "Seven feature layers — ref, foreign FFI, state machines, commands with undo/redo, render loops, keyed lists, client-first app shell. Zero new platform primitives.")
(dict :label "Rust/WASM Host" :href "/sx/(etc.(plan.rust-wasm-host))"
:summary "Bootstrap the SX spec to Rust, compile to WASM, replace sx-browser.js. Shared platform layer for DOM, phased rollout from parse to full parity.")))
:summary "Bootstrap the SX spec to Rust, compile to WASM, replace sx-browser.js. Shared platform layer for DOM, phased rollout from parse to full parity.")
(dict :label "Isolated Evaluator" :href "/sx/(etc.(plan.isolated-evaluator))"
:summary "Core/application split, shared sx-platform.js, isolated JS evaluator, Rust WASM via handle table. Only language-defining spec gets bootstrapped; everything else is runtime-evaluated .sx.")
(dict :label "Mother Language" :href "/sx/(etc.(plan.mother-language))"
:summary "SX as its own compiler. OCaml as substrate (closest to CEK), Koka as alternative (compile-time linearity), ultimately self-hosting. One language, every target.")))
(define reactive-islands-nav-items (list
(dict :label "Overview" :href "/sx/(geography.(reactive))"

View File

@@ -0,0 +1,578 @@
;; ---------------------------------------------------------------------------
;; Mother Language — SX as its own compiler, OCaml as the substrate
;; ---------------------------------------------------------------------------
(defcomp ~plans/mother-language/plan-mother-language-content ()
(~docs/page :title "Mother Language"
(p :class "text-stone-500 text-sm italic mb-8"
"The ideal language for evaluating the SX core spec is SX itself. "
"The path: OCaml as the initial substrate (closest existing language to what CEK is), "
"Koka as an alternative (compile-time linearity), ultimately a self-hosting SX compiler "
"that emits machine code directly from the spec. One language. Every target.")
;; -----------------------------------------------------------------------
;; The argument
;; -----------------------------------------------------------------------
(~docs/section :title "The Argument" :id "argument"
(h4 :class "font-semibold mt-4 mb-2" "What the evaluator actually does")
(p "The CEK machine is a " (code "state \u2192 state") " loop over sum types. "
"Each step pattern-matches on the Control register, consults the Environment, "
"and transforms the Kontinuation. Every SX expression, every component render, "
"every signal update goes through this loop. It's the hot path.")
(p "The ideal host language is one where this loop compiles to a tight jump table "
"with minimal allocation. That means: algebraic types, pattern matching, "
"persistent data structures, and a native effect system.")
(h4 :class "font-semibold mt-6 mb-2" "Why multiple hosts is the wrong goal")
(p "The current architecture bootstraps the spec to Python, JavaScript, and Rust. "
"Each host has impedance mismatches:")
(ul :class "list-disc list-inside space-y-1 mt-2"
(li (strong "Python") " \u2014 slow. Tree-walk overhead is 100\u20131000x vs native. "
"The async adapter is complex because Python's async model is cooperative, not effect-based.")
(li (strong "JavaScript") " \u2014 no sum types, prototype-based dispatch, GC pauses unpredictable. "
"The bootstrapper works around JS limitations rather than mapping naturally.")
(li (strong "Rust") " \u2014 ownership fights shared immutable trees. Every " (code "Rc<Vec<Value>>")
" is overhead. Closures capturing environments need " (code "Arc") " gymnastics."))
(p "Each host makes the evaluator work, but none make it " (em "natural") ". "
"The translation is structure-" (em "creating") ", not structure-" (em "preserving") ".")
(h4 :class "font-semibold mt-6 mb-2" "The Mother Language is SX")
(p "The spec defines the semantics. The CEK machine is the most explicit form of those semantics. "
"The ideal \"language\" is one that maps 1:1 onto CEK transitions and compiles them to "
"optimal machine code. That language is SX itself \u2014 compiled, not interpreted.")
(p "The hosts (Python, JS, Haskell, Rust) become " (em "platform layers") " \u2014 "
"they provide IO, DOM, database, GPU access. The evaluator itself is always the same "
"compiled SX core, embedded as a native library or WASM module."))
;; -----------------------------------------------------------------------
;; Why OCaml
;; -----------------------------------------------------------------------
(~docs/section :title "Why OCaml" :id "ocaml"
(p "OCaml is the closest existing language to what the CEK machine is. "
"The translation from " (code "cek.sx") " to OCaml is nearly mechanical.")
(h4 :class "font-semibold mt-4 mb-2" "Natural mapping")
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
(table :class "w-full text-left text-sm"
(thead (tr :class "border-b border-stone-200 bg-stone-100"
(th :class "px-3 py-2 font-medium text-stone-600" "SX concept")
(th :class "px-3 py-2 font-medium text-stone-600" "OCaml primitive")
(th :class "px-3 py-2 font-medium text-stone-600" "Notes")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Value (Nil | Num | Str | List | ...)")
(td :class "px-3 py-2 text-stone-700" "Algebraic variant type")
(td :class "px-3 py-2 text-stone-600" "Direct mapping, no boxing"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Frame (IfFrame | ArgFrame | MapFrame | ...)")
(td :class "px-3 py-2 text-stone-700" "Algebraic variant type")
(td :class "px-3 py-2 text-stone-600" "20+ variants, pattern match dispatch"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Environment (persistent map)")
(td :class "px-3 py-2 text-stone-700" (code "Map.S"))
(td :class "px-3 py-2 text-stone-600" "Built-in balanced tree, structural sharing"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Continuation (list of frames)")
(td :class "px-3 py-2 text-stone-700" "Immutable list")
(td :class "px-3 py-2 text-stone-600" "cons/match, O(1) push/pop"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "cek-step (pattern match on C)")
(td :class "px-3 py-2 text-stone-700" (code "match") " expression")
(td :class "px-3 py-2 text-stone-600" "Compiles to jump table"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "shift/reset")
(td :class "px-3 py-2 text-stone-700" (code "perform") " / " (code "continue"))
(td :class "px-3 py-2 text-stone-600" "Native in OCaml 5"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "Concurrent CEK (fibers)")
(td :class "px-3 py-2 text-stone-700" "Domains + effect handlers")
(td :class "px-3 py-2 text-stone-600" "One fiber per CEK machine"))
(tr
(td :class "px-3 py-2 text-stone-700" "Linear continuations")
(td :class "px-3 py-2 text-stone-700" "One-shot continuations (default)")
(td :class "px-3 py-2 text-stone-600" "Runtime-enforced, not compile-time")))))
(h4 :class "font-semibold mt-4 mb-2" "Compilation targets")
(ul :class "list-disc list-inside space-y-1 mt-2"
(li (strong "Native") " \u2014 OCaml's native compiler produces fast binaries, small footprint. "
"Embed in Python via C ABI (ctypes/cffi). Embed in Node via N-API.")
(li (strong "WASM") " \u2014 " (code "wasm_of_ocaml") " is mature (used by Facebook's Flow/Reason). "
"Produces compact " (code ".wasm") " modules. Loaded via " (code "sx-platform.js") " like the Rust WASM plan.")
(li (strong "JavaScript") " \u2014 " (code "js_of_ocaml") " for legacy browser targets. "
"Falls back to JS when WASM isn't available."))
(h4 :class "font-semibold mt-4 mb-2" "What OCaml replaces")
(p "The Haskell and Rust evaluator implementations become unnecessary. "
"OCaml covers both server (native) and client (WASM) from one codebase. "
"The sx-haskell and sx-rust work proved the spec is host-independent \u2014 "
"OCaml is the convergence point.")
(p "Python and JavaScript evaluators remain as " (em "platform layers") " \u2014 "
"they provide IO primitives, not evaluation logic. The Python web framework calls "
"the OCaml evaluator via FFI for rendering. The browser loads the WASM evaluator "
"and connects it to " (code "sx-platform.js") " for DOM access."))
;; -----------------------------------------------------------------------
;; Koka as alternative
;; -----------------------------------------------------------------------
(~docs/section :title "Koka as Alternative" :id "koka"
(p "Koka (Daan Leijen, MSR) addresses OCaml's one weakness: "
(strong "compile-time linearity") ".")
(h4 :class "font-semibold mt-4 mb-2" "Where Koka wins")
(ul :class "list-disc list-inside space-y-2 mt-2"
(li (strong "Perceus reference counting") " \u2014 the compiler tracks which values are used linearly. "
"Linear values are mutated in-place (zero allocation). "
"Non-linear values use reference counting (no GC at all).")
(li (strong "Effect types") " \u2014 effects are tracked in the type system. "
"A function's type says exactly which effects it can perform. "
"The type checker enforces that handlers exist for every effect.")
(li (strong "One-shot continuations by default") " \u2014 like OCaml 5, but " (em "statically enforced") ". "
"The type system prevents invoking a linear continuation twice. "
"No runtime check needed."))
(h4 :class "font-semibold mt-4 mb-2" "Where Koka is weaker")
(ul :class "list-disc list-inside space-y-2 mt-2"
(li (strong "Maturity") " \u2014 research language. Smaller ecosystem, fewer FFI bindings, "
"less battle-tested than OCaml.")
(li (strong "WASM backend") " \u2014 compiles to C \u2192 WASM (via Emscripten). "
"Works but less optimized than " (code "wasm_of_ocaml") " or Rust's " (code "wasm-pack") ".")
(li (strong "Embeddability") " \u2014 C FFI works but less established for Python/Node embedding."))
(p "Koka is the right choice " (em "if") " compile-time linearity proves essential for correctness. "
"The decision point is Step 5 (Linear Effects) of the foundations plan. "
"If SX's own type system can enforce linearity at spec-validation time, "
"OCaml's runtime enforcement is sufficient. If not, Koka becomes the better substrate."))
;; -----------------------------------------------------------------------
;; The path to self-hosting
;; -----------------------------------------------------------------------
(~docs/section :title "The Path to Self-Hosting" :id "self-hosting"
(p "The end state: SX compiles itself. No intermediate language, no general-purpose host.")
(h4 :class "font-semibold mt-4 mb-2" "Phase 1: OCaml bootstrapper")
(p "Write " (code "bootstrap_ml.py") " \u2014 reads " (code "cek.sx") " + " (code "frames.sx")
" + " (code "primitives.sx") " + " (code "eval.sx") ", emits OCaml source. "
"Same pattern as the existing Rust/Python/JS bootstrappers.")
(p "The OCaml output is a standalone module:")
(~docs/code :code (highlight "type value =\n | Nil | Bool of bool | Num of float | Str of string\n | Sym of string | Kw of string\n | List of value list | Dict of (value * value) list\n | Lambda of params * value list * env\n | Component of string * params * value list * env\n | Handle of int (* opaque FFI reference *)\n\ntype frame =\n | IfFrame of value list * value list * env\n | ArgFrame of value list * value list * env\n | MapFrame of value * value list * value list * env\n | ReactiveResetFrame of value\n | DerefFrame of value\n (* ... 20+ frame types from frames.sx *)\n\ntype kont = frame list\ntype state = value * env * kont\n\nlet step ((ctrl, env, kont) : state) : state =\n match ctrl with\n | Lit v -> continue_val v kont\n | Var name -> continue_val (Env.find name env) kont\n | App (f, args) -> (f, env, ArgFrame(args, [], env) :: kont)\n | ..." "ocaml"))
(h4 :class "font-semibold mt-6 mb-2" "Phase 2: Native + WASM builds")
(p "Compile the OCaml output to:")
(ul :class "list-disc list-inside space-y-1 mt-2"
(li (code "sx_core.so") " / " (code "sx_core.dylib") " \u2014 native shared library, C ABI")
(li (code "sx_core.wasm") " \u2014 via " (code "wasm_of_ocaml") " for browser")
(li (code "sx_core.js") " \u2014 via " (code "js_of_ocaml") " as JS fallback"))
(p "Python web framework calls " (code "sx_core.so") " via cffi. "
"Browser loads " (code "sx_core.wasm") " via " (code "sx-platform.js") ".")
(h4 :class "font-semibold mt-6 mb-2" "Phase 3: SX evaluates web framework")
(p "The compiled core evaluator loads web framework " (code ".sx") " at runtime "
"(signals, engine, orchestration, boot). Same as the "
(a :href "/sx/(etc.(plan.isolated-evaluator))" "Isolated Evaluator") " plan, "
"but the evaluator is compiled OCaml/WASM instead of bootstrapped JS.")
(h4 :class "font-semibold mt-6 mb-2" "Phase 4: SX linearity checking")
(p "Extend " (code "types.sx") " with quantity annotations:")
(~docs/code :code (highlight ";; Quantity annotations on types\n(define-type (Signal a) :quantity :affine) ;; use at most once per scope\n(define-type (Channel a) :quantity :linear) ;; must be consumed exactly once\n\n;; Effect declarations with linearity\n(define-io-primitive \"send-message\"\n :params (channel message)\n :quantity :linear\n :effects [io]\n :doc \"Must be handled exactly once.\")\n\n;; The type checker (specced in .sx, compiled to OCaml) validates\n;; linearity at component registration time. Runtime enforcement\n;; by OCaml's one-shot continuations is the safety net." "lisp"))
(p "The type checker runs at spec-validation time. The compiled evaluator "
"executes already-verified code. SX's type system provides the linearity "
"guarantees, not the host language.")
(h4 :class "font-semibold mt-6 mb-2" "Phase 5: Self-hosting compiler")
(p "Write the compiler itself in SX:")
(ul :class "list-disc list-inside space-y-1 mt-2"
(li "Spec the CEK-to-native code generation in " (code ".sx") " files")
(li "The Phase 2 OCaml evaluator compiles the compiler spec")
(li "The compiled compiler can then compile itself")
(li "OCaml becomes the bootstrap language only \u2014 "
"needed once to get the self-hosting loop started, then never touched again"))
(~docs/code :code (highlight ";; The bootstrap chain\n\nStep 0: Python evaluator (existing)\n \u2193 evaluates bootstrap_ml.py\nStep 1: OCaml evaluator (compiled from spec by Python)\n \u2193 evaluates compiler.sx\nStep 2: SX compiler (compiled from .sx by OCaml evaluator)\n \u2193 compiles itself\nStep 3: SX compiler (compiled by itself)\n \u2193 compiles everything\n \u2193 emits native, WASM, JS from .sx spec\n \u2193 OCaml is no longer in the chain" "text"))
(p "At Step 3, the only language is SX. The compiler reads " (code ".sx") " and emits machine code. "
"OCaml was the scaffolding. The scaffolding comes down."))
;; -----------------------------------------------------------------------
;; Concurrent CEK on OCaml 5
;; -----------------------------------------------------------------------
(~docs/section :title "Concurrent CEK on OCaml 5" :id "concurrent-cek"
(p "OCaml 5's concurrency model maps directly onto the "
(a :href "/sx/(etc.(plan.foundations))" "Foundations") " plan's concurrent CEK spec.")
(h4 :class "font-semibold mt-4 mb-2" "Mapping")
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
(table :class "w-full text-left text-sm"
(thead (tr :class "border-b border-stone-200 bg-stone-100"
(th :class "px-3 py-2 font-medium text-stone-600" "SX primitive")
(th :class "px-3 py-2 font-medium text-stone-600" "OCaml 5")
(th :class "px-3 py-2 font-medium text-stone-600" "Characteristic")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" (code "spawn"))
(td :class "px-3 py-2 text-stone-700" "Fiber via " (code "perform Spawn"))
(td :class "px-3 py-2 text-stone-600" "Lightweight, scheduled by effect handler"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" (code "channel"))
(td :class "px-3 py-2 text-stone-700" (code "Eio.Stream"))
(td :class "px-3 py-2 text-stone-600" "Typed, bounded, backpressure"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" (code "yield!"))
(td :class "px-3 py-2 text-stone-700" (code "perform Yield"))
(td :class "px-3 py-2 text-stone-600" "Cooperative, zero-cost"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" (code "select"))
(td :class "px-3 py-2 text-stone-700" (code "Eio.Fiber.any"))
(td :class "px-3 py-2 text-stone-600" "First-to-complete"))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" (code "fork-join"))
(td :class "px-3 py-2 text-stone-700" (code "Eio.Fiber.all"))
(td :class "px-3 py-2 text-stone-600" "Structured concurrency"))
(tr
(td :class "px-3 py-2 text-stone-700" "DAG scheduler")
(td :class "px-3 py-2 text-stone-700" "Domains + fiber pool")
(td :class "px-3 py-2 text-stone-600" "True parallelism across cores")))))
(p "Each concurrent CEK machine is a fiber. The scheduler is an effect handler. "
"This isn't simulating concurrency \u2014 it's using native concurrency whose mechanism " (em "is") " effects.")
(h4 :class "font-semibold mt-4 mb-2" "The Art DAG connection")
(p "Art DAG's 3-phase execution (analyze \u2192 plan \u2192 execute) maps onto "
"concurrent CEK + OCaml domains:")
(ul :class "list-disc list-inside space-y-1 mt-2"
(li "Analyze: single CEK machine walks the DAG graph (one fiber)")
(li "Plan: resolve dependencies, topological sort (pure computation)")
(li "Execute: spawn one fiber per independent node, fan out to domains (true parallelism)")
(li "GPU kernels: fiber performs " (code "Gpu_dispatch") " effect, handler calls into GPU via C FFI")))
;; -----------------------------------------------------------------------
;; Linear effects
;; -----------------------------------------------------------------------
(~docs/section :title "Linear Effects" :id "linear-effects"
(p "The linearity axis from foundations. Two enforcement layers:")
(h4 :class "font-semibold mt-4 mb-2" "Layer 1: SX type system (primary)")
(p "Quantity annotations in " (code "types.sx") " checked at spec-validation time:")
(ul :class "list-disc list-inside space-y-1 mt-2"
(li (code ":linear") " (1) \u2014 must be used exactly once")
(li (code ":affine") " (\u22641) \u2014 may be used at most once (can drop)")
(li (code ":unrestricted") " (\u03c9) \u2014 may be used any number of times"))
(p "Linear effects guarantee: a " (code "send-message") " effect is handled exactly once. "
"A channel is consumed. A resource handle is closed. "
"The type checker proves this before the evaluator ever runs.")
(h4 :class "font-semibold mt-4 mb-2" "Layer 2: Host runtime (safety net)")
(p "OCaml 5's one-shot continuations enforce linearity at runtime. "
"A continuation can only be " (code "continue") "'d once \u2014 second invocation raises an exception. "
"This catches any bugs in the type checker itself.")
(p "If Koka replaces OCaml: compile-time enforcement replaces runtime enforcement. "
"The safety net becomes a proof. Same semantics, stronger guarantees.")
(h4 :class "font-semibold mt-4 mb-2" "Decision point")
(p "When Step 5 (Linear Effects) of the foundations plan is reached:")
(ul :class "list-disc list-inside space-y-2 mt-2"
(li "If SX's type checker can enforce linearity reliably \u2192 "
(strong "stay on OCaml") ". Runtime one-shot is sufficient.")
(li "If linearity bugs keep slipping through \u2192 "
(strong "switch to Koka") ". Compile-time enforcement closes the gap.")
(li "If the self-hosting compiler (Phase 5) reaches maturity \u2192 "
(strong "it doesn't matter") ". SX compiles itself, the substrate is an implementation detail.")))
;; -----------------------------------------------------------------------
;; Compiled all the way down
;; -----------------------------------------------------------------------
(~docs/section :title "Compiled All the Way Down" :id "compiled"
(p "The self-hosting compiler doesn't just compile the evaluator. "
"It compiles " (em "everything") ". Component definitions, page layouts, "
"event handlers, signal computations \u2014 all compiled to native machine code. "
"SX is not an interpreted scripting language with a nice spec. "
"It's a compiled language whose compiler also runs in the browser.")
(h4 :class "font-semibold mt-4 mb-2" "JIT in the browser")
(p "The server sends SX (component definitions, page content). "
"The client receives it and " (strong "compiles to WASM and executes") ". "
"Not interprets. Not dispatches bytecodes. Compiles.")
(~docs/code :code (highlight "Server sends: (defcomp ~card (&key title) (div :class \"card\" (h2 title)))\n\nClient does:\n 1. Parse SX source (fast \u2014 it's s-expressions)\n 2. Hash AST \u2192 CID\n 3. Cache hit? Call the already-compiled WASM function\n 4. Cache miss? Compile to WASM, cache by CID, call it\n\nStep 4 is the JIT. The compiler (itself WASM) emits a WASM\nfunction, instantiates it via WebAssembly.instantiate, caches\nthe module by CID. Next time: direct function call, zero overhead." "text"))
(p "This is what V8 does with JavaScript. What LuaJIT does with Lua. "
"The difference: SX's semantics are simpler (no prototype chains, no " (code "this")
" binding, no implicit coercion), so the compiler is simpler. "
"And content-addressing means compiled artifacts are cacheable by CID \u2014 "
"compile once, store forever.")
(h4 :class "font-semibold mt-4 mb-2" "The compilation tiers")
(~docs/code :code (highlight "Tier 0: .sx source \u2192 tree-walking CEK (correct, slow \u2014 current)\nTier 1: .sx source \u2192 bytecodes \u2192 dispatch loop (correct, fast)\nTier 2: .sx source \u2192 WASM functions \u2192 execute (correct, fastest)\nTier 3: .sx source \u2192 native machine code (ahead-of-time, maximum)" "text"))
(p "Each tier is faster. Tier 1 (bytecodes) is the "
(a :href "/sx/(etc.(plan.wasm-bytecode-vm))" "WASM Bytecode VM")
" plan \u2014 compact wire format, no parse overhead, better cache locality. "
"Tier 2 is JIT \u2014 the compiler emitting WASM functions on the fly. "
"Tier 3 is AOT \u2014 the entire app precompiled. "
"All tiers use the same spec, same platform layer, same platform primitives.")
(h4 :class "font-semibold mt-4 mb-2" "Server-side precompilation")
(p "The server can compile too. Instead of sending SX source for the client to JIT, "
"send precompiled WASM:")
(~docs/code :code (highlight ";; Option A: send SX source, client JIT compiles\nContent-Type: text/sx\n\n(div :class \"card\" (h2 \"hello\"))\n\n;; Option B: send precompiled WASM, client instantiates directly\nContent-Type: application/wasm\nX-Sx-Cid: bafyrei...\n\n<binary WASM module>" "text"))
(p "Option B skips parsing and compilation entirely. The client instantiates "
"the WASM module and calls it. The server did all the work.")
(h4 :class "font-semibold mt-4 mb-2" "Content-addressed compilation cache")
(p "Every " (code ".sx") " expression has a CID. Every compiled artifact has a CID. "
"The mapping is deterministic \u2014 the compiler is a pure function:")
(~docs/code :code (highlight "source CID \u2192 compiled WASM CID\nbafyrei... \u2192 bafyrei...\n\nThis mapping is cacheable everywhere:\n\u2022 Browser cache \u2014 first visitor compiles, second visitor gets cached WASM\n\u2022 CDN \u2014 compiled artifacts served at the edge\n\u2022 IPFS \u2014 content-addressed by definition, globally deduplicated\n\u2022 Local disk \u2014 offline apps work from cached compiled components" "text"))
(h4 :class "font-semibold mt-4 mb-2" "Entire apps as machine code")
(p "The entire application can be ahead-of-time compiled to a WASM binary. "
"Component definitions, page layouts, event handlers, signal computations \u2014 "
"all compiled to native WASM functions. The \"app\" is a " (code ".wasm") " file. "
"The platform layer provides DOM and fetch. Everything in between is compiled machine code.")
(p "The only thing that stays JIT-compiled is truly dynamic content \u2014 "
"user-generated SX, REPL input, " (code "eval") "'d strings. "
"And even those get JIT'd on first use and cached by CID.")
(h4 :class "font-semibold mt-4 mb-2" "The architecture")
(~docs/code :code (highlight "sx-platform.js \u2190 DOM, fetch, timers (the real world)\n \u2191 calls\nsx-compiler.wasm \u2190 the SX compiler (itself compiled to WASM)\n \u2191 compiles\n.sx source \u2190 received from server / cache / inline\n \u2193 emits\nnative WASM functions \u2190 cached by CID, instantiated on demand\n \u2193 executes\nactual DOM mutations via platform primitives" "text"))
(p "The compiler is WASM. The code it produces is WASM. "
"It's compiled code all the way down. "
"The only interpreter in the system is the CPU."))
;; -----------------------------------------------------------------------
;; Security model
;; -----------------------------------------------------------------------
(~docs/section :title "Security Model" :id "security"
(p "Compiled SX running as WASM is " (em "more secure") " than plain JavaScript, "
"not less. JS has ambient access to the full browser API. "
"WASM + the platform layer means compiled SX code has "
(strong "zero ambient capabilities") " \u2014 every capability is explicitly granted.")
(h4 :class "font-semibold mt-4 mb-2" "Five defence layers")
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
(table :class "w-full text-left text-sm"
(thead (tr :class "border-b border-stone-200 bg-stone-100"
(th :class "px-3 py-2 font-medium text-stone-600" "Layer")
(th :class "px-3 py-2 font-medium text-stone-600" "Enforced by")
(th :class "px-3 py-2 font-medium text-stone-600" "What it prevents")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "1. WASM sandbox")
(td :class "px-3 py-2 text-stone-700" "Browser")
(td :class "px-3 py-2 text-stone-600" "Memory isolation, no system calls, no DOM access except via explicit imports. Validated before execution."))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "2. Platform capabilities")
(td :class "px-3 py-2 text-stone-700" (code "sx-platform.js"))
(td :class "px-3 py-2 text-stone-600" "Compiled code can only call functions you register. No fetch? Can't fetch. No localStorage? Can't read storage. The platform is a capability system."))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "3. Content-addressed verification")
(td :class "px-3 py-2 text-stone-700" "CID determinism")
(td :class "px-3 py-2 text-stone-600" "Compiler is deterministic: same source \u2192 same CID. Client can re-compile and verify. Tampered WASM produces wrong CID \u2192 reject."))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "4. Per-component attenuation")
(td :class "px-3 py-2 text-stone-700" "Platform scoping")
(td :class "px-3 py-2 text-stone-600" "Different components get different capability subsets. User-generated content gets a locked-down platform \u2014 can render DOM but can't fetch or listen to events."))
(tr
(td :class "px-3 py-2 text-stone-700" "5. Source-first fallback")
(td :class "px-3 py-2 text-stone-700" "Client compiler")
(td :class "px-3 py-2 text-stone-600" "Don't trust precompiled WASM? Compile from source locally. The client has the compiler. Precompilation is an optimisation, not a trust requirement.")))))
(h4 :class "font-semibold mt-4 mb-2" "Content-addressed tamper detection")
(p "The server sends both SX source and precompiled WASM CID. The client can verify:")
(~docs/code :code (highlight ";; Server sends:\nContent-Type: application/wasm\nX-Sx-Source-Cid: bafyrei..source\nX-Sx-Compiled-Cid: bafyrei..compiled\n\n;; Client verifies (optional, configurable):\n1. Hash the WASM binary \u2192 matches X-Sx-Compiled-Cid?\n2. Compile source locally \u2192 produces same compiled CID?\n3. Check manifest of pinned CIDs \u2192 CID is expected?\n\n;; Any mismatch = tampered = reject" "text"))
(h4 :class "font-semibold mt-4 mb-2" "Capability attenuation per component")
(p "The platform scopes capabilities per evaluator instance. "
"App shell gets full access. Third-party or user-generated content gets the minimum:")
(~docs/code :code (highlight "// Full capabilities for the app shell\nplatform.registerAll(appShellCompiler);\n\n// Restricted for user-generated content\nplatform.registerSubset(userContentCompiler, {\n allow: [\"dom-create-element\", \"dom-set-attr\", \"dom-append\",\n \"dom-create-text-node\", \"dom-set-text\"],\n deny: [\"fetch\", \"localStorage\", \"dom-listen\",\n \"dom-set-inner-html\", \"eval\"]\n});\n\n// The restricted compiler's WASM module literally doesn't\n// have imports for the denied functions. Not just blocked\n// at runtime \u2014 absent from the binary." "javascript"))
(h4 :class "font-semibold mt-4 mb-2" "Component manifests")
(p "The app ships with a manifest of expected CIDs for its core components. "
"Like subresource integrity (SRI) but for compiled code:")
(~docs/code :code (highlight ";; Component manifest (shipped with the app, signed)\n{\n \"~card\": \"bafyrei..abc\"\n \"~header\": \"bafyrei..def\"\n \"~nav-item\": \"bafyrei..ghi\"\n}\n\n;; On navigation: server sends component update\n;; Client compiles \u2192 checks CID against manifest\n;; Match = trusted, execute\n;; Mismatch = tampered, reject and report" "text"))
(p "The security model is " (em "structural") ", not bolt-on. "
"WASM isolation, platform capabilities, content-addressed verification, "
"and per-component attenuation all arise naturally from the architecture. "
"The platform layer that enables Rust/OCaml interop is the same layer "
"that enforces security boundaries."))
;; -----------------------------------------------------------------------
;; Isomorphic rendering + SEO
;; -----------------------------------------------------------------------
(~docs/section :title "Isomorphic Rendering" :id "isomorphic"
(p "WASM is invisible to search engines. But SX is already isomorphic \u2014 "
"the same spec, the same components, rendered to HTML on the server "
"and to DOM on the client. Compiled WASM doesn't change this. "
"It makes the client side faster without affecting what crawlers see.")
(h4 :class "font-semibold mt-4 mb-2" "The rendering pipeline")
(~docs/code :code (highlight "Crawler visits:\n GET /page\n \u2192 Server compiles SX (native OCaml)\n \u2192 render-to-html (adapter-html.sx)\n \u2192 Full static HTML with semantic markup\n \u2192 Google indexes it\n\nUser first visit:\n GET /page\n \u2192 Server renders HTML (same as crawler)\n \u2192 Browser displays immediately (no JS needed)\n \u2192 Client loads sx-compiler.wasm + sx-platform.js\n \u2192 Hydrates: attaches event handlers, activates islands\n \u2192 Page is interactive\n\nUser navigates (SPA):\n sx-get /next-page\n \u2192 Server sends SX wire format (aser)\n \u2192 Client compiles + renders via WASM\n \u2192 Morph engine patches the DOM" "text"))
(p "The server and client have the " (em "same compiler") " from the " (em "same spec") ". "
(code "adapter-html.sx") " produces HTML strings. "
(code "adapter-dom.sx") " produces DOM nodes. "
"Two rendering modes of one evaluator. The compiled WASM version "
"makes hydration and SPA navigation faster, but the initial HTML "
"is always server-rendered.")
(h4 :class "font-semibold mt-4 mb-2" "What crawlers see")
(ul :class "list-disc list-inside space-y-1 mt-2"
(li "Fully rendered HTML \u2014 no \"loading...\" skeleton, no JS-dependent content")
(li "Semantic markup \u2014 " (code "<h1>") ", " (code "<nav>") ", " (code "<article>") ", " (code "<a href>")
" \u2014 all from the SX component tree")
(li "Meta tags, canonical URLs, structured data \u2014 rendered server-side by " (code "shell.sx"))
(li "No WASM, no JS required \u2014 the HTML is the page, complete on first byte"))
(h4 :class "font-semibold mt-4 mb-2" "Content-addressed prerendering")
(p "The server can prerender every page to static HTML, hash it, "
"and cache it at the CDN edge:")
(~docs/code :code (highlight "Page source CID \u2192 Rendered HTML CID\nbafyrei..source \u2192 bafyrei..html\n\n\u2022 Crawler hits CDN \u2192 instant HTML, no server round-trip\n\u2022 Page content changes \u2192 new source CID \u2192 new HTML CID \u2192 CDN invalidated\n\u2022 Same CID = same HTML forever \u2192 infinite cache, zero revalidation" "text"))
(p "This is the same content-addressed caching as compiled WASM, "
"applied to the HTML output. Both the compiled client code and "
"the server-rendered HTML are cached by CID. "
"The entire delivery pipeline is content-addressed.")
(h4 :class "font-semibold mt-4 mb-2" "Progressive enhancement")
(p "The page works at every level:")
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
(table :class "w-full text-left text-sm"
(thead (tr :class "border-b border-stone-200 bg-stone-100"
(th :class "px-3 py-2 font-medium text-stone-600" "Client capability")
(th :class "px-3 py-2 font-medium text-stone-600" "Experience")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "No JS (crawler, reader mode)")
(td :class "px-3 py-2 text-stone-600" "Full HTML. Links work. Forms submit. Content is complete."))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700" "JS, no WASM")
(td :class "px-3 py-2 text-stone-600" "Falls back to js_of_ocaml evaluator or interpreted JS. SPA navigation, islands, signals all work."))
(tr
(td :class "px-3 py-2 text-stone-700" "JS + WASM")
(td :class "px-3 py-2 text-stone-600" "Full compiled pipeline. JIT compilation, cached WASM functions, near-native rendering speed."))))))
;; -----------------------------------------------------------------------
;; How this changes existing plans
;; -----------------------------------------------------------------------
(~docs/section :title "Impact on Existing Plans" :id "impact"
(div :class "overflow-x-auto rounded border border-stone-200 mb-4"
(table :class "w-full text-left text-sm"
(thead (tr :class "border-b border-stone-200 bg-stone-100"
(th :class "px-3 py-2 font-medium text-stone-600" "Plan")
(th :class "px-3 py-2 font-medium text-stone-600" "Impact")))
(tbody
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700"
(a :href "/sx/(etc.(plan.rust-wasm-host))" "Rust/WASM Host"))
(td :class "px-3 py-2 text-stone-600"
"Superseded. OCaml/WASM replaces Rust/WASM. The handle table and platform layer design carry over."))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700"
(a :href "/sx/(etc.(plan.isolated-evaluator))" "Isolated Evaluator"))
(td :class "px-3 py-2 text-stone-600"
"Architecture preserved. sx-platform.js and evaluator isolation apply to the OCaml evaluator too. The JS evaluator becomes a fallback."))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700"
(a :href "/sx/(etc.(plan.foundations))" "Foundations"))
(td :class "px-3 py-2 text-stone-600"
"Accelerated. OCaml 5 has native effects/continuations/fibers. Steps 4 (Concurrent CEK) and 5 (Linear Effects) map directly."))
(tr :class "border-b border-stone-100"
(td :class "px-3 py-2 text-stone-700"
(a :href "/sx/(etc.(plan.wasm-bytecode-vm))" "WASM Bytecode VM"))
(td :class "px-3 py-2 text-stone-600"
"Subsumed as Tier 1. Bytecodes become an intermediate step on the path to native WASM compilation. The dispatch loop is Tier 1; JIT to WASM functions is Tier 2; AOT is Tier 3."))
(tr
(td :class "px-3 py-2 text-stone-700"
(a :href "/sx/(etc.(plan.self-hosting-bootstrapper))" "Self-Hosting Bootstrapper"))
(td :class "px-3 py-2 text-stone-600"
"Converges. js.sx and py.sx are self-hosting emitters. The self-hosting SX compiler (Phase 5 here) is the logical endpoint."))))))
;; -----------------------------------------------------------------------
;; Principles
;; -----------------------------------------------------------------------
(~docs/section :title "Principles" :id "principles"
(ul :class "list-disc list-inside space-y-2"
(li (strong "SX is the language.") " Not OCaml, not Rust, not Haskell. "
"Those are substrates. SX defines the semantics, SX checks the types, "
"SX will ultimately compile itself.")
(li (strong "OCaml is scaffolding.") " The closest existing language to CEK. "
"Used to bootstrap the self-hosting compiler. Comes down when the compiler is mature.")
(li (strong "The spec is the compiler's input.") " " (code "cek.sx") ", " (code "frames.sx")
", " (code "primitives.sx") " \u2014 the same files that define the language "
"become the compiler's source. One truth, one artifact.")
(li (strong "Platforms provide effects, not evaluation.") " Python provides database/HTTP. "
"JavaScript provides DOM. GPU provides tensor ops. "
"The evaluator is always compiled SX, embedded via FFI or WASM.")
(li (strong "Linearity belongs in the spec.") " SX's type system checks it. "
"The host provides runtime backup. If the type system is sound, the runtime check never fires.")
(li (strong "One language, every target.") " Not \"one spec, multiple implementations.\" "
"One " (em "compiled") " evaluator, deployed as native/.wasm/.js depending on context. "
"The evaluator binary is the same code everywhere. Only the platform layer changes.")))
;; -----------------------------------------------------------------------
;; Outcome
;; -----------------------------------------------------------------------
(~docs/section :title "Outcome" :id "outcome"
(p "After completion:")
(ul :class "list-disc list-inside space-y-2 mt-2"
(li "SX core compiled from spec via OCaml \u2014 native + WASM from one codebase.")
(li "Python web framework calls native " (code "sx_core.so") " for rendering \u2014 "
"100\u20131000x faster than interpreted Python.")
(li "Browser loads " (code "sx_core.wasm") " \u2014 same compiler, same spec, near-native speed.")
(li "Server sends SX, client JIT-compiles to WASM functions and caches by CID.")
(li "Entire apps AOT-compiled to " (code ".wasm") " binaries. "
"Platform provides DOM/fetch. Everything else is machine code.")
(li "Content-addressed compilation cache: compile once, cache by CID, serve from CDN/IPFS forever.")
(li "Concurrent CEK runs on OCaml 5 fibers/domains \u2014 true parallelism for Art DAG.")
(li "Linear effects validated by SX type system, enforced by OCaml one-shot continuations.")
(li "Self-hosting compiler: SX compiles itself to machine code. OCaml scaffolding removed.")
(li "The only interpreter in the system is the CPU."))
(p :class "text-stone-500 text-sm italic mt-12"
"The Mother Language was always SX. We just needed to find the right scaffolding to stand it up."))))

View File

@@ -581,6 +581,8 @@
"cek-reactive" (~plans/cek-reactive/plan-cek-reactive-content)
"reactive-runtime" (~plans/reactive-runtime/plan-reactive-runtime-content)
"rust-wasm-host" (~plans/rust-wasm-host/plan-rust-wasm-host-content)
"isolated-evaluator" (~plans/isolated-evaluator/plan-isolated-evaluator-content)
"mother-language" (~plans/mother-language/plan-mother-language-content)
:else (~plans/index/plans-index-content))))
;; ---------------------------------------------------------------------------