Fix aser list flattening bug, add wire format test suite (41 tests)

The sync aser-call in adapter-sx.sx didn't flatten list results from
map/filter in positional children — serialize(list) wrapped in parens
creating ((div ...) ...) which re-parses as an invalid call. Rewrote
aser-call from reduce to for-each (bootstrapper can't nest for-each
inside reduce lambdas) and added list flattening in both aser-call
and aser-fragment.

Also adds test-aser.sx (41 tests), render-sx platform function,
expanded test-render.sx (+7 map/filter children tests), and specs
async-eval-slot-inner in adapter-async.sx.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 14:59:31 +00:00
parent c95e19dcf2
commit e843602ac9
7 changed files with 465 additions and 41 deletions

View File

@@ -1149,6 +1149,64 @@
results)))
;; --------------------------------------------------------------------------
;; async-eval-slot-inner — server-side slot expansion for aser mode
;; --------------------------------------------------------------------------
;;
;; Coordinates component expansion for server-rendered pages:
;; 1. If expression is a direct component call (~name ...), expand it
;; 2. Otherwise aser the expression, then check if result is a (~...)
;; call that should be re-expanded
;;
;; Platform primitives required:
;; (sx-parse src) — parse SX source string
;; (make-sx-expr s) — wrap as SxExpr
;; (sx-expr? x) — check if SxExpr
;; (set-expand-components!) — enable component expansion context var
(define-async async-eval-slot-inner
(fn (expr env ctx)
(let ((result
(if (and (list? expr) (not (empty? expr)))
(let ((head (first expr)))
(if (and (= (type-of head) "symbol")
(starts-with? (symbol-name head) "~"))
(let ((name (symbol-name head))
(val (if (env-has? env name) (env-get env name) nil)))
(if (component? val)
(async-aser-component val (rest expr) env ctx)
;; Islands and unknown components — fall through to aser
(async-maybe-expand-result (async-aser expr env ctx) env ctx)))
(async-maybe-expand-result (async-aser expr env ctx) env ctx)))
(async-maybe-expand-result (async-aser expr env ctx) env ctx))))
;; Normalize result to SxExpr
(if (sx-expr? result)
result
(if (nil? result)
(make-sx-expr "")
(if (string? result)
(make-sx-expr result)
(make-sx-expr (serialize result))))))))
(define-async async-maybe-expand-result
(fn (result env ctx)
;; If the aser result is a component call string like "(~foo ...)",
;; re-parse and expand it. This handles indirect component references
;; (e.g. a let binding that evaluates to a component call).
(let ((raw (if (sx-expr? result)
(trim (str result))
(if (string? result)
(trim result)
nil))))
(if (and raw (starts-with? raw "(~"))
(let ((parsed (sx-parse raw)))
(if (and parsed (not (empty? parsed)))
(async-eval-slot-inner (first parsed) env ctx)
result))
result))))
;; --------------------------------------------------------------------------
;; Platform interface — async adapter
;; --------------------------------------------------------------------------