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:
@@ -656,7 +656,8 @@ _b_int = int
|
||||
|
||||
PRIMITIVES = {}
|
||||
|
||||
# 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)
|
||||
@@ -680,7 +681,8 @@ def _sx_mul(*args):
|
||||
r *= a
|
||||
return r
|
||||
|
||||
# Comparison
|
||||
|
||||
# core.comparison
|
||||
PRIMITIVES["="] = lambda a, b: a == b
|
||||
PRIMITIVES["!="] = lambda a, b: a != b
|
||||
PRIMITIVES["<"] = lambda a, b: a < b
|
||||
@@ -688,28 +690,12 @@ PRIMITIVES[">"] = lambda a, b: a > b
|
||||
PRIMITIVES["<="] = lambda a, b: a <= b
|
||||
PRIMITIVES[">="] = lambda a, b: a >= b
|
||||
|
||||
# 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
|
||||
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)
|
||||
@@ -727,7 +713,22 @@ PRIMITIVES["odd?"] = lambda n: n % 2 != 0
|
||||
PRIMITIVES["even?"] = lambda n: n % 2 == 0
|
||||
PRIMITIVES["zero?"] = lambda n: n == 0
|
||||
|
||||
# Collections
|
||||
|
||||
# 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
|
||||
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)))
|
||||
@@ -739,22 +740,19 @@ 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
|
||||
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:
|
||||
@@ -768,12 +766,36 @@ def _sx_assoc(d, *kvs):
|
||||
out[kvs[i]] = kvs[i + 1]
|
||||
return out
|
||||
|
||||
|
||||
# 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
|
||||
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 — stubs (CSSX needs full runtime)
|
||||
|
||||
|
||||
# stdlib.debug
|
||||
PRIMITIVES["assert"] = lambda cond, msg="Assertion failed": (_ for _ in ()).throw(RuntimeError(f"Assertion error: {msg}")) if not sx_truthy(cond) else True
|
||||
|
||||
|
||||
def is_primitive(name):
|
||||
if name in PRIMITIVES:
|
||||
return True
|
||||
@@ -1161,4 +1183,4 @@ def render(expr, env=None):
|
||||
|
||||
def make_env(**kwargs):
|
||||
"""Create an environment dict with initial bindings."""
|
||||
return dict(kwargs)
|
||||
return dict(kwargs)
|
||||
Reference in New Issue
Block a user