# 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: - [x] 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. Note: Vector type + most prims were already present; added bounds-checked vector-ref/set! and optional start/end to vector-copy. 10/10 vector tests pass (r7rs suite). - [x] Spec: add vector entries to `spec/primitives.sx` with type signatures and descriptions. All 10 vector primitives now have :as type annotations, :returns, and :doc strings. make-vector: optional fill param; vector-copy: optional start/end (done prev step). - [ ] 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//**`. 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/-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 1 spec step done — all 10 vector primitives in spec/primitives.sx have full :as type annotations, :returns, :doc; make-vector optional fill param added. - 2026-04-25: Phase 1 OCaml step done — bounds-checked vector-ref/set!, vector-copy now accepts optional start/end, spec/primitives.sx doc updated. 10/10 r7rs vector tests pass, 4747 total (394 pre-existing hs-upstream fails unchanged). - 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.