Refactor spread to use provide/emit! internally
Spreads now emit their attrs into the nearest element's provide scope instead of requiring per-child spread? checks at every intermediate layer. emit! is tolerant (no-op when no provider), so spreads in non-element contexts silently vanish. - adapter-html: element/lake/marsh wrap children in provide, collect emitted; removed 14 spread filters from fragment, forms, components - adapter-sx: aser wraps result to catch spread values from fn calls; aser-call uses provide with attr-parts/child-parts ordering - adapter-async: same pattern for both render and aser paths - adapter-dom: added emit! in spread dispatch + provide in element rendering; kept spread? checks for reactive/island and DOM safety - platform: emit! returns NIL when no provider instead of erroring - 3 new aser tests: stored spread, nested element, silent drop Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -48,7 +48,7 @@
|
||||
"string" (escape-html expr)
|
||||
"number" (escape-html (str expr))
|
||||
"raw-html" (raw-html-content expr)
|
||||
"spread" expr
|
||||
"spread" (do (emit! "element-attrs" (spread-attrs expr)) "")
|
||||
"symbol" (let ((val (async-eval expr env ctx)))
|
||||
(async-render val env ctx))
|
||||
"keyword" (escape-html (keyword-name expr))
|
||||
@@ -80,10 +80,9 @@
|
||||
(= name "raw!")
|
||||
(async-render-raw args env ctx)
|
||||
|
||||
;; Fragment (spreads filtered — no parent element)
|
||||
;; Fragment
|
||||
(= name "<>")
|
||||
(join "" (filter (fn (r) (not (spread? r)))
|
||||
(async-map-render args env ctx)))
|
||||
(join "" (async-map-render args env ctx))
|
||||
|
||||
;; html: prefix
|
||||
(starts-with? name "html:")
|
||||
@@ -171,18 +170,19 @@
|
||||
(css-class-collect! (str class-val))))
|
||||
(if (contains? VOID_ELEMENTS tag)
|
||||
(str "<" tag (render-attrs attrs) ">")
|
||||
;; Render children, collecting spreads and content separately
|
||||
;; Provide scope for spread emit!
|
||||
(let ((token (if (or (= tag "svg") (= tag "math"))
|
||||
(svg-context-set! true)
|
||||
nil))
|
||||
(content-parts (list)))
|
||||
(provide-push! "element-attrs" nil)
|
||||
(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))))
|
||||
(fn (c) (append! content-parts (async-render c env ctx)))
|
||||
children)
|
||||
(for-each
|
||||
(fn (spread-dict) (merge-spread-attrs attrs spread-dict))
|
||||
(emitted "element-attrs"))
|
||||
(provide-pop! "element-attrs")
|
||||
(when token (svg-context-reset! token))
|
||||
(str "<" tag (render-attrs attrs) ">"
|
||||
(join "" content-parts)
|
||||
@@ -231,14 +231,11 @@
|
||||
(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)
|
||||
;; Pre-render children to raw HTML
|
||||
(when (component-has-children? comp)
|
||||
(let ((parts (list)))
|
||||
(for-each
|
||||
(fn (c)
|
||||
(let ((r (async-render c env ctx)))
|
||||
(when (not (spread? r))
|
||||
(append! parts r))))
|
||||
(fn (c) (append! parts (async-render c env ctx)))
|
||||
children)
|
||||
(env-set! local "children"
|
||||
(make-raw-html (join "" parts)))))
|
||||
@@ -259,14 +256,11 @@
|
||||
(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)
|
||||
;; Pre-render children
|
||||
(when (component-has-children? island)
|
||||
(let ((parts (list)))
|
||||
(for-each
|
||||
(fn (c)
|
||||
(let ((r (async-render c env ctx)))
|
||||
(when (not (spread? r))
|
||||
(append! parts r))))
|
||||
(fn (c) (append! parts (async-render c env ctx)))
|
||||
children)
|
||||
(env-set! local "children"
|
||||
(make-raw-html (join "" parts)))))
|
||||
@@ -367,14 +361,13 @@
|
||||
(async-render (nth expr 3) env ctx)
|
||||
"")))
|
||||
|
||||
;; when — single body: pass through (spread propagates). Multi: join strings.
|
||||
;; when — single body: pass through. Multi: join strings.
|
||||
(= name "when")
|
||||
(if (not (async-eval (nth expr 1) 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)))))
|
||||
(join "" (async-map-render (slice expr 2) env ctx))))
|
||||
|
||||
;; cond — uses cond-scheme? (every? check) from eval.sx
|
||||
(= name "cond")
|
||||
@@ -392,47 +385,39 @@
|
||||
(let ((local (async-process-bindings (nth expr 1) env 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)))))
|
||||
(join "" (async-map-render (slice expr 2) local ctx))))
|
||||
|
||||
;; begin / do — single body: pass through. Multi: join strings.
|
||||
(or (= name "begin") (= name "do"))
|
||||
(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))))
|
||||
(join "" (async-map-render (rest expr) env ctx)))
|
||||
|
||||
;; Definition forms
|
||||
(definition-form? name)
|
||||
(do (async-eval expr env ctx) "")
|
||||
|
||||
;; map — spreads filtered
|
||||
;; map
|
||||
(= name "map")
|
||||
(let ((f (async-eval (nth expr 1) env ctx))
|
||||
(coll (async-eval (nth expr 2) env ctx)))
|
||||
(join ""
|
||||
(filter (fn (r) (not (spread? r)))
|
||||
(async-map-fn-render f coll env ctx))))
|
||||
(join "" (async-map-fn-render f coll env ctx)))
|
||||
|
||||
;; map-indexed — spreads filtered
|
||||
;; map-indexed
|
||||
(= name "map-indexed")
|
||||
(let ((f (async-eval (nth expr 1) env ctx))
|
||||
(coll (async-eval (nth expr 2) env ctx)))
|
||||
(join ""
|
||||
(filter (fn (r) (not (spread? r)))
|
||||
(async-map-indexed-fn-render f coll env ctx))))
|
||||
(join "" (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) — spreads filtered
|
||||
;; for-each (render variant)
|
||||
(= name "for-each")
|
||||
(let ((f (async-eval (nth expr 1) env ctx))
|
||||
(coll (async-eval (nth expr 2) env ctx)))
|
||||
(join ""
|
||||
(filter (fn (r) (not (spread? r)))
|
||||
(async-map-fn-render f coll env ctx))))
|
||||
(join "" (async-map-fn-render f coll env ctx)))
|
||||
|
||||
;; provide — render-time dynamic scope
|
||||
(= name "provide")
|
||||
@@ -443,8 +428,7 @@
|
||||
(provide-push! prov-name prov-val)
|
||||
(let ((result (if (= body-count 1)
|
||||
(async-render (nth expr body-start) env ctx)
|
||||
(let ((results (async-map-render (slice expr body-start) env ctx)))
|
||||
(join "" (filter (fn (r) (not (spread? r))) results))))))
|
||||
(join "" (async-map-render (slice expr body-start) env ctx)))))
|
||||
(provide-pop! prov-name)
|
||||
result))
|
||||
|
||||
@@ -595,35 +579,34 @@
|
||||
|
||||
(define-async async-aser :effects [render io]
|
||||
(fn (expr (env :as dict) ctx)
|
||||
(case (type-of expr)
|
||||
"number" expr
|
||||
"string" expr
|
||||
"boolean" expr
|
||||
"nil" nil
|
||||
|
||||
"symbol"
|
||||
(let ((name (symbol-name expr)))
|
||||
(cond
|
||||
(env-has? env name) (env-get env name)
|
||||
(primitive? name) (get-primitive name)
|
||||
(= name "true") true
|
||||
(= name "false") false
|
||||
(= name "nil") nil
|
||||
:else (error (str "Undefined symbol: " name))))
|
||||
|
||||
"keyword" (keyword-name expr)
|
||||
|
||||
"dict" (async-aser-dict expr env ctx)
|
||||
|
||||
;; Spread — pass through for client rendering
|
||||
"spread" expr
|
||||
|
||||
"list"
|
||||
(if (empty? expr)
|
||||
(list)
|
||||
(async-aser-list expr env ctx))
|
||||
|
||||
:else expr)))
|
||||
(let ((t (type-of expr))
|
||||
(result nil))
|
||||
(cond
|
||||
(= t "number") (set! result expr)
|
||||
(= t "string") (set! result expr)
|
||||
(= t "boolean") (set! result expr)
|
||||
(= t "nil") (set! result nil)
|
||||
(= t "symbol")
|
||||
(let ((name (symbol-name expr)))
|
||||
(set! result
|
||||
(cond
|
||||
(env-has? env name) (env-get env name)
|
||||
(primitive? name) (get-primitive name)
|
||||
(= name "true") true
|
||||
(= name "false") false
|
||||
(= name "nil") nil
|
||||
:else (error (str "Undefined symbol: " name)))))
|
||||
(= t "keyword") (set! result (keyword-name expr))
|
||||
(= t "dict") (set! result (async-aser-dict expr env ctx))
|
||||
;; Spread — emit attrs to nearest element provider
|
||||
(= t "spread") (do (emit! "element-attrs" (spread-attrs expr))
|
||||
(set! result nil))
|
||||
(= t "list") (set! result (if (empty? expr) (list) (async-aser-list expr env ctx)))
|
||||
:else (set! result expr))
|
||||
;; Catch spread values from function calls and symbol lookups
|
||||
(if (spread? result)
|
||||
(do (emit! "element-attrs" (spread-attrs result)) nil)
|
||||
result))))
|
||||
|
||||
|
||||
(define-async async-aser-dict :effects [render io]
|
||||
@@ -775,7 +758,6 @@
|
||||
|
||||
(define-async async-aser-fragment :effects [render io]
|
||||
(fn ((children :as list) (env :as dict) ctx)
|
||||
;; Spreads are filtered — fragments have no parent element to merge into
|
||||
(let ((parts (list)))
|
||||
(for-each
|
||||
(fn (c)
|
||||
@@ -783,10 +765,10 @@
|
||||
(if (= (type-of result) "list")
|
||||
(for-each
|
||||
(fn (item)
|
||||
(when (and (not (nil? item)) (not (spread? item)))
|
||||
(when (not (nil? item))
|
||||
(append! parts (serialize item))))
|
||||
result)
|
||||
(when (and (not (nil? result)) (not (spread? result)))
|
||||
(when (not (nil? result))
|
||||
(append! parts (serialize result))))))
|
||||
children)
|
||||
(if (empty? parts)
|
||||
@@ -860,9 +842,12 @@
|
||||
(let ((token (if (or (= name "svg") (= name "math"))
|
||||
(svg-context-set! true)
|
||||
nil))
|
||||
(parts (list name))
|
||||
(attr-parts (list))
|
||||
(child-parts (list))
|
||||
(skip false)
|
||||
(i 0))
|
||||
;; Provide scope for spread emit!
|
||||
(provide-push! "element-attrs" nil)
|
||||
(for-each
|
||||
(fn (arg)
|
||||
(if skip
|
||||
@@ -872,39 +857,43 @@
|
||||
(< (inc i) (len args)))
|
||||
(let ((val (async-aser (nth args (inc i)) env ctx)))
|
||||
(when (not (nil? val))
|
||||
(append! parts (str ":" (keyword-name arg)))
|
||||
(append! attr-parts (str ":" (keyword-name arg)))
|
||||
(if (= (type-of val) "list")
|
||||
(let ((live (filter (fn (v) (not (nil? v))) val)))
|
||||
(if (empty? live)
|
||||
(append! parts "nil")
|
||||
(append! attr-parts "nil")
|
||||
(let ((items (map serialize live)))
|
||||
(if (some (fn (v) (sx-expr? v)) live)
|
||||
(append! parts (str "(<> " (join " " items) ")"))
|
||||
(append! parts (str "(list " (join " " items) ")"))))))
|
||||
(append! parts (serialize val))))
|
||||
(append! attr-parts (str "(<> " (join " " items) ")"))
|
||||
(append! attr-parts (str "(list " (join " " items) ")"))))))
|
||||
(append! attr-parts (serialize val))))
|
||||
(set! skip true)
|
||||
(set! i (inc i)))
|
||||
(let ((result (async-aser arg env ctx)))
|
||||
(when (not (nil? result))
|
||||
(if (spread? result)
|
||||
;; Spread child — merge attrs as keyword args into parent element
|
||||
(if (= (type-of result) "list")
|
||||
(for-each
|
||||
(fn (k)
|
||||
(let ((v (dict-get (spread-attrs result) k)))
|
||||
(append! parts (str ":" k))
|
||||
(append! parts (serialize v))))
|
||||
(keys (spread-attrs result)))
|
||||
(if (= (type-of result) "list")
|
||||
(for-each
|
||||
(fn (item)
|
||||
(when (not (nil? item))
|
||||
(append! parts (serialize item))))
|
||||
result)
|
||||
(append! parts (serialize result)))))
|
||||
(fn (item)
|
||||
(when (not (nil? item))
|
||||
(append! child-parts (serialize item))))
|
||||
result)
|
||||
(append! child-parts (serialize result))))
|
||||
(set! i (inc i))))))
|
||||
args)
|
||||
;; Collect emitted spread attrs — after explicit attrs, before children
|
||||
(for-each
|
||||
(fn (spread-dict)
|
||||
(for-each
|
||||
(fn (k)
|
||||
(let ((v (dict-get spread-dict k)))
|
||||
(append! attr-parts (str ":" k))
|
||||
(append! attr-parts (serialize v))))
|
||||
(keys spread-dict)))
|
||||
(emitted "element-attrs"))
|
||||
(provide-pop! "element-attrs")
|
||||
(when token (svg-context-reset! token))
|
||||
(make-sx-expr (str "(" (join " " parts) ")")))))
|
||||
(let ((parts (concat (list name) attr-parts child-parts)))
|
||||
(make-sx-expr (str "(" (join " " parts) ")"))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user