Extends match-pattern in spec/evaluator.sx with an ADT case: when the
pattern is (CtorName var...) and the value is an ADT dict (:_adt true),
check :_ctor matches, arity matches, then recursively bind field patterns.
Supports nested patterns, wildcard _, variable binding, and zero-arg ctors.
Changes step-sf-match to route no-clause errors through raise-eval-frame
instead of direct error, allowing guard to catch non-exhaustive matches.
40/40 ADT tests pass (20 define-type + 20 match). Zero regressions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
17 tests written inline with the implementation step. All 17 pass
on OCaml and JS. Phase 5 fully done as d98b5fa2.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
OCaml: StringBuffer of Buffer.t in sx_types.ml; 5 primitives in
sx_primitives.ml (make-string-buffer, string-buffer?, string-buffer-append!,
string-buffer->string, string-buffer-length); inspect case added.
JS: SxStringBuffer with array+join backend; _string_buffer marker for
typeOf dispatch and dict? exclusion (also excludes _vector from dict?).
spec/primitives.sx: 5 define-primitive entries.
17/17 tests pass on both OCaml and JS.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
10 new tests: state field transitions (ready/suspended/dead), yield from
nested helper function, initial resume arg ignored by ready coroutine,
mutable closure state via dict-set!, complex yield values (list/dict),
round-robin scheduling, factory creates independent coroutines, resuming
non-coroutine raises error.
27/27 pass on both OCaml and JS.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All CEK primitives (cek-step-loop/cek-resume/make-cek-state/cek-suspended?/
cek-io-request/cek-terminal?/cek-value) were already registered in sx-browser.js.
Root cause of test failure: (import (sx coroutines)) creates an io-suspended
state when the library isn't pre-loaded; overridden cekRun throws on suspension.
Fix: pre-load spec/signals.sx + spec/coroutines.sx before test files run.
17/17 coroutine tests pass in JS. 1965/2500 total (+25 vs 1940 baseline),
zero new failures.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
No native SxCoroutine type needed. dict-based coroutine identity +
cek-step-loop/cek-resume/perform/make-cek-state primitives already in
run_tests.ml fully implement the coroutine contract. 284/284 pass
(coroutines+vectors+numeric-tower+dynamic-wind), zero regressions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
spec/coroutines.sx: define-library with make-coroutine, coroutine-resume,
coroutine-yield, coroutine?, coroutine-alive?. Built on existing perform/
cek-step-loop/cek-resume suspension machinery.
spec/tests/test-coroutines.sx: 17 tests — multi-yield, final return,
arg passthrough, alive? predicate, nested coroutines, recursive iteration,
independent coroutine interleaving.
Key: coroutine body must use (define loop (fn…)) not named let — named let
transpiles to cek_call→cek_run which rejects IO suspension. All 17/17 pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
vector-ref and vector-set! now raise Eval_error on out-of-bounds index instead of
an OCaml array exception. vector-copy accepts optional start and end parameters for
slicing (R7RS §6.8). spec/primitives.sx doc updated to reflect slice params.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Threads declaration kind ("var"/"let"/"const") through js-transpile-var →
js-vardecl-forms so the transpiler knows which kind is being declared.
Infrastructure for full TDZ enforcement: js-tdz-check can wrap let/const
reads to raise TypeError before initialization.
Updates plans/js-on-sx.md: ticks [x] for TDZ, marks regex blocker RESOLVED,
adds progress log entry for 2026-04-25.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Plans + briefings for four new language loops, each with a delcc/JIT
showcase that the runtime already supports natively:
- common-lisp — conditions + restarts on delimited continuations
- apl — rank-polymorphic primitives + 6 operators on the JIT
- ruby — fibers as delcc, blocks/yield as escape continuations
- tcl — uplevel/upvar via first-class env chain, the Dodekalogue
Launcher scripts now spawn 12 windows (was 8).
Tracks the path from 1277/1496 (85.4%) to 100%. Records each blocker's
fix sketch, files in scope, and order of attack. Cluster #31 spec'd in
detail for the next focused sit-down.
sx-tree MCP file ops broken this session (Yojson Type_error "Expected
string, got null" on every file-based call — sx_read_subtree,
sx_find_all, sx_replace_by_pattern, sx_summarise, sx_pretty_print, even
sx_load_check on existing files works but summarise fails). Can't edit
integration.sx to add before:init/after:init dispatch. Additionally 4
of the 6 tests fundamentally require stricter parser error-rejection
(add - to currently parses to (set! nil ...); on click blargh end
accepts blargh as symbol expression) — out of single-cluster budget.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cluster 19 was implemented in 4be90bf2 but the plan/scoreboard rows
still marked it pending. Sync the plan state: mark done, add log entry,
bump merged total 1264 → 1277.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five new guest-language plans mirroring the js-on-sx / hs-loop pattern, each
with a phased roadmap (Progress log + Blockers), a self-contained agent
briefing for respawning a long-lived loop, and a shared restore-all.sh that
snapshots state across all seven language loops.
Briefings bake in the lessons from today's stall debugging: never call
sx_build (600s watchdog), only touch lib/<lang>/** + own plan file, commit
every feature, update Progress log on each commit, route shared-file
issues to Blockers rather than fixing them.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wire up the `ask` and `answer` commands end-to-end:
- tokenizer.sx: register `ask` and `answer` as hs-keywords.
- parser.sx: cmd-kw? gains both; parse-cmd dispatches to new
parse-ask-cmd (emits `(ask MSG)`) and parse-answer-cmd, which
reads `answer MSG [with YES or NO]`. The with/or pair reads
yes/no via parse-atom — parse-expr would collapse
`"Yes" or "No"` into `(or "Yes" "No")` before match-kw "or"
could fire. The no-`with` form emits `(answer-alert MSG)`.
- compiler.sx: three new cond branches (ask, answer, answer-alert)
compile to a let that binds __hs-a, sets `the-result` and `it`,
and returns the value — so `then put it into ...` works.
- runtime.sx: hs-ask / hs-answer / hs-answer-alert call
window.prompt / confirm / alert via host-call + host-global.
- tests/hs-run-filtered.js: test-name-keyed globalThis.{alert,
confirm,prompt}; __currentHsTestName is updated before each
test. Host-set! for innerHTML/textContent now coerces JS
null → "null" (browser behaviour) so `prompt → null` →
`put it into #out` renders literal text "null", which the
fourth test depends on.
Suite hs-upstream-askAnswer: 1/5 -> 5/5.
Smoke 0-195: 166/195 -> 170/195.
Three parser additions so scripts like `(on click (log me) (trigger foo))`
parse into a single feature with both commands in its body:
1. parse-feat: new cond branch for `paren-open` — advance, recurse
parse-feat, consume `paren-close`. Allows a feature like `(on click
...)` to be grouped in parens.
2. parse-cmd: two new cond branches — on `paren-close` return nil (so
cl-collect terminates at an outer group close), and on `paren-open`
advance / recurse / close. Allows single parenthesized commands like
`(log me)`.
3. cl-collect: previously only recursed when the next token was a
recognised command keyword (`cmd-kw?`), so after `(log me)` the
sibling `(trigger foo)` would end the feature body and re-surface as
a top-level feature. Extended the recursion predicate to also fire
when the next token is `paren-open`.
Suite hs-upstream-core/parser: 9/14 -> 10/14.
Smoke 0-195: 165/195 -> 166/195.
Two shared-file entries based on scoreboard patterns:
- Math trig/transcendental primitives missing. 34× "TypeError: not a
function" across Math category — sin/cos/tan/asin/acos/atan/atan2,
sinh/cosh/tanh/asinh/acosh/atanh, log/log2/log10/log1p/expm1,
clz32/imul/fround, variadic hypot/max/min. All need OCaml/JS platform
primitives; can't polyfill from pure SX and keep precision. Once
present in the runtime, `js-global.Math` gets one extension and all 34
failures flip together.
- Evaluator CPU bound at ~1 test/s on 2-core box. Runner already
auto-disables parallel workers on ≤2 cores. Optimization surface for
the shared evaluator: lexical addresses (vs name walk), inline caches
on js-get-prop (vs __proto__ walk), force-JIT transpiled JS bodies
(vs lazy), OCaml 5 domains (vs separate processes).
Progress-log entry for P0 harness cache added alongside.
process_hs_val stripped `//…` line comments with a naïve regex,
which devoured `https://yyy.xxxxxx.com/…` inside a backtick template
— the 'properly interpolates values 2' fixture was landing with
the HS source truncated at `https:`.
New helper _strip_hs_line_comments walks char-by-char and only
strips `//` / leading-whitespace `--` when not inside `'…'`, `"…"`,
or backticks; respects `\\`-escapes inside strings.
Suite hs-upstream-core/regressions: 11/16 → 12/16.
Smoke 0-195: 163/195 → 164/195.