;; ========================================================================== ;; 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 ;; --------------------------------------------------------------------------