primitives-loop: Phase 0 done — stop language loops, verify E38/E39

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-25 19:02:12 +00:00
parent f247cb2898
commit 3759575b29

View File

@@ -0,0 +1,197 @@
# SX Primitives — Meta-Loop Briefing
Goal: add fundamental missing SX primitives in sequence, then sweep all language
implementations to replace their workarounds. Full rationale: vectors fix O(n) array
access across every language; numeric tower fixes float/int conflation; dynamic-wind
fixes cleanup semantics; coroutine primitive unifies Ruby/Lua/Tcl; string buffer fixes
O(n²) concat; algebraic data types eliminate the tagged-dict pattern everywhere.
**Each fire: find the first unchecked `[ ]`, do it, commit, tick it, stop.**
Sub-items within a Phase may span multiple fires — just commit progress and tick what's done.
---
## Phase 0 — Prep (gate)
- [x] Stop new-language loops: send `/exit` to sx-loops windows for the four blank-slate
languages that haven't committed workarounds yet:
```
tmux send-keys -t sx-loops:common-lisp "/exit" Enter
tmux send-keys -t sx-loops:apl "/exit" Enter
tmux send-keys -t sx-loops:ruby "/exit" Enter
tmux send-keys -t sx-loops:tcl "/exit" Enter
```
Verify all four windows are idle (claude prompt, no active task).
- [x] E38 + E39 landed: check both Bucket-E branches for implementation commits.
```
git log --oneline hs-e38-sourceinfo | head -5
git log --oneline hs-e39-webworker | head -5
```
If either branch has only its base commit (no impl work yet): note "pending" and stop —
next fire re-checks. Proceed only when both have at least one implementation commit.
---
## Phase 1 — Vectors
Native mutable integer-indexed arrays. Fix: Lua O(n) sort, APL rank polymorphism, Ruby
Array, Tcl lists, Common Lisp vectors, all using string-keyed dicts today.
Primitives to add:
- `make-vector` `n` `[fill]` → vector of length n
- `vector?` `v` → bool
- `vector-ref` `v` `i` → element at index i (0-based)
- `vector-set!` `v` `i` `x` → mutate in place
- `vector-length` `v` → integer
- `vector->list` `v` → list
- `list->vector` `lst` → vector
- `vector-fill!` `v` `x` → fill all elements
- `vector-copy` `v` `[start]` `[end]` → fresh copy of slice
Steps:
- [ ] OCaml: add `SxVector of value array` to `hosts/ocaml/sx_types.ml`; implement all
primitives in `hosts/ocaml/sx_primitives.ml` (or equivalent); wire into evaluator.
- [ ] Spec: add vector entries to `spec/primitives.sx` with type signatures and descriptions.
- [ ] JS bootstrapper: implement vectors in `hosts/javascript/platform.js` (or equivalent);
ensure `sx-browser.js` rebuild picks them up.
- [ ] Tests: 40+ tests in `spec/tests/test-vectors.sx` covering construction, ref, set!,
length, conversions, fill, copy, bounds behaviour.
- [ ] Verify: full test suite still passes (`node hosts/javascript/run_tests.js --full`).
- [ ] Commit: `spec: vector primitive (make-vector/vector-ref/vector-set!/etc)`
---
## Phase 2 — Numeric tower
Float ≠ integer distinction. Fix: Erlang `=:=`, Lua `math.type()`, Haskell `Num`/`Integral`,
Common Lisp `integerp`/`floatp`/`ratio`, JS `Number.isInteger`.
Changes:
- `parse-number` preserves float identity: `"1.0"` → float 1.0, not integer 1
- New predicates: `integer?`, `float?`, `exact?`, `inexact?`
- New coercions: `exact->inexact`, `inexact->exact`
- Fix `floor`/`ceiling`/`truncate`/`round` to return integers when applied to floats
- `number->string` renders `1.0` as `"1.0"`, `1` as `"1"`
- Arithmetic: `(+ 1 1.0)` → `2.0` (float contagion), `(+ 1 1)` → `2` (integer)
Steps:
- [ ] OCaml: distinguish `SxInt of int` / `SxFloat of float` in `sx_types.ml`; update all
arithmetic primitives for float contagion; fix `parse-number`.
- [ ] Spec: update `spec/primitives.sx` with new predicates + coercions; document contagion rules.
- [ ] JS bootstrapper: update number representation and arithmetic.
- [ ] Tests: 40+ tests in `spec/tests/test-numeric-tower.sx`.
- [ ] Verify: full suite passes. Pay attention to any test that relied on `1.0 = 1`.
- [ ] Commit: `spec: numeric tower — float/int distinction + contagion`
---
## Phase 3 — Dynamic-wind
Fix: Common Lisp `unwind-protect`, Ruby `ensure`, JS `finally`, Tcl `catch`+cleanup,
Erlang `try...after` (currently uses double-nested guard workaround).
- [ ] Spec: implement `dynamic-wind` in `spec/evaluator.sx` such that the after-thunk fires
on both normal return AND non-local exit (raise/call-cc escape). Must compose with
`guard` — currently they don't interact.
- [ ] OCaml: wire `dynamic-wind` through the CEK machine with a `WindFrame` continuation.
- [ ] JS bootstrapper: update.
- [ ] Tests: 20+ tests covering normal return, raise, call/cc escape, nested dynamic-winds.
- [ ] Commit: `spec: dynamic-wind + guard integration`
---
## Phase 4 — Coroutine primitive
Unify Ruby fibers, Lua coroutines, Tcl coroutines — all currently reimplemented separately
using call/cc+perform/resume.
- [ ] Spec: add `make-coroutine`, `coroutine-resume`, `coroutine-yield`, `coroutine?`,
`coroutine-alive?` to `spec/primitives.sx`. Build on existing `perform`/`cek-resume`
machinery — coroutines ARE perform/resume with a stable identity.
- [ ] OCaml: implement coroutine type; wire resume/yield through CEK suspension.
- [ ] JS bootstrapper: update.
- [ ] Tests: 25+ tests — multi-yield, final return, arg passthrough, alive? predicate,
nested coroutines, "final return vs yield" distinction (the Lua gotcha).
- [ ] Commit: `spec: coroutine primitive (make-coroutine/resume/yield)`
---
## Phase 5 — String buffer
Fix O(n²) string concatenation in loops across Lua, Ruby, Common Lisp, Tcl.
- [ ] Spec + OCaml: add `make-string-buffer`, `string-buffer-append!`, `string-buffer->string`,
`string-buffer-length` to primitives. OCaml: `Buffer.t` wrapper. JS: array+join.
- [ ] Tests: 15+ tests.
- [ ] Commit: `spec: string-buffer primitive`
---
## Phase 6 — Algebraic data types
The deepest structural gap. Every language uses `{:tag "..." :field ...}` tagged dicts to
simulate sum types. A native `define-type` + `match` form eliminates this everywhere.
- [ ] Design: write `plans/designs/sx-adt.md` covering syntax, CEK dispatch, interaction with
existing `cond`/`case`, exhaustiveness checking, recursive types, pattern variables.
Draft, then stop — next fire reviews design before implementing.
- [ ] Spec: implement `define-type` special form in `spec/evaluator.sx`:
`(define-type Name (Ctor1 field...) (Ctor2 field...) ...)`
Creates constructor functions `Ctor1`, `Ctor2` + predicate `Name?`.
- [ ] Spec: implement `match` special form:
`(match expr ((Ctor1 a b) body) ((Ctor2 x) body) (else body))`
Exhaustiveness warning if not all constructors covered and no `else`.
- [ ] OCaml: add `SxAdt of string * value array` to types; implement constructors + match.
- [ ] JS bootstrapper: update.
- [ ] Tests: 40+ tests in `spec/tests/test-adt.sx`.
- [ ] Commit: `spec: algebraic data types (define-type + match)`
---
## Phase 7 — Language sweep
Replace workarounds with primitives. One language per fire (or per sub-item for big ones).
Start with blank slates (CL, APL, Ruby, Tcl) — they haven't committed to workarounds yet.
**Scope per language:** only `lib/<lang>/**`. Don't touch spec or other languages.
Brief each language's loop agent (or do inline) after rebasing their branch onto architecture.
- [ ] Restart CL/APL/Ruby/Tcl loops with updated briefing pointing to new primitives.
Add a note to each `plans/<lang>-on-sx.md` under a `## SX primitive baseline` section:
"Use vectors for arrays, numeric tower for numbers, ADTs for tagged data, coroutines
for fibers, string-buffer for mutable string building."
- [ ] Lua: replace string-keyed dict arrays → vectors in `lua-get`/`lua-set!`/`lua-len`;
remove `str` coercion from array paths; fix `lua-to-number` for float identity.
- [ ] Erlang: fix `er-equal?` float vs int; remove `er-mk-float?` workaround; numeric tower.
- [ ] Haskell: use numeric tower for `Num`/`Integral`/`Fractional` dispatch.
- [ ] JS: use vectors for Array internals; `Number.isInteger` via `integer?`.
- [ ] Smalltalk: use vectors for `Array new:`.
- [ ] Forth: use string-buffer for word-definition accumulation if applicable.
---
## Ground rules
- Work on the `architecture` branch in `/root/rose-ash` (main worktree).
- Use sx-tree MCP for all `.sx` file edits. Never use raw Edit/Write/Read on `.sx` files.
- Commit after each concrete unit of work. Never leave the branch broken.
- Never push to `main` — only push to `origin/architecture`.
- Update this checklist every fire: tick `[x]` done, add inline notes on blockers.
---
## Progress log
_Newest first._
- 2026-04-25: Phase 0 complete — stopped CL/APL/Ruby/Tcl loops (all 4 idle at shell); confirmed E38 (tokenizer :end/:line) and E39 (WebWorker stub) both have implementation commits.