Commit Graph

3223 Commits

Author SHA1 Message Date
885943c5ae Merge lib/smalltalk/refl-env into architecture: Smalltalk frame as third consumer for lib/guest/reflective/env.sx 2026-05-14 15:32:16 +00:00
87f503f54b Merge loops/smalltalk into architecture: briefing tweak 2026-05-14 15:32:09 +00:00
22411f7f80 hs: port loops/hs RPC test infrastructure to architecture's test runner
Two additions from loops/hs needed for the new WebSocket socket tests:
- unhandledRejection suppressor — synchronous test harness doesn't await RPC promises
- Fake setTimeout/clearTimeout + __hsFlushTimers — drain RPC timeout tests synchronously

Plan update: mark E36 WebSocket as DONE (previously "design-done, pending review").

Skipped: loops/hs's tests/playwright/generate-sx-tests.py — architecture's version
is 1468 lines vs loops/hs's 290; arch's is the further-evolved version.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 07:26:43 +00:00
c560f3d70d hs: port loops/hs WebSocket runtime + test suite (replaces arch's underscore-prefixed API)
Adopts loops/hs's cleaner WebSocket API on top of arch's hyperscript:
- Runtime: replace 5 arch socket functions (hs-try-json-parse, hs-socket-normalise-url,
  hs-socket-bind-name!, hs-socket-resolve-rpc!, hs-socket-register!) with loops/hs's
  versions. Wrapper fields now use external-style names (url, timeout, pending, handler,
  json?, closedFlag, dispatchEvent) instead of internal-style underscores (_url,
  _timeout, _pending, _hsSetupSocket).
- Tests: replace arch's 257-line hs-upstream-socket suite (which probed _pending,
  _hsSetupSocket etc.) with loops/hs's 162-line suite that checks the new field names.
  Both suites cover the same 16 E36 behavioral cases.

Parser/compiler unchanged: both branches emit (hs-socket-register! name-path url
timeout handler json?) so the call signature is compatible with either runtime.
Arch's parse-socket-feat / emit-socket are preserved.

Local hs test.sh: 23/25 (the 2 failures are pre-existing hide/show cmd compiler
issues, not socket-related).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 21:16:09 +00:00
5e7d431f15 Merge loops/lua into architecture: features (coroutine, math, string.format, pattern char sets, byte-level chars). GUEST-lex refactor + delay/force to be re-applied in follow-up.
# Conflicts:
#	lib/lua/runtime.sx
#	lib/lua/test.sh
#	lib/lua/tokenizer.sx
2026-05-13 20:59:49 +00:00
88c7ce4068 Merge loops/apl into architecture: Phase 10 runtime gaps (⍸ ∪ ∩ ⊥ ⊤ ⊆ ⍎), life.apl + quicksort.apl run as-written 2026-05-13 20:38:17 +00:00
c19bcc51cb Merge loops/forth into architecture: Hayes conformance 99% (632/638), JIT cooperation, full Forth-2012 core 2026-05-13 20:37:26 +00:00
129f11fdbc Merge lib/tcl/uplevel into architecture: kernel + reflective env extraction (Phase 1-7 kernel, 322+427 tests) 2026-05-13 20:34:07 +00:00
4fd376a348 Merge loops/datalog into architecture: tokenizer/parser, magic sets, negation, semi-naive (259/259 tests) 2026-05-13 19:52:11 +00:00
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
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
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
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
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
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
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
ba60db2eef datalog: reject malformed dict body literals
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 1m26s
A dict in a rule body that isn't `{:neg <positive-lit>}` (the only
recognised dict shape) used to silently fall through every dispatch
clause in dl-rule-check-safety, contributing zero bound variables.
The user would then see a confusing "head variable(s) X do not
appear in any positive body literal" pointing at the head — not at
the actual bug in the body. Typos like `{:negs ...}` are the typical
trigger.

dl-process-lit! now flags both:

  - a dict that lacks :neg
  - a bare number / string / symbol used as a body lit

with a clear error naming the offending literal.

1 new regression test; conformance 265/265.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 08:04:03 +00:00
00881f84eb datalog: arith / by zero raises instead of returning inf
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 47s
`is(R, /(X, 0))` was silently producing IEEE infinity:

  (dl-eval "p(10). q(R) :- p(X), is(R, /(X, 0))." "?- q(R).")
  => ({:R inf})

That value then flowed through comparisons (anything < inf, anything
> inf) and aggregations (sum of inf, max of inf) producing nonsense
results downstream. `dl-eval-arith` now checks the divisor before
the host `/` and raises "division by zero in <expr>" — surfacing
the bug at its source rather than letting infinity propagate.

1 new test; conformance 264/264.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 07:59:25 +00:00
9e380fd96e datalog: aggregate validates that agg-var appears in goal
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 37s
`count(N, Y, p(X))` silently returned `N = 1` because `Y` was never
bound by the goal — every match contributed the same unbound symbol
which dl-val-member? deduped to a single entry. Similarly:

  sum(S, Y, p(X))    => raises "expected number, got symbol"
  findall(L, Y, p(X)) => L = (Y)  (a list containing the unbound symbol)
  count(N, Y, p(X))   => N = 1    (silent garbage)

Added a third validator in dl-eval-aggregate: the agg-var must
syntactically appear among the goal's variables. Error names the
variable and the goal and explains why the result would be
meaningless.

1 new test; conformance 263/263.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 07:57:01 +00:00
c6f646607e datalog: dl-retract! preserves EDB in mixed relations
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
A "mixed" relation has both user-asserted facts AND rules with the
same head. Previously dl-retract! wiped every rule-head relation
wholesale before re-saturating — the saturator only re-derives the
IDB portion, so explicit EDB facts vanished even for a no-op retract
of a non-existent tuple. Repro:

  (let ((db (dl-program "p(a). p(b). p(X) :- q(X). q(c).")))
    (dl-retract! db (quote (p z)))
    (dl-query db (quote (p X))))

went from {a, b, c} to just {c}.

Fix: track :edb-keys provenance in the db.

  - dl-make-db now allocates an :edb-keys dict.
  - dl-add-fact! (public) marks (rel-key, tuple-key) in :edb-keys.
  - New internal dl-add-derived! does the append without marking.
  - Saturator (semi-naive + naive driver) now calls dl-add-derived!.
  - dl-retract! strips only the IDB-derived portion of rule-head
    relations (anything not in :edb-keys) and preserves the EDB
    portion through the re-saturate pass.

2 new regression tests; conformance 262/262.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 07:51:08 +00:00
0da39de68a kernel: Phase 3 $vau/$lambda/wrap/unwrap + 34 tests [shapes-reflective]
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 27s
User-defined operatives via $vau; applicatives via $lambda (wrap ∘ $vau).
wrap/unwrap as Kernel-level applicatives. kernel-call-operative forks
on :impl (primitive) vs :body (user) tag. kernel-base-env wires the
four combiners + operative?/applicative? predicates. Env-param sentinel
`_` / `#ignore` → :knl-ignore (skip dyn-env bind). Flat parameter list
only; destructuring later. Headline test: custom applicative + custom
operative composed from user code.
2026-05-11 07:43:45 +00:00