Add spread + collect primitives, rewrite ~cssx/tw as defcomp
New SX primitives for child-to-parent communication in the render tree: - spread type: make-spread, spread?, spread-attrs — child injects attrs onto parent element (class joins with space, style with semicolon) - collect!/collected/clear-collected! — render-time accumulation with dedup into named buckets ~cssx/tw is now a proper defcomp returning a spread value instead of a macro wrapping children. ~cssx/flush reads collected "cssx" rules and emits a single <style data-cssx> tag. All four render adapters (html, async, dom, aser) handle spread values. Both bootstraps (Python + JS) regenerated. Also fixes length→len in cssx.sx (length was never a registered primitive). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -48,6 +48,7 @@
|
||||
"string" (escape-html expr)
|
||||
"number" (escape-html (str expr))
|
||||
"raw-html" (raw-html-content expr)
|
||||
"spread" expr
|
||||
"symbol" (let ((val (async-eval expr env ctx)))
|
||||
(async-render val env ctx))
|
||||
"keyword" (escape-html (keyword-name expr))
|
||||
@@ -79,9 +80,10 @@
|
||||
(= name "raw!")
|
||||
(async-render-raw args env ctx)
|
||||
|
||||
;; Fragment
|
||||
;; Fragment (spreads filtered — no parent element)
|
||||
(= name "<>")
|
||||
(join "" (async-map-render args env ctx))
|
||||
(join "" (filter (fn (r) (not (spread? r)))
|
||||
(async-map-render args env ctx)))
|
||||
|
||||
;; html: prefix
|
||||
(starts-with? name "html:")
|
||||
@@ -167,16 +169,24 @@
|
||||
(let ((class-val (dict-get attrs "class")))
|
||||
(when (and (not (nil? class-val)) (not (= class-val false)))
|
||||
(css-class-collect! (str class-val))))
|
||||
;; Build opening tag
|
||||
(let ((opening (str "<" tag (render-attrs attrs) ">")))
|
||||
(if (contains? VOID_ELEMENTS tag)
|
||||
opening
|
||||
(let ((token (if (or (= tag "svg") (= tag "math"))
|
||||
(svg-context-set! true)
|
||||
nil))
|
||||
(child-html (join "" (async-map-render children env ctx))))
|
||||
(when token (svg-context-reset! token))
|
||||
(str opening child-html "</" tag ">")))))))
|
||||
(if (contains? VOID_ELEMENTS tag)
|
||||
(str "<" tag (render-attrs attrs) ">")
|
||||
;; Render children, collecting spreads and content separately
|
||||
(let ((token (if (or (= tag "svg") (= tag "math"))
|
||||
(svg-context-set! true)
|
||||
nil))
|
||||
(content-parts (list)))
|
||||
(for-each
|
||||
(fn (c)
|
||||
(let ((result (async-render c env ctx)))
|
||||
(if (spread? result)
|
||||
(merge-spread-attrs attrs (spread-attrs result))
|
||||
(append! content-parts result))))
|
||||
children)
|
||||
(when token (svg-context-reset! token))
|
||||
(str "<" tag (render-attrs attrs) ">"
|
||||
(join "" content-parts)
|
||||
"</" tag ">"))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
@@ -221,10 +231,17 @@
|
||||
(for-each
|
||||
(fn (p) (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil)))
|
||||
(component-params comp))
|
||||
;; Pre-render children to raw HTML (filter spreads — no parent element)
|
||||
(when (component-has-children? comp)
|
||||
(env-set! local "children"
|
||||
(make-raw-html
|
||||
(join "" (async-map-render children env ctx)))))
|
||||
(let ((parts (list)))
|
||||
(for-each
|
||||
(fn (c)
|
||||
(let ((r (async-render c env ctx)))
|
||||
(when (not (spread? r))
|
||||
(append! parts r))))
|
||||
children)
|
||||
(env-set! local "children"
|
||||
(make-raw-html (join "" parts)))))
|
||||
(async-render (component-body comp) local ctx)))))
|
||||
|
||||
|
||||
@@ -242,10 +259,17 @@
|
||||
(for-each
|
||||
(fn (p) (env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil)))
|
||||
(component-params island))
|
||||
;; Pre-render children (filter spreads — no parent element)
|
||||
(when (component-has-children? island)
|
||||
(env-set! local "children"
|
||||
(make-raw-html
|
||||
(join "" (async-map-render children env ctx)))))
|
||||
(let ((parts (list)))
|
||||
(for-each
|
||||
(fn (c)
|
||||
(let ((r (async-render c env ctx)))
|
||||
(when (not (spread? r))
|
||||
(append! parts r))))
|
||||
children)
|
||||
(env-set! local "children"
|
||||
(make-raw-html (join "" parts)))))
|
||||
(let ((body-html (async-render (component-body island) local ctx))
|
||||
(state-json (serialize-island-state kwargs)))
|
||||
(str "<span data-sx-island=\"" (escape-attr island-name) "\""
|
||||
@@ -343,11 +367,14 @@
|
||||
(async-render (nth expr 3) env ctx)
|
||||
"")))
|
||||
|
||||
;; when
|
||||
;; when — single body: pass through (spread propagates). Multi: join strings.
|
||||
(= name "when")
|
||||
(if (not (async-eval (nth expr 1) env ctx))
|
||||
""
|
||||
(join "" (async-map-render (slice expr 2) env ctx)))
|
||||
(if (= (len expr) 3)
|
||||
(async-render (nth expr 2) env ctx)
|
||||
(let ((results (async-map-render (slice expr 2) env ctx)))
|
||||
(join "" (filter (fn (r) (not (spread? r))) results)))))
|
||||
|
||||
;; cond — uses cond-scheme? (every? check) from eval.sx
|
||||
(= name "cond")
|
||||
@@ -360,43 +387,52 @@
|
||||
(= name "case")
|
||||
(async-render (async-eval expr env ctx) env ctx)
|
||||
|
||||
;; let / let*
|
||||
;; let / let* — single body: pass through. Multi: join strings.
|
||||
(or (= name "let") (= name "let*"))
|
||||
(let ((local (async-process-bindings (nth expr 1) env ctx)))
|
||||
(join "" (async-map-render (slice expr 2) local ctx)))
|
||||
(if (= (len expr) 3)
|
||||
(async-render (nth expr 2) local ctx)
|
||||
(let ((results (async-map-render (slice expr 2) local ctx)))
|
||||
(join "" (filter (fn (r) (not (spread? r))) results)))))
|
||||
|
||||
;; begin / do
|
||||
;; begin / do — single body: pass through. Multi: join strings.
|
||||
(or (= name "begin") (= name "do"))
|
||||
(join "" (async-map-render (rest expr) env ctx))
|
||||
(if (= (len expr) 2)
|
||||
(async-render (nth expr 1) env ctx)
|
||||
(let ((results (async-map-render (rest expr) env ctx)))
|
||||
(join "" (filter (fn (r) (not (spread? r))) results))))
|
||||
|
||||
;; Definition forms
|
||||
(definition-form? name)
|
||||
(do (async-eval expr env ctx) "")
|
||||
|
||||
;; map
|
||||
;; map — spreads filtered
|
||||
(= name "map")
|
||||
(let ((f (async-eval (nth expr 1) env ctx))
|
||||
(coll (async-eval (nth expr 2) env ctx)))
|
||||
(join ""
|
||||
(async-map-fn-render f coll env ctx)))
|
||||
(filter (fn (r) (not (spread? r)))
|
||||
(async-map-fn-render f coll env ctx))))
|
||||
|
||||
;; map-indexed
|
||||
;; map-indexed — spreads filtered
|
||||
(= name "map-indexed")
|
||||
(let ((f (async-eval (nth expr 1) env ctx))
|
||||
(coll (async-eval (nth expr 2) env ctx)))
|
||||
(join ""
|
||||
(async-map-indexed-fn-render f coll env ctx)))
|
||||
(filter (fn (r) (not (spread? r)))
|
||||
(async-map-indexed-fn-render f coll env ctx))))
|
||||
|
||||
;; filter — eval fully then render
|
||||
(= name "filter")
|
||||
(async-render (async-eval expr env ctx) env ctx)
|
||||
|
||||
;; for-each (render variant)
|
||||
;; for-each (render variant) — spreads filtered
|
||||
(= name "for-each")
|
||||
(let ((f (async-eval (nth expr 1) env ctx))
|
||||
(coll (async-eval (nth expr 2) env ctx)))
|
||||
(join ""
|
||||
(async-map-fn-render f coll env ctx)))
|
||||
(filter (fn (r) (not (spread? r)))
|
||||
(async-map-fn-render f coll env ctx))))
|
||||
|
||||
;; Fallback
|
||||
:else
|
||||
@@ -565,6 +601,9 @@
|
||||
|
||||
"dict" (async-aser-dict expr env ctx)
|
||||
|
||||
;; Spread — pass through for client rendering
|
||||
"spread" expr
|
||||
|
||||
"list"
|
||||
(if (empty? expr)
|
||||
(list)
|
||||
@@ -1250,6 +1289,14 @@
|
||||
;; (svg-context-reset! token) — reset SVG context
|
||||
;; (css-class-collect! val) — collect CSS classes
|
||||
;;
|
||||
;; Spread + collect (from render.sx):
|
||||
;; (spread? x) — check if spread value
|
||||
;; (spread-attrs s) — extract attrs dict from spread
|
||||
;; (merge-spread-attrs tgt src) — merge spread attrs onto target
|
||||
;; (collect! bucket value) — add to render-time accumulator
|
||||
;; (collected bucket) — read render-time accumulator
|
||||
;; (clear-collected! bucket) — clear accumulator
|
||||
;;
|
||||
;; Raw HTML:
|
||||
;; (is-raw-html? x) — check if raw HTML marker
|
||||
;; (make-raw-html s) — wrap string as raw HTML
|
||||
|
||||
Reference in New Issue
Block a user