Refactor SX primitives: modular, isomorphic, general-purpose

Spec modularization:
- Add (define-module :name) markers to primitives.sx creating 11 modules
  (7 core, 4 stdlib). Bootstrappers can now selectively include modules.
- Add parse_primitives_by_module() to boundary_parser.py.
- Remove split-ids primitive; inline at 4 call sites in blog/market queries.

Python file split:
- primitives.py: slimmed to registry + core primitives only (~350 lines)
- primitives_stdlib.py: NEW — stdlib primitives (format, text, style, debug)
- primitives_ctx.py: NEW — extracted 12 page context builders from IO
- primitives_io.py: add register_io_handler decorator, auto-derive
  IO_PRIMITIVES from registry, move sync IO bridges here

JS parity fixes:
- = uses === (strict equality), != uses !==
- round supports optional ndigits parameter
- concat uses nil-check not falsy-check (preserves 0, "", false)
- escape adds single quote entity (') matching Python/markupsafe
- assert added (was missing from JS entirely)

Bootstrapper modularization:
- PRIMITIVES_JS_MODULES / PRIMITIVES_PY_MODULES dicts keyed by module
- --modules CLI flag for selective inclusion (core.* always included)
- Regenerated sx-ref.js and sx_ref.py with all fixes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 01:45:29 +00:00
parent cfde5bc491
commit f77d7350dd
13 changed files with 3545 additions and 1303 deletions

View File

@@ -801,14 +801,30 @@ ADAPTER_FILES = {
}
def compile_ref_to_py(adapters: list[str] | None = None) -> str:
def compile_ref_to_py(
adapters: list[str] | None = None,
modules: list[str] | None = None,
) -> str:
"""Read reference .sx files and emit Python.
Args:
adapters: List of adapter names to include.
Valid names: html, sx.
None = include all server-side adapters.
modules: List of primitive module names to include.
core.* are always included. stdlib.* are opt-in.
None = include all modules (backward compatible).
"""
# Determine which primitive modules to include
prim_modules = None # None = all
if modules is not None:
prim_modules = [m for m in _ALL_PY_MODULES if m.startswith("core.")]
for m in modules:
if m not in prim_modules:
if m not in PRIMITIVES_PY_MODULES:
raise ValueError(f"Unknown module: {m!r}. Valid: {', '.join(PRIMITIVES_PY_MODULES)}")
prim_modules.append(m)
ref_dir = os.path.dirname(os.path.abspath(__file__))
emitter = PyEmitter()
@@ -849,7 +865,9 @@ def compile_ref_to_py(adapters: list[str] | None = None) -> str:
parts = []
parts.append(PREAMBLE)
parts.append(PLATFORM_PY)
parts.append(PRIMITIVES_PY)
parts.append(PRIMITIVES_PY_PRE)
parts.append(_assemble_primitives_py(prim_modules))
parts.append(PRIMITIVES_PY_POST)
for label, defines in all_sections:
parts.append(f"\n# === Transpiled from {label} ===\n")
@@ -1506,29 +1524,14 @@ def aser_special(name, expr, env):
return trampoline(result)
'''
PRIMITIVES_PY = '''
# =========================================================================
# Primitives
# =========================================================================
# ---------------------------------------------------------------------------
# Primitive modules — Python implementations keyed by spec module name.
# core.* modules are always included; stdlib.* are opt-in.
# ---------------------------------------------------------------------------
# Save builtins before shadowing
_b_len = len
_b_map = map
_b_filter = filter
_b_range = range
_b_list = list
_b_dict = dict
_b_max = max
_b_min = min
_b_round = round
_b_abs = abs
_b_sum = sum
_b_zip = zip
_b_int = int
PRIMITIVES = {}
# Arithmetic
PRIMITIVES_PY_MODULES: dict[str, str] = {
"core.arithmetic": '''
# core.arithmetic
PRIMITIVES["+"] = lambda *args: _b_sum(args)
PRIMITIVES["-"] = lambda a, b=None: -a if b is None else a - b
PRIMITIVES["*"] = lambda *args: _sx_mul(*args)
@@ -1551,37 +1554,25 @@ def _sx_mul(*args):
for a in args:
r *= a
return r
''',
# Comparison
"core.comparison": '''
# core.comparison
PRIMITIVES["="] = lambda a, b: a == b
PRIMITIVES["!="] = lambda a, b: a != b
PRIMITIVES["<"] = lambda a, b: a < b
PRIMITIVES[">"] = lambda a, b: a > b
PRIMITIVES["<="] = lambda a, b: a <= b
PRIMITIVES[">="] = lambda a, b: a >= b
''',
# Logic
"core.logic": '''
# core.logic
PRIMITIVES["not"] = lambda x: not sx_truthy(x)
''',
# String
PRIMITIVES["str"] = sx_str
PRIMITIVES["upper"] = lambda s: str(s).upper()
PRIMITIVES["lower"] = lambda s: str(s).lower()
PRIMITIVES["trim"] = lambda s: str(s).strip()
PRIMITIVES["split"] = lambda s, sep=" ": str(s).split(sep)
PRIMITIVES["join"] = lambda sep, coll: sep.join(coll)
PRIMITIVES["replace"] = lambda s, old, new: s.replace(old, new)
PRIMITIVES["starts-with?"] = lambda s, p: str(s).startswith(p)
PRIMITIVES["ends-with?"] = lambda s, p: str(s).endswith(p)
PRIMITIVES["slice"] = lambda c, a, b=None: c[a:b] if b is not None else c[a:]
PRIMITIVES["concat"] = lambda *args: _b_sum((a for a in args if a), [])
PRIMITIVES["strip-tags"] = lambda s: _strip_tags(str(s))
import re as _re
def _strip_tags(s):
return _re.sub(r"<[^>]+>", "", s)
# Predicates
"core.predicates": '''
# core.predicates
PRIMITIVES["nil?"] = lambda x: x is None or x is NIL
PRIMITIVES["number?"] = lambda x: isinstance(x, (int, float)) and not isinstance(x, bool)
PRIMITIVES["string?"] = lambda x: isinstance(x, str)
@@ -1598,8 +1589,25 @@ PRIMITIVES["contains?"] = lambda c, k: (
PRIMITIVES["odd?"] = lambda n: n % 2 != 0
PRIMITIVES["even?"] = lambda n: n % 2 == 0
PRIMITIVES["zero?"] = lambda n: n == 0
''',
# Collections
"core.strings": '''
# core.strings
PRIMITIVES["str"] = sx_str
PRIMITIVES["upper"] = lambda s: str(s).upper()
PRIMITIVES["lower"] = lambda s: str(s).lower()
PRIMITIVES["trim"] = lambda s: str(s).strip()
PRIMITIVES["split"] = lambda s, sep=" ": str(s).split(sep)
PRIMITIVES["join"] = lambda sep, coll: sep.join(coll)
PRIMITIVES["replace"] = lambda s, old, new: s.replace(old, new)
PRIMITIVES["starts-with?"] = lambda s, p: str(s).startswith(p)
PRIMITIVES["ends-with?"] = lambda s, p: str(s).endswith(p)
PRIMITIVES["slice"] = lambda c, a, b=None: c[a:b] if b is not None else c[a:]
PRIMITIVES["concat"] = lambda *args: _b_sum((a for a in args if a), [])
''',
"core.collections": '''
# core.collections
PRIMITIVES["list"] = lambda *args: _b_list(args)
PRIMITIVES["dict"] = lambda *args: {args[i]: args[i+1] for i in _b_range(0, _b_len(args)-1, 2)}
PRIMITIVES["range"] = lambda a, b, step=1: _b_list(_b_range(_b_int(a), _b_int(b), _b_int(step)))
@@ -1611,22 +1619,20 @@ PRIMITIVES["rest"] = lambda c: c[1:] if c else []
PRIMITIVES["nth"] = lambda c, n: c[n] if c and 0 <= n < _b_len(c) else NIL
PRIMITIVES["cons"] = lambda x, c: [x] + (c or [])
PRIMITIVES["append"] = lambda c, x: (c or []) + [x]
PRIMITIVES["chunk-every"] = lambda c, n: [c[i:i+n] for i in _b_range(0, _b_len(c), n)]
PRIMITIVES["zip-pairs"] = lambda c: [[c[i], c[i+1]] for i in _b_range(_b_len(c)-1)]
''',
"core.dict": '''
# core.dict
PRIMITIVES["keys"] = lambda d: _b_list((d or {}).keys())
PRIMITIVES["vals"] = lambda d: _b_list((d or {}).values())
PRIMITIVES["merge"] = lambda *args: _sx_merge_dicts(*args)
PRIMITIVES["assoc"] = lambda d, *kvs: _sx_assoc(d, *kvs)
PRIMITIVES["dissoc"] = lambda d, *ks: {k: v for k, v in d.items() if k not in ks}
PRIMITIVES["chunk-every"] = lambda c, n: [c[i:i+n] for i in _b_range(0, _b_len(c), n)]
PRIMITIVES["zip-pairs"] = lambda c: [[c[i], c[i+1]] for i in _b_range(_b_len(c)-1)]
PRIMITIVES["into"] = lambda target, coll: (_b_list(coll) if isinstance(target, _b_list) else {p[0]: p[1] for p in coll if isinstance(p, _b_list) and _b_len(p) >= 2})
PRIMITIVES["zip"] = lambda *colls: [_b_list(t) for t in _b_zip(*colls)]
# Format
PRIMITIVES["format-decimal"] = lambda v, p=2: f"{float(v):.{p}f}"
PRIMITIVES["parse-int"] = lambda v, d=0: _sx_parse_int(v, d)
PRIMITIVES["pluralize"] = lambda n, s="", p="s": s if n == 1 else p
PRIMITIVES["escape"] = escape_html
def _sx_merge_dicts(*args):
out = {}
for d in args:
@@ -1639,13 +1645,80 @@ def _sx_assoc(d, *kvs):
for i in _b_range(0, _b_len(kvs) - 1, 2):
out[kvs[i]] = kvs[i + 1]
return out
''',
"stdlib.format": '''
# stdlib.format
PRIMITIVES["format-decimal"] = lambda v, p=2: f"{float(v):.{p}f}"
PRIMITIVES["parse-int"] = lambda v, d=0: _sx_parse_int(v, d)
PRIMITIVES["parse-datetime"] = lambda s: str(s) if s else NIL
def _sx_parse_int(v, default=0):
try:
return _b_int(v)
except (ValueError, TypeError):
return default
''',
"stdlib.text": '''
# stdlib.text
PRIMITIVES["pluralize"] = lambda n, s="", p="s": s if n == 1 else p
PRIMITIVES["escape"] = escape_html
PRIMITIVES["strip-tags"] = lambda s: _strip_tags(str(s))
import re as _re
def _strip_tags(s):
return _re.sub(r"<[^>]+>", "", s)
''',
"stdlib.style": '''
# stdlib.style — stubs (CSSX needs full runtime)
''',
"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
''',
}
_ALL_PY_MODULES = list(PRIMITIVES_PY_MODULES.keys())
def _assemble_primitives_py(modules: list[str] | None = None) -> str:
"""Assemble Python primitive code from selected modules."""
if modules is None:
modules = _ALL_PY_MODULES
parts = []
for mod in modules:
if mod in PRIMITIVES_PY_MODULES:
parts.append(PRIMITIVES_PY_MODULES[mod])
return "\n".join(parts)
PRIMITIVES_PY_PRE = '''
# =========================================================================
# Primitives
# =========================================================================
# Save builtins before shadowing
_b_len = len
_b_map = map
_b_filter = filter
_b_range = range
_b_list = list
_b_dict = dict
_b_max = max
_b_min = min
_b_round = round
_b_abs = abs
_b_sum = sum
_b_zip = zip
_b_int = int
PRIMITIVES = {}
'''
PRIMITIVES_PY_POST = '''
def is_primitive(name):
if name in PRIMITIVES:
return True
@@ -1811,9 +1884,15 @@ def main():
default=None,
help="Comma-separated adapter names (html,sx). Default: all server-side.",
)
parser.add_argument(
"--modules",
default=None,
help="Comma-separated primitive modules (core.* always included). Default: all.",
)
args = parser.parse_args()
adapters = args.adapters.split(",") if args.adapters else None
print(compile_ref_to_py(adapters))
modules = args.modules.split(",") if args.modules else None
print(compile_ref_to_py(adapters, modules))
if __name__ == "__main__":