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:
@@ -524,6 +524,45 @@
|
||||
(map make-symbol (nth ast 2))
|
||||
(hs-to-sx (nth ast 3)))))
|
||||
((= head (quote behavior)) (emit-behavior ast))
|
||||
((= head (quote sx-eval))
|
||||
(let
|
||||
((src (nth ast 1)))
|
||||
(if
|
||||
(string? src)
|
||||
(first (sx-parse src))
|
||||
(list (quote cek-eval) (hs-to-sx src)))))
|
||||
((= head (quote component)) (make-symbol (nth ast 1)))
|
||||
((= head (quote render))
|
||||
(let
|
||||
((comp-raw (nth ast 1))
|
||||
(kwargs (nth ast 2))
|
||||
(pos (if (> (len ast) 3) (nth ast 3) nil))
|
||||
(target
|
||||
(if (> (len ast) 4) (hs-to-sx (nth ast 4)) nil)))
|
||||
(let
|
||||
((comp (if (string? comp-raw) (make-symbol comp-raw) (hs-to-sx comp-raw))))
|
||||
(define
|
||||
emit-kw-pairs
|
||||
(fn
|
||||
(pairs)
|
||||
(if
|
||||
(< (len pairs) 2)
|
||||
(list)
|
||||
(cons
|
||||
(make-keyword (first pairs))
|
||||
(cons
|
||||
(hs-to-sx (nth pairs 1))
|
||||
(emit-kw-pairs (rest (rest pairs))))))))
|
||||
(let
|
||||
((render-call (cons (quote render-to-html) (cons comp (emit-kw-pairs kwargs)))))
|
||||
(if
|
||||
pos
|
||||
(list
|
||||
(quote hs-put!)
|
||||
render-call
|
||||
pos
|
||||
(if target target (quote me)))
|
||||
render-call)))))
|
||||
(true ast))))))))
|
||||
|
||||
;; ── Convenience: source → SX ─────────────────────────────────
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
(define
|
||||
hs-parse
|
||||
(fn
|
||||
(tokens)
|
||||
(tokens src)
|
||||
(let
|
||||
((p 0) (tok-len (len tokens)))
|
||||
(define tp (fn () (if (< p tok-len) (nth tokens p) nil)))
|
||||
@@ -116,6 +116,13 @@
|
||||
(do (adv!) (list (quote not) (parse-expr))))
|
||||
((and (= typ "keyword") (= val "no"))
|
||||
(do (adv!) (list (quote no) (parse-expr))))
|
||||
((and (= typ "keyword") (= val "eval"))
|
||||
(do
|
||||
(adv!)
|
||||
(if
|
||||
(= (tp-type) "paren-open")
|
||||
(list (quote sx-eval) (collect-sx-source))
|
||||
(list (quote sx-eval) (parse-expr)))))
|
||||
((and (= typ "keyword") (= val "the"))
|
||||
(do (adv!) (parse-the-expr)))
|
||||
((and (= typ "keyword") (= val "me"))
|
||||
@@ -170,6 +177,8 @@
|
||||
(let
|
||||
((operand (parse-atom)))
|
||||
(list (quote -) 0 operand))))
|
||||
((= typ "component")
|
||||
(do (adv!) (list (quote component) val)))
|
||||
(true nil)))))
|
||||
(define
|
||||
parse-poss
|
||||
@@ -702,7 +711,68 @@
|
||||
(let
|
||||
((body (parse-feat-body)))
|
||||
(match-kw "end")
|
||||
(list (quote behavior) name params body)))))))
|
||||
(list (quote behavior) name params body))))))
|
||||
(define
|
||||
parse-render-kwargs
|
||||
(fn
|
||||
()
|
||||
(define
|
||||
collect-kw
|
||||
(fn
|
||||
(acc)
|
||||
(if
|
||||
(= (tp-type) "local")
|
||||
(let
|
||||
((key (tp-val)))
|
||||
(adv!)
|
||||
(let
|
||||
((val (parse-expr)))
|
||||
(collect-kw (append acc (list key val)))))
|
||||
acc)))
|
||||
(collect-kw (list))))
|
||||
(define
|
||||
parse-render-cmd
|
||||
(fn
|
||||
()
|
||||
(let
|
||||
((comp (cond ((= (tp-type) "component") (let ((name (tp-val))) (adv!) name)) ((= (tp-type) "paren-open") (do (adv!) (let ((expr (parse-expr))) (if (= (tp-type) "paren-close") (adv!) nil) expr))) (true (let ((name (tp-val))) (adv!) name)))))
|
||||
(let
|
||||
((kwargs (parse-render-kwargs)))
|
||||
(let
|
||||
((pos (cond ((match-kw "into") "into") ((match-kw "before") "before") ((match-kw "after") "after") (true nil))))
|
||||
(let
|
||||
((target (if pos (parse-expr) nil)))
|
||||
(if
|
||||
pos
|
||||
(list (quote render) comp kwargs pos target)
|
||||
(list (quote render) comp kwargs))))))))
|
||||
(define
|
||||
collect-sx-source
|
||||
(fn
|
||||
()
|
||||
(let
|
||||
((start-pos (get (tp) "pos")))
|
||||
(adv!)
|
||||
(define
|
||||
skip-to-close
|
||||
(fn
|
||||
(depth)
|
||||
(cond
|
||||
((at-end?) start-pos)
|
||||
((= (tp-type) "paren-open")
|
||||
(do (adv!) (skip-to-close (+ depth 1))))
|
||||
((= (tp-type) "paren-close")
|
||||
(if
|
||||
(= depth 0)
|
||||
(let
|
||||
((end-pos (+ (get (tp) "pos") 1)))
|
||||
(adv!)
|
||||
end-pos)
|
||||
(do (adv!) (skip-to-close (- depth 1)))))
|
||||
(true (do (adv!) (skip-to-close depth))))))
|
||||
(let
|
||||
((end-pos (skip-to-close 0)))
|
||||
(substring src start-pos end-pos))))))
|
||||
(define
|
||||
parse-cmd
|
||||
(fn
|
||||
@@ -768,6 +838,8 @@
|
||||
(do (adv!) (parse-install-cmd)))
|
||||
((and (= typ "keyword") (= val "measure"))
|
||||
(do (adv!) (parse-measure-cmd)))
|
||||
((and (= typ "keyword") (= val "render"))
|
||||
(do (adv!) (parse-render-cmd)))
|
||||
(true (parse-expr))))))
|
||||
(define
|
||||
parse-cmd-list
|
||||
@@ -853,4 +925,4 @@
|
||||
(cons (quote do) features))))))
|
||||
|
||||
;; ── Convenience: source string → AST ─────────────────────────────
|
||||
(define hs-compile (fn (src) (hs-parse (hs-tokenize src))))
|
||||
(define hs-compile (fn (src) (hs-parse (hs-tokenize src) src)))
|
||||
|
||||
@@ -137,7 +137,9 @@
|
||||
"install"
|
||||
"measure"
|
||||
"behavior"
|
||||
"called"))
|
||||
"called"
|
||||
"render"
|
||||
"eval"))
|
||||
|
||||
(define hs-keyword? (fn (word) (some (fn (k) (= k word)) hs-keywords)))
|
||||
|
||||
@@ -408,6 +410,14 @@
|
||||
(hs-advance! 1)
|
||||
(hs-emit! "attr" (read-ident pos) start)
|
||||
(scan!))
|
||||
(and
|
||||
(= ch "~")
|
||||
(< (+ pos 1) src-len)
|
||||
(hs-letter? (hs-peek 1)))
|
||||
(do
|
||||
(hs-advance! 1)
|
||||
(hs-emit! "component" (str "~" (read-ident pos)) start)
|
||||
(scan!))
|
||||
(and
|
||||
(= ch "*")
|
||||
(< (+ pos 1) src-len)
|
||||
|
||||
Reference in New Issue
Block a user