# 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). - [x] JS bootstrapper: implement vectors in `hosts/javascript/platform.js` (or equivalent); ensure `sx-browser.js` rebuild picks them up. Fixed index-of for lists (was returning -1 not NIL, breaking bind-lambda-params), added _lastErrorKont_/hostError/try-catch/without-io-hook stubs. Vectors work. - [x] Tests: 40+ tests in `spec/tests/test-vectors.sx` covering construction, ref, set!, length, conversions, fill, copy, bounds behaviour. 42 tests, all pass. 1847 standard / 2362 full passing (up from 5). - [x] Verify: full test suite still passes (`node hosts/javascript/run_tests.js --full`). 2362/4924 pass (improvement from pre-existing lambda binding bug, no regressions). - [x] Commit: `spec: vector primitive (make-vector/vector-ref/vector-set!/etc)` Committed as: js: fix lambda binding (index-of on lists), add vectors + R7RS platform stubs --- ## 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: - [x] OCaml: distinguish `Integer of int` / `Number of float` in `sx_types.ml`; update all arithmetic primitives for float contagion; fix `parse-number`. 92/92 numeric tower tests pass; 4874 total (394 pre-existing hs-upstream fails unchanged). - [x] Spec: update `spec/primitives.sx` with new predicates + coercions; document contagion rules. Added integer?/float? predicates; updated number? body; / returns "float"; floor/ceil/truncate return "integer"; +/-/* doc float contagion; fixed double-paren params; 4874/394 baseline. - [x] JS bootstrapper: update number representation and arithmetic. Added integer?/float?/exact?/inexact?/truncate/remainder/modulo/random-int/exact->inexact/ inexact->exact/parse-number. Fixed sx_server.ml epoch protocol for Integer type. JS: 1940 passed (+60); OCaml: 4874/394 unchanged. 6 tests JS-only fail (float≡int limitation). - [x] Tests: 92 tests in `spec/tests/test-numeric-tower.sx` — int-arithmetic, float-contagion, division, predicates, coercions, rounding, parse-number, equality, modulo, min-max, stringify. - [x] Verify: full suite passes. OCaml 4874/394 (baseline unchanged). JS 1940/2500 (+60 vs pre-tower). No regressions on any test that relied on `1.0 = 1` — those tests were already using integer literals which remain identical in JS. 6 JS-only failures are platform-inherent (JS float≡int). - [x] Commit: all work landed across 4 commits (c70bbdeb, 45ec5535, b12a22e6, f5acb31c). --- ## 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). - [x] 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. - [x] OCaml: wire `dynamic-wind` through the CEK machine with a `WindFrame` continuation. - [x] JS bootstrapper: update. - [x] Tests: 20+ tests covering normal return, raise, call/cc escape, nested dynamic-winds. - [x] 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. - [x] 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. Implemented as `spec/coroutines.sx` define-library; `make-coroutine` stub in evaluator.sx. 17/17 coroutine tests pass (OCaml). Drives iteration via define+fn recursion (not named let — named let uses cek_call→cek_run which errors on IO suspension). - [x] OCaml: implement coroutine type; wire resume/yield through CEK suspension. No new native type needed — dict-based coroutine identity + existing cek-step-loop/ cek-resume/perform primitives in run_tests.ml ARE the OCaml implementation. 17/17 pass. - [x] JS bootstrapper: update. All CEK primitives already in sx-browser.js. Fix: pre-load spec/coroutines.sx + spec/signals.sx in run_tests.js so (import (sx coroutines)) resolves without suspension. 17/17 pass in JS. 1965/2500 (+25 vs 1940 baseline). Zero new failures. - [x] Tests: 25+ tests — multi-yield, final return, arg passthrough, alive? predicate, nested coroutines, "final return vs yield" distinction (the Lua gotcha). 27 tests: added 10 new — state field inspection (ready/suspended/dead), yield from nested helper, initial resume arg ignored, mutable closure state, complex yield values, round-robin scheduling, factory-shared-no-state, non-coroutine error. 27/27 OCaml+JS. - [x] Commit: `spec: coroutine primitive (make-coroutine/resume/yield)` Phase 4 landed across 4 commits: 21cb9cf5 (spec library), 9eb12c66 (ocaml verified), b78e06a7 (js pre-load), 0ffe208e (27 tests). Phase 4 complete. --- ## Phase 5 — String buffer Fix O(n²) string concatenation in loops across Lua, Ruby, Common Lisp, Tcl. - [x] Spec + OCaml: add `make-string-buffer`, `string-buffer-append!`, `string-buffer->string`, `string-buffer-length` to primitives. OCaml: `Buffer.t` wrapper. JS: array+join. Also: string-buffer? predicate; SxStringBuffer._string_buffer marker for typeOf/dict? exclusion; inspect case in sx_types.ml. 17/17 tests OCaml+JS. - [x] Tests: 15+ tests. 17 tests written inline with Spec+OCaml step: construction, type-of, empty/length, single/multi-append, append-returns-nil, empty-string-append, reuse-after-to-string, independence, loop-building, CSV-row, unicode, repeated-to-string, join-pattern. 17/17 OCaml+JS. - [x] Commit: `spec: string-buffer primitive` Committed as d98b5fa2 — all work in one commit (OCaml type + primitives + JS + spec + 17 tests). --- ## 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. - [x] 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. Written: define-type/match syntax, AdtValue runtime rep, stepSfDefineType + MatchFrame CEK dispatch, exhaustiveness warnings via _adt_registry, recursive types, nested patterns, wildcard _, 3-phase impl plan (basic/nested/exhaustiveness), open questions on accessors/singletons/inspect. - [x] Spec: implement `define-type` special form in `spec/evaluator.sx`: `(define-type Name (Ctor1 field...) (Ctor2 field...) ...)` Creates constructor functions `Ctor1`, `Ctor2` + predicate `Name?`. - [x] 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`. - [x] OCaml: add `SxAdt of string * value array` to types; implement constructors + match. Dict-based ADT (no native type needed — matches spec). Hand-written sf_define_type in bootstrap.py FIXUPS; registered via register_special_form. 172 assertions pass. 4280/1080 full suite (37 improvement over old baseline 4243/1117). - [x] JS bootstrapper: update. No changes needed — define-type/match are spec-level; sx-browser.js rebuilt at 0dc7e159. 40/40 ADT tests pass JS. 2032/2500 total (+67 vs 1965 phase-4 baseline). - [x] Tests: 40+ tests in `spec/tests/test-adt.sx`. 40 tests written across two spec commits (6c872107+0dc7e159). All pass OCaml+JS. - [x] Commit: `spec: algebraic data types (define-type + match)` Phase 6 landed across 5 commits: 6c872107 (define-type spec), 0dc7e159 (match spec), 5d1913e7 (ocaml bootstrap), f63b2147 (plan tick). JS already current. --- ## Phase 7 — Bitwise operations Completely absent today. Needed by: Forth (core), APL (array masks), Erlang (bitmatch), JS (typed arrays, bitfields), Common Lisp (`logand`/`logior`/`logxor`/`lognot`/`ash`). Primitives to add: - `bitwise-and` `a` `b` → integer - `bitwise-or` `a` `b` → integer - `bitwise-xor` `a` `b` → integer - `bitwise-not` `a` → integer - `arithmetic-shift` `a` `count` → integer (left if count > 0, right if count < 0) - `bit-count` `a` → number of set bits (popcount) - `integer-length` `a` → number of bits needed to represent a Steps: - [x] Spec: add entries to `spec/primitives.sx` with type signatures. stdlib.bitwise module with 7 entries appended to spec/primitives.sx. - [x] OCaml: implement in `hosts/ocaml/sx_primitives.ml` using OCaml `land`/`lor`/`lxor`/`lnot`/`lsl`/`asr`. land/lor/lxor/lnot/lsl/asr in sx_primitives.ml. bit-count: Kernighan loop. integer-length: lsr loop. - [x] JS bootstrapper: implement in `hosts/javascript/platform.js` using JS `&`/`|`/`^`/`~`/`<<`/`>>`. stdlib.bitwise module added to PRIMITIVES_JS_MODULES. bit-count: Hamming weight. integer-length: Math.clz32. - [x] Tests: 25+ tests in `spec/tests/test-bitwise.sx` — basic ops, shift left/right, negative numbers, popcount. 26 tests, 158 assertions, all pass OCaml+JS. - [x] Commit: `spec: bitwise operations (bitwise-and/or/xor/not, arithmetic-shift, bit-count)` Committed a8a79dc9. Phase 7 complete in single commit. --- ## Phase 8 — Multiple values R7RS standard. Common Lisp uses them heavily; Haskell tuples map naturally; Erlang multi-return. Without them, every function returning two things encodes it as a list or dict. Primitives / forms to add: - `values` `v...` → multiple-value object - `call-with-values` `producer` `consumer` → applies consumer to values from producer - `let-values` `(((a b) expr) ...)` `body` — binding form (special form in evaluator) - `define-values` `(a b ...)` `expr` — top-level multi-value bind Steps: - [x] Spec: add `SxValues` type to evaluator; implement `values` + `call-with-values` in `spec/evaluator.sx`; add `let-values` / `define-values` special forms. - [x] OCaml: add `SxValues of value list` to `sx_types.ml`; wire through CEK. - [x] JS bootstrapper: implement values type + forms. - [x] Tests: 25+ tests in `spec/tests/test-values.sx` — basic producer/consumer, let-values destructuring, define-values, interaction with `begin`/`do`. - [x] Commit: `spec: multiple values (values/call-with-values/let-values)` --- ## Phase 9 — Promises (lazy evaluation) Critical for Haskell — lazy evaluation is so central that without it the Haskell implementation can't be idiomatic. Also useful for lazy lists in Common Lisp and lazy streams in Scheme-style code generally. Primitives / forms to add: - `delay` `expr` → promise (special form — expr not evaluated yet) - `force` `p` → evaluate promise, cache result, return it - `make-promise` `v` → already-forced promise wrapping v - `promise?` `v` → bool - `delay-force` `expr` → for iterative lazy sequences (avoids stack growth in lazy streams) Steps: - [x] Spec: add `delay` / `delay-force` special forms to `spec/evaluator.sx`; add promise type with mutable forced/value slots; `force` checks if already forced before eval. - [x] OCaml: add `SxPromise of { mutable forced: bool; mutable value: value; thunk: value }`; wire `delay`/`force`/`delay-force` through CEK. - [x] JS bootstrapper: implement promise type + forms. - [x] Tests: 25+ tests in `spec/tests/test-promises.sx` — basic delay/force, memoisation (forced only once), delay-force lazy stream, promise? predicate, make-promise. - [x] Commit: `spec: promises — delay/force/delay-force for lazy evaluation` --- ## Phase 10 — Mutable hash tables Distinct from SX's immutable dicts. Dict primitives copy on every update — fine for functional code, wrong for table-heavy language implementations. Lua tables, Smalltalk dicts, Erlang process dictionaries, and JS Map all need O(1) mutable associative storage. Primitives to add: - `make-hash-table` `[capacity]` → fresh mutable hash table - `hash-table?` `v` → bool - `hash-table-set!` `ht` `key` `val` → mutate in place - `hash-table-ref` `ht` `key` `[default]` → value or default/error - `hash-table-delete!` `ht` `key` → remove entry - `hash-table-size` `ht` → integer - `hash-table-keys` `ht` → list of keys - `hash-table-values` `ht` → list of values - `hash-table->alist` `ht` → list of (key . value) pairs - `hash-table-for-each` `ht` `fn` → iterate (fn key val) for side effects - `hash-table-merge!` `dst` `src` → merge src into dst in place Steps: - [x] Spec: add entries to `spec/primitives.sx`. stdlib.hash-table module with 11 define-primitive entries appended to spec/primitives.sx. - [x] OCaml: add `HashTable of (value, value) Hashtbl.t` to `sx_types.ml`; implement all primitives in `hosts/ocaml/sx_primitives.ml`. HashTable variant in sx_types.ml; type_of/inspect cases added; 11 primitives in sx_primitives.ml; fixed _cek_call_ref reference for hash-table-for-each. 4385/1080 (+28). - [x] JS bootstrapper: implement using JS `Map` in `hosts/javascript/platform.js`. SxHashTable class with Map; _hash_table marker; dict?/type-of exclusion; apply() for for-each. 2137/2500 (+4 vs phase-9 baseline). - [x] Tests: 30+ tests in `spec/tests/test-hash-table.sx` — set/ref/delete, size, iteration, default on missing key, merge, keys/values lists. 28 tests; all pass OCaml+JS. Used empty? not assert= for empty-list comparisons. - [x] Commit: `spec: mutable hash tables (make-hash-table/ref/set!/delete!/etc)` Committed 133bdf52. Phase 10 complete. --- ## Phase 11 — Sequence protocol Unified iteration over lists and vectors without conversion. Currently `map`/`filter`/ `for-each` only work on lists — you must `vector->list` first, which defeats the purpose of vectors. A sequence protocol makes all collection operations polymorphic. Approach: extend existing `map`/`filter`/`reduce`/`for-each`/`some`/`every?` to dispatch on type (list → existing path, vector → index loop, string → char iteration). Add: - `in-range` `start` `[end]` `[step]` → lazy range sequence (works with `for-each`/`map`) - `sequence->list` `s` → coerce any sequence to list - `sequence->vector` `s` → coerce any sequence to vector - `sequence-length` `s` → length of any sequence - `sequence-ref` `s` `i` → element by index (lists and vectors) - `sequence-append` `s1` `s2` → concatenate two same-type sequences Steps: - [x] Spec: extend `map`/`filter`/`reduce`/`for-each`/`some`/`every?` in `spec/evaluator.sx` to type-dispatch; add `in-range` lazy sequence type + helpers. - [x] OCaml: update HO form dispatch; add `SxRange` or use lazy list; implement `sequence-*` primitives. seq_to_list helper before let-rec block; ho_setup_dispatch wraps all 7 coll bindings; seq-to-list/sequence-to-list/vector/length/ref/append/in-range in sx_primitives.ml. 4385/1080 (all failures pre-existing hs-*/regex; 0 regressions). - [x] JS bootstrapper: update. Already done in Spec step (da4b526a) — sx-browser.js rebuilt with seqToList/sequenceToList/ sequenceToVector/sequenceLength/sequenceRef/sequenceAppend/inRange. 2137/2500 JS tests pass. - [x] Tests: 30+ tests in `spec/tests/test-sequences.sx` — map over vector, filter over range, for-each over string chars, sequence-append, sequence->list/vector coercions. 45 tests all passing: JS 2185/2498 (+48), OCaml 4424/1087 (+39). Fixed: vector? rename (isVector), vectorLength/vectorRef/reverse aliases, in-range letrec→build-range, sequence-length nil=0, assert-equal for list comparisons. Committed 0fe00bf7. - [x] Commit: `spec: sequence protocol — polymorphic map/filter/for-each over list/vector/range` Work landed across da4b526a (Spec), 7286629c (OCaml), 06a3eee1 (JS bootstrap), 0fe00bf7 (Tests). --- ## Phase 12 — gensym + symbol interning Unique symbol generation. Tiny to implement; broadly needed: Prolog uses it for fresh variable names, Common Lisp uses it constantly in macros, any hygienic macro system needs it, and Smalltalk uses it for anonymous class/method naming. Primitives to add: - `gensym` `[prefix]` → unique symbol, e.g. `g42`, `var-17`. Counter-based, monotonically increasing. - `symbol-interned?` `s` → bool — whether the symbol is in the global intern table - `intern` `str` → symbol — intern a string as a symbol (string->symbol already exists; this is the explicit interning operation for languages that distinguish interned vs uninterned) Steps: - [x] Spec: add `gensym` counter to evaluator state; implement in `spec/evaluator.sx`. `string->symbol` already exists — `gensym` is just a counter-suffixed variant. Added *gensym-counter*/gensym/string->symbol/symbol->string/intern/symbol-interned? to evaluator.sx. Added string->symbol/symbol->string transpiler renames + platform.py aliases. JS 2186/+1. OCaml builds. Committed edf4e525. - [x] OCaml: add global gensym counter; implement primitives. gensym_counter ref + gensym/string->symbol/symbol->string/intern/symbol-interned? in sx_primitives.ml. Also fixed ListRef case in seq_to_list (both sx_ref.ml + sx_primitives.ml). 4431/1080 (was 4385/1080). - [x] JS bootstrapper: implement. Already done in Spec step. JS 2186/2497, all sequence tests pass. - [x] Tests: 15+ tests in `spec/tests/test-gensym.sx` — uniqueness, prefix, symbol?, string->symbol round-trip. 19 tests. OCaml 4450/1080, JS 2205/2497, zero regressions. - [x] Commit: `spec: gensym + symbol interning` — 0862a614 --- ## Phase 13 — Character type Common Lisp and Haskell have a distinct `Char` type that is not a string. Without it both implementations are approximations — CL's `#\a` literal and Haskell's `'a'` both need a real char value, not a length-1 string. Primitives to add: - `char?` `v` → bool - `char->integer` `c` → Unicode codepoint integer - `integer->char` `n` → char - `char=?` `char?` `char<=?` `char>=?` → comparators - `char-ci=?` `char-cilist` extended to return chars (not length-1 strings) - `list->string` accepting chars Also: `#\a` reader syntax for char literals (parser addition). Steps: - [x] Spec: add `SxChar` type to evaluator; add char literal syntax `#\a`/`#\space`/`#\newline` to `spec/parser.sx`; implement all predicates + comparators. - [x] OCaml: add `SxChar of char` to `sx_types.ml`; implement primitives. - [x] JS bootstrapper: implement char type wrapping a codepoint integer. - [x] Tests: 30+ tests in `spec/tests/test-chars.sx` — literals, char->integer round-trip, comparators, predicates, upcase/downcase, string<->list with chars. - [x] Commit: `spec: character type (char? char->integer #\\a literals + predicates)` --- ## Phase 14 — String ports Needed for any language with a reader protocol: Common Lisp's `read`, Prolog's term parser, Smalltalk's `printString`. Without string ports these all do their own character walking on raw strings rather than treating a string as an I/O stream. Primitives to add: - `open-input-string` `str` → input port - `open-output-string` → output port - `get-output-string` `port` → string (flush output port to string) - `input-port?` `output-port?` `port?` → predicates - `read-char` `[port]` → char or eof-object - `peek-char` `[port]` → char or eof-object (non-consuming) - `read-line` `[port]` → string or eof-object - `write-char` `char` `[port]` → void - `write-string` `str` `[port]` → void - `eof-object` → the eof sentinel - `eof-object?` `v` → bool - `close-port` `port` → void Steps: - [x] Spec: add port type + eof-object to evaluator; implement all primitives. Ports are mutable objects with a position cursor (input) or accumulation buffer (output). - [x] OCaml: add `SxPort` variant covering string-input-port and string-output-port; Buffer.t for output, string+offset for input. - [x] JS bootstrapper: implement port type. - [x] Tests: 25+ tests in `spec/tests/test-ports.sx` — open/read/peek/eof, output accumulation, read-line, write-char, close. - [x] Commit: `spec: string ports (open-input-string/open-output-string/read-char/etc)` — 3d8937d7 --- ## Phase 15 — Math completeness Filling specific gaps that multiple language implementations need. ### 15a — modulo / remainder / quotient distinction They differ on negative numbers — critical for Erlang `rem`, Haskell `mod`/`rem`, CL `mod`/`rem`: - `quotient` `a` `b` → truncate toward zero (same sign as dividend) - `remainder` `a` `b` → sign follows dividend (truncation division) - `modulo` `a` `b` → sign follows divisor (floor division) — R7RS ### 15b — Trigonometry and transcendentals Lua, Haskell, Erlang, CL all need: `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `exp`, `log`, `sqrt`, `expt`. Check which are already present; add missing ones. ### 15c — GCD / LCM `gcd` `a` `b` → greatest common divisor; `lcm` `a` `b` → least common multiple. Needed by Haskell `Rational`, CL, and any language doing fraction arithmetic. ### 15d — Radix number parsing / formatting `(number->string n radix)` → e.g. `(number->string 255 16)` → `"ff"`. `(string->number s radix)` → e.g. `(string->number "ff" 16)` → `255`. Needed by: Common Lisp, Smalltalk, Erlang integer formatting. Steps: - [x] Audit which trig / math functions are already in `spec/primitives.sx`; note gaps. - [x] Spec + OCaml + JS: implement missing trig (`sin`/`cos`/`tan`/`asin`/`acos`/`atan`/`exp`/`log`). - [x] Spec + OCaml + JS: `quotient`/`remainder`/`modulo` with correct negative semantics. - [x] Spec + OCaml + JS: `gcd`/`lcm`. - [x] Spec + OCaml + JS: radix variants of `number->string`/`string->number`. - [x] Tests: 40+ tests in `spec/tests/test-math.sx`. - [x] Commit: `spec: math completeness — trig, quotient/remainder/modulo, gcd/lcm, radix` --- ## Phase 16 — Rational numbers Haskell's `Rational` type and Common Lisp ratios (`1/3`) both need this. Natural extension of the numeric tower (Phase 2) — rationals are the third numeric type alongside int and float. Primitives to add: - `make-rational` `numerator` `denominator` → rational (auto-reduced by GCD) - `rational?` `v` → bool - `numerator` `r` → integer - `denominator` `r` → integer - Reader syntax: `1/3` parsed as rational literal - Arithmetic: `(+ 1/3 1/6)` → `1/2`; `(* 1/3 3)` → `1`; mixed int/rational → rational - `exact->inexact` on rational → float; `inexact->exact` on float → rational approximation - `(number->string 1/3)` → `"1/3"` Steps: - [x] Spec: add `SxRational` type; add `n/d` reader syntax to `spec/parser.sx`; extend all arithmetic primitives for rational contagion (int op rational → rational, rational op float → float). - [x] OCaml: add `SxRational of int * int` (stored in reduced form); implement all arithmetic. as_number + safe_eq extended for cross-type rational equality (= 2.5 5/2) → true. - [x] JS bootstrapper: implement rational type. JS keeps int/int → float for CSS backward compatibility; SxRational class with _rational marker. - [x] Tests: 30+ tests in `spec/tests/test-rationals.sx` — literals, arithmetic, reduction, mixed numeric tower, exact<->inexact conversion. 62 tests, all pass. - [x] Commit: `spec: rational numbers — 1/3 literals, arithmetic, numeric tower integration` Committed 036022cc. JS: 2232 passed. OCaml: 4532 passed (+11). --- ## Phase 17 — read / write / display Completes the I/O model. Builds on string ports (Phase 14) and char type (Phase 13). `read` parses any SX value from a port; `write` serializes with quoting (round-trippable); `display` serializes without quoting (human-readable). Common Lisp's `read` macro, Prolog term I/O, and Smalltalk's `printString` all need this. Primitives to add: - `read` `[port]` → SX value or eof-object — full SX parser reading from a port - `read-char` already in Phase 14; `read` uses it internally - `write` `val` `[port]` → void — serializes with quotes: `"hello"`, `#\a`, `(1 2 3)` - `display` `val` `[port]` → void — serializes without quotes: `hello`, `a`, `(1 2 3)` - `newline` `[port]` → void — writes `\n` - `write-to-string` `val` → string — convenience: `(write val (open-output-string))` - `display-to-string` `val` → string — convenience Steps: - [x] Spec: implement `read` in `spec/evaluator.sx` — wraps the existing parser to read one datum from a port cursor; handles eof gracefully. - [x] Spec: implement `write`/`display`/`newline` — extend the existing serializer for port output; `write` quotes strings + uses `#\` for chars, `display` does not. - [x] OCaml: wire `read` through port type; implement `write`/`display` output path. - [x] JS bootstrapper: implement. - [x] Tests: 25+ tests in `spec/tests/test-read-write.sx` — read string literal, read list, read eof, write round-trip, display vs write quoting, newline, write-to-string. - [x] Commit: `spec: read/write/display — S-expression reader/writer on ports` --- ## Phase 18 — Sets O(1) membership testing. Distinct from hash tables (unkeyed) and lists (O(n)). Erlang has sets as a stdlib staple, Haskell `Data.Set`, APL uses set operations constantly, Common Lisp has `union`/`intersection` on lists but a native set is O(1). Primitives to add: - `make-set` `[list]` → fresh set, optionally seeded from list - `set?` `v` → bool - `set-add!` `s` `val` → void - `set-member?` `s` `val` → bool - `set-remove!` `s` `val` → void - `set-size` `s` → integer - `set->list` `s` → list (unspecified order) - `list->set` `lst` → set - `set-union` `s1` `s2` → new set - `set-intersection` `s1` `s2` → new set - `set-difference` `s1` `s2` → new set (elements in s1 not in s2) - `set-for-each` `s` `fn` → iterate for side effects - `set-map` `s` `fn` → new set of mapped values Steps: - [x] Spec: add entries to `spec/primitives.sx`. - [x] OCaml: implement using `Hashtbl.t` with unit values (or a proper `Set` functor with a comparison function); add `SxSet` to `sx_types.ml`. - [x] JS bootstrapper: implement using JS `Set`. - [x] Tests: 30+ tests in `spec/tests/test-sets.sx` — add/member/remove, union/intersection/ difference, list conversion, for-each, size. - [x] Commit: `spec: sets (make-set/set-add!/set-member?/union/intersection/etc)` --- ## Phase 19 — Regular expressions as primitives `lib/js/regex.sx` is a pure-SX regex engine already written. Promoting it to a primitive gives every language free regex without reinventing: Lua patterns, Tcl `regexp`, Ruby regex, JS regex, Erlang `re` module. Mostly a wiring job — the implementation exists. Primitives to add: - `make-regexp` `pattern` `[flags]` → regexp object (`flags`: `"i"` case-insensitive, `"g"` global, `"m"` multiline) - `regexp?` `v` → bool - `regexp-match` `re` `str` → match dict `{:match "..." :start N :end N :groups (...)}` or nil - `regexp-match-all` `re` `str` → list of match dicts - `regexp-replace` `re` `str` `replacement` → string with first match replaced - `regexp-replace-all` `re` `str` `replacement` → string with all matches replaced - `regexp-split` `re` `str` → list of strings (split on matches) - Reader syntax: `#/pattern/flags` for regexp literals (parser addition) Steps: - [x] Audit `lib/js/regex.sx` — understand the API it already exposes; map to the primitive API above. - [x] Spec: add `SxRegexp` type to evaluator; add `#/pattern/flags` literal syntax to `spec/parser.sx`; wire `lib/js/regex.sx` engine as the implementation. - [x] OCaml: implement using OCaml `Re` library (or `Str`); add `SxRegexp` to types. - [x] JS bootstrapper: use native JS `RegExp`; wrap in the primitive API. - [x] Tests: 30+ tests in `spec/tests/test-regexp.sx` — basic match, groups, replace, replace-all, split, flags (case-insensitive), no-match nil return. - [x] Commit: `spec: regular expressions (make-regexp/regexp-match/regexp-replace + #/pat/ literals)` --- ## Phase 20 — Bytevectors R7RS standard. Needed for WebSocket binary frames (E36), binary protocol parsing, and efficient string encoding. Also the foundation for proper Unicode: `string->utf8` / `utf8->string` require a byte array type. Primitives to add: - `make-bytevector` `n` `[fill]` → bytevector of n bytes (fill defaults to 0) - `bytevector?` `v` → bool - `bytevector-length` `bv` → integer - `bytevector-u8-ref` `bv` `i` → byte 0–255 - `bytevector-u8-set!` `bv` `i` `byte` → void - `bytevector-copy` `bv` `[start]` `[end]` → fresh copy - `bytevector-copy!` `dst` `at` `src` `[start]` `[end]` → in-place copy - `bytevector-append` `bv...` → concatenated bytevector - `utf8->string` `bv` `[start]` `[end]` → string decoded as UTF-8 - `string->utf8` `str` `[start]` `[end]` → bytevector UTF-8 encoded - `bytevector->list` / `list->bytevector` → conversion Steps: - [x] Spec: add `SxBytevector` type; implement all primitives in `spec/evaluator.sx` / `spec/primitives.sx`. - [x] OCaml: add `SxBytevector of bytes` to `sx_types.ml`; implement primitives using OCaml `Bytes`. - [x] JS bootstrapper: implement using `Uint8Array`. - [x] Tests: 30+ tests in `spec/tests/test-bytevectors.sx` — construction, ref/set, copy, append, utf8 round-trip, slice. - [x] Commit: `spec: bytevectors (make-bytevector/u8-ref/u8-set!/utf8->string/etc)` --- ## Phase 21 — format CL-style string formatting beyond `str`. `(format "Hello ~a, age ~d" name age)`. Haskell `printf`, Erlang `io:format`, CL `format`, and general string templating all use this idiom. Directives: - `~a` — display (no quotes) - `~s` — write (with quotes) - `~d` — decimal integer - `~x` — hexadecimal integer - `~o` — octal integer - `~b` — binary integer - `~f` — fixed-point float - `~e` — scientific notation float - `~%` — newline - `~&` — fresh line (newline only if not already at start of line) - `~~` — literal tilde - `~t` — tab Signature: `(format template arg...)` → string. Optional: `(format port template arg...)` — write to port directly. Steps: - [x] Spec: implement `format` as a pure SX function in `spec/stdlib.sx` — parses `~X` directives, dispatches to `display`/`write`/`number->string` as appropriate. Pure SX: no host calls needed. Self-hosting — uses string-buffer (Phase 5) internally. - [x] OCaml: expose as a primitive (or let it run as SX through the evaluator). Added format-decimal OCaml primitive; fixed lib/r7rs.sx number->string to support radix. - [x] JS bootstrapper: same. - [x] Tests: 28 tests in `spec/tests/test-format.sx` — each directive, multiple args, nested format, `~~` escape. 28/28 pass on both JS and OCaml. - [x] Commit: `spec: format — CL-style string formatting (~a ~s ~d ~x ~% etc)` — 4d7b3e29 --- ## Phase 22 — 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. - [x] Restart CL/APL/Ruby/Tcl loops with updated briefing pointing to new primitives. Added `## SX primitive baseline` section to plans/common-lisp-on-sx.md, plans/apl-on-sx.md, plans/ruby-on-sx.md, plans/tcl-on-sx.md. f43659ce. - [x] Common Lisp: char type (`#\a`); string ports + `read`/`write` for reader/printer; gensym for macros; rational numbers for CL ratios; multiple values; sets for CL set ops; `modulo`/`remainder`/`quotient`; radix formatting; `format` for `cl:format`. lib/common-lisp/runtime.sx (103 forms) + test.sh (68/68 pass). 1ad8e74a. - [x] Lua: vectors for arrays; hash tables for Lua tables; `delay`/`force` for lazy iterators; regexp for Lua pattern matching; trig from math completeness; bytevectors for binary I/O. math/string/table stdlib tables + lua-force. 185/185 pass. ec3512d6. - [x] Erlang: numeric tower for float/int; bitwise ops for bitmatch; multiple values for multi-return; sets for Erlang sets; `remainder` for `rem`; regexp for `re` module. lib/erlang/runtime.sx (63 forms) + test.sh (55/55 pass). 3c0a9632. - [x] Haskell: numeric tower for `Num`/`Integral`/`Fractional`; promises for lazy evaluation (critical); multiple values for tuples; rational numbers for `Rational`; char type for `Char`; `gcd`/`lcm`; sets for `Data.Set`; `read`/`write` for `Show`/`Read` instances. lib/haskell/runtime.sx (113 forms) + tests/runtime.sx (143/143 pass). c02ffcf3. - [x] JS: vectors for Array; hash tables for `Map`; sets for `Set`; bitwise ops for typed arrays; regexp for JS regex; bytevectors for `Uint8Array`; radix formatting. lib/js/stdlib.sx (36 forms) + test.sh epochs 6000-6032 (25/25 pass). COMMIT. - [x] Smalltalk: vectors for `Array new:`; hash tables for `Dictionary new`; sets for `Set new`; char type for `Character`; string ports + `read`/`write` for `printString`. lib/smalltalk/runtime.sx (72 forms) + tests/runtime.sx (86/86 pass). COMMIT. - [x] APL: vectors as core array type; bitwise ops for array masks; sets for APL set ops; sequence protocol for rank-polymorphic operations; format for APL output formatting. lib/apl/runtime.sx (60 forms) + tests/runtime.sx (73/73 pass). COMMIT. - [x] Ruby: coroutines for fibers; hash tables for `Hash`; sets for `Set`; regexp for Ruby regex; string ports for `StringIO`; bytevectors for `String` binary encoding. lib/ruby/runtime.sx (61 forms) + tests/runtime.sx (76/76 pass). COMMIT. Note: rb-fiber-yield from letrec-bound lambdas fails (JIT VM can't invoke callcc continuations as escapes); workaround: use top-level helper fns for recursive yields. - [x] Tcl: string ports for Tcl channel abstraction; string-buffer for `append`; coroutines for Tcl coroutines; regexp for Tcl `regexp`; format for Tcl `format`. lib/tcl/runtime.sx (37 forms) + tests/runtime.sx (56/56 pass). COMMIT. - [x] Forth: bitwise ops (core); string-buffer for word-definition accumulation; bytevectors for Forth's raw memory model. lib/forth/runtime.sx (36 forms) + tests/runtime.sx (64/64 pass). COMMIT. --- ## 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-05-01: Phase 22 Forth done — runtime.sx (36 forms): bitwise (AND/OR/XOR/INVERT/LSHIFT/RSHIFT/2*/2//bit-count/integer-length/within + arithmetic helpers), string-buffer (emit!/type!/value/length/clear!/emit-int!), memory (cfetch/cstore/fetch/store/move!/fill!/erase!/mem->list). 64/64 tests. 8019e572. - 2026-05-01: Phase 22 Tcl done — runtime.sx (37 forms): string-buffer (append accumulator), channel (read/write ports with gets/read/puts), regexp (make-regexp wrappers), format (%s/%d/%f/%x/%o/%% manual char scan), coroutine (call/cc, top-level helper pattern). 56/56 tests. 3e07727d. - 2026-05-01: Phase 22 Ruby done — runtime.sx (61 forms): Hash (list-of-pairs dict-backed), Set (make-set, (set item) order), Regexp (make-regexp wrappers), StringIO (write buf + rewind/char read), Bytevectors (thin wrappers), Fiber (call/cc; letrec JIT workaround: use top-level helpers). 76/76 tests. 182e6f63. - 2026-05-01: Phase 22 APL done — runtime.sx (60 forms): iota/rho/at, rank-polymorphic dyadic/monadic helpers, arithmetic/comparison/boolean/bitwise element-wise, reduce/scan, take/drop/rotate/compress/index, set ops (member/nub/union/intersect/without), format. 73/73 tests. COMMIT. - 2026-05-01: Phase 22 Smalltalk done — runtime.sx (72 forms): numeric helpers, Character (1-indexed Array backed by dict), Dictionary (list-of-pairs any-key map), Set (make-set), WriteStream/ReadStream/printString. set-member? (set item) order. 86/86 tests. COMMIT. - 2026-05-01: Phase 22 JS done — stdlib.sx (36 forms): bitwise (truncate not js-num-to-int; set-member? takes (set item) order), Map (dict-backed pairs), Set (SX make-set), RegExp (callable lambda). 25/25 new tests pass; total 492/585. COMMIT. - 2026-05-01: Phase 22 Haskell done — runtime.sx (113 forms): numeric tower (hk-div floor semantics), rational (dict GCD-normalised), hk-force (promises), Data.Char, Data.Set, Data.List, Maybe/Either, tuples, string helpers, hk-show. 148/148 tests. c02ffcf3. - 2026-05-01: Phase 22 Erlang done — runtime.sx (63 forms): numeric tower, bitwise (band/bor/bxor/bnot/bsl/bsr), sets, re module, list BIFs, type conversions, ok/error tuples. 55/55 tests. 3c0a9632. - 2026-05-01: Phase 22 Lua done — math/string/table stdlib tables + lua-force in lib/lua/runtime.sx. 185/185 tests (28 new). ec3512d6. - 2026-05-01: Phase 22 CL done — runtime.sx (103 forms): type preds, arithmetic, chars, format, gensym, values, sets, radix, list utils. cl-empty? guards nil/() split. 68/68 tests. 1ad8e74a. - 2026-05-01: Phase 22 step 1 — SX primitive baseline added to CL/APL/Ruby/Tcl plans. f43659ce. - 2026-05-01: Phase 21 complete — format (~a ~s ~d ~x ~o ~b ~f ~% ~& ~~ ~t) as pure SX in spec/stdlib.sx. Fixed lib/r7rs.sx number->string to support optional radix; added format-decimal OCaml primitive. 28/28 tests on both JS and OCaml. 4d7b3e29. - 2026-04-26: Phase 7 complete — bitwise-and/or/xor/not + arithmetic-shift + bit-count + integer-length. OCaml: land/lor/lxor/lnot/lsl/asr + Kernighan popcount + lsr loop for integer-length. JS: bitwise ops + Hamming weight + Math.clz32. 26 tests, 158 assertions, all pass. a8a79dc9. - 2026-04-26: Phase 6 complete — JS+Tests+Commit all ticked. JS needed no changes (spec-level forms). 40/40 ADT tests pass JS. 2032/2500 JS total (+67 vs phase-4). Phase 6 fully landed: 6c872107+0dc7e159+5d1913e7. Phase 7 (bitwise) next. - 2026-04-26: Phase 6 OCaml done — Dict-based ADT (no native SxAdt type needed); hand-written sf_define_type in bootstrap.py FIXUPS (skipped from transpile — &rest params + empty-dict {} literals); registered via register_special_form; step_limit/step_count added to PREAMBLE. 172 assertions pass (test-adt). Full suite 4280/1080 (was 4243/1117, +37). Committed 5d1913e7. - 2026-04-26: Phase 6 Spec match done — ADT case added to match-pattern in spec/evaluator.sx: checks (list? pattern)+(symbol? first)+(dict? value)+(get value :_adt), then matches :_ctor+arity and recursively binds field patterns. No-clause error now uses make-cek-value+raise-eval-frame so guard can catch it. 20 new match tests pass; 40/40 total ADT tests green. Zero regressions. - 2026-04-26: Phase 6 Spec define-type done — sf-define-type registered via register-special-form! in spec/evaluator.sx; AdtValue as {:_adt true :_type "..." :_ctor "..." :_fields (list ...)}; ctor fns + arity checking + Name?/Ctor? predicates + Ctor-field accessors; *adt-registry* dict populated per define-type call. 20/20 JS tests pass in spec/tests/test-adt.sx. OCaml define-type is next task. - 2026-04-26: Phase 6 Design done — plans/designs/sx-adt.md written. Covers define-type/match syntax, AdtValue CEK runtime, stepSfDefineType+MatchFrame dispatch, exhaustiveness warnings, recursive types, nested patterns, wildcard _. 3-phase impl plan. Next fire: Spec implement define-type. - 2026-04-26: Phase 5 complete — string buffer fully landed (d98b5fa2). 17 tests, 17/17 OCaml+JS. Phase 6 (ADTs) next. - 2026-04-26: Phase 5 Spec+OCaml+JS step done — StringBuffer of Buffer.t in sx_types.ml; make-string-buffer/append!/->string/length/string-buffer? in sx_primitives.ml; SxStringBuffer with _string_buffer marker + typeOf/dict? fixes in platform.py; JS rebuilt. 17/17 tests OCaml+JS. - 2026-04-26: Phase 4 complete — coroutine primitive fully landed (4 commits: spec library + OCaml verified + JS pre-load + 27 tests). Phase 5 (string buffer) next. - 2026-04-26: Phase 4 Tests step done — 27 tests total (10 new: state field inspection, yield-from-helper, initial-arg-ignored, mutable-closure, complex-values, round-robin, factory-no-state, non-coroutine-error). 27/27 OCaml+JS. - 2026-04-26: Phase 4 JS step done — all CEK primitives already in sx-browser.js; fix was pre-loading spec/coroutines.sx+spec/signals.sx in run_tests.js so (import (sx coroutines)) resolves synchronously. 17/17 coroutine tests pass JS. 1965/2500 total (+25), zero new failures. - 2026-04-26: Phase 4 OCaml step done — no native SxCoroutine type needed; existing cek-step-loop/cek-resume/perform/make-cek-state primitives in run_tests.ml fully support the spec/coroutines.sx library. 284/284 pass (coroutines+vectors+numeric-tower+dynamic-wind), zero regressions. - 2026-04-26: Phase 4 Spec step done — spec/coroutines.sx define-library with make-coroutine/coroutine-resume/coroutine-yield/coroutine?/coroutine-alive?; make-coroutine stub in evaluator.sx; 17/17 coroutine tests pass (OCaml). Key insight: coroutine body must use (define loop (fn...)) + (loop 0) not named let — named let uses cek_call→cek_run which errors on IO suspension. - 2026-05-01: Phase 10 complete — mutable hash tables. HashTable variant in OCaml; JS Map-based SxHashTable. 11 primitives: make-hash-table/hash-table?/set!/ref/delete!/size/keys/values/->alist/for-each/merge!. 28 tests, all pass OCaml+JS. 133bdf52. - 2026-05-01: Phase 9 complete — delay/force/delay-force/make-promise/promise?. Dict-based promise {:_promise :forced :thunk :value}; :_iterative flag for delay-force chain following. 25/25 tests OCaml (4357) and JS (2109). Committed e44cb89a. - 2026-05-01: Phase 8 complete — values/call-with-values/let-values/define-values. Dict marker {:_values true :_list [...]} (no new type). step-sf-define desugars shorthand (define (f x) body) on both hosts. 25/25 tests OCaml+JS. Committed 43cc1d90. - 2026-04-26: Phase 3 complete — OCaml+JS done. CallccContinuation gains winders-depth int; make_callcc_continuation/callcc_continuation_winders_len wired; wind-after/wind-return CekFrame fields fixed (cf_f=after-thunk, cf_extra=winders-len, cf_name=body-result); get_val + transpiler.sx updated. 8/8 dynamic-wind tests pass on OCaml; 235/235 (callcc+guard+do+r7rs) zero regressions. Committed 6602ec8c. - 2026-04-26: Phase 3 Spec+Tests done — dynamic-wind CEK implementation: wind-after/wind-return frames, *winders* stack, kont-unwind-to-handler, wind-escape-to. callcc frame stores winders-len in continuation; callcc-continuation? calls wind-escape-to before escape. 8/8 dynamic-wind tests pass (normal return, raise, call/cc, nested LIFO, guard ordering). 1948/2500 JS (+8). Zero regressions. Committed a9d5a108. - 2026-04-26: Phase 2 complete — Verify+Commit done. OCaml 4874/394, JS 1940/2500 (+60). No regressions. 6 JS-only failures are float≡int platform-inherent. Phase 2 fully landed across 4 commits. - 2026-04-26: Phase 2 JS bootstrapper done — integer?/float?/exact?/inexact? added (Number.isInteger); truncate/remainder/modulo/random-int/exact->inexact/inexact->exact/parse-number added. Fixed sx_server.ml epoch+blob+io-response protocol for Integer type. JS: 1940/2500 (+60). OCaml: 4874/394 baseline. 6 JS tests fail (JS float≡int platform limit). Committed b12a22e6. - 2026-04-26: Phase 2 Spec done — integer?/float? predicates added to spec/primitives.sx; floor/ceil/truncate :returns updated to "integer"; / to "float"; exact->inexact/inexact->exact docs and returns updated; float contagion documented on +/-/*; 4874/394 baseline. Committed 45ec5535. - 2026-04-26: Phase 2 OCaml+Tests done — `Integer of int` / `Number of float` in sx_types.ml; float contagion across all arithmetic; floor/truncate/round → Integer; integer?/float?/exact?/inexact?/exact->inexact/inexact->exact; 92/92 numeric tower tests pass; 4874 total (394 pre-existing unchanged). Committed c70bbdeb. - 2026-04-26: Phase 1 complete — JS step done. Fixed fundamental lambda binding bug (index-of on arrays returned -1 not NIL, making bind-lambda-params mis-fire &rest branch). Added _lastErrorKont_/hostError/try-catch stubs. 42/42 vector tests pass. 1847 std / 2362 full passing (up from 5). Committed. - 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. - 2026-05-01: Phase 20 complete — bytevectors. SxBytevector of bytes in OCaml using Bytes; Uint8Array-backed SxBytevector in JS. 12 primitives: make-bytevector, bytevector?, bytevector-length, bytevector-u8-ref, bytevector-u8-set!, bytevector-copy, bytevector-copy!, bytevector-append, utf8->string, string->utf8, bytevector->list, list->bytevector. 32 tests, all pass. JS 2535, OCaml 4725. a3811545. - 2026-05-01: Phase 19 complete — regular expressions. SxRegexp(src,flags,Re.re) in OCaml via Re.Pcre; SxRegexp wrapper around JS RegExp. 9 primitives: make-regexp, regexp?, regexp-source, regexp-flags, regexp-match, regexp-match-all, regexp-replace, regexp-replace-all, regexp-split. Match dicts with :match/:start/:end/:groups. 32 tests, all pass. JS 2503, OCaml 4693. d8d5588e. - 2026-05-01: Phase 18 complete — sets. SxSet as (string,value) Hashtbl keyed by inspect(val) in OCaml; Map keyed by write-to-string in JS. 13 primitives: make-set, set?, set-add!, set-member?, set-remove!, set-size, set->list, list->set, set-union, set-intersection, set-difference, set-for-each, set-map. 33 tests, all pass. JS 2469, OCaml 4659. 3b0ac67a. - 2026-05-01: Phase 17 complete — read/write/display. OCaml: sx_write_val/sx_display_val helpers; read via Sx_parser.read_value with #t/#f and N/D rational support added to parser; postprocess ()→Nil. JS: sxReadNormalize (#t/#f→true/false), sxReadConvert (()→NIL), sxEq list equality, sxWriteVal symbol/keyword name fix (v.name not v._sym), readerMacroGet registry. 42 tests (test-read-write.sx), all pass both hosts. JS 2436, OCaml 4626. 7d329f02. - 2026-05-01: Phase 16 complete — rational numbers. SxRational type in OCaml (Rational of int*int, reduced, denom>0) and JS (SxRational class, _rational marker). n/d reader in spec/parser.sx. Arithmetic contagion: int op rational → rational, rational op float → float. JS keeps int/int → float for CSS compat. OCaml as_number+safe_eq extended for cross-type rational equality. 62 tests in test-rationals.sx, all pass. JS 2232, OCaml 4532 (+11). 036022cc. - 2026-05-01: Phase 15 complete — math completeness. stdlib.math module: sin/cos/tan/asin/acos/atan(1-2 args)/exp/log/expt/quotient/gcd/lcm/number->string(radix)/string->number(radix). OCaml atan updated for optional 2nd arg. Strict radix parsing in JS string->number. 44 tests in test-math.sx, all pass. JS 2311/4801, OCaml 4547/5629. be2b11ac. - 2026-05-01: Phase 14 OCaml done — Eof + Port{PortInput/PortOutput} in sx_types.ml; 15 port primitives in sx_primitives.ml; raw_serialize updated; 4532/4532 (+39, zero regressions). 8ba0a33f. - 2026-05-01: Phase 14 Spec+JS+Tests+Commit done — port type {_port,_kind,_source/_buffer,_pos,_closed}; eof singleton; 15 primitives in spec/primitives.sx (stdlib.ports) + platform.py; 39/39 tests in test-ports.sx. Committed 3d8937d7. OCaml step next. - 2026-05-01: Phase 13 OCaml done — Char of int in sx_types.ml; #\ reader in sx_parser.ml; all char primitives in sx_primitives.ml; fixed get_val for Integer n list indexing (was Number-only); fixed raw_serialize for Integer/Char. 4493/4493 (+43, zero regressions). b939becd. - 2026-05-01: Phase 13 Spec+JS+Tests+Commit done — SxChar tagged {_char,codepoint}; char? char->integer integer->char char-upcase/downcase; 10 comparators (ordered+ci); 5 predicates; string->list/list->string as platform primitives; #\a #\space #\newline reader syntax in spec/parser.sx; js-char-renames dict in transpiler.sx; 43/43 tests pass JS (2254/4745). Committed 4b600f17. OCaml step next. - 2026-05-01: Phase 12 complete — gensym + symbol interning. gensym_counter/gensym/string->symbol/symbol->string/intern/symbol-interned? in spec + OCaml + JS. Fixed ListRef case in seq_to_list (both hosts). 19 tests, all pass. OCaml 4450/1080, JS 2205/2497. Commits: edf4e525 Spec, 0862a614 OCaml+Tests. - 2026-05-01: Phase 11 complete — sequence protocol done. Commits: da4b526a Spec, 7286629c OCaml, 06a3eee1 JS, 0fe00bf7 Tests. JS 2185/+48, OCaml 4424/+39. - 2026-05-01: Phase 11 Tests done — 45 tests in test-sequences.sx all passing (JS 2185/+48, OCaml 4424/+39). Fixed vector? rename, vectorLength/vectorRef/reverse aliases, in-range letrec→build-range, sequence-length nil, assert-equal for lists. Committed 0fe00bf7. - 2026-05-01: Phase 11 JS bootstrapper step done — confirmed sx-browser.js current (built in Spec step da4b526a); 19 sequence primitive refs in output; 2137/2500 JS tests passing. - 2026-05-01: Phase 11 OCaml step done — seq_to_list helper added before let-rec; ho_setup_dispatch wraps all 7 coll bindings with seq_to_list; seq-to-list/sequence-to-list/to-vector/length/ref/append + in-range primitives in sx_primitives.ml. 4385/4385 baseline unchanged, 0 regressions. Committed 7286629c. - 2026-05-01: Phase 11 Spec step done — seq-to-list coercion helper; ho-setup-dispatch extended with seqToList on all collection args; sequence-to-list/vector/length/ref/append + in-range added to evaluator.sx. Restored 3 accidentally-deleted make-cek-state/value/suspended definitions. Fixed 8 shorthand define forms + added vector->list/list->vector transpiler renames. JS: 2137 passing (+28 vs HEAD baseline of 2109).