The entire parallel CSS system (StyleValue type, style dictionary, keyword atom resolver, content-addressed class generation, runtime CSS injection, localStorage caching) was built but never adopted — the codebase already uses :class strings with defcomp components for all styling. Remove ~3,000 lines of unused infrastructure. Deleted: - cssx.sx spec module (317 lines) - style_dict.py (782 lines) and style_resolver.py (254 lines) - StyleValue type, defkeyframes special form, build-keyframes platform fn - Style dict JSON delivery (<script type="text/sx-styles">), cookies, localStorage - css/merge-styles primitives, inject-style-value, fnv1a-hash platform interface Simplified: - defstyle now binds any value (string, function) — no StyleValue type needed - render-attrs no longer special-cases :style StyleValue → class conversion - Boot sequence skips style dict init step Preserved: - tw.css parsing + CSS class delivery (SX-Css headers, <style id="sx-css">) - All component infrastructure (defcomp, caching, bundling, deps) - defstyle as a binding form for reusable class strings Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
312 lines
11 KiB
Plaintext
312 lines
11 KiB
Plaintext
;; ==========================================================================
|
|
;; adapter-html.sx — HTML string rendering adapter
|
|
;;
|
|
;; Renders evaluated SX expressions to HTML strings. Used server-side.
|
|
;;
|
|
;; Depends on:
|
|
;; render.sx — HTML_TAGS, VOID_ELEMENTS, BOOLEAN_ATTRS,
|
|
;; parse-element-args, render-attrs, definition-form?
|
|
;; eval.sx — eval-expr, trampoline, expand-macro, process-bindings,
|
|
;; eval-cond, env-has?, env-get, env-set!, env-merge,
|
|
;; lambda?, component?, macro?,
|
|
;; lambda-closure, lambda-params, lambda-body
|
|
;; ==========================================================================
|
|
|
|
|
|
(define render-to-html
|
|
(fn (expr env)
|
|
(case (type-of expr)
|
|
;; Literals — render directly
|
|
"nil" ""
|
|
"string" (escape-html expr)
|
|
"number" (str expr)
|
|
"boolean" (if expr "true" "false")
|
|
;; List — dispatch to render-list which handles HTML tags, special forms, etc.
|
|
"list" (if (empty? expr) "" (render-list-to-html expr env))
|
|
;; Symbol — evaluate then render
|
|
"symbol" (render-value-to-html (trampoline (eval-expr expr env)) env)
|
|
;; Keyword — render as text
|
|
"keyword" (escape-html (keyword-name expr))
|
|
;; Raw HTML passthrough
|
|
"raw-html" (raw-html-content expr)
|
|
;; Everything else — evaluate first
|
|
:else (render-value-to-html (trampoline (eval-expr expr env)) env))))
|
|
|
|
(define render-value-to-html
|
|
(fn (val env)
|
|
(case (type-of val)
|
|
"nil" ""
|
|
"string" (escape-html val)
|
|
"number" (str val)
|
|
"boolean" (if val "true" "false")
|
|
"list" (render-list-to-html val env)
|
|
"raw-html" (raw-html-content val)
|
|
:else (escape-html (str val)))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Render-aware form classification
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define RENDER_HTML_FORMS
|
|
(list "if" "when" "cond" "case" "let" "let*" "begin" "do"
|
|
"define" "defcomp" "defmacro" "defstyle" "defhandler"
|
|
"map" "map-indexed" "filter" "for-each"))
|
|
|
|
(define render-html-form?
|
|
(fn (name)
|
|
(contains? RENDER_HTML_FORMS name)))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; render-list-to-html — dispatch on list head
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define render-list-to-html
|
|
(fn (expr env)
|
|
(if (empty? expr)
|
|
""
|
|
(let ((head (first expr)))
|
|
(if (not (= (type-of head) "symbol"))
|
|
;; Data list — render each item
|
|
(join "" (map (fn (x) (render-value-to-html x env)) expr))
|
|
(let ((name (symbol-name head))
|
|
(args (rest expr)))
|
|
(cond
|
|
;; Fragment
|
|
(= name "<>")
|
|
(join "" (map (fn (x) (render-to-html x env)) args))
|
|
|
|
;; Raw HTML passthrough
|
|
(= name "raw!")
|
|
(join "" (map (fn (x) (str (trampoline (eval-expr x env)))) args))
|
|
|
|
;; HTML tag
|
|
(contains? HTML_TAGS name)
|
|
(render-html-element name args env)
|
|
|
|
;; Component or macro call (~name)
|
|
(starts-with? name "~")
|
|
(let ((val (env-get env name)))
|
|
(cond
|
|
(component? val)
|
|
(render-html-component val args env)
|
|
(macro? val)
|
|
(render-to-html
|
|
(expand-macro val args env)
|
|
env)
|
|
:else
|
|
(error (str "Unknown component: " name))))
|
|
|
|
;; Render-aware special forms
|
|
(render-html-form? name)
|
|
(dispatch-html-form name expr env)
|
|
|
|
;; Macro expansion
|
|
(and (env-has? env name) (macro? (env-get env name)))
|
|
(render-to-html
|
|
(expand-macro (env-get env name) args env)
|
|
env)
|
|
|
|
;; Fallback — evaluate then render result
|
|
:else
|
|
(render-value-to-html
|
|
(trampoline (eval-expr expr env))
|
|
env))))))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; dispatch-html-form — render-aware special form handling for HTML output
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define dispatch-html-form
|
|
(fn (name expr env)
|
|
(cond
|
|
;; if
|
|
(= name "if")
|
|
(let ((cond-val (trampoline (eval-expr (nth expr 1) env))))
|
|
(if cond-val
|
|
(render-to-html (nth expr 2) env)
|
|
(if (> (len expr) 3)
|
|
(render-to-html (nth expr 3) env)
|
|
"")))
|
|
|
|
;; when
|
|
(= name "when")
|
|
(if (not (trampoline (eval-expr (nth expr 1) env)))
|
|
""
|
|
(join ""
|
|
(map
|
|
(fn (i) (render-to-html (nth expr i) env))
|
|
(range 2 (len expr)))))
|
|
|
|
;; cond
|
|
(= name "cond")
|
|
(let ((branch (eval-cond (rest expr) env)))
|
|
(if branch
|
|
(render-to-html branch env)
|
|
""))
|
|
|
|
;; case
|
|
(= name "case")
|
|
(render-to-html (trampoline (eval-expr expr env)) env)
|
|
|
|
;; let / let*
|
|
(or (= name "let") (= name "let*"))
|
|
(let ((local (process-bindings (nth expr 1) env)))
|
|
(join ""
|
|
(map
|
|
(fn (i) (render-to-html (nth expr i) local))
|
|
(range 2 (len expr)))))
|
|
|
|
;; begin / do
|
|
(or (= name "begin") (= name "do"))
|
|
(join ""
|
|
(map
|
|
(fn (i) (render-to-html (nth expr i) env))
|
|
(range 1 (len expr))))
|
|
|
|
;; Definition forms — eval for side effects
|
|
(definition-form? name)
|
|
(do (trampoline (eval-expr expr env)) "")
|
|
|
|
;; map
|
|
(= name "map")
|
|
(let ((f (trampoline (eval-expr (nth expr 1) env)))
|
|
(coll (trampoline (eval-expr (nth expr 2) env))))
|
|
(join ""
|
|
(map
|
|
(fn (item)
|
|
(if (lambda? f)
|
|
(render-lambda-html f (list item) env)
|
|
(render-to-html (apply f (list item)) env)))
|
|
coll)))
|
|
|
|
;; map-indexed
|
|
(= name "map-indexed")
|
|
(let ((f (trampoline (eval-expr (nth expr 1) env)))
|
|
(coll (trampoline (eval-expr (nth expr 2) env))))
|
|
(join ""
|
|
(map-indexed
|
|
(fn (i item)
|
|
(if (lambda? f)
|
|
(render-lambda-html f (list i item) env)
|
|
(render-to-html (apply f (list i item)) env)))
|
|
coll)))
|
|
|
|
;; filter — evaluate fully then render
|
|
(= name "filter")
|
|
(render-to-html (trampoline (eval-expr expr env)) env)
|
|
|
|
;; for-each (render variant)
|
|
(= name "for-each")
|
|
(let ((f (trampoline (eval-expr (nth expr 1) env)))
|
|
(coll (trampoline (eval-expr (nth expr 2) env))))
|
|
(join ""
|
|
(map
|
|
(fn (item)
|
|
(if (lambda? f)
|
|
(render-lambda-html f (list item) env)
|
|
(render-to-html (apply f (list item)) env)))
|
|
coll)))
|
|
|
|
;; Fallback
|
|
:else
|
|
(render-value-to-html (trampoline (eval-expr expr env)) env))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; render-lambda-html — render a lambda body in HTML context
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define render-lambda-html
|
|
(fn (f args env)
|
|
(let ((local (env-merge (lambda-closure f) env)))
|
|
(for-each-indexed
|
|
(fn (i p)
|
|
(env-set! local p (nth args i)))
|
|
(lambda-params f))
|
|
(render-to-html (lambda-body f) local))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; render-html-component — expand and render a component
|
|
;; --------------------------------------------------------------------------
|
|
|
|
(define render-html-component
|
|
(fn (comp args env)
|
|
;; Expand component and render body through HTML adapter.
|
|
;; Component body contains rendering forms (HTML tags) that only the
|
|
;; adapter understands, so expansion must happen here, not in eval-expr.
|
|
(let ((kwargs (dict))
|
|
(children (list)))
|
|
;; Separate keyword args from positional children
|
|
(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 (trampoline
|
|
(eval-expr (nth args (inc (get state "i"))) env))))
|
|
(dict-set! kwargs (keyword-name arg) val)
|
|
(assoc state "skip" true "i" (inc (get state "i"))))
|
|
(do
|
|
(append! children arg)
|
|
(assoc state "i" (inc (get state "i"))))))))
|
|
(dict "i" 0 "skip" false)
|
|
args)
|
|
;; Build component env: closure + caller env + params
|
|
(let ((local (env-merge (component-closure comp) env)))
|
|
;; Bind params from kwargs
|
|
(for-each
|
|
(fn (p)
|
|
(env-set! local p (if (dict-has? kwargs p) (dict-get kwargs p) nil)))
|
|
(component-params comp))
|
|
;; If component accepts children, pre-render them to raw HTML
|
|
(when (component-has-children? comp)
|
|
(env-set! local "children"
|
|
(make-raw-html
|
|
(join "" (map (fn (c) (render-to-html c env)) children)))))
|
|
(render-to-html (component-body comp) local)))))
|
|
|
|
|
|
(define render-html-element
|
|
(fn (tag args env)
|
|
(let ((parsed (parse-element-args args env))
|
|
(attrs (first parsed))
|
|
(children (nth parsed 1))
|
|
(is-void (contains? VOID_ELEMENTS tag)))
|
|
(str "<" tag
|
|
(render-attrs attrs)
|
|
(if is-void
|
|
" />"
|
|
(str ">"
|
|
(join "" (map (fn (c) (render-to-html c env)) children))
|
|
"</" tag ">"))))))
|
|
|
|
|
|
;; --------------------------------------------------------------------------
|
|
;; Platform interface — HTML adapter
|
|
;; --------------------------------------------------------------------------
|
|
;;
|
|
;; Inherited from render.sx:
|
|
;; escape-html, escape-attr, raw-html-content
|
|
;;
|
|
;; From eval.sx:
|
|
;; eval-expr, trampoline, expand-macro, process-bindings, eval-cond
|
|
;; env-has?, env-get, env-set!, env-merge
|
|
;; lambda?, component?, macro?
|
|
;; lambda-closure, lambda-params, lambda-body
|
|
;; component-params, component-body, component-closure,
|
|
;; component-has-children?, component-name
|
|
;;
|
|
;; Raw HTML construction:
|
|
;; (make-raw-html s) → wrap string as raw HTML (not double-escaped)
|
|
;;
|
|
;; Iteration:
|
|
;; (for-each-indexed fn coll) → call fn(index, item) for each element
|
|
;; (map-indexed fn coll) → map fn(index, item) over each element
|
|
;; --------------------------------------------------------------------------
|