Add spread + collect primitives, rewrite ~cssx/tw as defcomp
New SX primitives for child-to-parent communication in the render tree: - spread type: make-spread, spread?, spread-attrs — child injects attrs onto parent element (class joins with space, style with semicolon) - collect!/collected/clear-collected! — render-time accumulation with dedup into named buckets ~cssx/tw is now a proper defcomp returning a spread value instead of a macro wrapping children. ~cssx/flush reads collected "cssx" rules and emits a single <style data-cssx> tag. All four render adapters (html, async, dom, aser) handle spread values. Both bootstraps (Python + JS) regenerated. Also fixes length→len in cssx.sx (length was never a registered primitive). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -84,6 +84,23 @@ class _RawHTML:
|
||||
self.html = html
|
||||
|
||||
|
||||
class _Spread:
|
||||
"""Attribute injection value — merges attrs onto parent element."""
|
||||
__slots__ = ("attrs",)
|
||||
def __init__(self, attrs: dict):
|
||||
self.attrs = dict(attrs) if attrs else {}
|
||||
|
||||
|
||||
# Render-time accumulator buckets (per render pass)
|
||||
_collect_buckets: dict[str, list] = {}
|
||||
|
||||
|
||||
def _collect_reset():
|
||||
"""Reset all collect buckets (call at start of each render pass)."""
|
||||
global _collect_buckets
|
||||
_collect_buckets = {}
|
||||
|
||||
|
||||
def sx_truthy(x):
|
||||
"""SX truthiness: everything is truthy except False, None, and NIL."""
|
||||
if x is False:
|
||||
@@ -167,6 +184,8 @@ def type_of(x):
|
||||
return "island"
|
||||
if isinstance(x, _Signal):
|
||||
return "signal"
|
||||
if isinstance(x, _Spread):
|
||||
return "spread"
|
||||
if isinstance(x, Macro):
|
||||
return "macro"
|
||||
if isinstance(x, _RawHTML):
|
||||
@@ -270,6 +289,38 @@ def make_thunk(expr, env):
|
||||
return _Thunk(expr, env)
|
||||
|
||||
|
||||
def make_spread(attrs):
|
||||
return _Spread(attrs if isinstance(attrs, dict) else {})
|
||||
|
||||
|
||||
def is_spread(x):
|
||||
return isinstance(x, _Spread)
|
||||
|
||||
|
||||
def spread_attrs(s):
|
||||
return s.attrs if isinstance(s, _Spread) else {}
|
||||
|
||||
|
||||
def sx_collect(bucket, value):
|
||||
"""Add value to named render-time accumulator (deduplicated)."""
|
||||
if bucket not in _collect_buckets:
|
||||
_collect_buckets[bucket] = []
|
||||
items = _collect_buckets[bucket]
|
||||
if value not in items:
|
||||
items.append(value)
|
||||
|
||||
|
||||
def sx_collected(bucket):
|
||||
"""Return all values in named render-time accumulator."""
|
||||
return list(_collect_buckets.get(bucket, []))
|
||||
|
||||
|
||||
def sx_clear_collected(bucket):
|
||||
"""Clear a named render-time accumulator bucket."""
|
||||
if bucket in _collect_buckets:
|
||||
_collect_buckets[bucket] = []
|
||||
|
||||
|
||||
def lambda_params(f):
|
||||
return f.params
|
||||
|
||||
@@ -881,6 +932,16 @@ def _strip_tags(s):
|
||||
"stdlib.debug": '''
|
||||
# stdlib.debug
|
||||
PRIMITIVES["assert"] = lambda cond, msg="Assertion failed": (_ for _ in ()).throw(RuntimeError(f"Assertion error: {msg}")) if not sx_truthy(cond) else True
|
||||
''',
|
||||
|
||||
"stdlib.spread": '''
|
||||
# stdlib.spread — spread + collect primitives
|
||||
PRIMITIVES["make-spread"] = make_spread
|
||||
PRIMITIVES["spread?"] = is_spread
|
||||
PRIMITIVES["spread-attrs"] = spread_attrs
|
||||
PRIMITIVES["collect!"] = sx_collect
|
||||
PRIMITIVES["collected"] = sx_collected
|
||||
PRIMITIVES["clear-collected!"] = sx_clear_collected
|
||||
''',
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user