Complete Python eval removal: epoch protocol, scope consolidation, JIT fixes

Route all rendering through OCaml bridge — render_to_html no longer uses
Python async_eval. Fix register_components to parse &key params and &rest
children from defcomp forms. Remove all dead sx_ref.py imports.

Epoch protocol (prevents pipe desync):
- Every command prefixed with (epoch N), all responses tagged with epoch
- Both sides discard stale-epoch messages — desync structurally impossible
- OCaml main loop discards stale io-responses between commands

Consolidate scope primitives into sx_scope.ml:
- Single source of truth for scope-push!/pop!/peek, collect!/collected,
  emit!/emitted, context, and 12 other scope operations
- Removes duplicate registrations from sx_server.ml (including bugs where
  scope-emit! and clear-collected! were registered twice with different impls)
- Bind scope prims into env so JIT VM finds them via OP_GLOBAL_GET

JIT VM fixes:
- Trampoline thunks before passing args to CALL_PRIM
- as_list resolves thunks via _sx_trampoline_fn
- len handles all value types (Bool, Number, RawHTML, SxExpr, Spread, etc.)

Other fixes:
- ~cssx/tw signature: (tokens) → (&key tokens) to match callers
- Minimal Python evaluator in html.py for sync sx() Jinja function
- Python scope primitive stubs (thread-local) for non-OCaml paths
- Reader macro resolution via OcamlSync instead of sx_ref.py

Tests: 1114 OCaml, 1078 JS, 35 Python regression, 6/6 Playwright SSR

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 16:14:40 +00:00
parent e887c0d978
commit f9f810ffd7
18 changed files with 1305 additions and 478 deletions

View File

@@ -38,10 +38,11 @@ def _resolve_sx_reader_macro(name: str):
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.
Uses the synchronous OCaml bridge (ocaml_sync) when available.
"""
try:
from .jinja_bridge import get_component_env
from .ref.sx_ref import trampoline as _trampoline, call_lambda as _call_lambda
from .types import Lambda
except ImportError:
return None
@@ -49,10 +50,18 @@ def _resolve_sx_reader_macro(name: str):
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
# Use sync OCaml bridge to invoke the lambda
try:
from .ocaml_sync import OcamlSync
_sync = OcamlSync()
_sync.start()
def _sx_handler(expr):
from .parser import serialize as _ser
result = _sync.eval(f"({name}-translate {_ser(expr)})")
return parse(result) if result else expr
return _sx_handler
except Exception:
return None
# ---------------------------------------------------------------------------