Remove CSSX style dictionary infrastructure — styling is just components

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>
This commit is contained in:
2026-03-08 00:00:23 +00:00
parent 81d8e55fb0
commit a8bfff9e0b
30 changed files with 109 additions and 3164 deletions

View File

@@ -38,7 +38,6 @@ Only these types may cross the host-SX boundary:
| list | `list` | `Array` | `Vec<SxValue>` |
| dict | `dict` | `Object` / `Map` | `HashMap<String, SxValue>` |
| sx-source | `SxExpr` wrapper | `string` | `String` |
| style-value | `StyleValue` | `StyleValue` | `StyleValue` |
**NOT allowed:** ORM models, datetime objects, request objects, raw callables, framework types. Convert at the edge before crossing.

View File

@@ -52,9 +52,6 @@
(create-fragment)
(render-dom-list expr env ns))
;; Style value → text of class name
"style-value" (create-text-node (style-value-class expr))
;; Fallback
:else (create-text-node (str expr)))))
@@ -147,8 +144,7 @@
(let ((new-ns (cond (= tag "svg") SVG_NS
(= tag "math") MATH_NS
:else ns))
(el (dom-create-element tag new-ns))
(extra-class nil))
(el (dom-create-element tag new-ns)))
;; Process args: keywords → attrs, others → children
(reduce
@@ -168,9 +164,6 @@
;; nil or false → skip
(or (nil? attr-val) (= attr-val false))
nil
;; :style StyleValue → convert to class
(and (= attr-name "style") (style-value? attr-val))
(set! extra-class (style-value-class attr-val))
;; Boolean attr
(contains? BOOLEAN_ATTRS attr-name)
(when attr-val (dom-set-attr el attr-name ""))
@@ -190,12 +183,6 @@
(dict "i" 0 "skip" false)
args)
;; Merge StyleValue class
(when extra-class
(let ((existing (dom-get-attr el "class")))
(dom-set-attr el "class"
(if existing (str existing " " extra-class) extra-class))))
el)))
@@ -297,7 +284,7 @@
(define RENDER_DOM_FORMS
(list "if" "when" "cond" "case" "let" "let*" "begin" "do"
"define" "defcomp" "defmacro" "defstyle" "defkeyframes" "defhandler"
"define" "defcomp" "defmacro" "defstyle" "defhandler"
"map" "map-indexed" "filter" "for-each"))
(define render-dom-form?
@@ -450,7 +437,6 @@
;;
;; From render.sx:
;; HTML_TAGS, VOID_ELEMENTS, BOOLEAN_ATTRS, definition-form?
;; style-value?, style-value-class
;;
;; From eval.sx:
;; eval-expr, trampoline, expand-macro, process-bindings, eval-cond

View File

@@ -41,7 +41,6 @@
"boolean" (if val "true" "false")
"list" (render-list-to-html val env)
"raw-html" (raw-html-content val)
"style-value" (style-value-class val)
:else (escape-html (str val)))))
@@ -51,7 +50,7 @@
(define RENDER_HTML_FORMS
(list "if" "when" "cond" "case" "let" "let*" "begin" "do"
"define" "defcomp" "defmacro" "defstyle" "defkeyframes" "defhandler"
"define" "defcomp" "defmacro" "defstyle" "defhandler"
"map" "map-indexed" "filter" "for-each"))
(define render-html-form?
@@ -293,7 +292,7 @@
;; --------------------------------------------------------------------------
;;
;; Inherited from render.sx:
;; escape-html, escape-attr, raw-html-content, style-value?, style-value-class
;; escape-html, escape-attr, raw-html-content
;;
;; From eval.sx:
;; eval-expr, trampoline, expand-macro, process-bindings, eval-cond

View File

@@ -26,7 +26,7 @@ import contextvars
import inspect
from typing import Any
from ..types import Component, Keyword, Lambda, Macro, NIL, StyleValue, Symbol
from ..types import Component, Keyword, Lambda, Macro, NIL, Symbol
from ..parser import SxExpr, serialize
from ..primitives_io import IO_PRIMITIVES, RequestContext, execute_io
from ..html import (
@@ -250,18 +250,6 @@ async def _arender_element(tag, args, env, ctx):
children.append(arg)
i += 1
# StyleValue → class
style_val = attrs.get("style")
if isinstance(style_val, StyleValue):
from ..css_registry import register_generated_rule
register_generated_rule(style_val)
existing = attrs.get("class")
if existing and existing is not NIL and existing is not False:
attrs["class"] = f"{existing} {style_val.class_name}"
else:
attrs["class"] = style_val.class_name
del attrs["style"]
class_val = attrs.get("class")
if class_val is not None and class_val is not NIL and class_val is not False:
collector = css_class_collector.get(None)
@@ -464,7 +452,6 @@ _ASYNC_RENDER_FORMS = {
"do": _arsf_begin,
"define": _arsf_define,
"defstyle": _arsf_define,
"defkeyframes": _arsf_define,
"defcomp": _arsf_define,
"defmacro": _arsf_define,
"defhandler": _arsf_define,
@@ -716,23 +703,18 @@ async def _aser_call(name, args, env, ctx):
if isinstance(arg, Keyword) and i + 1 < len(args):
val = await _aser(args[i + 1], env, ctx)
if val is not NIL and val is not None:
if arg.name == "style" and isinstance(val, StyleValue):
from ..css_registry import register_generated_rule
register_generated_rule(val)
extra_class = val.class_name
else:
parts.append(f":{arg.name}")
if isinstance(val, list):
live = [v for v in val if v is not NIL and v is not None]
items = [serialize(v) for v in live]
if not items:
parts.append("nil")
elif any(isinstance(v, SxExpr) for v in live):
parts.append("(<> " + " ".join(items) + ")")
else:
parts.append("(list " + " ".join(items) + ")")
parts.append(f":{arg.name}")
if isinstance(val, list):
live = [v for v in val if v is not NIL and v is not None]
items = [serialize(v) for v in live]
if not items:
parts.append("nil")
elif any(isinstance(v, SxExpr) for v in live):
parts.append("(<> " + " ".join(items) + ")")
else:
parts.append(serialize(val))
parts.append("(list " + " ".join(items) + ")")
else:
parts.append(serialize(val))
i += 2
else:
result = await _aser(arg, env, ctx)
@@ -996,7 +978,6 @@ _ASER_FORMS = {
"fn": _assf_lambda,
"define": _assf_define,
"defstyle": _assf_define,
"defkeyframes": _assf_define,
"defcomp": _assf_define,
"defmacro": _assf_define,
"defhandler": _assf_define,

View File

@@ -3,16 +3,14 @@
;;
;; Handles the browser startup lifecycle:
;; 1. CSS tracking init
;; 2. Style dictionary loading (from <script type="text/sx-styles">)
;; 3. Component script processing (from <script type="text/sx">)
;; 4. Hydration of [data-sx] elements
;; 5. Engine element processing
;; 2. Component script processing (from <script type="text/sx">)
;; 3. Hydration of [data-sx] elements
;; 4. Engine element processing
;;
;; Also provides the public mounting/hydration API:
;; mount, hydrate, update, render-component
;;
;; Depends on:
;; cssx.sx — load-style-dict
;; orchestration.sx — process-elements, engine-init
;; adapter-dom.sx — render-to-dom
;; render.sx — shared registries
@@ -275,58 +273,6 @@
(set-sx-comp-cookie hash))))))
;; --------------------------------------------------------------------------
;; Style dictionary initialization
;; --------------------------------------------------------------------------
(define init-style-dict
(fn ()
;; Process <script type="text/sx-styles"> tags with caching.
(let ((scripts (query-style-scripts)))
(for-each
(fn (s)
(when (not (is-processed? s "styles"))
(mark-processed! s "styles")
(let ((text (dom-text-content s))
(hash (dom-get-attr s "data-hash")))
(if (nil? hash)
;; No hash — just parse inline
(when (and text (not (empty? (trim text))))
(parse-and-load-style-dict text))
;; Hash-based caching
(let ((has-inline (and text (not (empty? (trim text))))))
(let ((cached-hash (local-storage-get "sx-styles-hash")))
(if (= cached-hash hash)
;; Cache hit
(if has-inline
(do
(local-storage-set "sx-styles-src" text)
(parse-and-load-style-dict text)
(log-info "styles: downloaded (cookie stale)"))
(let ((cached (local-storage-get "sx-styles-src")))
(if cached
(do
(parse-and-load-style-dict cached)
(log-info (str "styles: cached (" hash ")")))
(do
(clear-sx-styles-cookie)
(browser-reload)))))
;; Cache miss
(if has-inline
(do
(local-storage-set "sx-styles-hash" hash)
(local-storage-set "sx-styles-src" text)
(parse-and-load-style-dict text)
(log-info (str "styles: downloaded (" hash ")")))
(do
(local-storage-remove "sx-styles-hash")
(local-storage-remove "sx-styles-src")
(clear-sx-styles-cookie)
(browser-reload)))))
(set-sx-styles-cookie hash))))))
scripts))))
;; --------------------------------------------------------------------------
;; Page registry for client-side routing
;; --------------------------------------------------------------------------
@@ -375,7 +321,6 @@
(do
(log-info (str "sx-browser " SX_VERSION))
(init-css-tracking)
(init-style-dict)
(process-page-scripts)
(process-sx-scripts nil)
(sx-hydrate-elements nil)
@@ -389,9 +334,6 @@
;; From orchestration.sx:
;; process-elements, init-css-tracking
;;
;; From cssx.sx:
;; load-style-dict
;;
;; === DOM / Render ===
;; (resolve-mount-target target) → Element (string → querySelector, else identity)
;; (sx-render-with-env source extra-env) → DOM node (parse + render with componentEnv + extra)
@@ -420,7 +362,6 @@
;;
;; === Script queries ===
;; (query-sx-scripts root) → list of <script type="text/sx"> elements
;; (query-style-scripts) → list of <script type="text/sx-styles"> elements
;; (query-page-scripts) → list of <script type="text/sx-pages"> elements
;;
;; === localStorage ===
@@ -431,8 +372,6 @@
;; === Cookies ===
;; (set-sx-comp-cookie hash) → void
;; (clear-sx-comp-cookie) → void
;; (set-sx-styles-cookie hash) → void
;; (clear-sx-styles-cookie) → void
;;
;; === Env ===
;; (parse-env-attr el) → dict (parse data-sx-env JSON attr)
@@ -444,8 +383,6 @@
;; (log-parse-error label text err) → void (diagnostic parse error)
;;
;; === JSON parsing ===
;; (parse-and-load-style-dict text) → void (JSON.parse + load-style-dict)
;;
;; === Processing markers ===
;; (mark-processed! el key) → void
;; (is-processed? el key) → boolean

View File

@@ -191,10 +191,6 @@ class JSEmitter:
"ho-every": "hoEvery",
"ho-for-each": "hoForEach",
"sf-defstyle": "sfDefstyle",
"sf-defkeyframes": "sfDefkeyframes",
"build-keyframes": "buildKeyframes",
"style-value?": "isStyleValue",
"style-value-class": "styleValueClass",
"kf-name": "kfName",
"special-form?": "isSpecialForm",
"ho-form?": "isHoForm",
@@ -443,32 +439,6 @@ class JSEmitter:
"format-date": "formatDate",
"format-decimal": "formatDecimal",
"parse-int": "parseInt_",
# cssx.sx
"_style-atoms": "_styleAtoms",
"_pseudo-variants": "_pseudoVariants",
"_responsive-breakpoints": "_responsiveBreakpoints",
"_style-keyframes": "_styleKeyframes",
"_arbitrary-patterns": "_arbitraryPatterns",
"_child-selector-prefixes": "_childSelectorPrefixes",
"_style-cache": "_styleCache",
"_injected-styles": "_injectedStyles",
"load-style-dict": "loadStyleDict",
"split-variant": "splitVariant",
"resolve-atom": "resolveAtom",
"is-child-selector-atom?": "isChildSelectorAtom",
"hash-style": "hashStyle",
"resolve-style": "resolveStyle",
"merge-style-values": "mergeStyleValues",
"fnv1a-hash": "fnv1aHash",
"compile-regex": "compileRegex",
"regex-match": "regexMatch",
"regex-replace-groups": "regexReplaceGroups",
"make-style-value": "makeStyleValue_",
"style-value-declarations": "styleValueDeclarations",
"style-value-media-rules": "styleValueMediaRules",
"style-value-pseudo-rules": "styleValuePseudoRules",
"style-value-keyframes": "styleValueKeyframes_",
"inject-style-value": "injectStyleValue",
# boot.sx
"HEAD_HOIST_SELECTOR": "HEAD_HOIST_SELECTOR",
"hoist-head-elements-full": "hoistHeadElementsFull",
@@ -478,7 +448,6 @@ class JSEmitter:
"sx-render-component": "sxRenderComponent",
"process-sx-scripts": "processSxScripts",
"process-component-script": "processComponentScript",
"init-style-dict": "initStyleDict",
"SX_VERSION": "SX_VERSION",
"boot-init": "bootInit",
"resolve-suspense": "resolveSuspense",
@@ -490,21 +459,17 @@ class JSEmitter:
"set-document-title": "setDocumentTitle",
"remove-head-element": "removeHeadElement",
"query-sx-scripts": "querySxScripts",
"query-style-scripts": "queryStyleScripts",
"local-storage-get": "localStorageGet",
"local-storage-set": "localStorageSet",
"local-storage-remove": "localStorageRemove",
"set-sx-comp-cookie": "setSxCompCookie",
"clear-sx-comp-cookie": "clearSxCompCookie",
"set-sx-styles-cookie": "setSxStylesCookie",
"clear-sx-styles-cookie": "clearSxStylesCookie",
"parse-env-attr": "parseEnvAttr",
"store-env-attr": "storeEnvAttr",
"to-kebab": "toKebab",
"log-info": "logInfo",
"log-warn": "logWarn",
"log-parse-error": "logParseError",
"parse-and-load-style-dict": "parseAndLoadStyleDict",
"_page-routes": "_pageRoutes",
"process-page-scripts": "processPageScripts",
"query-page-scripts": "queryPageScripts",
@@ -1044,7 +1009,6 @@ ADAPTER_FILES = {
"dom": ("adapter-dom.sx", "adapter-dom"),
"engine": ("engine.sx", "engine"),
"orchestration": ("orchestration.sx","orchestration"),
"cssx": ("cssx.sx", "cssx"),
"boot": ("boot.sx", "boot"),
}
@@ -1052,8 +1016,7 @@ ADAPTER_FILES = {
ADAPTER_DEPS = {
"engine": ["dom"],
"orchestration": ["engine", "dom"],
"cssx": [],
"boot": ["dom", "engine", "orchestration", "cssx", "parser"],
"boot": ["dom", "engine", "orchestration", "parser"],
"parser": [],
}
@@ -1292,7 +1255,7 @@ ASYNC_IO_JS = '''
// define/defcomp/defmacro — eval for side effects
if (hname === "define" || hname === "defcomp" || hname === "defmacro" ||
hname === "defstyle" || hname === "defkeyframes" || hname === "defhandler") {
hname === "defstyle" || hname === "defhandler") {
trampoline(evalExpr(expr, env));
return null;
}
@@ -1414,11 +1377,7 @@ ASYNC_IO_JS = '''
})(attrName, attrVal);
} else {
if (!isNil(attrVal) && attrVal !== false) {
if (attrName === "class" && attrVal && attrVal._styleValue) {
el.setAttribute("class", (el.getAttribute("class") || "") + " " + attrVal.className);
} else if (attrName === "style" && attrVal && attrVal._styleValue) {
el.setAttribute("class", (el.getAttribute("class") || "") + " " + attrVal.className);
} else if (contains(BOOLEAN_ATTRS, attrName)) {
if (contains(BOOLEAN_ATTRS, attrName)) {
if (isSxTruthy(attrVal)) el.setAttribute(attrName, "");
} else if (attrVal === true) {
el.setAttribute(attrName, "");
@@ -1829,7 +1788,6 @@ def compile_ref_to_js(
"dom": PLATFORM_DOM_JS,
"engine": PLATFORM_ENGINE_PURE_JS,
"orchestration": PLATFORM_ORCHESTRATION_JS,
"cssx": PLATFORM_CSSX_JS,
"boot": PLATFORM_BOOT_JS,
}
@@ -1864,7 +1822,7 @@ def compile_ref_to_js(
("eval.sx", "eval"),
("render.sx", "render (core)"),
]
for name in ("parser", "html", "sx", "dom", "engine", "orchestration", "cssx", "boot"):
for name in ("parser", "html", "sx", "dom", "engine", "orchestration", "boot"):
if name in adapter_set:
sx_files.append(ADAPTER_FILES[name])
for name in sorted(spec_mod_set):
@@ -1895,7 +1853,6 @@ def compile_ref_to_js(
has_dom = "dom" in adapter_set
has_engine = "engine" in adapter_set
has_orch = "orchestration" in adapter_set
has_cssx = "cssx" in adapter_set
has_boot = "boot" in adapter_set
has_parser = "parser" in adapter_set
adapter_label = "+".join(sorted(adapter_set)) if adapter_set else "core-only"
@@ -1937,7 +1894,7 @@ def compile_ref_to_js(
# Platform JS for selected adapters
if not has_dom:
parts.append("\n var _hasDom = false;\n")
for name in ("dom", "engine", "orchestration", "cssx", "boot"):
for name in ("dom", "engine", "orchestration", "boot"):
if name in adapter_set and name in adapter_platform:
parts.append(adapter_platform[name])
@@ -1946,7 +1903,7 @@ def compile_ref_to_js(
parts.append(CONTINUATIONS_JS)
if has_dom:
parts.append(ASYNC_IO_JS)
parts.append(public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_cssx, has_boot, has_parser, adapter_label, has_deps, has_router))
parts.append(public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_boot, has_parser, adapter_label, has_deps, has_router))
parts.append(EPILOGUE)
from datetime import datetime, timezone
build_ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
@@ -2019,15 +1976,6 @@ PREAMBLE = '''\
function RawHTML(html) { this.html = html; }
RawHTML.prototype._raw = true;
function StyleValue(className, declarations, mediaRules, pseudoRules, keyframes) {
this.className = className;
this.declarations = declarations || "";
this.mediaRules = mediaRules || [];
this.pseudoRules = pseudoRules || [];
this.keyframes = keyframes || [];
}
StyleValue.prototype._styleValue = true;
function isSym(x) { return x != null && x._sym === true; }
function isKw(x) { return x != null && x._kw === true; }
@@ -2224,30 +2172,6 @@ PRIMITIVES_JS_MODULES: dict[str, str] = {
PRIMITIVES["strip-tags"] = function(s) { return String(s).replace(/<[^>]+>/g, ""); };
''',
"stdlib.style": '''
// stdlib.style
PRIMITIVES["css"] = function() {
var atoms = [];
for (var i = 0; i < arguments.length; i++) {
var a = arguments[i];
if (isNil(a) || a === false) continue;
atoms.push(isKw(a) ? a.name : String(a));
}
if (!atoms.length) return NIL;
return new StyleValue("sx-" + atoms.join("-"), atoms.join(";"), [], [], []);
};
PRIMITIVES["merge-styles"] = function() {
var valid = [];
for (var i = 0; i < arguments.length; i++) {
if (isStyleValue(arguments[i])) valid.push(arguments[i]);
}
if (!valid.length) return NIL;
if (valid.length === 1) return valid[0];
var allDecls = valid.map(function(v) { return v.declarations; }).join(";");
return new StyleValue("sx-merged", allDecls, [], [], []);
};
''',
"stdlib.debug": '''
// stdlib.debug
PRIMITIVES["assert"] = function(cond, msg) {
@@ -2295,7 +2219,6 @@ PLATFORM_JS_PRE = '''
if (x._component) return "component";
if (x._macro) return "macro";
if (x._raw) return "raw-html";
if (x._styleValue) return "style-value";
if (typeof Node !== "undefined" && x instanceof Node) return "dom-node";
if (Array.isArray(x)) return "list";
if (typeof x === "object") return "dict";
@@ -2342,27 +2265,6 @@ PLATFORM_JS_PRE = '''
function isComponent(x) { return x != null && x._component === true; }
function isMacro(x) { return x != null && x._macro === true; }
function isStyleValue(x) { return x != null && x._styleValue === true; }
function styleValueClass(x) { return x.className; }
function styleValue_p(x) { return x != null && x._styleValue === true; }
function buildKeyframes(kfName, steps, env) {
// Platform implementation of defkeyframes
var parts = [];
for (var i = 0; i < steps.length; i++) {
var step = steps[i];
var selector = isSym(step[0]) ? step[0].name : String(step[0]);
var body = trampoline(evalExpr(step[1], env));
var decls = isStyleValue(body) ? body.declarations : String(body);
parts.push(selector + "{" + decls + "}");
}
var kfRule = "@keyframes " + kfName + "{" + parts.join("") + "}";
var cn = "sx-ref-kf-" + kfName;
var sv = new StyleValue(cn, "animation-name:" + kfName, [], [], [[kfName, kfRule]]);
env[kfName] = sv;
return sv;
}
function envHas(env, name) { return name in env; }
function envGet(env, name) { return env[name]; }
function envSet(env, name, val) { env[name] = val; }
@@ -2488,7 +2390,7 @@ PLATFORM_JS_POST = '''
function isSpecialForm(n) { return n in {
"if":1,"when":1,"cond":1,"case":1,"and":1,"or":1,"let":1,"let*":1,
"lambda":1,"fn":1,"define":1,"defcomp":1,"defmacro":1,"defstyle":1,
"defkeyframes":1,"defhandler":1,"begin":1,"do":1,
"defhandler":1,"begin":1,"do":1,
"quote":1,"quasiquote":1,"->":1,"set!":1
}; }
function isHoForm(n) { return n in {
@@ -2499,7 +2401,7 @@ PLATFORM_JS_POST = '''
function isDefinitionForm(name) {
return name === "define" || name === "defcomp" || name === "defmacro" ||
name === "defstyle" || name === "defkeyframes" || name === "defhandler";
name === "defstyle" || name === "defhandler";
}
function indexOf_(s, ch) {
@@ -2877,11 +2779,7 @@ PLATFORM_DOM_JS = """
var attrVal = trampoline(evalExpr(args[i + 1], env));
i++; // skip value
if (isNil(attrVal) || attrVal === false) continue;
if (attrName === "class" && attrVal && attrVal._styleValue) {
extraClasses.push(attrVal.className);
} else if (attrName === "style" && attrVal && attrVal._styleValue) {
extraClasses.push(attrVal.className);
} else if (contains(BOOLEAN_ATTRS, attrName)) {
if (contains(BOOLEAN_ATTRS, attrName)) {
if (isSxTruthy(attrVal)) el.setAttribute(attrName, "");
} else if (attrVal === true) {
el.setAttribute(attrName, "");
@@ -3763,102 +3661,6 @@ PLATFORM_ORCHESTRATION_JS = """
}
"""
PLATFORM_CSSX_JS = """
// =========================================================================
// Platform interface — CSSX (style dictionary)
// =========================================================================
function fnv1aHash(input) {
var h = 0x811c9dc5;
for (var i = 0; i < input.length; i++) {
h ^= input.charCodeAt(i);
h = (h * 0x01000193) >>> 0;
}
return h.toString(16).padStart(8, "0").substring(0, 6);
}
function compileRegex(pattern) {
try { return new RegExp(pattern); } catch (e) { return null; }
}
function regexMatch(re, s) {
if (!re) return NIL;
var m = s.match(re);
return m ? Array.prototype.slice.call(m) : NIL;
}
function regexReplaceGroups(tmpl, match) {
var result = tmpl;
for (var j = 1; j < match.length; j++) {
result = result.split("{" + (j - 1) + "}").join(match[j]);
}
return result;
}
function makeStyleValue_(cn, decls, media, pseudo, kf) {
return new StyleValue(cn, decls || "", media || [], pseudo || [], kf || []);
}
function styleValueDeclarations(sv) { return sv.declarations; }
function styleValueMediaRules(sv) { return sv.mediaRules; }
function styleValuePseudoRules(sv) { return sv.pseudoRules; }
function styleValueKeyframes_(sv) { return sv.keyframes; }
function injectStyleValue(sv, atoms) {
if (_injectedStyles[sv.className]) return;
_injectedStyles[sv.className] = true;
if (!_hasDom) return;
var cssTarget = document.getElementById("sx-css");
if (!cssTarget) return;
var rules = [];
// Child-selector atoms are now routed to pseudoRules by the resolver
// with selector ">:not(:first-child)", so base declarations are always
// applied directly to the class.
if (sv.declarations) {
rules.push("." + sv.className + "{" + sv.declarations + "}");
}
for (var pi = 0; pi < sv.pseudoRules.length; pi++) {
var sel = sv.pseudoRules[pi][0], decls = sv.pseudoRules[pi][1];
if (sel.indexOf("&") >= 0) {
rules.push(sel.replace(/&/g, "." + sv.className) + "{" + decls + "}");
} else {
rules.push("." + sv.className + sel + "{" + decls + "}");
}
}
for (var mi = 0; mi < sv.mediaRules.length; mi++) {
rules.push("@media " + sv.mediaRules[mi][0] + "{." + sv.className + "{" + sv.mediaRules[mi][1] + "}}");
}
for (var ki = 0; ki < sv.keyframes.length; ki++) {
rules.push(sv.keyframes[ki][1]);
}
cssTarget.textContent += rules.join("");
}
// Replace stub css primitive with real CSSX implementation
PRIMITIVES["css"] = function() {
var atoms = [];
for (var i = 0; i < arguments.length; i++) {
var a = arguments[i];
if (isNil(a) || a === false) continue;
atoms.push(isKw(a) ? a.name : String(a));
}
if (!atoms.length) return NIL;
return resolveStyle(atoms);
};
PRIMITIVES["merge-styles"] = function() {
var valid = [];
for (var i = 0; i < arguments.length; i++) {
if (isStyleValue(arguments[i])) valid.push(arguments[i]);
}
if (!valid.length) return NIL;
if (valid.length === 1) return valid[0];
return mergeStyleValues(valid);
};
"""
PLATFORM_BOOT_JS = """
// =========================================================================
// Platform interface — Boot (mount, hydrate, scripts, cookies)
@@ -3916,12 +3718,6 @@ PLATFORM_BOOT_JS = """
r.querySelectorAll('script[type="text/sx"]'));
}
function queryStyleScripts() {
if (!_hasDom) return [];
return Array.prototype.slice.call(
document.querySelectorAll('script[type="text/sx-styles"]'));
}
function queryPageScripts() {
if (!_hasDom) return [];
return Array.prototype.slice.call(
@@ -3953,14 +3749,6 @@ PLATFORM_BOOT_JS = """
if (_hasDom) document.cookie = "sx-comp-hash=;path=/;max-age=0;SameSite=Lax";
}
function setSxStylesCookie(hash) {
if (_hasDom) document.cookie = "sx-styles-hash=" + hash + ";path=/;max-age=31536000;SameSite=Lax";
}
function clearSxStylesCookie() {
if (_hasDom) document.cookie = "sx-styles-hash=;path=/;max-age=0;SameSite=Lax";
}
// --- Env helpers ---
function parseEnvAttr(el) {
@@ -4009,10 +3797,6 @@ PLATFORM_BOOT_JS = """
}
}
function parseAndLoadStyleDict(text) {
try { loadStyleDict(JSON.parse(text)); }
catch (e) { if (typeof console !== "undefined") console.warn("[sx-ref] style dict parse error", e); }
}
"""
def fixups_js(has_html, has_sx, has_dom):
@@ -4039,7 +3823,7 @@ def fixups_js(has_html, has_sx, has_dom):
return "\n".join(lines)
def public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_cssx, has_boot, has_parser, adapter_label, has_deps=False, has_router=False):
def public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_boot, has_parser, adapter_label, has_deps=False, has_router=False):
# Parser: use compiled sxParse from parser.sx, or inline a minimal fallback
if has_parser:
parser = '''

View File

@@ -199,11 +199,6 @@ class PyEmitter:
"ho-every": "ho_every",
"ho-for-each": "ho_for_each",
"sf-defstyle": "sf_defstyle",
"sf-defkeyframes": "sf_defkeyframes",
"build-keyframes": "build_keyframes",
"style-value?": "is_style_value",
"style-value-class": "style_value_class",
"kf-name": "kf_name",
"special-form?": "is_special_form",
"ho-form?": "is_ho_form",
"strip-prefix": "strip_prefix",
@@ -1080,7 +1075,7 @@ from typing import Any
# =========================================================================
from shared.sx.types import (
NIL, Symbol, Keyword, Lambda, Component, Continuation, Macro, StyleValue,
NIL, Symbol, Keyword, Lambda, Component, Continuation, Macro,
HandlerDef, QueryDef, ActionDef, PageDef, _ShiftSignal,
)
from shared.sx.parser import SxExpr
@@ -1189,8 +1184,6 @@ def type_of(x):
return "macro"
if isinstance(x, _RawHTML):
return "raw-html"
if isinstance(x, StyleValue):
return "style-value"
if isinstance(x, Continuation):
return "continuation"
if isinstance(x, list):
@@ -1355,14 +1348,6 @@ def is_macro(x):
return isinstance(x, Macro)
def is_style_value(x):
return isinstance(x, StyleValue)
def style_value_class(x):
return x.class_name
def env_has(env, name):
return name in env
@@ -1513,8 +1498,6 @@ def serialize(val):
if t == "raw-html":
escaped = escape_string(raw_html_content(val))
return '(raw! "' + escaped + '")'
if t == "style-value":
return '"' + style_value_class(val) + '"'
if t == "list":
if not val:
return "()"
@@ -1534,7 +1517,7 @@ def serialize(val):
_SPECIAL_FORM_NAMES = frozenset([
"if", "when", "cond", "case", "and", "or",
"let", "let*", "lambda", "fn",
"define", "defcomp", "defmacro", "defstyle", "defkeyframes",
"define", "defcomp", "defmacro", "defstyle",
"defhandler", "defpage", "defquery", "defaction", "defrelation",
"begin", "do", "quote", "quasiquote",
"->", "set!",
@@ -1696,7 +1679,7 @@ def aser_special(name, expr, env):
fn(item)
return results if results else NIL
# Definition forms — evaluate for side effects
if name in ("define", "defcomp", "defmacro", "defstyle", "defkeyframes",
if name in ("define", "defcomp", "defmacro", "defstyle",
"defhandler", "defpage", "defquery", "defaction", "defrelation"):
trampoline(eval_expr(expr, env))
return NIL

View File

@@ -123,4 +123,4 @@
(define-boundary-types
(list "number" "string" "boolean" "nil" "keyword"
"list" "dict" "sx-source" "style-value"))
"list" "dict" "sx-source"))

View File

@@ -1,317 +0,0 @@
;; ==========================================================================
;; cssx.sx — On-demand CSS style dictionary
;;
;; Resolves keyword atoms (e.g. :flex, :gap-4, :hover:bg-sky-200) into
;; StyleValue objects with content-addressed class names. CSS rules are
;; injected into the document on first use.
;;
;; The style dictionary is loaded from a JSON blob (typically served
;; inline in a <script type="text/sx-styles"> tag) containing:
;; a — atom → CSS declarations map
;; v — pseudo-variant → CSS pseudo-selector map
;; b — responsive breakpoint → media query map
;; k — keyframe name → @keyframes rule map
;; p — arbitrary patterns: [[regex, template], ...]
;; c — child selector prefixes: ["space-x-", "space-y-", ...]
;;
;; Depends on:
;; render.sx — StyleValue type
;; ==========================================================================
;; --------------------------------------------------------------------------
;; State — populated by load-style-dict
;; --------------------------------------------------------------------------
(define _style-atoms (dict))
(define _pseudo-variants (dict))
(define _responsive-breakpoints (dict))
(define _style-keyframes (dict))
(define _arbitrary-patterns (list))
(define _child-selector-prefixes (list))
(define _style-cache (dict))
(define _injected-styles (dict))
;; --------------------------------------------------------------------------
;; Load style dictionary from parsed JSON data
;; --------------------------------------------------------------------------
(define load-style-dict
(fn (data)
(set! _style-atoms (or (get data "a") (dict)))
(set! _pseudo-variants (or (get data "v") (dict)))
(set! _responsive-breakpoints (or (get data "b") (dict)))
(set! _style-keyframes (or (get data "k") (dict)))
(set! _child-selector-prefixes (or (get data "c") (list)))
;; Compile arbitrary patterns from [regex, template] pairs
(set! _arbitrary-patterns
(map
(fn (pair)
(dict "re" (compile-regex (str "^" (first pair) "$"))
"tmpl" (nth pair 1)))
(or (get data "p") (list))))
;; Clear cache on reload
(set! _style-cache (dict))))
;; --------------------------------------------------------------------------
;; Variant splitting
;; --------------------------------------------------------------------------
(define split-variant
(fn (atom)
;; Parse variant prefixes: "sm:hover:bg-sky-200" → ["sm:hover", "bg-sky-200"]
;; Returns [variant, base] where variant is nil for no variant.
;; Check responsive prefix first
(let ((result nil))
(for-each
(fn (bp)
(when (nil? result)
(let ((prefix (str bp ":")))
(when (starts-with? atom prefix)
(let ((rest-atom (slice atom (len prefix))))
;; Check for compound variant (sm:hover:...)
(let ((inner-match nil))
(for-each
(fn (pv)
(when (nil? inner-match)
(let ((inner-prefix (str pv ":")))
(when (starts-with? rest-atom inner-prefix)
(set! inner-match
(list (str bp ":" pv)
(slice rest-atom (len inner-prefix))))))))
(keys _pseudo-variants))
(set! result
(or inner-match (list bp rest-atom)))))))))
(keys _responsive-breakpoints))
(when (nil? result)
;; Check pseudo variants
(for-each
(fn (pv)
(when (nil? result)
(let ((prefix (str pv ":")))
(when (starts-with? atom prefix)
(set! result (list pv (slice atom (len prefix))))))))
(keys _pseudo-variants)))
(or result (list nil atom)))))
;; --------------------------------------------------------------------------
;; Atom resolution
;; --------------------------------------------------------------------------
(define resolve-atom
(fn (atom)
;; Look up atom → CSS declarations string, or nil
(let ((decls (dict-get _style-atoms atom)))
(if (not (nil? decls))
decls
;; Dynamic keyframes: animate-{name}
(if (starts-with? atom "animate-")
(let ((kf-name (slice atom 8)))
(if (dict-has? _style-keyframes kf-name)
(str "animation-name:" kf-name)
nil))
;; Try arbitrary patterns
(let ((match-result nil))
(for-each
(fn (pat)
(when (nil? match-result)
(let ((m (regex-match (get pat "re") atom)))
(when m
(set! match-result
(regex-replace-groups (get pat "tmpl") m))))))
_arbitrary-patterns)
match-result))))))
;; --------------------------------------------------------------------------
;; Child selector detection
;; --------------------------------------------------------------------------
(define is-child-selector-atom?
(fn (atom)
(some
(fn (prefix) (starts-with? atom prefix))
_child-selector-prefixes)))
;; --------------------------------------------------------------------------
;; FNV-1a 32-bit hash → 6 hex chars
;; --------------------------------------------------------------------------
(define hash-style
(fn (input)
;; FNV-1a 32-bit hash for content-addressed class names
(fnv1a-hash input)))
;; --------------------------------------------------------------------------
;; Full style resolution pipeline
;; --------------------------------------------------------------------------
(define resolve-style
(fn (atoms)
;; Resolve a list of atom strings into a StyleValue.
;; Uses content-addressed caching.
(let ((key (join "\0" atoms)))
(let ((cached (dict-get _style-cache key)))
(if (not (nil? cached))
cached
;; Resolve each atom
(let ((base-decls (list))
(media-rules (list))
(pseudo-rules (list))
(kf-needed (list)))
(for-each
(fn (a)
(when a
(let ((clean (if (starts-with? a ":") (slice a 1) a)))
(let ((parts (split-variant clean)))
(let ((variant (first parts))
(base (nth parts 1))
(decls (resolve-atom base)))
(when decls
;; Check keyframes
(when (starts-with? base "animate-")
(let ((kf-name (slice base 8)))
(when (dict-has? _style-keyframes kf-name)
(append! kf-needed
(list kf-name (dict-get _style-keyframes kf-name))))))
(cond
(nil? variant)
(if (is-child-selector-atom? base)
(append! pseudo-rules
(list ">:not(:first-child)" decls))
(append! base-decls decls))
(dict-has? _responsive-breakpoints variant)
(append! media-rules
(list (dict-get _responsive-breakpoints variant) decls))
(dict-has? _pseudo-variants variant)
(append! pseudo-rules
(list (dict-get _pseudo-variants variant) decls))
;; Compound variant: "sm:hover"
:else
(let ((vparts (split variant ":"))
(media-part nil)
(pseudo-part nil))
(for-each
(fn (vp)
(cond
(dict-has? _responsive-breakpoints vp)
(set! media-part (dict-get _responsive-breakpoints vp))
(dict-has? _pseudo-variants vp)
(set! pseudo-part (dict-get _pseudo-variants vp))))
vparts)
(when media-part
(append! media-rules (list media-part decls)))
(when pseudo-part
(append! pseudo-rules (list pseudo-part decls)))
(when (and (nil? media-part) (nil? pseudo-part))
(append! base-decls decls))))))))))
atoms)
;; Build hash input
(let ((hash-input (join ";" base-decls)))
(for-each
(fn (mr)
(set! hash-input
(str hash-input "@" (first mr) "{" (nth mr 1) "}")))
media-rules)
(for-each
(fn (pr)
(set! hash-input
(str hash-input (first pr) "{" (nth pr 1) "}")))
pseudo-rules)
(for-each
(fn (kf)
(set! hash-input (str hash-input (nth kf 1))))
kf-needed)
(let ((cn (str "sx-" (hash-style hash-input)))
(sv (make-style-value cn
(join ";" base-decls)
media-rules
pseudo-rules
kf-needed)))
(dict-set! _style-cache key sv)
;; Inject CSS rules
(inject-style-value sv atoms)
sv))))))))
;; --------------------------------------------------------------------------
;; Merge multiple StyleValues
;; --------------------------------------------------------------------------
(define merge-style-values
(fn (styles)
(if (= (len styles) 1)
(first styles)
(let ((all-decls (list))
(all-media (list))
(all-pseudo (list))
(all-kf (list)))
(for-each
(fn (sv)
(when (style-value-declarations sv)
(append! all-decls (style-value-declarations sv)))
(set! all-media (concat all-media (style-value-media-rules sv)))
(set! all-pseudo (concat all-pseudo (style-value-pseudo-rules sv)))
(set! all-kf (concat all-kf (style-value-keyframes sv))))
styles)
(let ((hash-input (join ";" all-decls)))
(for-each
(fn (mr)
(set! hash-input
(str hash-input "@" (first mr) "{" (nth mr 1) "}")))
all-media)
(for-each
(fn (pr)
(set! hash-input
(str hash-input (first pr) "{" (nth pr 1) "}")))
all-pseudo)
(for-each
(fn (kf)
(set! hash-input (str hash-input (nth kf 1))))
all-kf)
(let ((cn (str "sx-" (hash-style hash-input)))
(merged (make-style-value cn
(join ";" all-decls)
all-media all-pseudo all-kf)))
(inject-style-value merged (list))
merged))))))
;; --------------------------------------------------------------------------
;; Platform interface — CSSX
;; --------------------------------------------------------------------------
;;
;; Hash:
;; (fnv1a-hash input) → 6-char hex string (FNV-1a 32-bit)
;;
;; Regex:
;; (compile-regex pattern) → compiled regex object
;; (regex-match re str) → match array or nil
;; (regex-replace-groups tmpl match) → string with {0},{1},... replaced
;;
;; StyleValue construction:
;; (make-style-value cn decls media pseudo kf) → StyleValue object
;; (style-value-declarations sv) → declarations string
;; (style-value-media-rules sv) → list of [query, decls] pairs
;; (style-value-pseudo-rules sv) → list of [selector, decls] pairs
;; (style-value-keyframes sv) → list of [name, rule] pairs
;;
;; CSS injection:
;; (inject-style-value sv atoms) → void (append CSS rules to <style id="sx-css">)
;; --------------------------------------------------------------------------

View File

@@ -143,7 +143,6 @@
(= name "defcomp") (sf-defcomp args env)
(= name "defmacro") (sf-defmacro args env)
(= name "defstyle") (sf-defstyle args env)
(= name "defkeyframes") (sf-defkeyframes args env)
(= name "defhandler") (sf-defhandler args env)
(= name "defpage") (sf-defpage args env)
(= name "defquery") (sf-defquery args env)
@@ -559,23 +558,13 @@
(define sf-defstyle
(fn (args env)
;; (defstyle name expr) — bind name to evaluated expr (typically a StyleValue)
;; (defstyle name expr) — bind name to evaluated expr (string, function, etc.)
(let ((name-sym (first args))
(value (trampoline (eval-expr (nth args 1) env))))
(env-set! env (symbol-name name-sym) value)
value)))
(define sf-defkeyframes
(fn (args env)
;; (defkeyframes name (selector body) ...) — build @keyframes rule,
;; register in keyframes dict, return StyleValue.
;; Delegates to platform: build-keyframes returns a StyleValue.
(let ((kf-name (symbol-name (first args)))
(steps (rest args)))
(build-keyframes kf-name steps env))))
(define sf-begin
(fn (args env)
(if (empty? args)
@@ -931,9 +920,6 @@
;; (zip lists...) → list of tuples
;;
;;
;; CSSX (style system):
;; (build-keyframes name steps env) → StyleValue (platform builds @keyframes)
;;
;; Dynamic wind (for dynamic-wind):
;; (push-wind! before after) → void (push wind record onto stack)
;; (pop-wind!) → void (pop wind record from stack)

View File

@@ -541,18 +541,6 @@
;; Stdlib — Style
;; --------------------------------------------------------------------------
(define-module :stdlib.style)
(define-primitive "css"
:params (&rest atoms)
:returns "style-value"
:doc "Resolve style atoms to a StyleValue with className and CSS declarations.
Atoms are keywords or strings: (css :flex :gap-4 :hover:bg-sky-200).")
(define-primitive "merge-styles"
:params (&rest styles)
:returns "style-value"
:doc "Merge multiple StyleValues into one combined StyleValue.")
;; --------------------------------------------------------------------------

View File

@@ -74,7 +74,7 @@
(define definition-form?
(fn (name)
(or (= name "define") (= name "defcomp") (= name "defmacro")
(= name "defstyle") (= name "defkeyframes") (= name "defhandler"))))
(= name "defstyle") (= name "defhandler"))))
(define parse-element-args
@@ -116,9 +116,6 @@
""
;; Nil values — skip
(nil? val) ""
;; StyleValue on :style → emit as class
(and (= key "style") (style-value? val))
(str " class=\"" (style-value-class val) "\"")
;; Normal attr
:else (str " " key "=\"" (escape-attr (str val)) "\""))))
(keys attrs)))))
@@ -202,10 +199,6 @@
;; (escape-attr s) → attribute-value-escaped string
;; (raw-html-content r) → unwrap RawHTML marker to string
;;
;; StyleValue:
;; (style-value? x) → boolean (is x a StyleValue?)
;; (style-value-class sv) → string (CSS class name)
;;
;; Serialization:
;; (serialize val) → SX source string representation of val
;;

View File

@@ -363,20 +363,12 @@
;; --------------------------------------------------------------------------
(define-special-form "defstyle"
:syntax (defstyle name atoms ...)
:doc "Define a named style. Evaluates atoms to a StyleValue and binds
it to name in the environment."
:syntax (defstyle name expr)
:doc "Define a named style value. Evaluates expr and binds the result
to name in the environment. The value is typically a class string
or a function that returns class strings."
:tail-position "none"
:example "(defstyle card-style :rounded-lg :shadow-md :p-4 :bg-white)")
(define-special-form "defkeyframes"
:syntax (defkeyframes name steps ...)
:doc "Define a CSS @keyframes animation. Steps are (percentage properties ...)
pairs. Produces a StyleValue with the animation name and keyframe rules."
:tail-position "none"
:example "(defkeyframes fade-in
(0 :opacity-0)
(100 :opacity-100))")
:example "(defstyle card-style \"rounded-lg shadow-md p-4 bg-white\")")
(define-special-form "defhandler"
:syntax (defhandler name (&key params ...) body)

View File

@@ -18,7 +18,7 @@ from typing import Any
# =========================================================================
from shared.sx.types import (
NIL, Symbol, Keyword, Lambda, Component, Continuation, Macro, StyleValue,
NIL, Symbol, Keyword, Lambda, Component, Continuation, Macro,
HandlerDef, QueryDef, ActionDef, PageDef, _ShiftSignal,
)
from shared.sx.parser import SxExpr
@@ -126,8 +126,6 @@ def type_of(x):
return "macro"
if isinstance(x, _RawHTML):
return "raw-html"
if isinstance(x, StyleValue):
return "style-value"
if isinstance(x, Continuation):
return "continuation"
if isinstance(x, list):
@@ -292,14 +290,6 @@ def is_macro(x):
return isinstance(x, Macro)
def is_style_value(x):
return isinstance(x, StyleValue)
def style_value_class(x):
return x.class_name
def env_has(env, name):
return name in env
@@ -450,8 +440,6 @@ def serialize(val):
if t == "raw-html":
escaped = escape_string(raw_html_content(val))
return '(raw! "' + escaped + '")'
if t == "style-value":
return '"' + style_value_class(val) + '"'
if t == "list":
if not val:
return "()"
@@ -471,7 +459,7 @@ def serialize(val):
_SPECIAL_FORM_NAMES = frozenset([
"if", "when", "cond", "case", "and", "or",
"let", "let*", "lambda", "fn",
"define", "defcomp", "defmacro", "defstyle", "defkeyframes",
"define", "defcomp", "defmacro", "defstyle",
"defhandler", "defpage", "defquery", "defaction", "defrelation",
"begin", "do", "quote", "quasiquote",
"->", "set!",
@@ -633,7 +621,7 @@ def aser_special(name, expr, env):
fn(item)
return results if results else NIL
# Definition forms — evaluate for side effects
if name in ("define", "defcomp", "defmacro", "defstyle", "defkeyframes",
if name in ("define", "defcomp", "defmacro", "defstyle",
"defhandler", "defpage", "defquery", "defaction", "defrelation"):
trampoline(eval_expr(expr, env))
return NIL
@@ -955,7 +943,7 @@ trampoline = lambda val: (lambda result: (trampoline(eval_expr(thunk_expr(result
eval_expr = lambda expr, env: _sx_case(type_of(expr), [('number', lambda: expr), ('string', lambda: expr), ('boolean', lambda: expr), ('nil', lambda: NIL), ('symbol', lambda: (lambda name: (env_get(env, name) if sx_truthy(env_has(env, name)) else (get_primitive(name) if sx_truthy(is_primitive(name)) else (True if sx_truthy((name == 'true')) else (False if sx_truthy((name == 'false')) else (NIL if sx_truthy((name == 'nil')) else error(sx_str('Undefined symbol: ', name))))))))(symbol_name(expr))), ('keyword', lambda: keyword_name(expr)), ('dict', lambda: map_dict(lambda k, v: trampoline(eval_expr(v, env)), expr)), ('list', lambda: ([] if sx_truthy(empty_p(expr)) else eval_list(expr, env))), (None, lambda: expr)])
# eval-list
eval_list = lambda expr, env: (lambda head: (lambda args: (map(lambda x: trampoline(eval_expr(x, env)), expr) if sx_truthy((not sx_truthy(((type_of(head) == 'symbol') if sx_truthy((type_of(head) == 'symbol')) else ((type_of(head) == 'lambda') if sx_truthy((type_of(head) == 'lambda')) else (type_of(head) == 'list')))))) else ((lambda name: (sf_if(args, env) if sx_truthy((name == 'if')) else (sf_when(args, env) if sx_truthy((name == 'when')) else (sf_cond(args, env) if sx_truthy((name == 'cond')) else (sf_case(args, env) if sx_truthy((name == 'case')) else (sf_and(args, env) if sx_truthy((name == 'and')) else (sf_or(args, env) if sx_truthy((name == 'or')) else (sf_let(args, env) if sx_truthy((name == 'let')) else (sf_let(args, env) if sx_truthy((name == 'let*')) else (sf_letrec(args, env) if sx_truthy((name == 'letrec')) else (sf_lambda(args, env) if sx_truthy((name == 'lambda')) else (sf_lambda(args, env) if sx_truthy((name == 'fn')) else (sf_define(args, env) if sx_truthy((name == 'define')) else (sf_defcomp(args, env) if sx_truthy((name == 'defcomp')) else (sf_defmacro(args, env) if sx_truthy((name == 'defmacro')) else (sf_defstyle(args, env) if sx_truthy((name == 'defstyle')) else (sf_defkeyframes(args, env) if sx_truthy((name == 'defkeyframes')) else (sf_defhandler(args, env) if sx_truthy((name == 'defhandler')) else (sf_defpage(args, env) if sx_truthy((name == 'defpage')) else (sf_defquery(args, env) if sx_truthy((name == 'defquery')) else (sf_defaction(args, env) if sx_truthy((name == 'defaction')) else (sf_begin(args, env) if sx_truthy((name == 'begin')) else (sf_begin(args, env) if sx_truthy((name == 'do')) else (sf_quote(args, env) if sx_truthy((name == 'quote')) else (sf_quasiquote(args, env) if sx_truthy((name == 'quasiquote')) else (sf_thread_first(args, env) if sx_truthy((name == '->')) else (sf_set_bang(args, env) if sx_truthy((name == 'set!')) else (sf_reset(args, env) if sx_truthy((name == 'reset')) else (sf_shift(args, env) if sx_truthy((name == 'shift')) else (sf_dynamic_wind(args, env) if sx_truthy((name == 'dynamic-wind')) else (ho_map(args, env) if sx_truthy((name == 'map')) else (ho_map_indexed(args, env) if sx_truthy((name == 'map-indexed')) else (ho_filter(args, env) if sx_truthy((name == 'filter')) else (ho_reduce(args, env) if sx_truthy((name == 'reduce')) else (ho_some(args, env) if sx_truthy((name == 'some')) else (ho_every(args, env) if sx_truthy((name == 'every?')) else (ho_for_each(args, env) if sx_truthy((name == 'for-each')) else ((lambda mac: make_thunk(expand_macro(mac, args, env), env))(env_get(env, name)) if sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))) else (render_expr(expr, env) if sx_truthy(is_render_expr(expr)) else eval_call(head, args, env))))))))))))))))))))))))))))))))))))))))(symbol_name(head)) if sx_truthy((type_of(head) == 'symbol')) else eval_call(head, args, env))))(rest(expr)))(first(expr))
eval_list = lambda expr, env: (lambda head: (lambda args: (map(lambda x: trampoline(eval_expr(x, env)), expr) if sx_truthy((not sx_truthy(((type_of(head) == 'symbol') if sx_truthy((type_of(head) == 'symbol')) else ((type_of(head) == 'lambda') if sx_truthy((type_of(head) == 'lambda')) else (type_of(head) == 'list')))))) else ((lambda name: (sf_if(args, env) if sx_truthy((name == 'if')) else (sf_when(args, env) if sx_truthy((name == 'when')) else (sf_cond(args, env) if sx_truthy((name == 'cond')) else (sf_case(args, env) if sx_truthy((name == 'case')) else (sf_and(args, env) if sx_truthy((name == 'and')) else (sf_or(args, env) if sx_truthy((name == 'or')) else (sf_let(args, env) if sx_truthy((name == 'let')) else (sf_let(args, env) if sx_truthy((name == 'let*')) else (sf_letrec(args, env) if sx_truthy((name == 'letrec')) else (sf_lambda(args, env) if sx_truthy((name == 'lambda')) else (sf_lambda(args, env) if sx_truthy((name == 'fn')) else (sf_define(args, env) if sx_truthy((name == 'define')) else (sf_defcomp(args, env) if sx_truthy((name == 'defcomp')) else (sf_defmacro(args, env) if sx_truthy((name == 'defmacro')) else (sf_defstyle(args, env) if sx_truthy((name == 'defstyle')) else (sf_defhandler(args, env) if sx_truthy((name == 'defhandler')) else (sf_defpage(args, env) if sx_truthy((name == 'defpage')) else (sf_defquery(args, env) if sx_truthy((name == 'defquery')) else (sf_defaction(args, env) if sx_truthy((name == 'defaction')) else (sf_begin(args, env) if sx_truthy((name == 'begin')) else (sf_begin(args, env) if sx_truthy((name == 'do')) else (sf_quote(args, env) if sx_truthy((name == 'quote')) else (sf_quasiquote(args, env) if sx_truthy((name == 'quasiquote')) else (sf_thread_first(args, env) if sx_truthy((name == '->')) else (sf_set_bang(args, env) if sx_truthy((name == 'set!')) else (sf_reset(args, env) if sx_truthy((name == 'reset')) else (sf_shift(args, env) if sx_truthy((name == 'shift')) else (sf_dynamic_wind(args, env) if sx_truthy((name == 'dynamic-wind')) else (ho_map(args, env) if sx_truthy((name == 'map')) else (ho_map_indexed(args, env) if sx_truthy((name == 'map-indexed')) else (ho_filter(args, env) if sx_truthy((name == 'filter')) else (ho_reduce(args, env) if sx_truthy((name == 'reduce')) else (ho_some(args, env) if sx_truthy((name == 'some')) else (ho_every(args, env) if sx_truthy((name == 'every?')) else (ho_for_each(args, env) if sx_truthy((name == 'for-each')) else ((lambda mac: make_thunk(expand_macro(mac, args, env), env))(env_get(env, name)) if sx_truthy((env_has(env, name) if not sx_truthy(env_has(env, name)) else is_macro(env_get(env, name)))) else (render_expr(expr, env) if sx_truthy(is_render_expr(expr)) else eval_call(head, args, env)))))))))))))))))))))))))))))))))))))))(symbol_name(head)) if sx_truthy((type_of(head) == 'symbol')) else eval_call(head, args, env))))(rest(expr)))(first(expr))
# eval-call
eval_call = lambda head, args, env: (lambda f: (lambda evaluated_args: (apply(f, evaluated_args) if sx_truthy((is_callable(f) if not sx_truthy(is_callable(f)) else ((not sx_truthy(is_lambda(f))) if not sx_truthy((not sx_truthy(is_lambda(f)))) else (not sx_truthy(is_component(f)))))) else (call_lambda(f, evaluated_args, env) if sx_truthy(is_lambda(f)) else (call_component(f, args, env) if sx_truthy(is_component(f)) else error(sx_str('Not callable: ', inspect(f)))))))(map(lambda a: trampoline(eval_expr(a, env)), args)))(trampoline(eval_expr(head, env)))
@@ -1051,9 +1039,6 @@ def parse_macro_params(params_expr):
# sf-defstyle
sf_defstyle = lambda args, env: (lambda name_sym: (lambda value: _sx_begin(_sx_dict_set(env, symbol_name(name_sym), value), value))(trampoline(eval_expr(nth(args, 1), env))))(first(args))
# sf-defkeyframes
sf_defkeyframes = lambda args, env: (lambda kf_name: (lambda steps: build_keyframes(kf_name, steps, env))(rest(args)))(symbol_name(first(args)))
# sf-begin
sf_begin = lambda args, env: (NIL if sx_truthy(empty_p(args)) else _sx_begin(for_each(lambda e: trampoline(eval_expr(e, env)), slice(args, 0, (len(args) - 1))), make_thunk(last(args), env)))
@@ -1164,13 +1149,13 @@ VOID_ELEMENTS = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'li
BOOLEAN_ATTRS = ['async', 'autofocus', 'autoplay', 'checked', 'controls', 'default', 'defer', 'disabled', 'formnovalidate', 'hidden', 'inert', 'ismap', 'loop', 'multiple', 'muted', 'nomodule', 'novalidate', 'open', 'playsinline', 'readonly', 'required', 'reversed', 'selected']
# definition-form?
is_definition_form = lambda name: ((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else ((name == 'defkeyframes') if sx_truthy((name == 'defkeyframes')) else (name == 'defhandler'))))))
is_definition_form = lambda name: ((name == 'define') if sx_truthy((name == 'define')) else ((name == 'defcomp') if sx_truthy((name == 'defcomp')) else ((name == 'defmacro') if sx_truthy((name == 'defmacro')) else ((name == 'defstyle') if sx_truthy((name == 'defstyle')) else (name == 'defhandler')))))
# parse-element-args
parse_element_args = lambda args, env: (lambda attrs: (lambda children: _sx_begin(reduce(lambda state, arg: (lambda skip: (assoc(state, 'skip', False, 'i', (get(state, 'i') + 1)) if sx_truthy(skip) else ((lambda val: _sx_begin(_sx_dict_set(attrs, keyword_name(arg), val), assoc(state, 'skip', True, 'i', (get(state, 'i') + 1))))(trampoline(eval_expr(nth(args, (get(state, 'i') + 1)), env))) if sx_truthy(((type_of(arg) == 'keyword') if not sx_truthy((type_of(arg) == 'keyword')) else ((get(state, 'i') + 1) < len(args)))) else _sx_begin(_sx_append(children, arg), assoc(state, 'i', (get(state, 'i') + 1))))))(get(state, 'skip')), {'i': 0, 'skip': False}, args), [attrs, children]))([]))({})
# render-attrs
render_attrs = lambda attrs: join('', map(lambda key: (lambda val: (sx_str(' ', key) if sx_truthy((contains_p(BOOLEAN_ATTRS, key) if not sx_truthy(contains_p(BOOLEAN_ATTRS, key)) else val)) else ('' if sx_truthy((contains_p(BOOLEAN_ATTRS, key) if not sx_truthy(contains_p(BOOLEAN_ATTRS, key)) else (not sx_truthy(val)))) else ('' if sx_truthy(is_nil(val)) else (sx_str(' class="', style_value_class(val), '"') if sx_truthy(((key == 'style') if not sx_truthy((key == 'style')) else is_style_value(val))) else sx_str(' ', key, '="', escape_attr(sx_str(val)), '"'))))))(dict_get(attrs, key)), keys(attrs)))
render_attrs = lambda attrs: join('', map(lambda key: (lambda val: (sx_str(' ', key) if sx_truthy((contains_p(BOOLEAN_ATTRS, key) if not sx_truthy(contains_p(BOOLEAN_ATTRS, key)) else val)) else ('' if sx_truthy((contains_p(BOOLEAN_ATTRS, key) if not sx_truthy(contains_p(BOOLEAN_ATTRS, key)) else (not sx_truthy(val)))) else ('' if sx_truthy(is_nil(val)) else sx_str(' ', key, '="', escape_attr(sx_str(val)), '"')))))(dict_get(attrs, key)), keys(attrs)))
# eval-cond
eval_cond = lambda clauses, env: (eval_cond_scheme(clauses, env) if sx_truthy(((not sx_truthy(empty_p(clauses))) if not sx_truthy((not sx_truthy(empty_p(clauses)))) else ((type_of(first(clauses)) == 'list') if not sx_truthy((type_of(first(clauses)) == 'list')) else (len(first(clauses)) == 2)))) else eval_cond_clojure(clauses, env))
@@ -1191,10 +1176,10 @@ process_bindings = lambda bindings, env: (lambda local: _sx_begin(for_each(lambd
render_to_html = lambda expr, env: _sx_case(type_of(expr), [('nil', lambda: ''), ('string', lambda: escape_html(expr)), ('number', lambda: sx_str(expr)), ('boolean', lambda: ('true' if sx_truthy(expr) else 'false')), ('list', lambda: ('' if sx_truthy(empty_p(expr)) else render_list_to_html(expr, env))), ('symbol', lambda: render_value_to_html(trampoline(eval_expr(expr, env)), env)), ('keyword', lambda: escape_html(keyword_name(expr))), ('raw-html', lambda: raw_html_content(expr)), (None, lambda: render_value_to_html(trampoline(eval_expr(expr, env)), env))])
# render-value-to-html
render_value_to_html = lambda val, env: _sx_case(type_of(val), [('nil', lambda: ''), ('string', lambda: escape_html(val)), ('number', lambda: sx_str(val)), ('boolean', lambda: ('true' if sx_truthy(val) else 'false')), ('list', lambda: render_list_to_html(val, env)), ('raw-html', lambda: raw_html_content(val)), ('style-value', lambda: style_value_class(val)), (None, lambda: escape_html(sx_str(val)))])
render_value_to_html = lambda val, env: _sx_case(type_of(val), [('nil', lambda: ''), ('string', lambda: escape_html(val)), ('number', lambda: sx_str(val)), ('boolean', lambda: ('true' if sx_truthy(val) else 'false')), ('list', lambda: render_list_to_html(val, env)), ('raw-html', lambda: raw_html_content(val)), (None, lambda: escape_html(sx_str(val)))])
# RENDER_HTML_FORMS
RENDER_HTML_FORMS = ['if', 'when', 'cond', 'case', 'let', 'let*', 'begin', 'do', 'define', 'defcomp', 'defmacro', 'defstyle', 'defkeyframes', 'defhandler', 'map', 'map-indexed', 'filter', 'for-each']
RENDER_HTML_FORMS = ['if', 'when', 'cond', 'case', 'let', 'let*', 'begin', 'do', 'define', 'defcomp', 'defmacro', 'defstyle', 'defhandler', 'map', 'map-indexed', 'filter', 'for-each']
# render-html-form?
is_render_html_form = lambda name: contains_p(RENDER_HTML_FORMS, name)