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:
79
sx/sx/language/bootstrapper/index.sx
Normal file
79
sx/sx/language/bootstrapper/index.sx
Normal 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"))))))))
|
||||
53
sx/sx/language/bootstrapper/javascript/index.sx
Normal file
53
sx/sx/language/bootstrapper/javascript/index.sx
Normal 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"))))))))
|
||||
@@ -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")))))))))))
|
||||
@@ -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))))
|
||||
100
sx/sx/language/bootstrapper/page-helpers/index.sx
Normal file
100
sx/sx/language/bootstrapper/page-helpers/index.sx
Normal 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))))
|
||||
53
sx/sx/language/bootstrapper/python/index.sx
Normal file
53
sx/sx/language/bootstrapper/python/index.sx
Normal 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"))))))))
|
||||
201
sx/sx/language/bootstrapper/self-hosting-js/index.sx
Normal file
201
sx/sx/language/bootstrapper/self-hosting-js/index.sx
Normal 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"))))))))
|
||||
115
sx/sx/language/bootstrapper/self-hosting/index.sx
Normal file
115
sx/sx/language/bootstrapper/self-hosting/index.sx
Normal 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"))))))))
|
||||
17
sx/sx/language/doc/components/index.sx
Normal file
17
sx/sx/language/doc/components/index.sx
Normal 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."))))
|
||||
10
sx/sx/language/doc/evaluator/index.sx
Normal file
10
sx/sx/language/doc/evaluator/index.sx
Normal 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")))))
|
||||
14
sx/sx/language/doc/getting-started/index.sx
Normal file
14
sx/sx/language/doc/getting-started/index.sx
Normal 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."))))
|
||||
17
sx/sx/language/doc/introduction/index.sx
Normal file
17
sx/sx/language/doc/introduction/index.sx
Normal 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")))))
|
||||
6
sx/sx/language/doc/primitives/index.sx
Normal file
6
sx/sx/language/doc/primitives/index.sx
Normal 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))))
|
||||
15
sx/sx/language/doc/server-rendering/index.sx
Normal file
15
sx/sx/language/doc/server-rendering/index.sx
Normal 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."))))
|
||||
8
sx/sx/language/doc/special-forms/index.sx
Normal file
8
sx/sx/language/doc/special-forms/index.sx
Normal 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))))
|
||||
42
sx/sx/language/spec/_shared/detail-content.sx
Normal file
42
sx/sx/language/spec/_shared/detail-content.sx
Normal 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"))))))
|
||||
9
sx/sx/language/spec/_shared/not-found.sx
Normal file
9
sx/sx/language/spec/_shared/not-found.sx
Normal 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.")))
|
||||
56
sx/sx/language/spec/_shared/overview-content.sx
Normal file
56
sx/sx/language/spec/_shared/overview-content.sx
Normal 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))))
|
||||
482
sx/sx/language/spec/index.sx
Normal file
482
sx/sx/language/spec/index.sx
Normal 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.")))))
|
||||
52
sx/sx/language/test/_shared/spec-content.sx
Normal file
52
sx/sx/language/test/_shared/spec-content.sx
Normal 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"))))))))
|
||||
133
sx/sx/language/test/index.sx
Normal file
133
sx/sx/language/test/index.sx
Normal 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"))))))
|
||||
109
sx/sx/language/test/runners/index.sx
Normal file
109
sx/sx/language/test/runners/index.sx
Normal 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"))))))
|
||||
Reference in New Issue
Block a user