Step 18 (part 7): Extensions — render components + SX escape

Two hyperscript extensions beyond stock:

render ~component :key val [into|before|after target]
  Tokenizer: ~ + ident → component token type
  Parser: render command with kwargs and optional position
  Compiler: emits (render-to-html ~comp :key val) or
            (hs-put! (render-to-html ...) pos target)
  Bridges hyperscript flow to SX component rendering

eval (sx-expression) — SX escape hatch
  Inside eval (...), content is SX syntax (not hyperscript)
  Parser: collect-sx-source extracts balanced parens from raw source
  Compiler: sx-parse at compile time, inlines AST directly
  Result: SX runs in handler scope — hyperscript variables visible!
  Also supports string form: eval '(+ 1 2)' for backward compat

  set name to "Giles"
  set greeting to eval (str "Hello " name)  -- name is visible!

16 new tests (parser + compiler + integration).
3127/3127 full build, zero regressions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-06 09:10:28 +00:00
parent f5da2bcfd5
commit 770c7fd821
6 changed files with 251 additions and 6 deletions

View File

@@ -203,6 +203,55 @@
(assert= (quote fn) (first (nth sx 1))))))
;; ── Return and throw ─────────────────────────────────────────
(defsuite
"hs-emit-render"
(deftest
"render emits render-to-html"
(let
((sx (hs-to-sx-from-source "render ~card")))
(assert= (quote render-to-html) (first sx))
(assert= (quote ~card) (nth sx 1))))
(deftest
"render with kwargs emits keywords"
(let
((sx (hs-to-sx-from-source "render ~card :title 'Hi'")))
(assert= (quote render-to-html) (first sx))
(assert= (quote ~card) (nth sx 1))
(assert= (make-keyword "title") (nth sx 2))
(assert= "Hi" (nth sx 3))))
(deftest
"render into emits hs-put!"
(let
((sx (hs-to-sx-from-source "render ~card into #box")))
(assert= (quote hs-put!) (first sx))
(assert= (quote render-to-html) (first (nth sx 1)))
(assert= "into" (nth sx 2))))
(deftest
"component ref emits symbol"
(let
((sx (hs-to-sx (list (quote component) "~badge"))))
(assert= (quote ~badge) sx))))
;; ── Increment / decrement ────────────────────────────────────
(defsuite
"hs-emit-sx-eval"
(deftest
"eval inlines SX at compile time"
(let
((sx (hs-to-sx-from-source "set x to eval (+ 1 2)")))
(assert= (quote set!) (first sx))
(let
((val (nth sx 2)))
(assert= (quote +) (first val))
(assert= 1 (nth val 1))
(assert= 2 (nth val 2)))))
(deftest
"eval preserves variable refs"
(let
((sx (hs-to-sx-from-source "eval (log x)")))
(assert= (quote log) (first sx))
(assert= (quote x) (nth sx 1)))))
(defsuite
"hs-emit-return-throw"
(deftest
@@ -227,7 +276,6 @@
(assert= (quote hs-wait-for) (first sx))
(assert= "transitionend" (nth sx 2)))))
;; ── Increment / decrement ────────────────────────────────────
(defsuite
"hs-emit-inc-dec"
(deftest