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:
@@ -30,6 +30,8 @@
|
||||
"keyword" (escape-html (keyword-name expr))
|
||||
;; Raw HTML passthrough
|
||||
"raw-html" (raw-html-content expr)
|
||||
;; Spread — pass through as-is (parent element will merge attrs)
|
||||
"spread" expr
|
||||
;; Everything else — evaluate first
|
||||
:else (render-value-to-html (trampoline (eval-expr expr env)) env))))
|
||||
|
||||
@@ -42,6 +44,7 @@
|
||||
"boolean" (if val "true" "false")
|
||||
"list" (render-list-to-html val env)
|
||||
"raw-html" (raw-html-content val)
|
||||
"spread" val
|
||||
:else (escape-html (str val)))))
|
||||
|
||||
|
||||
@@ -70,14 +73,16 @@
|
||||
""
|
||||
(let ((head (first expr)))
|
||||
(if (not (= (type-of head) "symbol"))
|
||||
;; Data list — render each item
|
||||
(join "" (map (fn (x) (render-value-to-html x env)) expr))
|
||||
;; Data list — render each item (spreads filtered — no parent element)
|
||||
(join "" (filter (fn (x) (not (spread? x)))
|
||||
(map (fn (x) (render-value-to-html x env)) expr)))
|
||||
(let ((name (symbol-name head))
|
||||
(args (rest expr)))
|
||||
(cond
|
||||
;; Fragment
|
||||
;; Fragment (spreads filtered — no parent element)
|
||||
(= name "<>")
|
||||
(join "" (map (fn (x) (render-to-html x env)) args))
|
||||
(join "" (filter (fn (x) (not (spread? x)))
|
||||
(map (fn (x) (render-to-html x env)) args)))
|
||||
|
||||
;; Raw HTML passthrough
|
||||
(= name "raw!")
|
||||
@@ -147,14 +152,15 @@
|
||||
(render-to-html (nth expr 3) env)
|
||||
"")))
|
||||
|
||||
;; when
|
||||
;; when — single body: pass through (spread propagates). Multi: join strings.
|
||||
(= name "when")
|
||||
(if (not (trampoline (eval-expr (nth expr 1) env)))
|
||||
""
|
||||
(join ""
|
||||
(map
|
||||
(fn (i) (render-to-html (nth expr i) env))
|
||||
(range 2 (len expr)))))
|
||||
(if (= (len expr) 3)
|
||||
(render-to-html (nth expr 2) env)
|
||||
(let ((results (map (fn (i) (render-to-html (nth expr i) env))
|
||||
(range 2 (len expr)))))
|
||||
(join "" (filter (fn (r) (not (spread? r))) results)))))
|
||||
|
||||
;; cond
|
||||
(= name "cond")
|
||||
@@ -167,64 +173,69 @@
|
||||
(= name "case")
|
||||
(render-to-html (trampoline (eval-expr expr env)) env)
|
||||
|
||||
;; let / let*
|
||||
;; let / let* — single body: pass through. Multi: join strings.
|
||||
(or (= name "let") (= name "let*"))
|
||||
(let ((local (process-bindings (nth expr 1) env)))
|
||||
(join ""
|
||||
(map
|
||||
(fn (i) (render-to-html (nth expr i) local))
|
||||
(range 2 (len expr)))))
|
||||
(if (= (len expr) 3)
|
||||
(render-to-html (nth expr 2) local)
|
||||
(let ((results (map (fn (i) (render-to-html (nth expr i) local))
|
||||
(range 2 (len expr)))))
|
||||
(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 ""
|
||||
(map
|
||||
(fn (i) (render-to-html (nth expr i) env))
|
||||
(range 1 (len expr))))
|
||||
(if (= (len expr) 2)
|
||||
(render-to-html (nth expr 1) env)
|
||||
(let ((results (map (fn (i) (render-to-html (nth expr i) env))
|
||||
(range 1 (len expr)))))
|
||||
(join "" (filter (fn (r) (not (spread? r))) results))))
|
||||
|
||||
;; Definition forms — eval for side effects
|
||||
(definition-form? name)
|
||||
(do (trampoline (eval-expr expr env)) "")
|
||||
|
||||
;; map
|
||||
;; map — spreads filtered (no parent element in list context)
|
||||
(= name "map")
|
||||
(let ((f (trampoline (eval-expr (nth expr 1) env)))
|
||||
(coll (trampoline (eval-expr (nth expr 2) env))))
|
||||
(join ""
|
||||
(map
|
||||
(fn (item)
|
||||
(if (lambda? f)
|
||||
(render-lambda-html f (list item) env)
|
||||
(render-to-html (apply f (list item)) env)))
|
||||
coll)))
|
||||
(filter (fn (r) (not (spread? r)))
|
||||
(map
|
||||
(fn (item)
|
||||
(if (lambda? f)
|
||||
(render-lambda-html f (list item) env)
|
||||
(render-to-html (apply f (list item)) env)))
|
||||
coll))))
|
||||
|
||||
;; map-indexed
|
||||
;; map-indexed — spreads filtered
|
||||
(= name "map-indexed")
|
||||
(let ((f (trampoline (eval-expr (nth expr 1) env)))
|
||||
(coll (trampoline (eval-expr (nth expr 2) env))))
|
||||
(join ""
|
||||
(map-indexed
|
||||
(fn (i item)
|
||||
(if (lambda? f)
|
||||
(render-lambda-html f (list i item) env)
|
||||
(render-to-html (apply f (list i item)) env)))
|
||||
coll)))
|
||||
(filter (fn (r) (not (spread? r)))
|
||||
(map-indexed
|
||||
(fn (i item)
|
||||
(if (lambda? f)
|
||||
(render-lambda-html f (list i item) env)
|
||||
(render-to-html (apply f (list i item)) env)))
|
||||
coll))))
|
||||
|
||||
;; filter — evaluate fully then render
|
||||
(= name "filter")
|
||||
(render-to-html (trampoline (eval-expr expr env)) env)
|
||||
|
||||
;; for-each (render variant)
|
||||
;; for-each (render variant) — spreads filtered
|
||||
(= name "for-each")
|
||||
(let ((f (trampoline (eval-expr (nth expr 1) env)))
|
||||
(coll (trampoline (eval-expr (nth expr 2) env))))
|
||||
(join ""
|
||||
(map
|
||||
(fn (item)
|
||||
(if (lambda? f)
|
||||
(render-lambda-html f (list item) env)
|
||||
(render-to-html (apply f (list item)) env)))
|
||||
coll)))
|
||||
(filter (fn (r) (not (spread? r)))
|
||||
(map
|
||||
(fn (item)
|
||||
(if (lambda? f)
|
||||
(render-lambda-html f (list item) env)
|
||||
(render-to-html (apply f (list item)) env)))
|
||||
coll))))
|
||||
|
||||
;; Fallback
|
||||
:else
|
||||
@@ -281,10 +292,17 @@
|
||||
(env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil)))
|
||||
(component-params comp))
|
||||
;; If component accepts children, pre-render them to raw HTML
|
||||
;; Spread values are filtered out (no parent element to merge onto)
|
||||
(when (component-has-children? comp)
|
||||
(env-set! local "children"
|
||||
(make-raw-html
|
||||
(join "" (map (fn (c) (render-to-html c env)) children)))))
|
||||
(let ((parts (list)))
|
||||
(for-each
|
||||
(fn (c)
|
||||
(let ((r (render-to-html c env)))
|
||||
(when (not (spread? r))
|
||||
(append! parts r))))
|
||||
children)
|
||||
(env-set! local "children"
|
||||
(make-raw-html (join "" parts)))))
|
||||
(render-to-html (component-body comp) local)))))
|
||||
|
||||
|
||||
@@ -294,12 +312,19 @@
|
||||
(attrs (first parsed))
|
||||
(children (nth parsed 1))
|
||||
(is-void (contains? VOID_ELEMENTS tag)))
|
||||
(str "<" tag
|
||||
(render-attrs attrs)
|
||||
(if is-void
|
||||
" />"
|
||||
(str ">"
|
||||
(join "" (map (fn (c) (render-to-html c env)) children))
|
||||
(if is-void
|
||||
(str "<" tag (render-attrs attrs) " />")
|
||||
;; Render children, collecting spreads and content separately
|
||||
(let ((content-parts (list)))
|
||||
(for-each
|
||||
(fn (c)
|
||||
(let ((result (render-to-html c env)))
|
||||
(if (spread? result)
|
||||
(merge-spread-attrs attrs (spread-attrs result))
|
||||
(append! content-parts result))))
|
||||
children)
|
||||
(str "<" tag (render-attrs attrs) ">"
|
||||
(join "" content-parts)
|
||||
"</" tag ">"))))))
|
||||
|
||||
|
||||
@@ -335,9 +360,19 @@
|
||||
(assoc state "i" (inc (get state "i"))))))))
|
||||
(dict "i" 0 "skip" false)
|
||||
args)
|
||||
(str "<" lake-tag " data-sx-lake=\"" (escape-attr (or lake-id "")) "\">"
|
||||
(join "" (map (fn (c) (render-to-html c env)) children))
|
||||
"</" lake-tag ">"))))
|
||||
;; Render children, handling spreads
|
||||
(let ((lake-attrs (dict "data-sx-lake" (or lake-id "")))
|
||||
(content-parts (list)))
|
||||
(for-each
|
||||
(fn (c)
|
||||
(let ((result (render-to-html c env)))
|
||||
(if (spread? result)
|
||||
(merge-spread-attrs lake-attrs (spread-attrs result))
|
||||
(append! content-parts result))))
|
||||
children)
|
||||
(str "<" lake-tag (render-attrs lake-attrs) ">"
|
||||
(join "" content-parts)
|
||||
"</" lake-tag ">")))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
@@ -375,9 +410,19 @@
|
||||
(assoc state "i" (inc (get state "i"))))))))
|
||||
(dict "i" 0 "skip" false)
|
||||
args)
|
||||
(str "<" marsh-tag " data-sx-marsh=\"" (escape-attr (or marsh-id "")) "\">"
|
||||
(join "" (map (fn (c) (render-to-html c env)) children))
|
||||
"</" marsh-tag ">"))))
|
||||
;; Render children, handling spreads
|
||||
(let ((marsh-attrs (dict "data-sx-marsh" (or marsh-id "")))
|
||||
(content-parts (list)))
|
||||
(for-each
|
||||
(fn (c)
|
||||
(let ((result (render-to-html c env)))
|
||||
(if (spread? result)
|
||||
(merge-spread-attrs marsh-attrs (spread-attrs result))
|
||||
(append! content-parts result))))
|
||||
children)
|
||||
(str "<" marsh-tag (render-attrs marsh-attrs) ">"
|
||||
(join "" content-parts)
|
||||
"</" marsh-tag ">")))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
@@ -427,10 +472,17 @@
|
||||
(component-params island))
|
||||
|
||||
;; If island accepts children, pre-render them to raw HTML
|
||||
;; Spread values filtered out (no parent element)
|
||||
(when (component-has-children? island)
|
||||
(env-set! local "children"
|
||||
(make-raw-html
|
||||
(join "" (map (fn (c) (render-to-html c env)) children)))))
|
||||
(let ((parts (list)))
|
||||
(for-each
|
||||
(fn (c)
|
||||
(let ((r (render-to-html c env)))
|
||||
(when (not (spread? r))
|
||||
(append! parts r))))
|
||||
children)
|
||||
(env-set! local "children"
|
||||
(make-raw-html (join "" parts)))))
|
||||
|
||||
;; Render the island body as HTML
|
||||
(let ((body-html (render-to-html (component-body island) local))
|
||||
|
||||
Reference in New Issue
Block a user