diff --git a/plans/agent-briefings/primitives-loop.md b/plans/agent-briefings/primitives-loop.md new file mode 100644 index 00000000..32bb0521 --- /dev/null +++ b/plans/agent-briefings/primitives-loop.md @@ -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//**`. 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 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.