HS parser: fix number+comparison keyword collision, eval-hs uses hs-compile

Parser: skip unit suffix when next ident is a comparison keyword
(starts, ends, contains, matches, is, does, in, precedes, follows).
Fixes "123 starts with '12'" returning "123starts" instead of true.

eval-hs: use hs-compile directly instead of hs-to-sx-from-source with
"return " prefix, which was causing the parser to consume the comparison
as a string suffix.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-15 11:29:01 +00:00
parent a93e5924df
commit 4f02f82f4e
377 changed files with 9517 additions and 8694 deletions

View File

@@ -0,0 +1,79 @@
(defcomp
()
(~docs/page
:title "Bootstrappers"
(div
(~tw :tokens "space-y-6")
(p
(~tw :tokens "text-lg text-stone-600")
"A bootstrapper reads the canonical "
(code (~tw :tokens "text-violet-700 text-sm") ".sx")
" specification files and emits a native implementation for a specific target. "
"The spec files are the single source of truth — bootstrappers are the bridge from specification to runnable code.")
(p
(~tw :tokens "text-stone-600")
"Each bootstrapper is a compiler that understands the restricted SX subset used in the spec files "
"(roughly 20 special forms and 80 primitives) and translates it into idiomatic target code. "
"Platform-specific operations (DOM access, HTTP, timers) are emitted as native implementations "
"rather than translated from SX.")
(div
(~tw :tokens "overflow-x-auto rounded border border-stone-200")
(table
(~tw :tokens "w-full text-left text-sm")
(thead
(tr
(~tw :tokens "border-b border-stone-200 bg-stone-100")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Target")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Bootstrapper")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Output")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Status")))
(tbody
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") "JavaScript")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(bootstrapper.javascript))"
(~tw :tokens "hover:underline")
"bootstrap_js.py"))
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-stone-500")
"sx-browser.js")
(td (~tw :tokens "px-3 py-2 text-green-600") "Live"))
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Python")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(bootstrapper.python))"
(~tw :tokens "hover:underline")
"bootstrap_py.py"))
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-stone-500")
"sx_ref.py")
(td (~tw :tokens "px-3 py-2 text-green-600") "Live"))
(tr
(~tw :tokens "border-b border-stone-100 bg-green-50")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Python (self-hosting)")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(bootstrapper.self-hosting))"
(~tw :tokens "hover:underline")
"py.sx"))
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-stone-500")
"sx_ref.py")
(td (~tw :tokens "px-3 py-2 text-green-600") "G0 == G1"))
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Rust")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-stone-400")
"bootstrap_rs.py")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-stone-400")
"sx-native")
(td (~tw :tokens "px-3 py-2 text-stone-400") "Planned"))))))))

View File

@@ -0,0 +1,53 @@
(defcomp
(&key bootstrapper-source bootstrapped-output)
(~docs/page
:title "JavaScript Bootstrapper"
(div
(~tw :tokens "space-y-8")
(div
(~tw :tokens "space-y-3")
(p
(~tw :tokens "text-stone-600")
"This page reads the canonical "
(code (~tw :tokens "text-violet-700 text-sm") ".sx")
" spec files, runs the Python bootstrapper, and displays both the compiler source and its generated JavaScript output. "
"The generated code below is live — it was produced by the bootstrapper at page load time, not served from a static file.")
(p
(~tw :tokens "text-xs text-stone-400 italic")
"The sx-browser.js powering this page IS the bootstrapped output. "
"This page re-runs the bootstrapper to display the source and result."))
(div
(~tw :tokens "space-y-3")
(div
(~tw :tokens "flex items-baseline gap-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "Bootstrapper")
(span (~tw :tokens "text-sm text-stone-400 font-mono") "bootstrap_js.py"))
(p
(~tw :tokens "text-sm text-stone-500")
"The compiler reads "
(code (~tw :tokens "text-violet-700 text-sm") ".sx")
" spec files (parser, eval, primitives, render, adapters, engine, orchestration, boot, cssx) "
"and emits a standalone JavaScript file. Platform bridge functions (DOM operations, fetch, timers) "
"are emitted as native JS implementations.")
(div
(~tw :tokens "not-prose bg-stone-100 rounded-lg p-5 max-h-96 overflow-y-auto border border-stone-200")
(pre
(~tw :tokens "text-xs leading-relaxed whitespace-pre-wrap break-words")
(code (highlight bootstrapper-source "python")))))
(div
(~tw :tokens "space-y-3")
(div
(~tw :tokens "flex items-baseline gap-3")
(h2
(~tw :tokens "text-2xl font-semibold text-stone-800")
"Generated Output")
(span (~tw :tokens "text-sm text-stone-400 font-mono") "sx-browser.js"))
(p
(~tw :tokens "text-sm text-stone-500")
"The JavaScript below was generated by running the bootstrapper against the current spec files. "
"It is a complete, self-contained SX runtime — parser, evaluator, DOM adapter, engine, and CSS system.")
(div
(~tw :tokens "not-prose bg-stone-100 rounded-lg p-5 max-h-96 overflow-y-auto border border-violet-300")
(pre
(~tw :tokens "text-xs leading-relaxed whitespace-pre-wrap break-words")
(code (highlight bootstrapped-output "javascript"))))))))

View File

@@ -0,0 +1,134 @@
;; ---------------------------------------------------------------------------
;; Client-side island — runs spec functions in the browser on button click
;; ---------------------------------------------------------------------------
(defisland (&key sf-source attr-detail req-attrs attr-keys)
(let ((results (signal nil))
(running (signal false))
(run-demo (fn (e)
(reset! running true)
(let* ((t0 (now-ms))
;; 1. categorize-special-forms
(t1 (now-ms))
(sf-exprs (sx-parse sf-source))
(sf-result (categorize-special-forms sf-exprs))
(sf-ms (- (now-ms) t1))
(sf-cats {})
(sf-total 0)
;; 2. build-reference-data
(t2 (now-ms))
(ref-result (build-reference-data "attributes"
{"req-attrs" req-attrs "beh-attrs" (list) "uniq-attrs" (list)}
attr-keys))
(ref-ms (- (now-ms) t2))
(ref-sample (slice (or (get ref-result "req-attrs") (list)) 0 3))
;; 3. build-attr-detail
(t3 (now-ms))
(attr-result (build-attr-detail "sx-get" attr-detail))
(attr-ms (- (now-ms) t3))
;; 4. build-component-source
(t4 (now-ms))
(comp-result (build-component-source
{"type" "component" "name" "~demo-card"
"params" (list "title" "subtitle")
"has-children" true
"body-sx" "(div :class \"card\"\n (h2 title)\n (when subtitle (p subtitle))\n children)"
"affinity" "auto"}))
(comp-ms (- (now-ms) t4))
;; 5. build-routing-analysis
(t5 (now-ms))
(routing-result (build-routing-analysis (list
{"name" "home" "path" "/" "has-data" false "content-src" "(~home-content)"}
{"name" "dashboard" "path" "/dash" "has-data" true "content-src" "(~dashboard)"}
{"name" "about" "path" "/about" "has-data" false "content-src" "(~about-content)"}
{"name" "settings" "path" "/settings" "has-data" true "content-src" "(~settings)"})))
(routing-ms (- (now-ms) t5))
(total-ms (- (now-ms) t0)))
;; Post-process sf-result: count forms per category
(for-each (fn (k)
(dict-set! sf-cats k (len (get sf-result k))))
(keys sf-result))
(reset! results
{"sf-cats" sf-cats
"sf-total" (reduce (fn (acc k) (+ acc (get sf-cats k))) 0 (keys sf-cats))
"sf-ms" sf-ms
"ref-sample" ref-sample "ref-ms" ref-ms
"attr-result" attr-result "attr-ms" attr-ms
"comp-result" comp-result "comp-ms" comp-ms
"routing-result" routing-result "routing-ms" routing-ms
"total-ms" total-ms})))))
(<>
(button
:class (if (deref running)
"px-4 py-2 rounded-md bg-blue-600 text-white font-medium text-sm cursor-default mb-4"
"px-4 py-2 rounded-md bg-violet-600 text-white font-medium text-sm hover:bg-violet-700 transition-colors mb-4")
:on-click run-demo
(if (deref running)
(str "Done (" (get (deref results) "total-ms") "ms total)")
"Run in Browser"))
(when (deref results)
(let ((r (deref results)))
(div (~tw :tokens "grid grid-cols-1 md:grid-cols-2 gap-4")
(~page-helpers-demo/demo-result-card
:title "categorize-special-forms"
:ms (get r "sf-ms") :theme "blue"
:desc "Parses special-forms.sx and classifies each form by category (control flow, binding, quoting, etc)."
(p (~tw :tokens "text-sm mb-1")
(str (get r "sf-total") " forms in "
(len (keys (get r "sf-cats"))) " categories"))
(map (fn (k)
(div (str k ": " (get (get r "sf-cats") k))))
(keys (get r "sf-cats"))))
(~page-helpers-demo/demo-result-card
:title "build-reference-data"
:ms (get r "ref-ms") :theme "blue"
:desc "Takes raw attribute tuples and generates reference table rows with documentation hrefs."
(p (~tw :tokens "text-sm mb-1")
(str (len (get r "ref-sample")) " attributes with detail page links"))
(map (fn (item)
(div (str (get item "name") " → "
(or (get item "href") "no detail page"))))
(get r "ref-sample")))
(~page-helpers-demo/demo-result-card
:title "build-attr-detail"
:ms (get r "attr-ms") :theme "blue"
:desc "Builds a detail page data structure for a single attribute (sx-get): title, wire ID, handler status."
(div (str "title: " (get (get r "attr-result") "attr-title")))
(div (str "wire-id: " (or (get (get r "attr-result") "attr-wire-id") "none")))
(div (str "has handler: " (if (get (get r "attr-result") "attr-handler") "yes" "no"))))
(~page-helpers-demo/demo-result-card
:title "build-component-source"
:ms (get r "comp-ms") :theme "blue"
:desc "Reconstructs a defcomp source definition from a component metadata dict (name, params, body)."
(pre (~tw :tokens "bg-blue-50 p-2 rounded overflow-x-auto")
(get r "comp-result")))
(div (~tw :tokens "rounded-lg border border-blue-200 bg-blue-50/30 p-4 md:col-span-2")
(h4 (~tw :tokens "font-semibold text-blue-700 text-sm mb-1")
"build-routing-analysis "
(span (~tw :tokens "text-xs text-blue-400") (str (get r "routing-ms") "ms")))
(p (~tw :tokens "text-xs text-blue-500 mb-2")
"Classifies pages as client-routable or server-only based on whether they have data dependencies.")
(div (~tw :tokens "text-xs text-blue-600")
(p (~tw :tokens "text-sm mb-1")
(str (get (get r "routing-result") "total-pages") " pages: "
(get (get r "routing-result") "client-count") " client-routable, "
(get (get r "routing-result") "server-count") " server-only"))
(div (~tw :tokens "space-y-0.5")
(map (fn (pg)
(div (str (get pg "name") " → " (get pg "mode")
(when (not (empty? (get pg "reason")))
(str " (" (get pg "reason") ")")))))
(get (get r "routing-result") "pages")))))))))))

View File

@@ -0,0 +1,22 @@
;; page-helpers-demo.sx — Demo: same SX spec functions on server and client
;;
;; Shows page-helpers.sx functions running on Python (server-side, via sx_ref.py)
;; and JavaScript (client-side, via sx-browser.js) with identical results.
;; Server renders with render-to-html. Client runs as a defisland — pure SX,
;; no JavaScript file. The button click triggers spec functions via signals.
;; ---------------------------------------------------------------------------
;; Shared card component — used by both server and client results
;; ---------------------------------------------------------------------------
(defcomp (&key (title :as string) (ms :as number) (desc :as string) (theme :as string) &rest children)
(let ((border (if (= theme "blue") "border-blue-200 bg-blue-50/30" "border-stone-200"))
(title-c (if (= theme "blue") "text-blue-700" "text-stone-700"))
(badge-c (if (= theme "blue") "text-blue-400" "text-stone-400"))
(desc-c (if (= theme "blue") "text-blue-500" "text-stone-500"))
(body-c (if (= theme "blue") "text-blue-600" "text-stone-600")))
(div :class (str "rounded-lg border p-4 " border)
(h4 :class (str "font-semibold text-sm mb-1 " title-c)
title " "
(span :class (str "text-xs " badge-c) (str ms "ms")))
(p :class (str "text-xs mb-2 " desc-c) desc)
(div :class (str "text-xs space-y-0.5 " body-c)
children))))

View File

@@ -0,0 +1,100 @@
;; ---------------------------------------------------------------------------
;; Main page component — server-rendered content + client island
;; ---------------------------------------------------------------------------
(defcomp (&key
sf-categories sf-total sf-ms
ref-sample ref-ms
attr-result attr-ms
comp-source comp-ms
routing-result routing-ms
server-total-ms
sf-source
attr-detail req-attrs attr-keys)
(div (~tw :tokens "max-w-3xl mx-auto px-4")
(div (~tw :tokens "mb-8")
(h2 (~tw :tokens "text-2xl font-bold text-stone-800 mb-2") "Bootstrapped Page Helpers")
(p (~tw :tokens "text-stone-600 mb-4")
"These functions are defined once in "
(code (~tw :tokens "text-violet-700") "page-helpers.sx")
" and bootstrapped to both Python ("
(code (~tw :tokens "text-violet-700") "sx_ref.py")
") and JavaScript ("
(code (~tw :tokens "text-violet-700") "sx-browser.js")
"). The server ran them in Python during this page load. Click the button below to run the identical functions client-side in the browser — same spec, same inputs, same results."))
;; Server results
(div (~tw :tokens "mb-8")
(h3 (~tw :tokens "text-lg font-semibold text-stone-700 mb-3")
"Server Results "
(span (~tw :tokens "text-sm font-normal text-stone-500")
(str "(Python, " server-total-ms "ms total)")))
(div (~tw :tokens "grid grid-cols-1 md:grid-cols-2 gap-4")
(~page-helpers-demo/demo-result-card
:title "categorize-special-forms"
:ms sf-ms :theme "stone"
:desc "Parses special-forms.sx and classifies each form by category (control flow, binding, quoting, etc)."
(p (~tw :tokens "text-sm mb-1")
(str sf-total " forms in "
(len (keys sf-categories)) " categories"))
(map (fn (k)
(div (str k ": " (get sf-categories k))))
(keys sf-categories)))
(~page-helpers-demo/demo-result-card
:title "build-reference-data"
:ms ref-ms :theme "stone"
:desc "Takes raw attribute tuples and generates reference table rows with documentation hrefs."
(p (~tw :tokens "text-sm mb-1")
(str (len ref-sample) " attributes with detail page links"))
(map (fn (item)
(div (str (get item "name") " → "
(or (get item "href") "no detail page"))))
ref-sample))
(~page-helpers-demo/demo-result-card
:title "build-attr-detail"
:ms attr-ms :theme "stone"
:desc "Builds a detail page data structure for a single attribute (sx-get): title, wire ID, handler status."
(div (str "title: " (get attr-result "attr-title")))
(div (str "wire-id: " (or (get attr-result "attr-wire-id") "none")))
(div (str "has handler: " (if (get attr-result "attr-handler") "yes" "no"))))
(~page-helpers-demo/demo-result-card
:title "build-component-source"
:ms comp-ms :theme "stone"
:desc "Reconstructs a defcomp source definition from a component metadata dict (name, params, body)."
(pre (~tw :tokens "bg-stone-50 p-2 rounded overflow-x-auto")
comp-source))
(div (~tw :tokens "rounded-lg border border-stone-200 p-4 md:col-span-2")
(h4 (~tw :tokens "font-semibold text-stone-700 text-sm mb-1")
"build-routing-analysis "
(span (~tw :tokens "text-xs text-stone-400") (str routing-ms "ms")))
(p (~tw :tokens "text-xs text-stone-500 mb-2")
"Classifies pages as client-routable or server-only based on whether they have data dependencies.")
(div (~tw :tokens "text-xs text-stone-600")
(p (~tw :tokens "text-sm mb-1")
(str (get routing-result "total-pages") " pages: "
(get routing-result "client-count") " client-routable, "
(get routing-result "server-count") " server-only"))
(div (~tw :tokens "space-y-0.5")
(map (fn (pg)
(div (str (get pg "name") " → " (get pg "mode")
(when (not (empty? (get pg "reason")))
(str " (" (get pg "reason") ")")))))
(get routing-result "pages")))))))
;; Client execution area — pure SX island, no JavaScript file
(div (~tw :tokens "mb-8")
(h3 (~tw :tokens "text-lg font-semibold text-stone-700 mb-3")
"Client Results "
(span (~tw :tokens "text-sm font-normal text-stone-500") "(JavaScript, sx-browser.js)"))
(~page-helpers-demo/demo-client-runner
:sf-source sf-source
:attr-detail attr-detail
:req-attrs req-attrs
:attr-keys attr-keys))))

View File

@@ -0,0 +1,53 @@
(defcomp
(&key bootstrapper-source bootstrapped-output)
(~docs/page
:title "Python Bootstrapper"
(div
(~tw :tokens "space-y-8")
(div
(~tw :tokens "space-y-3")
(p
(~tw :tokens "text-stone-600")
"This page reads the canonical "
(code (~tw :tokens "text-violet-700 text-sm") ".sx")
" spec files, runs the Python bootstrapper, and displays both the compiler source and its generated Python output. "
"The generated code below is live — it was produced by the bootstrapper at page load time.")
(p
(~tw :tokens "text-xs text-stone-400 italic")
"With SX_USE_REF=1, the server-side SX evaluator running this page IS the bootstrapped output. "
"This page re-runs the bootstrapper to display the source and result."))
(div
(~tw :tokens "space-y-3")
(div
(~tw :tokens "flex items-baseline gap-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "Bootstrapper")
(span (~tw :tokens "text-sm text-stone-400 font-mono") "bootstrap_py.py"))
(p
(~tw :tokens "text-sm text-stone-500")
"The compiler reads "
(code (~tw :tokens "text-violet-700 text-sm") ".sx")
" spec files (eval, primitives, render, adapter-html) "
"and emits a standalone Python module. Platform bridge functions (type constructors, environment ops) "
"are emitted as native Python implementations.")
(div
(~tw :tokens "not-prose bg-stone-100 rounded-lg p-5 max-h-96 overflow-y-auto border border-stone-200")
(pre
(~tw :tokens "text-xs leading-relaxed whitespace-pre-wrap break-words")
(code (highlight bootstrapper-source "python")))))
(div
(~tw :tokens "space-y-3")
(div
(~tw :tokens "flex items-baseline gap-3")
(h2
(~tw :tokens "text-2xl font-semibold text-stone-800")
"Generated Output")
(span (~tw :tokens "text-sm text-stone-400 font-mono") "sx_ref.py"))
(p
(~tw :tokens "text-sm text-stone-500")
"The Python below was generated by running the bootstrapper against the current spec files. "
"It is a complete server-side SX evaluator — eval, primitives, and HTML renderer.")
(div
(~tw :tokens "not-prose bg-stone-100 rounded-lg p-5 max-h-96 overflow-y-auto border border-violet-300")
(pre
(~tw :tokens "text-xs leading-relaxed whitespace-pre-wrap break-words")
(code (highlight bootstrapped-output "python"))))))))

View File

@@ -0,0 +1,201 @@
(defcomp
(&key
js-sx-source
defines-matched
defines-total
js-sx-lines
verification-status)
(~docs/page
:title "Self-Hosting Bootstrapper (js.sx)"
(div
(~tw :tokens "space-y-8")
(div
(~tw :tokens "space-y-3")
(p
(~tw :tokens "text-stone-600")
(code (~tw :tokens "text-violet-700 text-sm") "js.sx")
" is an SX-to-JavaScript translator written in SX. "
"This page runs it live: loads js.sx into the evaluator, translates every spec file, "
"and verifies each define matches "
(code (~tw :tokens "text-violet-700 text-sm") "bootstrap_js.py")
"'s JSEmitter.")
(div
(~tw :tokens "rounded-lg p-4")
:class (if
(= verification-status "identical")
"bg-green-50 border border-green-200"
"bg-red-50 border border-red-200")
(div
(~tw :tokens "flex items-center gap-3")
(span
(~tw :tokens "inline-flex items-center rounded-full px-3 py-1 text-sm font-semibold")
:class (if
(= verification-status "identical")
"bg-green-100 text-green-800"
"bg-red-100 text-red-800")
(if (= verification-status "identical") "G0 == G1" "MISMATCH"))
(p
(~tw :tokens "text-sm")
:class (if
(= verification-status "identical")
"text-green-700"
"text-red-700")
defines-matched
"/"
defines-total
" defines match across all spec files. "
js-sx-lines
" lines of SX."))))
(div
(~tw :tokens "space-y-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "G0 Bug Discovery")
(div
(~tw :tokens "rounded-lg bg-amber-50 border border-amber-200 p-4")
(div
(~tw :tokens "flex items-start gap-3")
(span
(~tw :tokens "inline-flex items-center rounded-full bg-amber-100 px-3 py-1 text-sm font-semibold text-amber-800")
"Fixed")
(div
(~tw :tokens "text-sm text-amber-700 space-y-2")
(p
"Building js.sx revealed a bug in "
(code "bootstrap_js.py")
"'s "
(code "_emit_define")
" method. The Python code:")
(pre
(~tw :tokens "bg-amber-100 rounded p-2 text-xs font-mono")
"val = self.emit(fn_expr) if fn_expr else \"NIL\"")
(p
"Python's "
(code "if fn_expr")
" treats "
(code "0")
", "
(code "False")
", and "
(code "\"\"")
" as falsy. So "
(code "(define *batch-depth* 0)")
" emitted "
(code "var _batchDepth = NIL")
" instead of "
(code "var _batchDepth = 0")
". Similarly, "
(code "(define _css-hash \"\")")
" emitted "
(code "var _cssHash = NIL")
" instead of "
(code "var _cssHash = \"\"")
".")
(p
"Fix: "
(code "if fn_expr is not None")
" — explicit None check. "
"js.sx never had this bug because SX's "
(code "nil?")
" only matches "
(code "nil")
", not "
(code "0")
" or "
(code "false")
". "
"The self-hosting bootstrapper caught a host language bug.")))))
(div
(~tw :tokens "space-y-3")
(h2
(~tw :tokens "text-2xl font-semibold text-stone-800")
"Translation Differences from py.sx")
(p
(~tw :tokens "text-sm text-stone-500")
"Both py.sx and js.sx translate the same SX ASTs, but target languages differ:")
(div
(~tw :tokens "overflow-x-auto rounded border border-stone-200")
(table
(~tw :tokens "w-full text-sm")
(thead
(~tw :tokens "bg-stone-50")
(tr
(th
(~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700")
"Feature")
(th
(~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700")
"py.sx → Python")
(th
(~tw :tokens "px-4 py-2 text-left font-semibold text-stone-700")
"js.sx → JavaScript")))
(tbody
(tr
(~tw :tokens "border-t border-stone-100")
(td (~tw :tokens "px-4 py-2 text-stone-600") "Name mangling")
(td
(~tw :tokens "px-4 py-2 font-mono text-xs")
"eval-expr → eval_expr")
(td
(~tw :tokens "px-4 py-2 font-mono text-xs")
"eval-expr → evalExpr"))
(tr
(~tw :tokens "border-t border-stone-100")
(td (~tw :tokens "px-4 py-2 text-stone-600") "Declarations")
(td (~tw :tokens "px-4 py-2 font-mono text-xs") "name = value")
(td (~tw :tokens "px-4 py-2 font-mono text-xs") "var name = value;"))
(tr
(~tw :tokens "border-t border-stone-100")
(td (~tw :tokens "px-4 py-2 text-stone-600") "Functions")
(td (~tw :tokens "px-4 py-2 font-mono text-xs") "lambda x: body")
(td
(~tw :tokens "px-4 py-2 font-mono text-xs")
"function(x) { return body; }"))
(tr
(~tw :tokens "border-t border-stone-100")
(td (~tw :tokens "px-4 py-2 text-stone-600") "set! (mutation)")
(td
(~tw :tokens "px-4 py-2 font-mono text-xs")
"_cells dict (closure hack)")
(td
(~tw :tokens "px-4 py-2 font-mono text-xs")
"Direct assignment (JS captures by ref)"))
(tr
(~tw :tokens "border-t border-stone-100")
(td (~tw :tokens "px-4 py-2 text-stone-600") "Tail recursion")
(td (~tw :tokens "px-4 py-2 font-mono text-xs") "—")
(td
(~tw :tokens "px-4 py-2 font-mono text-xs")
"while(true) { continue; }"))
(tr
(~tw :tokens "border-t border-stone-100")
(td (~tw :tokens "px-4 py-2 text-stone-600") "let binding")
(td
(~tw :tokens "px-4 py-2 font-mono text-xs")
"(lambda x: body)(val)")
(td
(~tw :tokens "px-4 py-2 font-mono text-xs")
"(function() { var x = val; return body; })()"))
(tr
(~tw :tokens "border-t border-stone-100")
(td (~tw :tokens "px-4 py-2 text-stone-600") "and/or")
(td (~tw :tokens "px-4 py-2 font-mono text-xs") "ternary chains")
(td (~tw :tokens "px-4 py-2 font-mono text-xs") "&& / sxOr()"))))))
(div
(~tw :tokens "space-y-3")
(div
(~tw :tokens "flex items-baseline gap-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "js.sx Source")
(span
(~tw :tokens "text-sm text-stone-400 font-mono")
"shared/sx/ref/js.sx"))
(p
(~tw :tokens "text-sm text-stone-500")
"The SX-to-JavaScript translator — 61 "
(code "define")
" forms. "
"camelCase mangling (500+ RENAMES), expression/statement emission, "
"self-tail-recursive while loop optimization.")
(div
(~tw :tokens "not-prose bg-stone-100 rounded-lg p-5 max-h-96 overflow-y-auto border border-stone-200")
(pre
(~tw :tokens "text-xs leading-relaxed whitespace-pre-wrap break-words")
(code (highlight js-sx-source "lisp"))))))))

View File

@@ -0,0 +1,115 @@
(defcomp
(&key
(py-sx-source :as string)
(g0-output :as string)
(g1-output :as string)
(defines-matched :as number)
(defines-total :as number)
(g0-lines :as number)
(g0-bytes :as number)
(verification-status :as string))
(~docs/page
:title "Self-Hosting Bootstrapper (py.sx)"
(div
(~tw :tokens "space-y-8")
(div
(~tw :tokens "space-y-3")
(p
(~tw :tokens "text-stone-600")
(code (~tw :tokens "text-violet-700 text-sm") "py.sx")
" is an SX-to-Python translator written in SX. "
"This page runs it live: loads py.sx into the evaluator, translates each spec file, "
"and diffs the result against "
(code (~tw :tokens "text-violet-700 text-sm") "bootstrap_py.py")
".")
(div
(~tw :tokens "rounded-lg p-4")
:class (if
(= verification-status "identical")
"bg-green-50 border border-green-200"
"bg-red-50 border border-red-200")
(div
(~tw :tokens "flex items-center gap-3")
(span
(~tw :tokens "inline-flex items-center rounded-full px-3 py-1 text-sm font-semibold")
:class (if
(= verification-status "identical")
"bg-green-100 text-green-800"
"bg-red-100 text-red-800")
(if (= verification-status "identical") "G0 == G1" "MISMATCH"))
(p
(~tw :tokens "text-sm")
:class (if
(= verification-status "identical")
"text-green-700"
"text-red-700")
defines-matched
"/"
defines-total
" defines match. "
g0-lines
" lines, "
g0-bytes
" bytes."))))
(div
(~tw :tokens "space-y-3")
(div
(~tw :tokens "flex items-baseline gap-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "py.sx Source")
(span
(~tw :tokens "text-sm text-stone-400 font-mono")
"shared/sx/ref/py.sx"))
(p
(~tw :tokens "text-sm text-stone-500")
"The SX-to-Python translator — 55 "
(code "define")
" forms. "
"Name mangling (200+ RENAMES), expression emission, statement emission, "
"cell variable detection for "
(code "set!")
" across lambda boundaries.")
(div
(~tw :tokens "not-prose bg-stone-100 rounded-lg p-5 max-h-96 overflow-y-auto border border-stone-200")
(pre
(~tw :tokens "text-xs leading-relaxed whitespace-pre-wrap break-words")
(code (highlight py-sx-source "lisp")))))
(div
(~tw :tokens "space-y-3")
(div
(~tw :tokens "flex items-baseline gap-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "G0 Output")
(span
(~tw :tokens "text-sm text-stone-400 font-mono")
"bootstrap_py.py → sx_ref.py"))
(p
(~tw :tokens "text-sm text-stone-500")
"Generated by the hand-written Python bootstrapper.")
(div
(~tw :tokens "not-prose bg-stone-100 rounded-lg p-5 max-h-96 overflow-y-auto border border-stone-200")
(pre
(~tw :tokens "text-xs leading-relaxed whitespace-pre-wrap break-words")
(code (highlight g0-output "python")))))
(div
(~tw :tokens "space-y-3")
(div
(~tw :tokens "flex items-baseline gap-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "G1 Output")
(span
(~tw :tokens "text-sm text-stone-400 font-mono")
"py.sx → sx_ref.py"))
(p
(~tw :tokens "text-sm text-stone-500")
"Generated by py.sx running on the Python evaluator. "
(if
(= verification-status "identical")
(strong "Byte-for-byte identical to G0.")
"Differs from G0 — see mismatch details."))
(div
(~tw :tokens "not-prose bg-stone-100 rounded-lg p-5 max-h-96 overflow-y-auto border")
:class (if
(= verification-status "identical")
"border-green-200"
"border-red-200")
(pre
(~tw :tokens "text-xs leading-relaxed whitespace-pre-wrap break-words")
(code (highlight g1-output "python"))))))))

View File

@@ -0,0 +1,17 @@
(defcomp ()
(~docs/page :title "Components"
(~docs/section :title "defcomp" :id "defcomp"
(p (~tw :tokens "text-stone-600")
"Components are defined with defcomp. They take keyword parameters and optional children:")
(~docs/code :src (highlight "(defcomp ~docs-content/card (&key title subtitle &rest children)\n (div :class \"border rounded p-4\"\n (h2 :class \"font-bold\" title)\n (when subtitle (p :class \"text-stone-500\" subtitle))\n (div :class \"mt-3\" children)))" "lisp"))
(p (~tw :tokens "text-stone-600")
"Use components with the ~ prefix:")
(~docs/code :src (highlight "(~docs-content/card :title \"My Card\" :subtitle \"A description\"\n (p \"First child\")\n (p \"Second child\"))" "lisp")))
(~docs/section :title "Component caching" :id "caching"
(p (~tw :tokens "text-stone-600")
"Component definitions are sent in a <script type=\"text/sx\" data-components> block. The client caches them in localStorage keyed by a content hash. On subsequent page loads, the client sends an SX-Components header listing what it has. The server only sends definitions the client is missing.")
(p (~tw :tokens "text-stone-600")
"This means the first page load sends all component definitions (~5-15KB). Subsequent navigations send zero component bytes — just the page content."))
(~docs/section :title "Parameters" :id "params"
(p (~tw :tokens "text-stone-600")
"&key declares keyword parameters. &rest children captures remaining positional arguments. Missing parameters evaluate to nil. Components always receive all declared parameters — use (when param ...) or (if param ... ...) to handle optional values."))))

View File

@@ -0,0 +1,10 @@
(defcomp ()
(~docs/page :title "Evaluator"
(~docs/section :title "Special forms" :id "special"
(p (~tw :tokens "text-stone-600")
"Special forms have lazy evaluation — arguments are not evaluated before the form runs:")
(~docs/code :src (highlight ";; Conditionals\n(if condition then-expr else-expr)\n(when condition body...)\n(cond (test1 body1) (test2 body2) (else default))\n\n;; Bindings\n(let ((name value) (name2 value2)) body...)\n(define name value)\n\n;; Functions\n(lambda (x y) (+ x y))\n(fn (x) (* x x))\n\n;; Sequencing\n(do expr1 expr2 expr3)\n(begin expr1 expr2)\n\n;; Threading\n(-> value (fn1 arg) (fn2 arg))" "lisp")))
(~docs/section :title "Higher-order forms" :id "higher"
(p (~tw :tokens "text-stone-600")
"These operate on collections with function arguments:")
(~docs/code :src (highlight "(map (fn (x) (* x 2)) (list 1 2 3)) ;; => (2 4 6)\n(filter (fn (x) (> x 2)) (list 1 2 3 4 5)) ;; => (3 4 5)\n(reduce (fn (acc x) (+ acc x)) 0 (list 1 2 3)) ;; => 6\n(some (fn (x) (> x 3)) (list 1 2 3 4)) ;; => true\n(every? (fn (x) (> x 0)) (list 1 2 3)) ;; => true" "lisp")))))

View File

@@ -0,0 +1,14 @@
(defcomp ()
(~docs/page :title "Getting Started"
(~docs/section :title "Minimal example" :id "minimal"
(p (~tw :tokens "text-stone-600")
"An sx response is s-expression source code with content type text/sx:")
(~docs/code :src (highlight "(div :class \"p-4 bg-white rounded\"\n (h1 :class \"text-2xl font-bold\" \"Hello, world!\")\n (p \"This is rendered from an s-expression.\"))" "lisp"))
(p (~tw :tokens "text-stone-600")
"Add sx-get to any element to make it fetch and render sx:"))
(~docs/section :title "Hypermedia attributes" :id "attrs"
(p (~tw :tokens "text-stone-600")
"Like htmx, sx adds attributes to HTML elements to trigger HTTP requests:")
(~docs/code :src (highlight "(button\n :sx-get \"/api/data\"\n :sx-target \"#result\"\n :sx-swap \"innerHTML\"\n \"Load data\")" "lisp"))
(p (~tw :tokens "text-stone-600")
"sx-get, sx-post, sx-put, sx-delete, sx-patch — all work the same way. The response is parsed as sx and rendered into the target element."))))

View File

@@ -0,0 +1,17 @@
(defcomp ()
(~docs/page :title "Introduction"
(~docs/section :title "What is sx?" :id "what"
(p (~tw :tokens "text-stone-600")
"sx is an s-expression language for building web UIs. It combines htmx's server-first hypermedia approach with React's component model. The server sends s-expression source code over the wire. The client parses, evaluates, and renders it to DOM.")
(p (~tw :tokens "text-stone-600")
"The same evaluator runs on both server (Python) and client (JavaScript). Components defined once render identically in both environments."))
(~docs/section :title "Design decisions" :id "design"
(p (~tw :tokens "text-stone-600")
"HTML elements are first-class: (div :class \"card\" (p \"hello\")) renders exactly what you'd expect. Components use defcomp with keyword parameters and optional children. The evaluator supports let bindings, conditionals, lambda, map/filter/reduce, and ~80 primitives.")
(p (~tw :tokens "text-stone-600")
"sx replaces the pattern of shipping a JS framework + build step + client-side router + state management library just to render some server data. For most applications, sx eliminates the need for JavaScript entirely — htmx attributes handle interactivity, hyperscript handles small behaviours, and the server handles everything else."))
(~docs/section :title "What sx is not" :id "not"
(ul (~tw :tokens "space-y-2 text-stone-600")
(li "Not a general-purpose programming language — it's a UI rendering language")
(li "Not a full Lisp — it has macros, TCO, and delimited continuations, but no full call/cc")
(li "Not production-hardened at scale — it runs one website")))))

View File

@@ -0,0 +1,6 @@
(defcomp (&key prims)
(~docs/page :title "Primitives"
(~docs/section :title "Built-in functions" :id "builtins"
(p (~tw :tokens "text-stone-600")
"sx provides ~80 built-in pure functions. They work identically on server (Python) and client (JavaScript).")
(div (~tw :tokens "space-y-6") prims))))

View File

@@ -0,0 +1,15 @@
(defcomp ()
(~docs/page :title "Server Rendering"
(~docs/section :title "Python API" :id "python"
(p (~tw :tokens "text-stone-600")
"The server-side sx library provides several entry points for rendering:")
(~docs/code :src (highlight "from shared.sx.helpers import sx_page, sx_response, sx_call\nfrom shared.sx.parser import SxExpr\n\n# Build a component call from Python kwargs\nsx_call(\"card\", title=\"Hello\", subtitle=\"World\")\n\n# Return an sx wire-format response\nreturn sx_response(sx_call(\"card\", title=\"Hello\"))\n\n# Return a full HTML page shell with sx boot\nreturn sx_page(ctx, page_sx)" "python")))
(~docs/section :title "sx_call" :id "sx-call"
(p (~tw :tokens "text-stone-600")
"sx_call converts Python kwargs to an s-expression component call. Snake_case becomes kebab-case. SxExpr values are inlined without quoting. None becomes nil. Bools become true/false."))
(~docs/section :title "sx_response" :id "sx-response"
(p (~tw :tokens "text-stone-600")
"sx_response returns a Quart Response with content type text/sx. It prepends missing component definitions, scans for CSS classes, and sets SX-Css-Hash and SX-Css-Add headers."))
(~docs/section :title "sx_page" :id "sx-page"
(p (~tw :tokens "text-stone-600")
"sx_page returns a minimal HTML document that boots the page from sx source. The browser loads component definitions and page sx from inline <script> tags, then sx.js renders everything client-side. CSS rules are pre-scanned and injected."))))

View File

@@ -0,0 +1,8 @@
(defcomp (&key forms)
(~docs/page :title "Special Forms"
(~docs/section :title "Syntactic constructs" :id "special-forms"
(p (~tw :tokens "text-stone-600")
"Special forms are syntactic constructs whose arguments are NOT evaluated before dispatch. Each form has its own evaluation rules — unlike primitives, which receive pre-evaluated values. Together with primitives, special forms define the complete language surface.")
(p (~tw :tokens "text-stone-600")
"Forms marked with a tail position enable " (a :href "/sx/(etc.(essay.tail-call-optimization))" (~tw :tokens "text-violet-600 hover:underline") "tail-call optimization") " — recursive calls in tail position use constant stack space.")
(div (~tw :tokens "space-y-10") forms))))

View File

@@ -0,0 +1,42 @@
(defcomp
(&key
(spec-title :as string)
(spec-desc :as string)
(spec-filename :as string)
(spec-source :as string)
(spec-prose :as string?))
(~docs/page
:title spec-title
(div
(~tw :tokens "flex items-center gap-3 mb-4")
(span (~tw :tokens "text-sm text-stone-400 font-mono") spec-filename)
(span (~tw :tokens "text-sm text-stone-500 flex-1") spec-desc)
(a
:href (str
"/sx/(language.(spec.(explore."
(replace spec-filename ".sx" "")
")))")
:sx-get (str
"/sx/(language.(spec.(explore."
(replace spec-filename ".sx" "")
")))")
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
(~tw :tokens "text-sm text-violet-600 hover:text-violet-800 font-medium whitespace-nowrap")
"Explore"))
(when
spec-prose
(div
(~tw :tokens "mb-6 space-y-3")
(p (~tw :tokens "text-stone-600 leading-relaxed") spec-prose)
(p
(~tw :tokens "text-xs text-stone-400 italic")
"The s-expression source below is the canonical specification. "
"The English description above is a summary.")))
(div
(~tw :tokens "not-prose bg-stone-100 rounded-lg p-5 mx-auto max-w-3xl")
(pre
(~tw :tokens "text-sm leading-relaxed whitespace-pre-wrap break-words")
(code (highlight spec-source "sx"))))))

View File

@@ -0,0 +1,9 @@
(defcomp
(&key (slug :as string))
(~docs/page
:title "Spec Not Found"
(p
(~tw :tokens "text-stone-600")
"No specification found for \""
slug
"\". This spec may not exist yet.")))

View File

@@ -0,0 +1,56 @@
(defcomp
(&key (spec-title :as string) (spec-files :as list))
(~docs/page
:title (or spec-title "Specs")
(p
(~tw :tokens "text-stone-600 mb-6")
(case
spec-title
"Core Language"
"The core specification defines the language itself — parsing, evaluation, primitives, special forms, and shared rendering definitions. These five files are platform-independent and sufficient to implement SX on any target."
"Adapters"
"Adapters connect the core language to specific environments. Each adapter takes evaluated expression trees and produces output for its target — DOM nodes, HTML strings, SX wire format, or async-aware server rendering."
"Browser Runtime"
"The browser runtime handles the client-side lifecycle: parsing triggers, making requests, swapping content, managing history, and booting the page. Split into pure logic (engine), browser wiring (orchestration), startup (boot), and URL matching (router)."
"Reactive System"
"Fine-grained reactive primitives for client-side islands. Signals, computed values, effects, and batching — the reactive graph that powers L2-L3 interactivity without a virtual DOM."
"Host Interface"
"The contract between SX and its host environment. Boundary declarations specify what the host must provide, forms define server-side application constructs, and page helpers offer pure data transformations."
"Extensions"
"Optional bolt-on specifications that extend the core language. Bootstrappers include them only when the target requests them. Code that doesn't use extensions pays zero cost."
:else ""))
(div
(~tw :tokens "space-y-8")
(map
(fn
(spec)
(div
(~tw :tokens "space-y-3")
(div
(~tw :tokens "flex items-baseline gap-3")
(h2
(~tw :tokens "text-2xl font-semibold text-stone-800")
(a
:href (get spec "href")
:sx-get (get spec "href")
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
(~tw :tokens "text-violet-700 hover:text-violet-900 underline")
(get spec "title")))
(span
(~tw :tokens "text-sm text-stone-400 font-mono")
(get spec "filename")))
(p (~tw :tokens "text-stone-600") (get spec "desc"))
(when
(get spec "prose")
(p
(~tw :tokens "text-sm text-stone-500 leading-relaxed")
(get spec "prose")))
(div
(~tw :tokens "not-prose bg-stone-100 rounded-lg p-5 max-h-72 overflow-y-auto")
(pre
(~tw :tokens "text-xs leading-relaxed whitespace-pre-wrap break-words")
(code (highlight (get spec "source") "sx"))))))
spec-files))))

View File

@@ -0,0 +1,482 @@
(defcomp
()
(~docs/page
:title "Spec Architecture"
(div
(~tw :tokens "space-y-8")
(div
(~tw :tokens "space-y-4")
(p
(~tw :tokens "text-lg text-stone-600")
"SX is defined in SX. The canonical specification is a set of s-expression files that are both documentation and executable definition. Bootstrap compilers read these files to generate native implementations in JavaScript, Python, Rust, or any other target.")
(p
(~tw :tokens "text-stone-600")
"The spec is organized into six sections: "
(strong "Core")
" (the language itself), "
(strong "Adapters")
" (rendering backends), "
(strong "Browser")
" (client-side runtime), "
(strong "Reactive")
" (signal system), "
(strong "Host Interface")
" (platform contract), and "
(strong "Extensions")
" (optional add-ons)."))
(div
(~tw :tokens "space-y-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "Core")
(p
(~tw :tokens "text-stone-600")
"The core is platform-independent. It defines how SX source is parsed, how expressions are evaluated, what primitives exist, and what shared rendering definitions all adapters use. These four files are the language.")
(div
(~tw :tokens "overflow-x-auto rounded border border-stone-200")
(table
(~tw :tokens "w-full text-left text-sm")
(thead
(tr
(~tw :tokens "border-b border-stone-200 bg-stone-100")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Role")))
(tbody
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(spec.parser))"
(~tw :tokens "hover:underline")
:sx-get "/sx/(language.(spec.parser))"
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
"parser.sx"))
(td
(~tw :tokens "px-3 py-2 text-stone-700")
"Tokenization and parsing of SX source text into AST"))
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(spec.evaluator))"
(~tw :tokens "hover:underline")
:sx-get "/sx/(language.(spec.evaluator))"
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
"eval.sx"))
(td
(~tw :tokens "px-3 py-2 text-stone-700")
"Tree-walking evaluation of SX expressions"))
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(spec.primitives))"
(~tw :tokens "hover:underline")
:sx-get "/sx/(language.(spec.primitives))"
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
"primitives.sx"))
(td
(~tw :tokens "px-3 py-2 text-stone-700")
"All built-in pure functions and their signatures"))
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(spec.special-forms))"
(~tw :tokens "hover:underline")
:sx-get "/sx/(language.(spec.special-forms))"
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
"special-forms.sx"))
(td
(~tw :tokens "px-3 py-2 text-stone-700")
"All special forms — syntactic constructs with custom evaluation rules"))
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(spec.renderer))"
(~tw :tokens "hover:underline")
:sx-get "/sx/(language.(spec.renderer))"
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
"render.sx"))
(td
(~tw :tokens "px-3 py-2 text-stone-700")
"Shared rendering registries and utilities used by all adapters"))))))
(div
(~tw :tokens "space-y-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "Adapters")
(p
(~tw :tokens "text-stone-600")
"Adapters are selectable rendering backends. Each one takes the same evaluated expression tree and produces output for a specific environment. You only need the adapters relevant to your target.")
(div
(~tw :tokens "overflow-x-auto rounded border border-stone-200")
(table
(~tw :tokens "w-full text-left text-sm")
(thead
(tr
(~tw :tokens "border-b border-stone-200 bg-stone-100")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Output")
(th
(~tw :tokens "px-3 py-2 font-medium text-stone-600")
"Environment")))
(tbody
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(spec.adapter-dom))"
(~tw :tokens "hover:underline")
:sx-get "/sx/(language.(spec.adapter-dom))"
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
"adapter-dom.sx"))
(td (~tw :tokens "px-3 py-2 text-stone-700") "Live DOM nodes")
(td (~tw :tokens "px-3 py-2 text-stone-500") "Browser"))
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(spec.adapter-html))"
(~tw :tokens "hover:underline")
:sx-get "/sx/(language.(spec.adapter-html))"
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
"adapter-html.sx"))
(td (~tw :tokens "px-3 py-2 text-stone-700") "HTML strings")
(td (~tw :tokens "px-3 py-2 text-stone-500") "Server"))
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(spec.adapter-sx))"
(~tw :tokens "hover:underline")
:sx-get "/sx/(language.(spec.adapter-sx))"
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
"adapter-sx.sx"))
(td (~tw :tokens "px-3 py-2 text-stone-700") "SX wire format")
(td (~tw :tokens "px-3 py-2 text-stone-500") "Server to client"))
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(spec.adapter-async))"
(~tw :tokens "hover:underline")
:sx-get "/sx/(language.(spec.adapter-async))"
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
"adapter-async.sx"))
(td
(~tw :tokens "px-3 py-2 text-stone-700")
"HTML/SX with awaited I/O")
(td (~tw :tokens "px-3 py-2 text-stone-500") "Server (async)"))))))
(div
(~tw :tokens "space-y-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "Browser Runtime")
(p
(~tw :tokens "text-stone-600")
"The browser runtime handles the full client-side lifecycle: parsing triggers, making HTTP requests, swapping content, managing history, and booting the page. Split into pure logic (engine), browser wiring (orchestration), startup (boot), and URL matching (router).")
(div
(~tw :tokens "overflow-x-auto rounded border border-stone-200")
(table
(~tw :tokens "w-full text-left text-sm")
(thead
(tr
(~tw :tokens "border-b border-stone-200 bg-stone-100")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Role")))
(tbody
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(spec.engine))"
(~tw :tokens "hover:underline")
:sx-get "/sx/(language.(spec.engine))"
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
"engine.sx"))
(td
(~tw :tokens "px-3 py-2 text-stone-700")
"Pure logic — trigger parsing, swap algorithms, morph, history, SSE, indicators"))
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(spec.orchestration))"
(~tw :tokens "hover:underline")
:sx-get "/sx/(language.(spec.orchestration))"
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
"orchestration.sx"))
(td
(~tw :tokens "px-3 py-2 text-stone-700")
"Browser wiring — binds engine to DOM events, fetch, request lifecycle"))
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(spec.boot))"
(~tw :tokens "hover:underline")
:sx-get "/sx/(language.(spec.boot))"
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
"boot.sx"))
(td
(~tw :tokens "px-3 py-2 text-stone-700")
"Browser startup — mount, hydrate, script processing, head hoisting"))
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(spec.router))"
(~tw :tokens "hover:underline")
:sx-get "/sx/(language.(spec.router))"
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
"router.sx"))
(td
(~tw :tokens "px-3 py-2 text-stone-700")
"Client-side route matching — Flask-style patterns, param extraction"))))))
(div
(~tw :tokens "space-y-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "Reactive")
(p
(~tw :tokens "text-stone-600")
"Fine-grained reactive primitives for client-side islands. Signals notify subscribers on change, computed values derive lazily, effects run side-effects with cleanup, and batch coalesces updates.")
(div
(~tw :tokens "overflow-x-auto rounded border border-stone-200")
(table
(~tw :tokens "w-full text-left text-sm")
(thead
(tr
(~tw :tokens "border-b border-stone-200 bg-stone-100")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Role")))
(tbody
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(spec.signals))"
(~tw :tokens "hover:underline")
:sx-get "/sx/(language.(spec.signals))"
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
"signals.sx"))
(td
(~tw :tokens "px-3 py-2 text-stone-700")
"Signal runtime — signal, deref, reset!, swap!, computed, effect, batch, island scope"))))))
(div
(~tw :tokens "space-y-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "Host Interface")
(p
(~tw :tokens "text-stone-600")
"The contract between SX and its host environment. Boundary declares what the host must provide. Forms define server-side application constructs. Page helpers offer pure data transformations for rendering.")
(div
(~tw :tokens "overflow-x-auto rounded border border-stone-200")
(table
(~tw :tokens "w-full text-left text-sm")
(thead
(tr
(~tw :tokens "border-b border-stone-200 bg-stone-100")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Role")))
(tbody
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(spec.boundary))"
(~tw :tokens "hover:underline")
:sx-get "/sx/(language.(spec.boundary))"
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
"boundary.sx"))
(td
(~tw :tokens "px-3 py-2 text-stone-700")
"Language boundary — I/O primitives, page helpers, tier declarations"))
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(spec.forms))"
(~tw :tokens "hover:underline")
:sx-get "/sx/(language.(spec.forms))"
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
"forms.sx"))
(td
(~tw :tokens "px-3 py-2 text-stone-700")
"Definition forms — defhandler, defquery, defaction, defpage"))
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(spec.page-helpers))"
(~tw :tokens "hover:underline")
:sx-get "/sx/(language.(spec.page-helpers))"
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
"page-helpers.sx"))
(td
(~tw :tokens "px-3 py-2 text-stone-700")
"Pure data-transformation helpers for page rendering"))))))
(div
(~tw :tokens "space-y-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "Dependency graph")
(div
(~tw :tokens "not-prose bg-stone-100 rounded-lg p-5 mx-auto max-w-3xl")
(pre
(~tw :tokens "text-sm leading-relaxed whitespace-pre-wrap break-words font-mono text-stone-700")
";; Core\nparser.sx (standalone — no dependencies)\nprimitives.sx (standalone — declarative registry)\nspecial-forms.sx (standalone — declarative registry)\neval.sx depends on: parser, primitives, special-forms\nrender.sx (standalone — shared registries)\n\n;; Adapters\nadapter-dom.sx depends on: render, eval\nadapter-html.sx depends on: render, eval\nadapter-sx.sx depends on: render, eval\nadapter-async.sx depends on: adapter-html, adapter-sx, eval\n\n;; Browser Runtime\nengine.sx depends on: eval, adapter-dom\norchestration.sx depends on: engine, adapter-dom\nboot.sx depends on: orchestration, adapter-dom, render\nrouter.sx (standalone — pure string/list ops)\n\n;; Reactive\nsignals.sx depends on: eval\n\n;; Host Interface\nboundary.sx (standalone — declarative contract)\nforms.sx depends on: eval\npage-helpers.sx (standalone — declarative registry)\n\n;; Extensions (optional)\ncontinuations.sx depends on: eval\ncallcc.sx depends on: eval\ntypes.sx depends on: eval, primitives\ndeps.sx depends on: eval")))
(div
(~tw :tokens "space-y-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "Extensions")
(p
(~tw :tokens "text-stone-600")
"Optional bolt-on specifications that extend the core language. Bootstrappers include them only when the target requests them. Code that doesn't use extensions pays zero cost.")
(div
(~tw :tokens "overflow-x-auto rounded border border-stone-200")
(table
(~tw :tokens "w-full text-left text-sm")
(thead
(tr
(~tw :tokens "border-b border-stone-200 bg-stone-100")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "File")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Role")))
(tbody
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(spec.continuations))"
(~tw :tokens "hover:underline")
:sx-get "/sx/(language.(spec.continuations))"
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
"continuations.sx"))
(td
(~tw :tokens "px-3 py-2 text-stone-700")
"Delimited continuations — shift/reset"))
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(spec.callcc))"
(~tw :tokens "hover:underline")
:sx-get "/sx/(language.(spec.callcc))"
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
"callcc.sx"))
(td
(~tw :tokens "px-3 py-2 text-stone-700")
"Full first-class continuations — call/cc"))
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(spec.types))"
(~tw :tokens "hover:underline")
:sx-get "/sx/(language.(spec.types))"
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
"types.sx"))
(td
(~tw :tokens "px-3 py-2 text-stone-700")
"Gradual type system — registration-time checking, zero runtime cost"))
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700")
(a
:href "/sx/(language.(spec.deps))"
(~tw :tokens "hover:underline")
:sx-get "/sx/(language.(spec.deps))"
:sx-target "#sx-content"
:sx-select "#sx-content"
:sx-swap "outerHTML"
:sx-push-url "true"
"deps.sx"))
(td
(~tw :tokens "px-3 py-2 text-stone-700")
"Component dependency analysis — bundling, IO detection, CSS scoping"))))))
(div
(~tw :tokens "space-y-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "Self-hosting")
(p
(~tw :tokens "text-stone-600")
"Every spec file is written in the same restricted subset of SX that the evaluator itself defines. A bootstrap compiler for a new target only needs to understand this subset — roughly 20 special forms and 80 primitives — to generate a fully native implementation. The spec files are the single source of truth; implementations are derived artifacts.")
(p
(~tw :tokens "text-stone-600")
"This is not a theoretical exercise. The JavaScript implementation ("
(code (~tw :tokens "text-violet-700 text-sm") "sx.js")
") and the Python implementation ("
(code (~tw :tokens "text-violet-700 text-sm") "shared/sx/")
") are both generated from these spec files via "
(code (~tw :tokens "text-violet-700 text-sm") "bootstrap_js.py")
" and its Python counterpart.")))))

View File

@@ -0,0 +1,52 @@
;; ---------------------------------------------------------------------------
;; Per-spec test page (reusable for eval, parser, router, render)
;; ---------------------------------------------------------------------------
(defcomp (&key (spec-name :as string) (spec-title :as string) (spec-desc :as string) (spec-source :as string) (framework-source :as string) (server-results :as dict?))
(~docs/page :title spec-title
(div (~tw :tokens "space-y-8")
;; Description
(div (~tw :tokens "space-y-4")
(p (~tw :tokens "text-lg text-stone-600") spec-desc))
;; Server-side results
(when server-results
(div (~tw :tokens "space-y-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "Server: Python evaluator")
(p (~tw :tokens "text-stone-600")
"Ran "
(code (~tw :tokens "text-violet-700 text-sm") (str "test-" spec-name ".sx"))
" when this page loaded — "
(strong (str (get server-results "passed") "/" (get server-results "total") " passed"))
" in " (str (get server-results "elapsed-ms")) "ms.")
(pre (~tw :tokens "text-sm font-mono bg-stone-900 text-green-400 rounded-lg p-4 overflow-x-auto max-h-96 overflow-y-auto")
(get server-results "output"))))
;; Browser test runner
(div (~tw :tokens "space-y-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "Browser: JavaScript evaluator")
(p (~tw :tokens "text-stone-600")
"Run this spec in the browser:")
(div (~tw :tokens "flex items-center gap-4")
(button :id (str "test-btn-" spec-name)
(~tw :tokens "px-4 py-2 rounded-md bg-violet-600 text-white font-medium text-sm hover:bg-violet-700 cursor-pointer")
:onclick (str "sxRunModularTests('" spec-name "','test-output-" spec-name "','test-btn-" spec-name "')")
(str "Run " spec-title)))
(pre :id (str "test-output-" spec-name)
(~tw :tokens "text-sm font-mono bg-stone-900 text-green-400 rounded-lg p-4 overflow-x-auto max-h-96 overflow-y-auto")
:style "display:none"
"")
;; Hidden: spec source + framework source for the browser runner
(textarea :id (str "test-spec-" spec-name) :style "display:none" spec-source)
(textarea :id "test-framework-source" :style "display:none" framework-source)
(script :src (asset-url "/scripts/sx-test-runner.js")))
;; Full source
(div (~tw :tokens "space-y-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "Specification source")
(p (~tw :tokens "text-xs text-stone-400 italic")
(str "test-" spec-name ".sx")
" — the canonical test specification for this module.")
(div (~tw :tokens "not-prose bg-stone-100 rounded-lg p-5 mx-auto max-w-3xl")
(pre (~tw :tokens "text-sm leading-relaxed whitespace-pre-wrap break-words")
(code (highlight spec-source "sx"))))))))

View File

@@ -0,0 +1,133 @@
;; Testing section — SX tests SX across every host.
;; Modular test specs: eval, parser, router, render.
;; Each page shows spec source, runs server-side, offers browser-side run button.
;; ---------------------------------------------------------------------------
;; Overview page
;; ---------------------------------------------------------------------------
(defcomp (&key server-results framework-source eval-source parser-source router-source render-source deps-source engine-source)
(~docs/page :title "Testing"
(div (~tw :tokens "space-y-8")
;; Intro
(div (~tw :tokens "space-y-4")
(p (~tw :tokens "text-lg text-stone-600")
"SX tests itself. Test specs are written in SX and executed by SX evaluators on every host. "
"The same assertions run in Python, Node.js, and in the browser — from the same source files.")
(p (~tw :tokens "text-stone-600")
"The framework defines two macros ("
(code (~tw :tokens "text-violet-700 text-sm") "deftest")
" and "
(code (~tw :tokens "text-violet-700 text-sm") "defsuite")
") and nine assertion helpers, all in SX. Each host provides only "
(strong "five platform functions")
" — everything else is pure SX."))
;; Architecture
(div (~tw :tokens "space-y-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "Architecture")
(div (~tw :tokens "not-prose bg-stone-100 rounded-lg p-5 mx-auto max-w-3xl")
(pre (~tw :tokens "text-sm leading-relaxed whitespace-pre-wrap break-words font-mono text-stone-700")
"test-framework.sx Macros + assertion helpers (loaded first)
|
+-- test-eval.sx 81 tests: evaluator + primitives
+-- test-parser.sx 39 tests: tokenizer, parser, serializer
+-- test-router.sx 18 tests: route matching + param extraction
+-- test-render.sx 23 tests: HTML rendering + components
+-- test-deps.sx 33 tests: dependency analysis + IO detection
+-- test-engine.sx 37 tests: trigger/swap/retry parsing
Runners:
run.js Node.js — injects platform fns, runs specs
run.py Python — injects platform fns, runs specs
sx-test-runner.js Browser — runs specs in this page
Platform functions (5 total):
try-call (thunk) -> {:ok true} | {:ok false :error \"msg\"}
report-pass (name) -> output pass
report-fail (name error) -> output fail
push-suite (name) -> push suite context
pop-suite () -> pop suite context
Per-spec platform functions:
parser: sx-parse, sx-serialize, make-symbol, make-keyword, ...
router: (none — pure spec, uses bootstrapped functions)
render: render-html (wraps parse + render-to-html)
deps: test-env (returns current evaluation environment)
engine: (none — pure spec, uses bootstrapped functions)")))
;; Server results
(when server-results
(div (~tw :tokens "space-y-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "Server: Python evaluator")
(p (~tw :tokens "text-stone-600")
"Ran all test specs when this page loaded — "
(strong (str (get server-results "passed") "/" (get server-results "total") " passed"))
" in " (str (get server-results "elapsed-ms")) "ms.")
(pre (~tw :tokens "text-sm font-mono bg-stone-900 text-green-400 rounded-lg p-4 overflow-x-auto max-h-96 overflow-y-auto")
(get server-results "output"))))
;; Browser test runner
(div (~tw :tokens "space-y-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "Browser: JavaScript evaluator")
(p (~tw :tokens "text-stone-600")
"Run all test specs in the browser using "
(code (~tw :tokens "text-violet-700 text-sm") "sx-browser.js")
":")
(div (~tw :tokens "flex items-center gap-4")
(button :id "test-btn-all"
(~tw :tokens "px-4 py-2 rounded-md bg-violet-600 text-white font-medium text-sm hover:bg-violet-700 cursor-pointer")
:onclick "sxRunModularTests('all','test-output-all','test-btn-all')"
"Run all tests"))
(pre :id "test-output-all"
(~tw :tokens "text-sm font-mono bg-stone-900 text-green-400 rounded-lg p-4 overflow-x-auto max-h-96 overflow-y-auto")
:style "display:none"
"")
;; Hidden: all spec sources for the browser runner
(textarea :id "test-framework-source" :style "display:none" framework-source)
(textarea :id "test-spec-eval" :style "display:none" eval-source)
(textarea :id "test-spec-parser" :style "display:none" parser-source)
(textarea :id "test-spec-router" :style "display:none" router-source)
(textarea :id "test-spec-render" :style "display:none" render-source)
(textarea :id "test-spec-deps" :style "display:none" deps-source)
(textarea :id "test-spec-engine" :style "display:none" engine-source)
(script :src (asset-url "/scripts/sx-test-runner.js")))
;; Test spec index
(div (~tw :tokens "space-y-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "Test specs")
(div (~tw :tokens "grid grid-cols-1 md:grid-cols-2 gap-4")
(a :href "/sx/(language.(test.eval))" (~tw :tokens "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors")
(h3 (~tw :tokens "font-semibold text-stone-800") "Evaluator")
(p (~tw :tokens "text-sm text-stone-500") "81 tests — literals, arithmetic, strings, lists, dicts, special forms, lambdas, components, macros")
(p (~tw :tokens "text-xs text-violet-600 mt-1") "test-eval.sx"))
(a :href "/sx/(language.(test.parser))" (~tw :tokens "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors")
(h3 (~tw :tokens "font-semibold text-stone-800") "Parser")
(p (~tw :tokens "text-sm text-stone-500") "39 tests — tokenization, parsing, escape sequences, quote sugar, serialization, round-trips")
(p (~tw :tokens "text-xs text-violet-600 mt-1") "test-parser.sx"))
(a :href "/sx/(language.(test.router))" (~tw :tokens "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors")
(h3 (~tw :tokens "font-semibold text-stone-800") "Router")
(p (~tw :tokens "text-sm text-stone-500") "18 tests — path splitting, pattern parsing, segment matching, parameter extraction")
(p (~tw :tokens "text-xs text-violet-600 mt-1") "test-router.sx"))
(a :href "/sx/(language.(test.render))" (~tw :tokens "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors")
(h3 (~tw :tokens "font-semibold text-stone-800") "Renderer")
(p (~tw :tokens "text-sm text-stone-500") "23 tests — elements, attributes, void elements, fragments, escaping, control flow, components")
(p (~tw :tokens "text-xs text-violet-600 mt-1") "test-render.sx"))
(a :href "/sx/(language.(test.deps))" (~tw :tokens "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors")
(h3 (~tw :tokens "font-semibold text-stone-800") "Dependencies")
(p (~tw :tokens "text-sm text-stone-500") "33 tests — scan-refs, transitive-deps, components-needed, IO detection, purity classification")
(p (~tw :tokens "text-xs text-violet-600 mt-1") "test-deps.sx"))
(a :href "/sx/(language.(test.engine))" (~tw :tokens "block rounded-lg border border-stone-200 p-5 hover:border-violet-300 hover:bg-violet-50 transition-colors")
(h3 (~tw :tokens "font-semibold text-stone-800") "Engine")
(p (~tw :tokens "text-sm text-stone-500") "37 tests — parse-time, trigger specs, swap specs, retry logic, param filtering")
(p (~tw :tokens "text-xs text-violet-600 mt-1") "test-engine.sx"))))
;; What it proves
(div (~tw :tokens "rounded-lg border border-blue-200 bg-blue-50 p-5 space-y-3")
(h2 (~tw :tokens "text-lg font-semibold text-blue-900") "What this proves")
(ol (~tw :tokens "list-decimal list-inside text-blue-800 space-y-2 text-sm")
(li "Test specs are " (strong "written in SX") " and " (strong "executed by SX") " — no code generation")
(li "The same tests run on " (strong "Python, Node.js, and in the browser") " from the same files")
(li "Each host provides only " (strong "5 platform functions") " — everything else is pure SX")
(li "Modular specs test each part independently — evaluator, parser, router, renderer")
(li "Per-spec platform functions extend the 5-function contract for module-specific capabilities")
(li "Platform divergences are " (strong "exposed by the tests") ", not hidden"))))))

View File

@@ -0,0 +1,109 @@
;; ---------------------------------------------------------------------------
;; Runners page
;; ---------------------------------------------------------------------------
(defcomp ()
(~docs/page :title "Test Runners"
(div (~tw :tokens "space-y-8")
(div (~tw :tokens "space-y-4")
(p (~tw :tokens "text-lg text-stone-600")
"Three runners execute the same test specs on different hosts. Each injects the five platform functions and any per-spec extensions, then evaluates the SX source directly.")
(p (~tw :tokens "text-stone-600")
"All runners produce "
(a :href "https://testanything.org/" (~tw :tokens "text-violet-700 underline") "TAP")
" output and accept module names as arguments."))
;; Node.js runner
(div (~tw :tokens "space-y-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "Node.js: run.js")
(div (~tw :tokens "rounded-lg border border-stone-200 bg-white p-5 space-y-3")
(h3 (~tw :tokens "text-sm font-medium text-stone-500 uppercase tracking-wide") "Usage")
(~docs/code :src
(highlight "# Run all specs\nnode shared/sx/tests/run.js\n\n# Run specific specs\nnode shared/sx/tests/run.js eval parser\n\n# Legacy mode (monolithic test.sx)\nnode shared/sx/tests/run.js --legacy" "bash")))
(p (~tw :tokens "text-stone-600 text-sm")
"Uses "
(code (~tw :tokens "text-violet-700 text-sm") "sx-browser.js")
" as the evaluator. Router tests use bootstrapped functions from "
(code (~tw :tokens "text-violet-700 text-sm") "Sx.splitPathSegments")
" etc. Render tests use "
(code (~tw :tokens "text-violet-700 text-sm") "Sx.renderToHtml")
"."))
;; Python runner
(div (~tw :tokens "space-y-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "Python: run.py")
(div (~tw :tokens "rounded-lg border border-stone-200 bg-white p-5 space-y-3")
(h3 (~tw :tokens "text-sm font-medium text-stone-500 uppercase tracking-wide") "Usage")
(~docs/code :src
(highlight "# Run all specs\npython shared/sx/tests/run.py\n\n# Run specific specs\npython shared/sx/tests/run.py eval parser\n\n# Legacy mode (monolithic test.sx)\npython shared/sx/tests/run.py --legacy" "bash")))
(p (~tw :tokens "text-stone-600 text-sm")
"Uses the hand-written Python evaluator ("
(code (~tw :tokens "text-violet-700 text-sm") "evaluator.py")
"). Router tests import bootstrapped functions from "
(code (~tw :tokens "text-violet-700 text-sm") "sx_ref.py")
" because the hand-written evaluator's "
(code (~tw :tokens "text-violet-700 text-sm") "set!")
" doesn't propagate across lambda closure copies."))
;; Browser runner
(div (~tw :tokens "space-y-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "Browser: sx-test-runner.js")
(p (~tw :tokens "text-stone-600")
"Runs in the browser on each test page. The spec source is embedded in a hidden textarea; the runner parses and evaluates it using the same "
(code (~tw :tokens "text-violet-700 text-sm") "sx-browser.js")
" that renders the page itself.")
(p (~tw :tokens "text-stone-600")
"This is the strongest proof of cross-host parity — the browser evaluator that users depend on is the same one running the tests."))
;; Platform functions table
(div (~tw :tokens "space-y-3")
(h2 (~tw :tokens "text-2xl font-semibold text-stone-800") "Platform functions")
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200")
(table (~tw :tokens "w-full text-left text-sm")
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Function")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Scope")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Purpose")))
(tbody
(tr (~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "try-call")
(td (~tw :tokens "px-3 py-2") "All specs")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Call a thunk, catch errors, return result dict"))
(tr (~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "report-pass")
(td (~tw :tokens "px-3 py-2") "All specs")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Report a passing test"))
(tr (~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "report-fail")
(td (~tw :tokens "px-3 py-2") "All specs")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Report a failing test with error message"))
(tr (~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "push-suite")
(td (~tw :tokens "px-3 py-2") "All specs")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Push suite name onto context stack"))
(tr (~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "pop-suite")
(td (~tw :tokens "px-3 py-2") "All specs")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Pop suite name from context stack"))
(tr (~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "sx-parse")
(td (~tw :tokens "px-3 py-2") "Parser")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Parse SX source to AST"))
(tr (~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "sx-serialize")
(td (~tw :tokens "px-3 py-2") "Parser")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Serialize AST back to SX source text"))
(tr (~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "render-html")
(td (~tw :tokens "px-3 py-2") "Renderer")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Parse SX source and render to HTML string"))))))
;; Adding a new host
(div (~tw :tokens "rounded-lg border border-blue-200 bg-blue-50 p-5 space-y-3")
(h2 (~tw :tokens "text-lg font-semibold text-blue-900") "Adding a new host")
(ol (~tw :tokens "list-decimal list-inside text-blue-800 space-y-2 text-sm")
(li "Implement the 5 platform functions in your target language")
(li "Load " (code (~tw :tokens "text-sm") "test-framework.sx") " — it defines deftest/defsuite macros")
(li "Load the spec file(s) — the evaluator runs the tests automatically")
(li "Add per-spec platform functions if testing parser or renderer")
(li "Compare TAP output across hosts — divergences reveal platform-specific semantics"))))))