Files
rose-ash/plans/agent-briefings/primitives-loop.md
giles 5a332fa430 spec: vector primitive — complete type signatures in spec/primitives.sx
All 10 vector primitives now have :as type annotations on every parameter,
:returns types, and :doc strings. make-vector gains optional fill annotation;
vector uses :rest for its variadic args; vector-ref/set! document bounds error.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 19:33:39 +00:00

9.5 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 /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).

  • 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. 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).
  • 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/<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 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.