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"))))))))
|
||||
Reference in New Issue
Block a user