Commit Graph

2418 Commits

Author SHA1 Message Date
5a28cf5dd3 merge: loops/apl — APL on SX runtime + transpile + 362 tests 2026-05-07 11:31:17 +00:00
f480eb943c merge: bugs/resume-letrec — cek_run propagates IO suspension via hook
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
2026-05-07 11:27:04 +00:00
edc7e865b4 merge: bugs/jit-bytecode-loop — OP_CLOSURE Integer/Number fix (+690 JIT tests) 2026-05-07 11:26:57 +00:00
fc13acb805 fix: cek_run propagates IO suspension via _cek_io_suspend_hook
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m52s
When a `perform` fired inside a tree-walked eval_expr path — sf_letrec init
exprs / non-last body exprs, expand_macro body, qq_expand unquote,
sf_dynamic_wind / sf_scope / sf_provide bodies — cek_run raised
"IO suspension in non-IO context" and swallowed the suspension. The hook
that converts the CEK suspended state to VmSuspended (so the outer driver
sees it as a resumable suspension object) was defined in sx_vm.ml but
never invoked from cek_run.

Repro in Node.js (hosts/ocaml/browser/test_letrec_resume.js):
  (letrec ((x (perform {:op "io"}))) "ok")           ;; threw the error
  (letrec ((x 1)) (perform {:op "io"}) "after")      ;; threw the error

The originally reported browser symptom — "[sx] resume: Not callable: nil"
after hs-wait resumes inside a letrec — was the same root cause showing
through the JIT/VM resume path instead of as a top-level error.

Fix: cek_run and cek_run_iterative now check !_cek_io_suspend_hook and
invoke it when the loop terminates in a suspended state. The hook (set by
sx_vm.ml in the browser, by run_tests.ml in the test runner) converts the
suspension to VmSuspended / resolves IO synchronously. When the hook is
unset (pure-CEK harness), the legacy Eval_error is raised so misuse stays
visible.

Also patches:
- hosts/ocaml/bootstrap.py — regex-patches the transpiled cek_run on regen
  so the fix survives a fresh `python3 hosts/ocaml/bootstrap.py` cycle.
- hosts/ocaml/browser/sx_browser.ml — api_eval / api_eval_vm / api_eval_expr
  now catch VmSuspended and surface a clean error string (K.eval has no
  driver to resume; callers who want resumption use callFn).

Tests:
- spec/tests/test-letrec-resume-treewalk.sx — 7 CEK-level regression tests
  covering letrec init / non-last body, scope/provide bodies, sibling
  fn-after-perform. All 7 fail in baseline ("IO suspension in non-IO
  context"), all 7 pass with the fix.
- hosts/ocaml/browser/test_letrec_resume.js — 13 WASM kernel tests via
  callFn driveSync, including the wait-boot pattern from the briefing.
  All 13 pass.

Suite results: 4557 pass / 1338 fail (was 4550 / 1339); +7 new passes,
-1 flaky timeout (hs-upstream-if sieve), no regressions.
2026-05-07 10:13:48 +00:00
ca151d7ed5 ocaml: VM OP_CLOSURE upvalue-count handles Integer values
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m44s
After the Integer/Number numeric tower split (c70bbdeb), the bytecode
compiler emits :upvalue-count as Integer, but the VM and SXBC loader
only matched Number. The fallback `_ -> 0` made the VM skip reading
upvalue descriptors entirely, so the IP advanced into raw upvalue
bytes which were then misread as opcodes.

Symptom: JIT runs of nested closures (curried functions, Y combinator,
component bodies that close over outer let-bindings) produced "VM:
CONST index N out of bounds (pool size M)" with N values like 256,
4096, 5120, 12800, 13056 — all of the form `byte | (opcode << 8)`,
i.e. an upvalue descriptor (lo) followed by the next instruction's
opcode (hi) being read as a u16 operand.

Fix all five sites that decode upvalue-count to also accept Integer:
- hosts/ocaml/lib/sx_vm.ml: OP_CLOSURE handler, trace_run, disassemble
- hosts/ocaml/lib/sx_vm_ref.ml + hosts/ocaml/sx_vm_ref.ml + bootstrap_vm.py:
  vm_create_closure preamble (the bootstrap source-of-truth and both
  generated copies)
- hosts/ocaml/browser/sx_browser.ml: SXBC loader's parse_kv

Test impact: JIT 3848 -> 4538 passing (+690). No-JIT unchanged at 4550.
The previously-failing curried/Y/higher-order tests in
spec/tests/test-cek-advanced.sx now pass under --jit and serve as
regression coverage.

This fixes a real current bug. The 28-day-old memory file describing
parser-combinator JIT bugs predates the numeric tower split and
described a different problem; with this fix the parser-combinator
broken-name list (`_jit_is_broken_name` in sx_vm.ml) is no longer
strictly required for correctness, but keeping it avoids a TIMEOUT
regression in one hyperscript test, so it remains in place.
2026-05-07 09:48:21 +00:00
322eb1d034 plans: tick Phase 5 channel I/O — 349/349 green
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 2m18s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 09:29:14 +00:00
d755caeb9a apl: idiom corpus — 34 classic idioms; entire plan complete (362/362)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m2s
2026-05-07 07:29:04 +00:00
3e77dd4ded apl: ⎕ system functions + drive corpus to 100+ (+13 tests, 328/328)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 51s
2026-05-07 06:56:20 +00:00
0f13052900 apl: quicksort recursive partition — Phase 6 classics complete (+9 tests, 315/315)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m0s
2026-05-07 06:23:03 +00:00
e37167a58e apl: n-queens via permute + diagonal filter, q(8)=92 (+10 tests, 306/306)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m5s
2026-05-07 05:46:54 +00:00
49eb22243a apl: mandelbrot real-axis batched z=z²+c (+9 tests, 296/296)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m5s
2026-05-07 05:07:25 +00:00
20a61de693 apl: life Conway via 9-shift toroidal sum (+7 tests, 287/287)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m8s
2026-05-07 04:36:49 +00:00
ed0853f4a0 apl: primes sieve (2=+⌿0=A∘.|A)/A←⍳N + apl-compress (+11 tests, 280/280)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m6s
2026-05-07 04:07:09 +00:00
ec26b61cbe apl: conformance.sh + scoreboard.{json,md} — Phase 5 complete (269/269)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m3s
2026-05-07 03:37:58 +00:00
bee4e0846c apl: niladic/monadic/dyadic valence dispatch (+14 tests, 269/269)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m6s
2026-05-07 03:10:07 +00:00
f591ee17c3 apl: control words :If/:While/:For/:Select (+10 tests, 255/255)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m3s
2026-05-07 02:42:28 +00:00
a5044cfc08 plan: record step 14 commit hash — roadmap complete
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 02:38:57 +00:00
6c171d4906 sx: step 14 — inline JIT primitives (-69% fib, -62% loop, -50% sum on bench_vm)
The bytecode compiler emitted OP_CALL_PRIM (52) for every primitive call, even
for arithmetic and comparison hot-paths. The VM had specialized opcodes
(OP_ADD, OP_SUB, OP_EQ, etc.) defined but unused.

- lib/compiler.sx (compile-call): emit specialized 1-byte opcode when the
  primitive name + arity matches one of {+, -, *, /, =, <, >, cons, not, len,
  first, rest}. Falls back to CALL_PRIM otherwise. fib bytecode: 50 → 38 bytes.
- hosts/ocaml/lib/sx_compiler.ml: mirror change in the auto-generated OCaml
  compiler so SXBC export from mcp_tree uses the same emission.
- hosts/ocaml/lib/sx_vm.ml: extend OP_ADD/SUB/MUL/DIV to handle Integer+Integer
  (not just Number+Number). Inline OP_EQ via Sx_runtime._fast_eq. Inline
  OP_LT/GT mixed-numeric comparisons. Avoids Hashtbl lookup on the fallback
  path for the common integer cases that dominate tight loops.
- hosts/ocaml/bin/bench_vm.ml: VM-only benchmark — loads compiler.sx via CEK,
  JIT-compiles each fn, measures Sx_vm.call_closure throughput.

Median improvements (best of 3 runs of 9-min, bench_vm.exe):
  fib(22)         107.87ms →  33.13ms   -69%
  loop(200000)    429.64ms → 161.16ms   -62%
  sum-to(50000)    72.85ms →  36.74ms   -50%
  count-lt(20000)  28.44ms →  17.58ms   -38%
  count-eq(20000)  37.23ms →  15.46ms   -58%

Tests: 4550/4550 OCaml passing (unchanged). Zero regressions.

Last step in the sx-improvements roadmap — all 14 steps complete.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 02:38:47 +00:00
4cb5302232 plan: record step 13 commit hash
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 02:17:11 +00:00
0e022ab670 sx: step 13 — buffer primitives + buffer-based inspect
Added short aliases make-buffer / buffer? / buffer-append! / buffer->string /
buffer-length on both OCaml and JS hosts, sharing the existing StringBuffer
value type. buffer-append! auto-coerces non-strings via inspect.

Rewrote the OCaml host inspect function to walk a single shared Buffer.t
instead of allocating O(n) intermediate strings via String.concat at every
recursion level. inspect underlies sx-serialize and error-path formatting,
so this benefits the tightest serialization paths.

Median improvements (bin/bench_inspect.exe, best-of-3 of 9-run min):
  tree-d8 (75KB):    5.31ms -> 1.30ms  (-76%)
  tree-d10 (679KB): 81.89ms -> 16.02ms (-80%)
  dict-1000:         0.80ms -> 0.31ms  (-61%)
  list-2000:         0.74ms -> 0.33ms  (-55%)

Tests: OCaml 4545 -> 4550. JS 2591 -> 2596. Zero regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 02:16:59 +00:00
1900726fc9 apl: tradfn ∇ header — line-numbered stmts + :branch goto (+10 tests, 245/245)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m4s
2026-05-07 02:13:00 +00:00
c48911e591 plan: record step 12 commit hash
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 01:46:31 +00:00
a66c0f66f0 sx: step 12 — prim_call fast path (-66% fib, -86% reduce)
CEK frames were already records (cek_frame in sx_types.ml), so the actual
hot-path bottleneck was prim_call "=" [...] in step_continue/step_eval
dispatch: each step did a Hashtbl lookup + 2x list cons + pattern match
just to compare frame-type strings.

Added a short-circuit fast path in prim_call (sx_runtime.ml) for the
hot operators: =, <, >, <=, >=, empty?, first, rest, len. These bypass
the primitives Hashtbl entirely and dispatch directly on value shape.
Inlined _fast_eq for scalar/string equality, which dominates frame-type
dispatch comparisons.

Added bin/bench_cek.exe with five tight-loop benchmarks (fib, loop,
map, reduce, let-heavy). Median of 7 runs:

  fib(18)            2789ms -> 941ms   (-66%)
  loop(5000)         2018ms -> 620ms   (-69%)
  map sq xs(1000)    108ms  -> 48ms    (-56%)
  reduce + ys(2000)  72ms   -> 10ms    (-86%)
  let-heavy(2000)    491ms  -> 271ms   (-45%)

Tests: 4545/4545 passing baseline preserved (1339 pre-existing failures
unchanged).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 01:46:23 +00:00
16167c5d9b apl: dfn complete — guards, locals, ∇ recursion, ⍺← default (+9 tests, 235/235)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m3s
2026-05-07 01:44:19 +00:00
1fbfdfe4ae plan: record step 11 commit hash 2026-05-07 01:20:44 +00:00
6328b810bd sx: step 11 — migrate prolog hook + add worker plugin
Move `hs-prolog-hook` / `hs-set-prolog-hook!` / `prolog` out of
`lib/hyperscript/runtime.sx` into a self-contained plugin file at
`lib/hyperscript/plugins/prolog.sx`. The API surface is preserved —
`lib/prolog/hs-bridge.sx::pl-install-hs-hook!` still calls
`hs-set-prolog-hook!` exactly as before, just resolved to the plugin
file's binding rather than runtime.sx's.

Move the E39 worker stub registration out of `lib/hyperscript/parser.sx`
into `lib/hyperscript/plugins/worker.sx`. The plugin calls
`(hs-register-feature! "worker" ...)` at file load time. Behaviour is
identical — `worker MyWorker ...` raises the same helpful "plugin not
installed" error, just routed through the registry from a separate
file. The pre-existing `behavioral` test for the helpful error
("raises a helpful error when the worker plugin is not installed")
still passes via the new path.

Wire-up:
- OCaml `bin/run_tests.ml`: load `plugins/worker.sx` and
  `plugins/prolog.sx` after `runtime.sx`, before `integration.sx`.
- JS `tests/hs-kernel-eval.js`: extend HS module list with
  `hs-worker` / `hs-prolog`; add `HS_PLUGINS` resolver branch so the
  `hs-` prefix maps to `lib/hyperscript/plugins/`.
- WASM `hosts/ocaml/browser/bundle.sh`: copy plugin files into
  `dist/sx/hs-<name>.sx`.
- WASM `hosts/ocaml/browser/compile-modules.js`: add `hs-worker` /
  `hs-prolog` to `FILES`, `HS_DEPS`, and `HS_LAZY` so the lazy loader
  resolves them on first reference.
- Worker plugin carries a sentinel `(define hs-worker-loaded? true)`
  so `extractDefines` indexes it in the module manifest (the lazy
  loader skips files with no defines).

Mirrors `shared/static/wasm/sx/hs-{parser,runtime}.sx` are byte-identical
to source; new mirrors `hs-{prolog,worker}.sx` written via sx_write_file.

OCaml: 4545 passed, 1339 failed — matches baseline.
JS: 2591 passed, 2465 failed — matches baseline.
Smoke tests: `(prolog ...)` raises "prolog hook not installed" cleanly,
`(hs-set-prolog-hook! ...)` then `(prolog ...)` returns the hook result,
`(hs-compile "worker MyWorker def noop() end end")` raises the worker
stub error via the registry path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 01:20:32 +00:00
84d210b6b3 apl: dfn foundation — transpile.sx + apl-eval-ast (+15 tests, 226/226)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m2s
2026-05-07 00:57:59 +00:00
c08e217e2a plan: record step 10 commit hash
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 00:51:09 +00:00
d22361e471 sx: step 10 — compiler command + as converter registries
Add `_hs-command-registry` and `_hs-converter-registry` dicts plus
`hs-register-command!` / `hs-register-converter!` to
`lib/hyperscript/compiler.sx`. Inside `hs-to-sx`, before the existing
`cond` over head symbols, check both registries: an `as` form whose
type-name has a registered converter dispatches to that converter; any
list head whose name (`(str head)`) is in the command registry
dispatches to that compile-fn. On registry miss, the original ~180
hardcoded branches handle the form.

Each registered fn receives a ctx dict (built per call) exposing
`:hs-to-sx` for recursion plus the AST fields the dispatch needs
(`:ast :head` for commands; `:ast :value-ast :type-name` for
converters). Mirrors Step 9's parser feature registry shape.

Smoke tested: register custom command + converter, both dispatch;
built-in `(as x \"Int\")` still produces `(hs-coerce x \"Int\")`.

Mirror `shared/static/wasm/sx/hs-compiler.sx` copied byte-identical.
OCaml: 4545/1339, JS: 2591/2465 — both match baseline, zero regressions.

Second piece of plans/designs/hs-plugin-system.md (Step 11 next).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 00:50:53 +00:00
00121e137e plan: record step 9 commit hash 2026-05-07 00:39:36 +00:00
986d6411d0 sx: step 9 — parser feature registry
Add `_hs-feature-registry` dict and `hs-register-feature!` to
`lib/hyperscript/parser.sx`. Replace `parse-feat`'s hardcoded `cond`
on feature names with a registry lookup; the paren-open and
default-expression branches remain as fallthroughs.

Each parse-fn receives a `ctx` dict (built per call by `parse-feat-ctx`)
exposing parser internals (`:adv!`, `:tp-val`, `:tp-type`, `:at-end?`,
`:parse-cmd-list`, `:parse-expr`) and the per-feature handlers
(`:parse-on-feat` … `:parse-socket-feat`). All nine builtins
(`on`, `init`, `def`, `behavior`, `live`, `when`, `worker`, `bind`,
`socket`) are registered at file load time, so plugins added later via
`hs-register-feature!` persist across `hs-parse` calls.

Worker stub still raises identically. Mirror `shared/static/wasm/sx/hs-parser.sx`
copied byte-identical. OCaml: 4545/1339, JS: 2591/2465 — both match
baseline, zero regressions.

First piece of plans/designs/hs-plugin-system.md (Steps 10/11 follow).
2026-05-07 00:39:25 +00:00
3628a504db plans: tick Phase 4 40+ tests (operators.sx has 117)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 57s
2026-05-07 00:27:55 +00:00
4c71c5a75e apl: at @ replace+apply (+10 tests, 211/211)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Has been cancelled
2026-05-07 00:27:40 +00:00
621e99e456 plan: record step 8 commit hash
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 00:13:53 +00:00
6d39111992 sx: step 8 — non-exhaustive match warnings
Emit a warning when a `match` expression on an ADT value misses one
or more constructors and lacks an `else`/`_` clause. Behaviour is
non-fatal — the match still runs, the warning goes to stderr.

- spec/evaluator.sx: helpers `match-clause-is-else?`, `match-clause-ctor-name`,
  `match-warn-non-exhaustive`, `match-check-exhaustiveness`. The latter
  reads the `*adt-registry*` (already populated by `define-type`),
  collects constructor patterns from clauses, and dedupes via an
  `*adt-warned*` env-bound dict so each (type, missing-set) warns once.
  Wired into `step-sf-match` via a `do` block before clause dispatch.

- hosts/javascript/platform.py: `host-warn` primitive (`console.warn`)
  + matching `hostWarn` js-id helper so the JS-transpiled spec code
  can call it directly. Spec code reaches JS via `sx_build target=js`.

- hosts/ocaml/lib/sx_runtime.ml + sx_primitives.ml: `host-warn` runtime
  helper (`prerr_endline`) and registered primitive.

- hosts/ocaml/lib/sx_ref.ml: HAND-PATCHED. `step_sf_match` now calls
  a hand-written `match_check_exhaustiveness` that handles both
  `AdtValue` and back-compat dict-shape ADT values. The OCaml side
  is *not* retranspiled because regenerating sx_ref.ml drops
  several preamble fixes (seq_to_list, string->symbol mangling,
  empty-dict literal bug). Future retranspile must reapply this patch.

- spec/tests/test-adt.sx: 5 new tests covering exhaustive,
  non-exhaustive (warning is non-fatal), `else` suppression,
  partial coverage with one missing constructor, and `_` wildcard
  suppression. Tests assert return values only — warnings go to
  stderr and are not captured.

Warning format: `[sx] match: non-exhaustive — TypeName: missing Ctor1, Ctor2`
Both hosts emit identical messages.

Tests: OCaml 4540 → 4545 (+5), JS 2586 → 2591 (+5). Zero regressions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 00:13:41 +00:00
9eecbde61e apl: rank f⍤k cell decomposition (+10 tests, 201/201)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m0s
2026-05-07 00:00:14 +00:00
4dbd3a0b34 apl: power f⍣n + fixed-point f⍣≡ (+9 tests, 191/191)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 56s
2026-05-06 23:32:26 +00:00
7b050fb217 plan: record step 7 commit hash
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:20:08 +00:00
0679edf568 sx: step 7 — nested constructor patterns in match
Extend the ADT test suite with nested-pattern coverage. The spec-level
match-pattern function in spec/evaluator.sx already recurses through
constructor sub-patterns via the dict-shape shim ((get value :_adt|
:_ctor|:_fields)), and already handles _ wildcards, quoted literals,
and bare-symbol variable bindings. Step 5+6 added the AdtValue native
type with the same dict-key access surface, so no host changes are
needed for nesting.

Added 8 new deftests covering:
- nested constructor sanity (Just x / Nothing)
- nested constructor binds inner fields ((Just (Pair a b)) -> a+b)
- nested wildcard ((Just _) -> "yes")
- nested literal equality ((Just 42) literal vs (else) var)
- nested literal-vs-var fall-through (literal fails, var binds)
- deeply nested constructors (W1(W2(L3 n)) -> n)
- mixed bind+wildcard ((BoxM (PairM x _)) -> x)
- nested ctor fail-through (WX (LeftX) vs WX (RightX))

Tests: OCaml 4532 -> 4540 (+8), JS 2578 -> 2586 (+8). Zero regressions
on either host (failures unchanged at 1339 / 2465 baselines).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:20:01 +00:00
fa2cdee164 GUEST-plan: claim step 4 — pratt.sx
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:06:44 +00:00
5dd85b86ef GUEST-plan: log step 3 done
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:06:23 +00:00
559b0df900 GUEST: step 3 — lib/guest/lex.sx character-class + token primitives
Extracted shared tokeniser primitives:
- Char-class predicates: lex-digit?, lex-hex-digit?, lex-alpha?
  (alias lex-letter?), lex-alnum?, lex-ident-start?, lex-ident-char?,
  lex-space? (no newline), lex-whitespace? (incl newline). All nil-safe.
- Token record: lex-make-token, lex-make-token-spanning, accessors.

Ported lib/lua/tokenizer.sx and lib/tcl/tokenizer.sx — 7 lua and 5 tcl
predicate definitions collapsed into prefix-rename calls that alias
lua-/tcl- names to lex- primitives. Test scripts (lua/test.sh,
tcl/test.sh, tcl/conformance.sh) load lib/guest/lex.sx and prefix.sx
before the per-language tokenizer.

Verification:
- lua/test.sh: 185/185 = baseline
- tcl/test.sh: 342/342 (parse 67 + eval 169 + error 39 + namespace 22
                       + coro 20 + idiom 25)
- tcl/conformance.sh: 3/4 = baseline (event-loop failure is pre-existing)

Two consumers verified — step complete.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:06:12 +00:00
ba9ab4e65a plan: record step 6 commit hash
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:05:41 +00:00
fc8a391656 sx: step 6 — JS AdtValue + define-type + match
Mirror of OCaml Step 5 to the JavaScript host. Native ADT representation
for define-type instances, with the same dict-shaped shim approach so
spec-level match-pattern code in evaluator.sx works without changes.

- platform.py typeOf: recognize ._adtv tag, return ._type (so
  (type-of (Just 42)) returns "Maybe" not "dict").
- platform.py adds makeAdtValue/isAdtValue helpers and registers
  PRIMITIVES["adt?"], "make-adt-value", "adt-value?".
- platform.py inspect: format AdtValue as "(Ctor f1 f2 ...)" and
  register as a primitive (was missing entirely on JS).
- fixups_js: hand-written define-type override that constructs
  AdtValue via makeAdtValue, with arity check, type/ctor predicates,
  and field accessors. Re-registered via registerSpecialForm so the
  CEK dispatch routes through it.
- dict? unchanged: AdtValue still passes (no _adtv exclusion) so
  the existing (and (dict? v) (get v :_adt) ...) checks in spec
  predicates keep working.

Tests: 2578 pass (was 2575), zero regressions. All 43 ADT tests
pass on the JS host (was 40, the 3 new Step 5 tests for type-of /
adt? / inspect are now green).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:05:33 +00:00
3d2bdc52b5 apl: compose f∘g (+9 tests, 182/182)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 46s
2026-05-06 23:03:14 +00:00
d441807c8e GUEST-plan: claim step 3 — lex.sx
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:00:44 +00:00
e1cf75103b GUEST-plan: log step 2 partial — pending lua consumer
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:00:21 +00:00
2ef773a3c9 GUEST: step 2 — lib/guest/prefix.sx prefix-rename macro (partial)
lib/guest/prefix.sx defines a single (defmacro prefix-rename PREFIX ENTRIES)
form that takes a prefix string and a quoted list of entries. Each entry
is either a bare symbol (same-name alias: cl-foo = foo) or a 2-element
list (alias target) for renames (cl-mod = modulo).

Ported lib/common-lisp/runtime.sx: 47 hand-written (define cl-X Y) lines
across 13 contiguous groups now collapse into prefix-rename calls. Loaded
lib/guest/prefix.sx in the conformance preamble so the macro is available
when runtime.sx is parsed.

Verification: cl scoreboard 518/518, up from a stale baseline of 309/309
— Phase 2 (evaluator, +182) and Phase 6 (stdlib, +27) had under-counted
historical results, not affected by this change. No regressions; baseline
updated to reflect true counts.

PARTIAL — pending second consumer. lua/runtime.sx (the brief's specified
second consumer) has zero pure same-name aliases — every lua- definition
wraps custom logic. Step left [partial — pending lua] until a consumer
fits, or the second-consumer choice is revisited (js/runtime.sx has 2
candidates: isFinite/isNaN).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 23:00:12 +00:00
30722dfe1c plan: record step 5 commit hash
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:54:41 +00:00
1f49242ae3 sx: step 5 — OCaml AdtValue + define-type + match
Native algebraic data type representation in the OCaml SX evaluator.
Replaces the dict-based shim that simulated ADT values via tagged dicts.

- sx_types.ml: add AdtValue variant + adt_value record (av_type, av_ctor,
  av_fields). type_of returns the type name (e.g. "Maybe"); inspect renders
  as a constructor call (e.g. "(Just 42)" or "(Nothing)").
- sx_runtime.ml: get_val handles AdtValue with :_adt/:_type/:_ctor/:_fields
  keys for back-compat with spec-level match-pattern code.
- sx_primitives.ml: dict? returns true for AdtValue (so existing match
  dispatch keeps working); new adt? predicate distinguishes ADT values.
- sx_ref.ml: sf_define_type now constructs AdtValue instead of Dict.
  Predicates (Name?, Ctor?) and accessors (Ctor-field) match on AdtValue
  with proper type/ctor name and field index checks.
- spec/tests/test-adt.sx: 3 new tests covering type-of, adt?, and inspect.

Tests: 4532 passed (was 4529 + 3 new), 1339 failed (unchanged baseline).
All 43 ADT tests pass on the native representation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 22:54:33 +00:00