Step 18 (part 6): _hyperscript integration — _="..." attribute wiring

lib/hyperscript/integration.sx — connects compiled hyperscript to DOM:
  hs-handler(src) — compile source → callable (fn (me) ...) via eval-expr-cek
  hs-activate!(el) — read _="...", compile, execute with me=element
  hs-boot!() — scan document for [_] elements, activate all
  hs-boot-subtree!(root) — activate within subtree (for HTMX swaps)

Handler wraps compiled SX in (fn (me) (let ((it nil) (event nil)) ...))
so each element gets its own me binding and clean it/event state.
Double-activation prevented via data-hs-active marker.

12 integration tests verify full pipeline: source → compile → eval.
Handlers correctly bind me, support arithmetic, conditionals, sequences,
for loops, and repeat. 3111/3111 full build, zero regressions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-06 08:48:58 +00:00
parent 9a57bd5beb
commit f5da2bcfd5
3 changed files with 143 additions and 0 deletions

View File

@@ -1313,6 +1313,7 @@ let run_spec_tests env test_files =
load_module "parser.sx" hs_dir;
load_module "compiler.sx" hs_dir;
load_module "runtime.sx" hs_dir;
load_module "integration.sx" hs_dir;
load_module "types.sx" lib_dir;
load_module "sx-swap.sx" lib_dir;
(* Shared templates: TW styling engine *)

View File

@@ -0,0 +1,66 @@
;; _hyperscript integration — wire _="..." attributes to compiled SX
;;
;; Entry points:
;; (hs-handler src) — compile source to callable (fn (me) ...)
;; (hs-activate! el) — activate hyperscript on a single element
;; (hs-boot!) — scan DOM, activate all _="..." elements
;; (hs-boot-subtree! root) — activate within a subtree (for HTMX swaps)
;; ── Compile source to a handler function ────────────────────────
;; Returns a function (fn (me) ...) that can be called with a DOM element.
;; Uses eval-expr-cek to turn the SX data structure into a live closure.
(define
hs-handler
(fn
(src)
(let
((sx (hs-to-sx-from-source src)))
(eval-expr-cek
(list
(quote fn)
(list (quote me))
(list
(quote let)
(list (list (quote it) nil) (list (quote event) nil))
sx))))))
;; ── Activate a single element ───────────────────────────────────
;; Reads the _="..." attribute, compiles, and executes with me=element.
;; Marks the element to avoid double-activation.
(define
hs-activate!
(fn
(el)
(let
((src (dom-get-attr el "_")))
(when
(and src (not (dom-get-data el "hs-active")))
(dom-set-data el "hs-active" true)
(let ((handler (hs-handler src))) (handler el))))))
;; ── Boot: scan entire document ──────────────────────────────────
;; Called once at page load. Finds all elements with _ attribute,
;; compiles their hyperscript, and activates them.
(define
hs-boot!
(fn
()
(let
((elements (dom-query-all (dom-body) "[_]")))
(for-each (fn (el) (hs-activate! el)) elements))))
;; ── Boot subtree: for dynamic content ───────────────────────────
;; Called after HTMX swaps or dynamic DOM insertion.
;; Only activates elements within the given root.
(define
hs-boot-subtree!
(fn
(root)
(let
((elements (dom-query-all root "[_]")))
(for-each (fn (el) (hs-activate! el)) elements))
(when (dom-get-attr root "_") (hs-activate! root))))

View File

@@ -0,0 +1,76 @@
;; _hyperscript integration tests
;; Tests hs-handler: source → callable function via eval-expr-cek.
;; DOM-dependent tests (hs-boot!, hs-activate!) deferred to Playwright.
;; ── Handler compilation ───────────────────────────────────────
(defsuite
"hs-handler-compilation"
(deftest
"handler returns a function"
(let ((h (hs-handler "log 'hello'"))) (assert= true (procedure? h))))
(deftest
"handler accepts me argument"
(let
((h (hs-handler "set x to 42")))
(h "mock-element")
(assert= 42 x)))
(deftest
"handler can return a value"
(let ((h (hs-handler "return 99"))) (assert= 99 (h "el"))))
(deftest
"handler binds me from argument"
(let
((h (hs-handler "set x to me")))
(h "my-element")
(assert= "my-element" x)))
(deftest
"handler initializes it to nil"
(let ((h (hs-handler "set x to it"))) (h "el") (assert= nil x))))
;; ── Arithmetic in handler ─────────────────────────────────────
(defsuite
"hs-handler-expressions"
(deftest
"arithmetic works in handler"
(let ((h (hs-handler "set x to 2 + 3"))) (h "el") (assert= 5 x)))
(deftest
"string concatenation via set"
(let
((h (hs-handler "set x to 'hello'")))
(h "el")
(assert= "hello" x)))
(deftest
"conditional in handler"
(let
((h (hs-handler "if true set x to 1 end")))
(h "el")
(assert= 1 x)))
(deftest
"if-else in handler"
(let
((h (hs-handler "if false set x to 1 else set x to 2 end")))
(h "el")
(assert= 2 x))))
;; ── Sequence / control flow ───────────────────────────────────
(defsuite
"hs-handler-control"
(deftest
"then chains commands"
(let
((h (hs-handler "set x to 1 then set y to 2")))
(h "el")
(assert= 1 x)
(assert= 2 y)))
(deftest
"for iterates"
(let
((h (hs-handler "set total to 0 then for n in [1, 2, 3] set total to total + n end")))
(h "el")
(assert= 6 total)))
(deftest
"repeat counts"
(let
((h (hs-handler "set count to 0 then repeat 3 times set count to count + 1 end")))
(h "el")
(assert= 3 count))))