Removes the 5993-line bootstrapped Python evaluator (sx_ref.py) and all
code that depended on it exclusively. Both bootstrappers (JS + OCaml)
now use a new synchronous OCaml bridge (ocaml_sync.py) to run the
transpiler. JS build produces identical output; OCaml bootstrap produces
byte-identical sx_ref.ml.
Key changes:
- New shared/sx/ocaml_sync.py: sync subprocess bridge to sx_server.exe
- hosts/javascript/bootstrap.py: serialize defines → temp file → OCaml eval
- hosts/ocaml/bootstrap.py: same pattern for OCaml transpiler
- shared/sx/{html,async_eval,resolver,jinja_bridge,handlers,pages,deps,helpers}:
stub or remove sx_ref imports; runtime uses OCaml bridge (SX_USE_OCAML=1)
- sx/sxc/pages: parse defpage/defhandler from AST instead of Python eval
- hosts/ocaml/lib/sx_primitives.ml: append handles non-list 2nd arg per spec
- Deleted: sx_ref.py, async_eval_ref.py, 6 Python test runners, misc ref/ files
Test results: JS 1078/1078, OCaml 1114/1114.
sx_docs SSR has pre-existing rendering issues to investigate separately.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
These are domain definition forms (same pattern as defhandler, defpage,
etc.), not core language constructs. Moving them to web-forms.sx keeps
the core evaluator + types.sx cleaner for WASM compilation.
web-forms.sx now loaded in both JS and Python build pipelines.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The irreducible primitive set drops from 79 to 33. Everything that can
be expressed in SX is now a library function in stdlib.sx, loaded after
evaluator.sx and before render.sx.
Moved to stdlib.sx (pure SX, no host dependency):
- Logic: not
- Comparison: != <= >= eq? eqv? equal?
- Predicates: nil? boolean? number? string? list? dict? continuation?
empty? odd? even? zero? contains?
- Arithmetic: inc dec abs ceil round min max clamp
- Collections: first last rest nth cons append reverse flatten range
chunk-every zip-pairs vals has-key? merge assoc dissoc into
- Strings: upcase downcase string-length substring string-contains?
starts-with? ends-with? split join replace
- Text: pluralize escape assert parse-datetime
Remaining irreducible primitives (33):
+ - * / mod floor pow sqrt = < > type-of symbol-name keyword-name
str slice index-of upper lower trim char-from-code list dict concat
get len keys dict-set! append! random-int json-encode format-date
parse-int format-decimal strip-tags sx-parse error apply
All hosts: JS 957+1080, Python 744, OCaml 952 — zero regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Evaluator: data-first higher-order forms — ho-swap-args auto-detects
(map coll fn) vs (map fn coll), both work. Threading + HO: (-> data
(map fn)) dispatches through CEK HO machinery via quoted-value splice.
17 new tests in test-cek-advanced.sx.
Fix plan pages: add mother-language, isolated-evaluator, rust-wasm-host
to page-functions.sx plan() — were in defpage but missing from URL router.
Aser error handling: pages.py now catches EvalError separately, renders
visible error banner instead of silently sending empty content. All
except blocks include traceback in logs.
Scope primitives: register collect!/collected/clear-collected!/emitted/
emit!/context in shared/sx/primitives.py so hand-written _aser can
resolve them (fixes ~cssx/flush expansion failure).
New test file: shared/sx/tests/test_aser_errors.py — 19 pytest tests
for error propagation through all aser control flow forms.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New test files:
- test-cek-advanced.sx (63): deep nesting, complex calls, macro
interaction, environment stress, edge cases
- test-signals-advanced.sx (24): signal types, computed chains,
effects, batch, swap patterns
- test-integration.sx (38): parse-eval roundtrip, render pipeline,
macro-render, data-driven rendering, error recovery, complex patterns
Bugs found:
- -> (thread-first) doesn't work with HO special forms (map, filter)
because they're dispatched by name, not as env values. Documented
as known limitation — use nested calls instead of ->.
- batch returns nil, not thunk's return value
- upcase not a primitive (use upper)
Data-first HO forms attempted but reverted — the swap logic in
ho-setup-dispatch caused subtle paren/nesting issues. Needs more
careful implementation in a future session.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
test-continuations-advanced.sx (41 tests):
multi-shot continuations, composition, provide/context basics,
provide across shift, scope/emit basics, scope across shift
test-render-advanced.sx (27 tests):
nested components, dynamic content, list patterns,
component patterns, special elements
Bugs found and documented:
- case in render context returns DOM object (CEK dispatches case
before HTML adapter sees it — use cond instead for render)
- context not visible in shift body (correct: shift body runs
outside the reset/provide boundary)
- Multiple shifts consume reset (correct: each shift needs its own
reset)
Python runner: skip test-continuations-advanced.sx without --full.
JS 815/815 standard, 938/938 full, Python 706/706.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The core spec is now one file: spec/evaluator.sx (2275 lines).
Three parts:
Part 1: CEK frames — state and continuation frame constructors
Part 2: Evaluation utilities — call, parse, define, macro, strict
Part 3: CEK machine — the sole evaluator
Deleted:
- spec/eval.sx (merged into evaluator.sx)
- spec/frames.sx (merged into evaluator.sx)
- spec/cek.sx (merged into evaluator.sx)
- spec/continuations.sx (dead — CEK handles shift/reset natively)
Updated bootstrappers (JS + Python) to load evaluator.sx as core.
Removed frames/cek from SPEC_MODULES (now part of core).
Bundle size: 392KB → 377KB standard, 418KB → 403KB full.
All tests unchanged: JS 747/747, Full 864/870, Python 679/679.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New test files:
- test-collections.sx (79): list/dict edge cases, interop, equality
- test-scope.sx (48): let/define/set!/closure/letrec/env isolation
Python test runner (hosts/python/tests/run_tests.py):
- Runs all spec tests against bootstrapped sx_ref.py
- Tree-walk evaluator with full primitive env
- Skips CEK/types/strict/continuations without --full
Cross-host fixes (tests now host-neutral):
- cons onto nil: platform-defined (JS: pair, Python: single)
- = on lists: test identity only (JS: shallow, Python: deep)
- str(true): accept "true" or "True"
- (+ "a" 1): platform-defined (JS: coerces, Python: throws)
- min/max: test with two args (Python single-arg expects iterable)
- TCO depth: lowered to 500 (works on both hosts)
- Strict mode tests moved to test-strict.sx (skipped on Python)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two fundamental environment bugs fixed:
1. env-set! was used for both binding creation (let, define, params)
and mutation (set!). Binding creation must NOT walk the scope chain
— it should set on the immediate env. Only set! should walk.
Fix: introduce env-bind! for all binding creation. env-set! now
exclusively means "mutate existing binding, walk scope chain".
Changed across spec (eval.sx, cek.sx, render.sx) and all web
adapters (dom, html, sx, async, boot, orchestration, forms).
2. makeLambda/makeComponent/makeMacro/makeIsland used merge(env) to
flatten the closure into a plain object, destroying the prototype
chain. This meant set! inside closures couldn't reach the original
binding — it modified a snapshot copy instead.
Fix: store env directly as closure (no merge). The prototype chain
is preserved, so set! walks up to the original scope.
Tests: 499/516 passing (96.7%), up from 485/516.
Fixed: define self-reference, let scope isolation, set! through
closures, counter-via-closure pattern, recursive functions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Python: bootstrap.py, platform.py, transpiler.sx, boundary_parser.py, tests/
JavaScript: bootstrap.py, cli.py, platform.py, transpiler.sx
Both bootstrappers verified — build from new locations, output to shared/.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>