Files
mono/shared/sx/ref/adapter-sx.sx
giles daeecab310 Restructure SX ref spec into core + selectable adapters
Split monolithic render.sx into core (tag registries, shared utils) plus
four adapter .sx files: adapter-html (server HTML strings), adapter-sx
(SX wire format), adapter-dom (browser DOM nodes), and engine (SxEngine
triggers, morphing, swaps). All adapters written in s-expressions with
platform interface declarations for JS bridge functions.

Bootstrap compiler now accepts --adapters flag to emit targeted builds:
  -a html        → server-only (1108 lines)
  -a dom,engine  → browser-only (1634 lines)
  -a html,sx     → server with SX wire (1169 lines)
  (default)      → all adapters (1800 lines)

Fixes: keyword arg i-counter desync in reduce across all adapters,
render-aware special forms (let/if/when/cond/map) in HTML adapter,
component children double-escaping, ~prefixed macro dispatch.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 11:49:44 +00:00

148 lines
5.1 KiB
Plaintext

;; ==========================================================================
;; adapter-sx.sx — SX wire format rendering adapter
;;
;; Serializes SX expressions for client-side rendering.
;; Component calls are NOT expanded — they're sent to the client as-is.
;; HTML tags are serialized as SX source text. Special forms are evaluated.
;;
;; Depends on:
;; render.sx — HTML_TAGS
;; eval.sx — eval-expr, trampoline, call-lambda, expand-macro
;; ==========================================================================
(define render-to-sx
(fn (expr env)
(let ((result (aser expr env)))
;; aser-call already returns serialized SX strings;
;; only serialize non-string values
(if (= (type-of result) "string")
result
(serialize result)))))
(define aser
(fn (expr env)
;; Evaluate for SX wire format — serialize rendering forms,
;; evaluate control flow and function calls.
(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)
"list"
(if (empty? expr)
(list)
(aser-list expr env))
:else expr)))
(define aser-list
(fn (expr env)
(let ((head (first expr))
(args (rest expr)))
(if (not (= (type-of head) "symbol"))
(map (fn (x) (aser x env)) expr)
(let ((name (symbol-name head)))
(cond
;; Fragment — serialize children
(= name "<>")
(aser-fragment args env)
;; Component call — serialize WITHOUT expanding
(starts-with? name "~")
(aser-call name args env)
;; HTML tag — serialize
(contains? HTML_TAGS name)
(aser-call name args env)
;; Special/HO forms — evaluate (produces data)
(or (special-form? name) (ho-form? name))
(aser-special name expr env)
;; Macro — expand then aser
(and (env-has? env name) (macro? (env-get env name)))
(aser (expand-macro (env-get env name) args env) env)
;; Function call — evaluate fully
:else
(let ((f (trampoline (eval-expr head env)))
(evaled-args (map (fn (a) (trampoline (eval-expr a env))) args)))
(cond
(and (callable? f) (not (lambda? f)) (not (component? f)))
(apply f evaled-args)
(lambda? f)
(trampoline (call-lambda f evaled-args env))
(component? f)
(aser-call (str "~" (component-name f)) args env)
:else (error (str "Not callable: " (inspect f)))))))))))
(define aser-fragment
(fn (children env)
;; Serialize (<> child1 child2 ...) to sx source string
(let ((parts (filter
(fn (x) (not (nil? x)))
(map (fn (c) (aser c env)) children))))
(if (empty? parts)
""
(str "(<> " (join " " (map serialize parts)) ")")))))
(define aser-call
(fn (name args env)
;; Serialize (name :key val child ...) — evaluate args but keep as sx
(let ((parts (list name)))
(reduce
(fn (state arg)
(let ((skip (get state "skip")))
(if skip
(assoc state "skip" false "i" (inc (get state "i")))
(if (and (= (type-of arg) "keyword")
(< (inc (get state "i")) (len args)))
(let ((val (aser (nth args (inc (get state "i"))) env)))
(when (not (nil? val))
(append! parts (str ":" (keyword-name arg)))
(append! parts (serialize val)))
(assoc state "skip" true "i" (inc (get state "i"))))
(let ((val (aser arg env)))
(when (not (nil? val))
(append! parts (serialize val)))
(assoc state "i" (inc (get state "i"))))))))
(dict "i" 0 "skip" false)
args)
(str "(" (join " " parts) ")"))))
;; --------------------------------------------------------------------------
;; Platform interface — SX wire adapter
;; --------------------------------------------------------------------------
;;
;; Serialization:
;; (serialize val) → SX source string representation of val
;;
;; Form classification:
;; (special-form? name) → boolean
;; (ho-form? name) → boolean
;; (aser-special name expr env) → evaluate special/HO form through aser
;;
;; From eval.sx:
;; eval-expr, trampoline, call-lambda, expand-macro
;; env-has?, env-get, callable?, lambda?, component?, macro?
;; primitive?, get-primitive, component-name
;; --------------------------------------------------------------------------