8.7 KiB
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)
-
Stop new-language loops: send
/exitto 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" EnterVerify all four windows are idle (claude prompt, no active task). -
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 -5If 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-vectorn[fill]→ vector of length nvector?v→ boolvector-refvi→ element at index i (0-based)vector-set!vix→ mutate in placevector-lengthv→ integervector->listv→ listlist->vectorlst→ vectorvector-fill!vx→ fill all elementsvector-copyv[start][end]→ fresh copy of slice
Steps:
- OCaml: add
SxVector of value arraytohosts/ocaml/sx_types.ml; implement all primitives inhosts/ocaml/sx_primitives.ml(or equivalent); wire into evaluator. - Spec: add vector entries to
spec/primitives.sxwith type signatures and descriptions. - JS bootstrapper: implement vectors in
hosts/javascript/platform.js(or equivalent); ensuresx-browser.jsrebuild picks them up. - Tests: 40+ tests in
spec/tests/test-vectors.sxcovering 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-numberpreserves 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/roundto return integers when applied to floats number->stringrenders1.0as"1.0",1as"1"- Arithmetic:
(+ 1 1.0)→2.0(float contagion),(+ 1 1)→2(integer)
Steps:
- OCaml: distinguish
SxInt of int/SxFloat of floatinsx_types.ml; update all arithmetic primitives for float contagion; fixparse-number. - Spec: update
spec/primitives.sxwith 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-windinspec/evaluator.sxsuch that the after-thunk fires on both normal return AND non-local exit (raise/call-cc escape). Must compose withguard— currently they don't interact. - OCaml: wire
dynamic-windthrough the CEK machine with aWindFramecontinuation. - 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?tospec/primitives.sx. Build on existingperform/cek-resumemachinery — 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-lengthto primitives. OCaml:Buffer.twrapper. 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.mdcovering syntax, CEK dispatch, interaction with existingcond/case, exhaustiveness checking, recursive types, pattern variables. Draft, then stop — next fire reviews design before implementing. -
Spec: implement
define-typespecial form inspec/evaluator.sx:(define-type Name (Ctor1 field...) (Ctor2 field...) ...)Creates constructor functionsCtor1,Ctor2+ predicateName?. -
Spec: implement
matchspecial form:(match expr ((Ctor1 a b) body) ((Ctor2 x) body) (else body))Exhaustiveness warning if not all constructors covered and noelse. -
OCaml: add
SxAdt of string * value arrayto 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.mdunder a## SX primitive baselinesection: "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; removestrcoercion from array paths; fixlua-to-numberfor float identity. -
Erlang: fix
er-equal?float vs int; removeer-mk-float?workaround; numeric tower. -
Haskell: use numeric tower for
Num/Integral/Fractionaldispatch. -
JS: use vectors for Array internals;
Number.isIntegerviainteger?. -
Smalltalk: use vectors for
Array new:. -
Forth: use string-buffer for word-definition accumulation if applicable.
Ground rules
- Work on the
architecturebranch in/root/rose-ash(main worktree). - Use sx-tree MCP for all
.sxfile edits. Never use raw Edit/Write/Read on.sxfiles. - Commit after each concrete unit of work. Never leave the branch broken.
- Never push to
main— only push toorigin/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.