Commit Graph

3362 Commits

Author SHA1 Message Date
a7665a7b25 js-on-sx: js-display filter to format JS values without leaking internals
The OCaml epoch-protocol printer serializes raw SX dicts. JS object literals
now carry __proto__ / __js_order__ bookkeeping that points into Object.prototype,
a complex dict containing lambdas that close over Object — the printer
recurses indefinitely and hangs.

js-display walks the value once, dropping any dict key that matches the
__name__ dunder convention. js-eval calls it on its return value so the
output is the user-facing shape only. Restores 587/593 passing (up from
191/593 post-merge and 492/585 pre-merge) — the surviving 6 failures are
legitimate pre-existing test mismatches (illegal return/break/continue,
parseFloat float vs rational, escaped backtick).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 08:38:47 +00:00
95c2d0b64a HS scoreboard: io-wait-event fix landed — both wait regressions cleared
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 43s
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:33:50 +00:00
cfbab3b2f9 HS test runner: unwrap value handles in io-wait-event interceptor
Some checks are pending
Test, Build, and Deploy / test-build-deploy (push) Waiting to run
The new WASM ABI wraps numbers, strings, and other atoms as opaque
value-handles ({_type, __sx_handle}) inside the perform request args.
The io-wait-event mock checks typeof against 'number' and 'string'
directly, so under the new ABI:

  - typeof timeout === 'number'  →  false  (timeout is a handle)
  - typeof items[2] === 'string' →  false  (event name is a handle)

so the "timeout wins" branch never triggered, and the test fell into
the "neither timeout nor event" else that resumed with nil but never
fired the post-wait `then add .bar` command.

Apply _unwrapHandle to the three args (target, evName, timeout) before
the type checks. This is the same pattern the rest of the host-* native
sweep already follows (commit 29ef89d4).

Effect: hs-upstream-wait goes from 5/7 → 7/7.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:33:24 +00:00
4d92eafb36 HS scoreboard: dict-eq fix entry + post-JIT-Phase-2 regression note
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 39s
Records that the 1514/1514 claim was relative to the kernel as of
92619301; the value-handle ABI + numeric tower + JIT Phase 2 commits
introduced three regressions (1 dict-eq, now fixed in 4db1f85f, and 2
event-or-timeout wait tests still pending).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:22:00 +00:00
4db1f85fe8 Fix dict equality: structural eq for plain dicts, Integer/Number in equal?
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 44s
Two related kernel bugs were causing the HS conformance test
"arrays containing objects work" to fail with the misleading message
"Expected ({:a 1} {:b 2}) but got ({:a 1} {:b 2})".

1. sx_primitives.ml safe_eq: Dict/Dict only returned true for DOM-wrapped
   dicts (those carrying __host_handle); all other dict pairs returned
   false unconditionally. Plain dict literals can never have been =
   to each other. Add the structural-equality fallback: when neither
   side has a host handle, compare lengths and walk keys.

2. sx_browser.ml deep_equal (the kernel binding for equal?): had a
   Number/Number branch but no Integer/Integer or cross-Integer/Number
   branches, so since the numeric tower change Integer 1 vs Integer 1
   was falling through to the catch-all and returning false. Mirror the
   cases from run_tests.ml deep_equal which already had them.

Verified via direct kernel probe:
  (= {:a 1} {:a 1})                        => true   (was false)
  (= {:a 1 :b 2} {:b 2 :a 1})              => true   (was false)
  (equal? 1 1)                             => true   (was false)
  (equal? {:a 1} {:a 1})                   => true   (was false)
  (equal? (list {:a 1}) (list {:a 1}))     => true   (was false)

HS suite arrayLiteral: 7/8 → 8/8.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 21:20:43 +00:00
4563a7ae97 method-chain: plan — current status + future-consumer notes
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 40s
Documents the extraction (Smalltalk + CLOS migrated, kit landed,
counts unchanged), lists plausible third consumers (JS proto chain,
Ruby ancestors, Python MRO), and notes which other patterns stayed
unextracted and why (method-cache invalidation, inline cache, and
the five reflective siblings all need consumers that don't exist
yet in the codebase).

Closes the session's extraction work at five branches: env (3
consumers), class-chain (2), test-runner (POC), plus the chain
of intermediate branches. The Scheme port is the next high-leverage
move; it would unlock four more reflective kits in one stroke.
2026-05-12 21:14:28 +00:00
2981a479e8 reflective: extract class-chain.sx — Smalltalk + CLOS method dispatch share parent-walk
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 35s
lib/guest/reflective/class-chain.sx — class inheritance walker with
adapter cfg for single-parent (Smalltalk) and multi-parent (CLOS)
hierarchies. Three primitives:

- refl-class-chain-find-with CFG CN PROBE
    DFS through parents, returns first non-nil probe result.
    Smalltalk method lookup uses this.

- refl-class-chain-depth-with CFG CN ANCESTOR
    Min hop distance via any parent path, or nil if unreachable.
    CLOS method specificity uses this.

- refl-class-chain-ancestors-with CFG CN
    Flat DFS-ordered list of all reachable ancestor names.

Adapter cfg has two keys: :parents-of (CN → list of parent names,
possibly empty) and :class? (predicate; short-circuits walk on
non-existent class names mid-chain).

Migrations:
- lib/smalltalk/runtime.sx: st-method-lookup-walk now a 9-line
  thin probe through the kit (was 20 lines of inline recursion);
  st-class-cfg wraps the single-parent :superclass field into a
  1-element list for the cfg.

- lib/common-lisp/clos.sx: clos-specificity is a one-line wrapper
  around refl-class-chain-depth-with (was 28 lines); clos-class-cfg
  reads the multi-parent :parents field.

Both consumers green:
- Smalltalk: 847/847 (unchanged)
- CL: 222/240 (unchanged baseline; 18 pre-existing failures, all
  in stdlib functions like cl-set-memberp, unrelated to CLOS).

This is the second extracted reflective kit (env.sx was first).
The adapter-cfg pattern continues to bridge structurally divergent
consumers (Smalltalk single-inheritance vs CLOS multiple-inheritance
with method-precedence distance) via a uniform :parents-of callback.
2026-05-12 21:09:07 +00:00
54a890db71 HS: install Phase 2 WASM as default + fix batched total to 1514
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 50s
The shared/static/wasm/sx_browser.bc.js artifact now reflects the OCaml
kernel with JIT Phase 1 (tiered compilation), Phase 2 (LRU eviction),
and Phase 3 (manual reset) — same source as previously committed,
just the rebuilt binary so test/dev consumers pick it up without
needing a local sx_build.

tests/hs-run-batched.js: TOTAL default 1496 → 1514. The conformance
suite grew by 18 tests since the constant was last set; without this
the batched runner stops short of the final 14 tests.

Verified via batched run (75-test batches, parallelism=2):
  1436 / 1439 reported pass (3 failures, all in suites where the
  underlying parser/dict-equality gap is independent of WASM).
  Batch 150-225 didn't complete inside 15 min — 75 reactivity /
  regressions / runtime tests at 5-11s each blow past the wall; a
  per-batch deadline raise is the right knob, not a kernel change.

Per-test timing (new vs old WASM, slice 170-195) is comparable
(60s vs 78s on new/threshold=4 — Phase 1+2 is NOT a perf regression
on HS code; the slow tests are slow on both kernels because the
underlying CEK path doesn't get JIT-compiled either way — HS emits
anonymous lambdas that bypass the named-only JIT gate).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-12 20:59:46 +00:00
480462646d Merge loops/js into architecture: var hoisting + ASI + matchAll on top of regex engine / TDZ scaffolding
Combined arch's regex platform dispatch (runtime.sx) with loops/js's var-hoisting transpiler
(transpile.sx) and matchAll string method. Plan checkboxes updated to reflect both done.
Conflicts: lib/js/runtime.sx, lib/js/transpile.sx, lib/js/test.sh, plans/js-on-sx.md.
2026-05-12 20:47:05 +00:00
decaf818fa Merge loops/ocaml into architecture: OCaml-on-SX language port (Phase 5.1 + ~200 baselines)
# Conflicts:
#	plans/ocaml-on-sx.md
2026-05-12 20:39:48 +00:00
03d4e350d7 test-runner: plan — per-guest migration playbook for Phase 2
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
Documents what's already done (kit + Kernel 7 files) and what's left
across 7 guests (35 std-pattern files + variant flavours in Tcl/APL).

Each guest is its own commit due to local naming and shape variants.
Prolog is the biggest single migration (23 files). Tcl and APL need
small variant adapters because their failure-records hold strings or
use slightly different signatures.

Reference: /tmp/migrate_harness.py is the regex-driven mechanical
migration tool; works on the standard pattern, skips variants for
human review.
2026-05-12 19:41:29 +00:00
4504b8ae5e test-runner: extract harness kit + migrate Kernel (7 files, 84 LoC saved)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 33s
lib/guest/test-runner.sx — per-suite mutable state {:pass :fail :fails}
+ refl-test recorder + refl-test-report. Replaces the identical
4-define harness that appears in 142+ test files across the codebase.

Each migrated file goes from:
  (define X-test-pass 0)
  (define X-test-fail 0)
  (define X-test-fails (list))
  (define X-test (fn (name actual expected) (if (= actual expected)
    (set! X-test-pass (+ X-test-pass 1)) (begin ...))))
  ;; ... tests ...
  (define X-tests-run! (fn () {:total ... :passed ... :failed ... :fails ...}))

to:
  (define X-suite (refl-make-test-suite))
  (define X-test  (fn (n a e) (refl-test X-suite n a e)))
  ;; ... tests ...
  (define X-tests-run! (fn () (refl-test-report X-suite)))

All 322 Kernel tests pass unchanged (parse 62, eval 36, vau 38,
standard 127, encap 19, hygiene 26, metacircular 14). 84 LoC removed.

Migration is mechanical (the prefix is the only difference between
suites); /tmp/migrate_harness.py drives the regex. Other guests
(Tcl, Smalltalk, APL, CL, Erlang, Haskell, etc.) migrated in
subsequent commits.
2026-05-12 19:39:45 +00:00
9efbf4ad38 reflective: third consumer — Smalltalk frame adopts env.sx — 847+322+427 tests green
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 43s
lib/guest/reflective/env.sx — added refl-env-find-frame-with (returns
the scope where NAME is bound, or nil). Needed by consumers like
Smalltalk that mutate variables at the source frame rather than
shadowing at the current one. Also added refl-env-find-frame for
the canonical shape.

lib/smalltalk/eval.sx — new st-frame-cfg adapter for the kit.
st-lookup-local now delegates parent-walk to refl-env-find-frame-with
while preserving its Smalltalk-flavoured {:found :value :frame}
return shape (which is used to mutate at the binding's source
frame, not the current one).

lib/smalltalk/test.sh + compare.sh — load lib/guest/reflective/env.sx
before lib/smalltalk/eval.sx.

Three genuinely different wire shapes now share the parent-walk:
- Kernel: {:refl-tag :env :bindings :parent}      mutable bindings
- Tcl:    {:level :locals :parent}                 functional update
- Smalltalk: {:self :method-class :locals :parent  mutable bindings,
              :return-k :active-cell}              rich metadata

All three consumers' full test suites unchanged: Smalltalk 847/847,
Kernel 322/322, Tcl 427/427. The cfg adapter pattern (modelled after
lib/guest/match.sx) cleanly handles all three.
2026-05-12 15:19:19 +00:00
4e904a2782 merge: loops/smalltalk into lib/smalltalk/refl-env — bring in third consumer 2026-05-12 14:50:05 +00:00
dea2a6e390 Merge loops/haskell into architecture: Phase 17 — import decls, type annotations, typecheck 15/15 2026-05-12 14:45:44 +00:00
c27db9b78f reflective: Phase 3 docs — mark env.sx extraction DONE, others still blocked
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 19s
plans/kernel-on-sx.md — Phase 7 header updated from "partial" to
"env.sx EXTRACTED 2026-05-12"; second-consumer-found checkbox ticked
for env.sx specifically. Other five files (combiner, evaluator,
hygiene, quoting, short-circuit) stay blocked pending their own
second consumers.

plans/lib-guest-reflective.md — Phases 1-3 ticked off with date
stamps; Outcome section added summarising the three commits, file
stats (124 LoC, within 80-200 bound), and the third-consumer
adoption protocol (cfg with five keys, no changes to env.sx).
2026-05-12 07:04:17 +00:00
39381fda92 reflective: Tcl adapter cfg — second consumer wired, 427+322 tests green
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
Phase 2 of the lib-guest-reflective extraction.

lib/tcl/runtime.sx — frame-lookup and frame-set-top now delegate to
refl-env-lookup-or-nil-with and refl-env-bind!-with via a new
tcl-frame-cfg adapter. Tcl keeps its existing {:level :locals :parent}
frame shape unchanged; the cfg bridges it to the kit's generic
algorithms. Functional update semantics preserved (cfg's :bind!
returns the new frame via assoc).

lib/tcl/test.sh + conformance.sh — load lib/guest/reflective/env.sx
before lib/tcl/runtime.sx.

Both consumers' full test suites unchanged:
- Tcl: 427/427 (parse 67, eval 169, error 39, namespace 22, coro 20,
       idiom 110)
- Kernel: 322/322 across 7 suites

The extraction is now real: two consumers, two genuinely different
wire shapes (mutable canonical vs functional frame), sharing the
parent-walk algorithm via cfg adapter — same pattern as
lib/guest/match.sx.
2026-05-12 07:02:56 +00:00
2e7e3141d4 reflective: extract env.sx + migrate Kernel — 322 tests green
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 56s
Phase 1 of the lib-guest-reflective extraction plan.

lib/guest/reflective/env.sx — canonical wire shape
{:refl-tag :env :bindings DICT :parent ENV-OR-NIL} with mutable
defaults (dict-set!), plus *-with adapter-cfg variants for consumers
with their own shape (modelled after lib/guest/match.sx). 13 forms,
~5 KB.

lib/kernel/eval.sx — env block collapses from ~30 lines to 6 thin
wrappers (kernel-env? = refl-env?, etc.). No semantic change; envs
now carry :refl-tag :env instead of :knl-tag :env. All 322 Kernel
tests pass unchanged across 7 suites (parse 62, eval 36, vau 38,
standard 127, encap 19, hygiene 26, metacircular 14).

Next: Phase 2 — Tcl adapter cfg in lib/tcl/runtime.sx using
refl-env-lookup-with against the existing :level/:locals/:parent
frame shape.
2026-05-12 06:59:07 +00:00
edfc37636f merge: loops/kernel into lib/tcl/uplevel — bring in first consumer for extraction 2026-05-12 06:55:00 +00:00
58f019bc14 JIT: lib/jit.sx — SX-level convenience layer
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 41s
Three primitives + a wrapper, all portable across hosts:

  with-jit-threshold N body...  — temporarily set threshold, restore on exit
  with-jit-budget    N body...  — temporarily set LRU budget
  with-fresh-jit       body...  — clear cache before & after body

  jit-report                    — human-readable stats string for logging
  jit-disable!  / jit-enable!   — convenience around set-budget! 0

The host (OCaml here, will be JS/Python eventually) only needs to provide
the underlying primitives (jit-stats, jit-set-threshold!, jit-set-budget!,
jit-reset-cache!, jit-reset-counters!). The ergonomics live in shared SX.

Used together with Phase 1 (tiered compilation) and Phase 2 (LRU eviction)
to give application developers fine-grained control over the JIT cache:
isolated test runs use with-fresh-jit, hot benchmark sections use
with-jit-threshold 1, memory-constrained pages use jit-set-budget! to
cap the cache.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 22:26:45 +00:00
1f466186f9 JIT: Phase 2 (LRU eviction) + Phase 3 (manual reset)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 46s
sx_types.ml:
  - Add l_uid field on lambda (unique identity for cache tracking)
  - Add lambda_uid_counter + next_lambda_uid () minted on construction
  - Add jit_budget (default 5000) and jit_evicted_count counter
  - Add jit_cache_queue : (int * value) Queue.t — FIFO of compiled lambdas
  - jit_cache_size () helper for stats

sx_vm.ml:
  - On successful JIT compile, push (uid, Lambda l) onto jit_cache_queue
  - While queue length exceeds jit_budget, pop head (oldest entry) and
    clear that lambda's l_compiled slot — evicted entries fall through
    to cek_call_or_suspend on next call (correct, just slower)
  - Guard JIT trigger by !jit_budget > 0 (budget=0 disables JIT entirely)

sx_primitives.ml:
  Phase 2:
    - jit-set-budget! N — change cache budget at runtime
    - jit-stats includes budget, cache-size, evicted
  Phase 3:
    - jit-reset-cache! — clear all compiled VmClosures (hot paths re-JIT
      on next threshold crossing)
    - jit-reset-counters! also resets evicted counter

run_tests.ml:
  - Update test-fixture lambda construction to include l_uid

Effect: cache size bounded regardless of input pattern. The HS test harness
compiles ~3000 distinct one-shot lambdas, but tiered compilation (Phase 1)
keeps most below threshold so they never enter the cache. Steady-state count
stays in single digits for typical workloads. When a misbehaving caller
saturates the cache (eval-hs in a tight loop, REPL-style host), LRU
eviction caps memory at jit_budget compiled closures × ~1KB each.

Verification: 4771 passed, 1111 failed in run_tests — identical to
pre-Phase-2 baseline. No regressions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 22:22:37 +00:00
24d8e362d5 plans: lib-guest-reflective extraction kicked off — Tcl uplevel as second consumer
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 51s
The kernel-on-sx loop documented six candidate reflective API files
gated on the two-consumer rule. This plan opens that block by
selecting Tcl's existing uplevel/upvar machinery as the second
consumer for env.sx specifically (the highest-fit candidate).

Discovery: Kernel and Tcl have identical scope-chain semantics but
diverge on mutable-vs-functional update. Solution: adapter-cfg
pattern, same as lib/guest/match.sx. Canonical wire shape with
mutable defaults for Kernel; Tcl provides its own cfg keeping
the functional model.

Roadmap: env.sx extracted, both consumers migrated, all tests green.
The other five candidate files (combiner, evaluator, hygiene,
quoting, short-circuit) stay deferred — Tcl has no operatives.
2026-05-11 22:12:26 +00:00
29ef89d473 HS: native unwrap sweep — make all 21 host-* natives ABI-compatible
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 19s
Following the host-call/host-new precedent, audit the remaining natives
that pass user-supplied values into native JS, and unwrap value handles
({_type, __sx_handle}) at the boundary. Patterns:

  host-global         arg[0]  → string name for globalThis lookup
  host-get            arg[1]  → property key
  host-set!           arg[1]  → property key
                      arg[2]  → value being stored
  host-call           arg[1]  → method name (was missing in initial fix)
                      args... → method arguments
  host-call-fn        argList items → function call arguments
                                      (was sxToJs; now also unwraps atoms)
  host-new            arg[0]  → constructor name
                      args... → constructor arguments
  host-make-js-thrower arg[0] → value to throw (must be primitive in JS)
  host-typeof         arg[0]  → recognize wrapped handles and report their
                                underlying type instead of "object"
  host-iter?          arg[0]  → object to test for [Symbol.iterator]
  host-to-list        arg[0]  → object to spread
  host-new-function   args    → param-name strings and body string

All wraps are forward-compatible: _unwrapHandle is a no-op on plain values
returned by the legacy kernel. The shim activates only when the runtime
encounters real wrapped handles from the new kernel.

Verification — 100 tests pass on the new WASM after sweep (test 27
'can append a value to a set' previously broken by Set value-handle
aliasing now resolves correctly).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:46:14 +00:00
f7bd3a6bf1 kernel: loop summary — 18 commits, 322 tests, 6 reflective API candidates [proposes-reflective-extraction]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 42s
Loop closer documenting what 18 feature commits produced. Kernel-on-SX
is 1,398 LoC substrate + 1,747 LoC tests = 3,145 LoC total. Zero
substrate fixes required across the loop. R-1RK core + extras
implemented. Six proposed lib/guest/reflective/ files awaiting second
consumer. Substrate verdict: env-as-value generalises to
evaluator-as-value; the m-eval demo proves it.
2026-05-11 21:28:10 +00:00
d5d77a3611 kernel: type predicates + metacircular demo + map/filter/reduce fix [shapes-reflective]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 44s
Five type predicates (number?, string?, list?, boolean?, symbol?).
New tests/metacircular.sx: m-eval defined in Kernel walks expressions
itself, recursing on applicative-call args and delegating to host
eval only for operatives and symbol lookup. 14 demo tests.

The demo surfaced a real bug: map/filter/reduce called kernel-combine
on applicative head-vals directly, which re-evaluates already-
evaluated element values; nested-list elements crashed. Fix: extracted
knl-apply-op (unwrap-applicative-or-pass-through) and use it in all
three combinators before kernel-combine. Mirrors apply's approach.

Added knl-apply-op as a proposed entry in the reflective combiner.sx
API. 322 tests total.
2026-05-11 21:27:23 +00:00
40dff449ef apl: het-inner-product encloses (+4); life.apl restored to as-written
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
apl-inner now wraps its result in (enclose result) when A's ravel
contains any dict element (a boxed array). This matches Hui's
semantics where `1 ⍵ ∨.∧ X` produces a rank-0 wrapping the
(5 5) board, then ⊃ unwraps to bare matrix.

Homogeneous inner product unaffected (+.× over numbers and
matrices still produces bare arrays — none of those ravels
contain dicts).

life.apl restored to true as-written form:
  life ← {⊃1 ⍵ ∨.∧ 3 4 = +/ +/ ¯1 0 1 ∘.⊖ ¯1 0 1 ⌽¨ ⊂⍵}

4 pipeline tests + 5 e2e tests verify heterogeneous case and
that ⊃ unwraps to the underlying (5 5) board.

Full suite 589/589. Phase 11 complete.
2026-05-11 21:19:06 +00:00
67449f5b0c kernel: append + reverse + 11 tests [nothing]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 33s
Variadic append concatenates lists; reverse is unary. 307 tests total.
2026-05-11 21:19:01 +00:00
6d8f11e093 kernel: apply combinator + 7 tests [shapes-reflective]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 32s
(apply F (list V1 V2 V3)) ≡ (F V1 V2 V3). Unwrap applicative first to
skip auto-eval (args are values), then kernel-combine with the
underlying operative. Universal pattern in reflective Lisps —
sketched into the combiner.sx API. 296 tests total.
2026-05-11 21:17:24 +00:00
78dab5b28c kernel: map/filter/reduce + with-env applicative constructor + 10 tests [shapes-reflective]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
Added kernel-make-primitive-applicative-with-env in eval.sx — IMPL
receives (args dyn-env), needed by combinators that re-enter the
evaluator. map/filter/reduce in runtime.sx use it to call user-supplied
combiners on each element with the caller's dynamic env preserved.
Sketched the env-blind vs env-aware applicative split as a new entry
in the proposed combiner.sx reflective API. 289 tests total.
2026-05-11 21:15:54 +00:00
1fb852ef64 kernel: variadic +-*/, chained <>=? + 19 tests [nothing]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
knl-fold-app for n-ary fold with zero-arity identity and one-arity
special-case (- negates, / inverts). knl-chain-cmp for chained
boolean comparison. 279 tests total.
2026-05-11 21:13:13 +00:00
b80871ac4f kernel: $let* sequential let + multi-body $let + 8 tests [nothing]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
$let* nests env-extensions one per binding — each binding sees earlier
ones. $let now also accepts multi-expression bodies. 260 tests total.
2026-05-11 21:11:01 +00:00
9ff5d1b464 kernel: $and? / $or? short-circuit + 10 tests [shapes-reflective]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
Operatives (not applicatives) so untaken args are not evaluated. Empty
$and? = true, empty $or? = false (Kernel identity convention). Returns
last evaluated value, not bool-coerced. Sketched reflective short-
circuit API: identical protocol across reflective Lisps because
operative semantics are forced — an applicative variant defeats the
purpose. 252 tests total.
2026-05-11 21:09:20 +00:00
5fa6c6ecc1 kernel: $cond/$when/$unless + 12 tests [nothing]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s
Standard Kernel control flow. $cond walks clauses in order with `else`
catch-all; clauses past the first match are NOT evaluated. $when/$unless
are simple guards. 12 tests, 242 total.
2026-05-11 21:08:08 +00:00
a4a7753314 kernel: $quasiquote runtime + reflective/quoting.sx sketch [shapes-reflective]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
kernel-quasiquote-operative walks the template via mutually-recursive
knl-quasi-walk ↔ knl-quasi-walk-list. $unquote forms eval in dyn-env;
$unquote-splicing splices list-valued results. No depth tracking
(nested quasiquotes flatten). 8 new tests, 230 total. Sketched the
universal reflective quoting kit API for the eventual Phase 7 extraction.
2026-05-11 21:06:35 +00:00
f12c19eaa3 HS: test runner — unwrap value handles before native interop
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 22s
The new kernel ABI wraps atoms (number, string, boolean, nil) in opaque
handles {_type, __sx_handle}. When such handles flow through host-call
into native JS functions, value equality breaks: each integer literal
becomes a unique handle object, so JS Set.add(handle_for_1) does NOT
dedup against a prior set.add(handle_for_1). Same problem for any JS
API that uses identity or value equality on incoming arguments.

Fix: add _unwrapHandle that converts handles back to JS primitives via
K.stringify, and apply it to argument lists in host-call and host-new
(the two natives that pass user values into native JS constructors /
methods). Forward-compatible: no-op when called with already-unwrapped
plain values from the legacy kernel.

Root-cause analysis traced through:
  1. Test 27 'can append a value to a set' failed (Expected 3, got 4)
     on the new WASM only. Set was admitting duplicates.
  2. dbg-set.js minimal repro confirmed each `1` literal arriving at
     set.add as a different {_type, __sx_handle} object.
  3. JS Set.add uses SameValueZero — handle objects with the same
     underlying value are still distinct identity.
  4. Unwrapping in host-call/host-new resolves the equality issue.

This is preparation for the JIT Phase 1 WASM rollout (which still
needs more native-interop unwrap audits before it can replace the
pre-merge WASM that the test tree currently pins).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 21:04:30 +00:00
af8d10a717 kernel: multi-expression body for $vau/$lambda + 5 tests [nothing]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 31s
:body slot holds a LIST of forms now (was single expression). New
knl-eval-body in eval.sx evaluates each form in sequence, returning
the last. $vau and $lambda accept (formals env-param body...) /
(formals body...). No $sequence dependency. 223 tests total.
2026-05-11 21:04:19 +00:00
c21eb9d5ad kernel: reader macros + 8 tests (Phase 1 closure) [consumes-lex]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 44s
Parser now reads 'expr, \`expr, ,expr, ,@expr as the four standard
shorthands. Quote uses existing $quote operative; quasiquote /
unquote / unquote-splicing recognised but not yet expanded at runtime
(left for first consumer to drive). 218 tests total across six suites.
2026-05-11 21:01:01 +00:00
d896685555 kernel: Phase 7 reflective API proposal — partial [proposes-reflective-extraction]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 32s
Two-consumer rule blocks actual extraction. API surface fully
documented across four candidate files: env.sx (Phase 2), combiner.sx
(Phase 3), evaluator.sx (Phase 4), hygiene.sx (Phase 6). ~25 functions,
~500 LoC estimate when second consumer materialises. Candidates listed
in priority order: metacircular Scheme, CL macro evaluator, Maru.
Loop complete: 210 tests, 7 commits, one feature per commit.
2026-05-11 20:58:41 +00:00
bf7ec55e92 kernel: Phase 6 hygiene — $let + $define-in! + 18 tests [shapes-reflective]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 29s
Hygiene-by-default was already present: user operatives close over
static-env and bind formals + body $define!s in (extend STATIC-ENV),
caller's env untouched. $let evaluates values in caller env, binds
in fresh child env, runs body there. $define-in! explicitly targets
an env. Full scope-set / frame-stamp hygiene is research-grade
and documented as deferred future work in the reflective API notes.
2026-05-11 20:57:47 +00:00
45789520ce kernel: Phase 5 encapsulations + promise demo + 19 tests [nothing]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 30s
make-encapsulation-type returns (encapsulator predicate decapsulator).
Fresh empty dict per call as family identity — SX dict reference
equality gives unique per-family opacity. Encap/decap/pred close over
the family marker; foreign values fail both predicate and decap.
Classic promise demo: (force (delay (lambda () (+ 19 23)))) → 42.
2026-05-11 20:54:31 +00:00
b91d8cf72e kernel: Phase 4 standard env + factorial + 49 tests [shapes-reflective]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 30s
kernel-standard-env extends kernel-base-env with $if/$define!/$sequence/
$quote, reflection (eval/make-environment/get-current-environment),
binary arithmetic, comparison, list/pair, boolean primitives. Headline
test is recursive factorial (5! = 120, 10! = 3628800). Recursive sum,
length, map-add1, closures, curried arithmetic, and a $vau-using-$define!
demo also covered.
2026-05-11 20:50:34 +00:00
6e997e9382 HS: test runner — auto-unwrap shim for new WASM kernel ABI
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 26s
Post-JIT-Phase-1 OCaml kernels return atomic values (number, string,
boolean, nil) as opaque handles {_type, __sx_handle} instead of plain
JS values. The 23 K.eval call sites in hs-run-filtered.js were written
against the pre-rewrite ABI and expect plain values.

Add a wrapper at boot that auto-unwraps via K.stringify when the result
is a handle. No-op on the legacy kernel (handles don't appear, so the
check falls through). Forward-compatible: when the new WASM is the
default, the shim transparently restores test compatibility.

Note: This unblocks future browser-WASM rollout of JIT Phase 1. A
separate issue (Set-append size regression — Expected 3, got 4 on
test 27) in newer architecture-branch kernel changes still blocks the
WASM rollout; the test tree continues to pin the pre-merge WASM until
that regression is identified and fixed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 20:30:32 +00:00
0df5e92c46 datalog: magic pre-saturation is conditional, not unconditional
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
Previously dl-magic-query always pre-saturated the source db so it
gave correct results for stratified programs (where the rewriter
doesn't propagate magic to aggregate inner-goals or negated rels).
Pure positive programs paid the full bottom-up cost twice.

Add dl-rules-need-presaturation? — checks whether any rule body
contains an aggregate or negation. Only pre-saturate in that case.
Pure positive programs (the common case for magic-sets) keep their
full goal-directed efficiency.

276/276; identical answers on the existing aggregate-of-IDB test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 10:15:05 +00:00
fadcdbd6a9 datalog: dl-set-strategy! validates known strategy values
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s
`dl-set-strategy!` accepted any keyword silently — typos like
`:semi_naive` or `:semiNaive` were stored uninspected and the
saturator then used the default. The user never learned their
setting was wrong.

Validator added: strategy must be one of `:semi-naive`, `:naive`,
`:magic` (the values currently recognised by the saturator and
magic-sets driver). Unknown values raise with a clear message that
lists the accepted set.

1 regression test; conformance 276/276.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 09:40:29 +00:00
ce98d97728 datalog: anonymous-renamer avoids user _anon<N> collision
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
The renamer for anonymous `_` variables started at counter 0 and
produced `_anon1, _anon2, ...` unconditionally. A user writing the
same naming convention would see their variables shadowed:

  (dl-eval "p(a, b). p(c, d). q(_anon1) :- p(_anon1, _)."
           "?- q(X).")
  => ()    ; should be ({:X a} {:X c})

The `_` got renamed to `_anon1` too, collapsing the two positions
of `p` to a single var (forcing args to be equal — which neither
tuple satisfies).

Fix: scan each rule (and query goal) for the highest `_anon<N>`
already present and start the renamer past it. New helpers
`dl-max-anon-num` / `dl-max-anon-num-list` / `dl-try-parse-int`
walk the rule tree; `dl-make-anon-renamer` now takes a `start`
argument; `dl-rename-anon-rule` and the query-time renamer in
`dl-query` both compute the start from the input.

1 regression test; conformance 275/275.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 09:34:41 +00:00
82dfa20e82 datalog: dl-magic-query pre-saturates for aggregate correctness
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 13s
dl-magic-query could silently diverge from dl-query when an
aggregate's inner-goal relation was IDB. The rewriter passes
aggregate body lits through unchanged (no magic propagation
generated for them), so the inner relation was empty in the magic
db and the aggregate returned 0. Repro:

  (dl-eval-magic
    "u(a). u(b). u(c). u(d). banned(b). banned(d).
     active(X) :- u(X), not(banned(X)).
     n(N) :- count(N, X, active(X))."
    "?- n(N).")
  => ({:N 0})   ; should be ({:N 2})

dl-magic-query now pre-saturates the source db before copying facts
into the magic db. This guarantees equivalence with dl-query for
every stratified program; the magic benefit still comes from
goal-directed re-derivation of the query relation under the seed
(which matters for large recursive joins). The existing test cases
happened to dodge this because their aggregate inner-goals were all
EDB.

1 new regression test; conformance 274/274.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 08:59:28 +00:00
66aa003461 datalog: anonymous _ in negation is existential, not unbound
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
The canonical Datalog idiom for "no X has any Y":

  orphan(X) :- person(X), not(parent(X, _)).

was rejected by the safety check with "negation refers to unbound
variable(s) (\"_anon1\")". The parser renames each anonymous `_`
to a fresh `_anon*` symbol so multiple `_` occurrences don't unify
with each other, and the negation safety walk then demanded all
free vars in the negated lit be bound by an earlier positive body
lit — including the renamed anonymous vars.

Anonymous vars in a negation are existentially quantified within
the negation, not requirements from outside. Added dl-non-anon-vars
to strip `_anon*` names from the `needed` set before the binding
check in dl-process-neg!. Real vars (like `X` in the orphan idiom)
still must be bound by an earlier positive body lit, just as before.

2 new regression tests (orphan idiom + multi-anon "solo" pattern);
conformance 273/273.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 08:49:20 +00:00
6bae94bae1 datalog: reject compound terms in fact / rule-head args
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 25s
Datalog has no function symbols in argument positions, but the
existing dl-add-fact! / dl-add-rule! validators only checked that
literals were ground (no free variables). A compound like `+(1, 2)`
contains no variables, so:

  p(+(1, 2)).
  => stored as the unreduced tuple `(p (+ 1 2))`

  double(*(X, 2)) :- n(X).  n(3).
  => saturates `double((* 3 2))` instead of `double(6)`

Added dl-simple-term? (number / string / symbol) and an
args-simple? walker, used by:

  - dl-add-fact!: all args must be simple terms
  - dl-add-rule!: rule head args must be simple terms (variables
    are symbols, so they pass)

Compounds remain legal in body literals where they encode `is` /
arithmetic / aggregate sub-goals. Error messages name the offending
literal and point the user at the body-only mechanism.

2 new regression tests; conformance 271/271.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 08:44:30 +00:00
7a94a47e26 datalog: quoted 'atoms' tokenize as strings
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 23s
Quoted atoms with uppercase- or underscore-leading names were
misclassified as variables. `p('Hello World').` flowed through the
tokenizer's "atom" branch and through the parser's string->symbol,
producing a symbol named "Hello World". dl-var? inspects the first
character — "H" is uppercase, so the fact was rejected as non-ground
("expected ground literal").

Tokenizer now emits "string" for any '...' quoted form. Quoted atoms
become opaque string constants — matching how Datalog idiomatically
treats them, and avoiding a per-symbol "quoted" marker that would
have rippled through unification and dl-var?. The trade-off is that
'a' and a are no longer the same value (string vs symbol); for
Datalog this is the safer default.

Updated the existing "quoted atom" tokenize test, added a regression
case for an uppercase-named quoted atom, and a parse-level test that
verifies the AST. Conformance 269/269.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 08:39:24 +00:00
917ffe5ccc datalog: comparison ops require same-type operands
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 28s
Type-mixed comparisons were silently inconsistent:

  <("hello", 5)  =>  no result, no error  (silent false)
  <(a, 5)        =>  raises "Expected number, got symbol"

Both should fail loudly with a comprehensible message. Added
dl-compare-typeok?: <, <=, >, >= now require both operands to share
a primitive type (both numbers or both strings) and raise a clear
"comparison <op> requires same-type operands" error otherwise.

`!=` is exempted because it's the polymorphic inequality test
built on dl-tuple-equal? — cross-type pairs are legitimately unequal
and the existing semantics for that case match user intuition.

2 new regression tests; conformance 267/267.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 08:07:40 +00:00