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:
@@ -25,33 +25,38 @@
|
||||
;; Evaluate for SX wire format — serialize rendering forms,
|
||||
;; evaluate control flow and function calls.
|
||||
(set-render-active! true)
|
||||
(case (type-of expr)
|
||||
"number" expr
|
||||
"string" expr
|
||||
"boolean" expr
|
||||
"nil" nil
|
||||
(let ((result
|
||||
(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))))
|
||||
"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)
|
||||
"keyword" (keyword-name expr)
|
||||
|
||||
"list"
|
||||
(if (empty? expr)
|
||||
(list)
|
||||
(aser-list expr env))
|
||||
"list"
|
||||
(if (empty? expr)
|
||||
(list)
|
||||
(aser-list expr env))
|
||||
|
||||
;; Spread — pass through for client rendering
|
||||
"spread" expr
|
||||
;; Spread — emit attrs to nearest element provider
|
||||
"spread" (do (emit! "element-attrs" (spread-attrs expr)) nil)
|
||||
|
||||
:else expr)))
|
||||
:else expr)))
|
||||
;; Catch spread values from function calls and symbol lookups
|
||||
(if (spread? result)
|
||||
(do (emit! "element-attrs" (spread-attrs result)) nil)
|
||||
result))))
|
||||
|
||||
|
||||
(define aser-list :effects [render]
|
||||
@@ -110,7 +115,6 @@
|
||||
(fn ((children :as list) (env :as dict))
|
||||
;; Serialize (<> child1 child2 ...) to sx source string
|
||||
;; Must flatten list results (e.g. from map/filter) to avoid nested parens
|
||||
;; Spreads are filtered — fragments have no parent element to merge into
|
||||
(let ((parts (list)))
|
||||
(for-each
|
||||
(fn (c)
|
||||
@@ -118,10 +122,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)
|
||||
@@ -134,9 +138,13 @@
|
||||
;; Serialize (name :key val child ...) — evaluate args but keep as sx
|
||||
;; Uses for-each + mutable state (not reduce) so bootstrapper emits for-loops
|
||||
;; that can contain nested for-each for list flattening.
|
||||
(let ((parts (list name))
|
||||
;; Separate attrs and children so emitted spread attrs go before children.
|
||||
(let ((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
|
||||
@@ -146,30 +154,34 @@
|
||||
(< (inc i) (len args)))
|
||||
(let ((val (aser (nth args (inc i)) env)))
|
||||
(when (not (nil? val))
|
||||
(append! parts (str ":" (keyword-name arg)))
|
||||
(append! parts (serialize val)))
|
||||
(append! attr-parts (str ":" (keyword-name arg)))
|
||||
(append! attr-parts (serialize val)))
|
||||
(set! skip true)
|
||||
(set! i (inc i)))
|
||||
(let ((val (aser arg env)))
|
||||
(when (not (nil? val))
|
||||
(if (spread? val)
|
||||
;; Spread child — merge attrs as keyword args into parent element
|
||||
(if (= (type-of val) "list")
|
||||
(for-each
|
||||
(fn (k)
|
||||
(let ((v (dict-get (spread-attrs val) k)))
|
||||
(append! parts (str ":" k))
|
||||
(append! parts (serialize v))))
|
||||
(keys (spread-attrs val)))
|
||||
(if (= (type-of val) "list")
|
||||
(for-each
|
||||
(fn (item)
|
||||
(when (not (nil? item))
|
||||
(append! parts (serialize item))))
|
||||
val)
|
||||
(append! parts (serialize val)))))
|
||||
(fn (item)
|
||||
(when (not (nil? item))
|
||||
(append! child-parts (serialize item))))
|
||||
val)
|
||||
(append! child-parts (serialize val))))
|
||||
(set! i (inc i))))))
|
||||
args)
|
||||
(str "(" (join " " parts) ")"))))
|
||||
;; Collect emitted spread attrs — goes 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")
|
||||
(let ((parts (concat (list name) attr-parts child-parts)))
|
||||
(str "(" (join " " parts) ")")))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user