Self-hosted z3.sx translator, prove.sx prover, parser unicode, auto reader macros

- z3.sx: SX-to-SMT-LIB translator written in SX (359 lines), replaces Python translation logic
- prove.sx: SMT-LIB satisfiability checker in SX — proves all 91 primitives sat by construction
- Parser: support unicode characters (em-dash, accented letters) in symbols
- Auto-resolve reader macros: #name finds name-translate in component env, no Python registration
- Platform primitives: type-of, symbol-name, keyword-name, sx-parse registered in primitives.py
- Cond heuristic: predicates ending in ? recognized as Clojure-style tests
- Library loading: z3.sx loaded at startup with reload callbacks for hot-reload ordering
- reader_z3.py: rewritten as thin shell delegating to z3.sx
- Split monolithic .sx files: essays (22), plans (13), reactive-islands (6) into separate files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 22:47:53 +00:00
parent 8b1333de96
commit 3ca89ef765
53 changed files with 5970 additions and 5222 deletions

View File

@@ -32,6 +32,29 @@ def register_reader_macro(name: str, handler: Any) -> None:
_READER_MACROS[name] = handler
def _resolve_sx_reader_macro(name: str):
"""Auto-resolve a reader macro from the component env.
If a file like z3.sx defines (define z3-translate ...), then #z3 is
automatically available as a reader macro without any Python registration.
Looks for {name}-translate as a Lambda in the component env.
"""
try:
from .jinja_bridge import get_component_env
from .evaluator import _trampoline, _call_lambda
from .types import Lambda
except ImportError:
return None
env = get_component_env()
fn = env.get(f"{name}-translate")
if fn is None or not isinstance(fn, Lambda):
return None
# Return a Python callable that invokes the SX lambda
def _sx_handler(expr):
return _trampoline(_call_lambda(fn, [expr], env))
return _sx_handler
# ---------------------------------------------------------------------------
# SxExpr — pre-built sx source marker
# ---------------------------------------------------------------------------
@@ -114,8 +137,8 @@ class Tokenizer:
NUMBER = re.compile(r"-?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?")
KEYWORD = re.compile(r":[a-zA-Z_~*+\-><=/!?&\[]{1}[a-zA-Z0-9_~*+\-><=/!?.:&/\[\]#,]*")
# Symbols may start with alpha, _, or common operator chars, plus ~ for components,
# <> for the fragment symbol, and & for &key/&rest.
SYMBOL = re.compile(r"[a-zA-Z_~*+\-><=/!?&][a-zA-Z0-9_~*+\-><=/!?.:&]*")
# <> for the fragment symbol, & for &key/&rest, and unicode letters (é, ñ, em-dash…).
SYMBOL = re.compile(r"[a-zA-Z_~*+\-><=/!?&\u0080-\uFFFF][a-zA-Z0-9_~*+\-><=/!?.:&\u0080-\uFFFF]*")
def __init__(self, text: str):
self.text = text
@@ -321,6 +344,9 @@ def _parse_expr(tok: Tokenizer) -> Any:
if dispatch.isalpha() or dispatch in "_~":
macro_name = tok._read_ident()
handler = _READER_MACROS.get(macro_name)
if handler is None:
# Auto-resolve: look for {name}-translate in component env
handler = _resolve_sx_reader_macro(macro_name)
if handler is None:
raise ParseError(f"Unknown reader macro: #{macro_name}",
tok.pos, tok.line, tok.col)