Compare commits

..

13 Commits

Author SHA1 Message Date
1eb9d0f8d2 merge: loops/apl — Phase 8 quick-wins, named fns, multi-axis, trains, perf
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 54s
2026-05-07 19:46:21 +00:00
f182d04e6a GUEST-plan: log step 8 partial — algebra + literal rule, assembly deferred
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 19:45:23 +00:00
ab2c40c14c GUEST: step 8 — lib/guest/hm.sx Hindley-Milner foundations
Ships the algebra for HM-style type inference, riding on
lib/guest/match.sx (terms + unify) and ast.sx (canonical AST):

  • Type constructors: hm-tv, hm-arrow, hm-con, hm-int, hm-bool, hm-string
  • Schemes: hm-scheme / hm-monotype + accessors
  • Free type-vars: hm-ftv, hm-ftv-scheme, hm-ftv-env
  • Substitution: hm-apply, hm-apply-scheme, hm-apply-env, hm-compose
  • Generalize / Instantiate (with shared fresh-tv counter)
  • hm-fresh-tv (counter is a (list N) the caller threads)
  • hm-infer-literal (the only fully-closed inference rule)

24 self-tests in lib/guest/tests/hm.sx covering every function above.

The lambda / app / let inference rules — the substitution-threading
core of Algorithm W — intentionally live in HOST CODE rather than the
kit, because each host's AST shape and substitution-threading idiom
differ subtly enough that forcing one shared assembly here proved
brittle in practice (an earlier inline-assembled hm-infer faulted with
"Not callable: nil" only when defined in the kit, despite working when
inline-eval'd or in a separate file — a load/closure interaction not
worth chasing inside this step's budget). The host gets the algebra
plus a spec; assembly stays close to the AST it reasons over.

PARTIAL — algebra + literal rule shipped; full Algorithm W deferred
to host consumers (haskell/infer.sx, lib/ocaml/types.sx when
OCaml-on-SX Phase 5 lands per the brief's sequencing note). Haskell
infer.sx untouched; haskell scoreboard still 156/156 baseline.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 19:45:10 +00:00
d3c34b46b9 GUEST-plan: claim step 8 — hm.sx
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 19:35:05 +00:00
80dac0051d apl: perf — fix quadratic append in permutations, restore queens(8)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 43s
apl-permutations was doing (append acc <new-perms>) which is
O(|acc|) and acc grows ~N! big — total cost O(N!²).

Swapped to (append <new-perms> acc) — append is O(|first|)
so cost is O((n+1)·N!_prev) per layer, total O(N!).  q(7)
went from 32s to 12s; q(8)=92 now finishes well within the
300s timeout, so the queens(8) test is restored.

497/497.  Phase 8 complete.
2026-05-07 19:33:09 +00:00
b661318a45 apl: train/fork notation (f g h) and (g h) (+6 tests, 496/496)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 57s
Parser: when a parenthesised subexpression contains only function
segments (>= 2), collect-segments-loop now emits a :train AST node
instead of treating it as a value-producing expression.

Resolver: apl-resolve-{monadic,dyadic} handle :train.
- monadic 2-train (atop):  (g h)⍵ = g (h ⍵)
- monadic 3-train (fork):  (f g h)⍵ = (f ⍵) g (h ⍵)
- dyadic 2-train:          ⍺(g h)⍵ = g (⍺ h ⍵)
- dyadic 3-train:          ⍺(f g h)⍵ = (⍺ f ⍵) g (⍺ h ⍵)

apl-run "(+/÷≢) 1 2 3 4 5"  → 3   (mean)
apl-run "(- ⌊) 5"           → -5  (atop)
apl-run "2 (+ × -) 5"       → -21 (dyadic fork)
apl-run "(⌈/-⌊/) 3 1 4 …"   → 8   (range)
2026-05-07 19:02:17 +00:00
47d9d07f2e GUEST-plan: log step 7 partial — kit + synthetic, haskell port deferred
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 18:55:48 +00:00
d75c61d408 GUEST: step 7 — lib/guest/layout.sx off-side / layout-sensitive lexer
Configurable layout pass that inserts virtual open / close / separator
tokens based on indentation. Supports both styles the brief calls out:

  • Haskell-flavour: layout opens AFTER a reserved keyword
    (let/where/do/of) and resolves to the next token's column. Module
    prelude wraps the whole input in an implicit block. Explicit `{`
    after the keyword suppresses virtual layout.

  • Python-flavour: layout opens via an :open-trailing-fn predicate
    fired AFTER the trigger token (e.g. trailing `:`) — and resolves
    to the column of the next token, which in real source is on a
    fresh line. No module prelude.

Public entry: (layout-pass cfg tokens). Token shape: dict with at
least :type :value :line :col; everything else passes through. Newline
filler tokens are NOT used — line-break detection is via :line.

lib/guest/tests/layout.sx — 6 tests covering both flavours:
  haskell-do-block / haskell-explicit-brace / haskell-do-inline /
  haskell-module-prelude / python-if-block / python-nested.

Per the brief's gotcha note ("Don't ship lib/guest/layout.sx unless
the haskell scoreboard equals baseline") — haskell/layout.sx is left
UNTOUCHED. The kit isn't yet a drop-in replacement for the full
Haskell 98 algorithm (Note 5, multi-stage pre-pass, etc.) and forcing
a port would risk the 156 currently passing programs. Haskell
scoreboard remains at 156/156 baseline because no haskell file
changed. The synthetic Python-ish fixture is the second consumer per
the brief's wording.

PARTIAL — kit + synthetic fixture shipped; haskell port deferred until
the kit grows the missing Haskell-98 wrinkles.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 18:55:38 +00:00
a677585639 apl: programs-e2e + ⌿/⍀ glyph fix (+15 tests, 490/490)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 53s
programs-e2e.sx exercises the classic-algorithm shapes from
lib/apl/tests/programs/*.apl via the full pipeline (apl-run on
embedded source strings).  Tests include factorial-via-∇,
triangular numbers, sum-of-squares, prime-mask building blocks
(divisor counts via outer mod), named-fn composition,
dyadic max-of-two, and a single Newton sqrt step.

The original one-liners (e.g. primes' inline ⍵←⍳⍵) need parser
features we haven't built (compress-as-fn, inline assign) — the
e2e tests use multi-statement equivalents.  No file-reading
primitive in OCaml SX, so source is embedded.

Side-fix: ⌿ (first-axis reduce) and ⍀ (first-axis scan) were
silently skipped by the tokenizer — added to apl-glyph-set
and apl-parse-op-glyphs.
2026-05-07 18:31:57 +00:00
c04f38a1ba apl: multi-axis bracket A[I;J] / A[I;] / A[;J] (+8 tests, 475/475)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 50s
Parser: split-bracket-content splits inner tokens on :semi at
depth 0; maybe-bracket emits (:bracket arr axis-exprs...) for
multi-axis access, with :all marker for empty axes.

Runtime: apl-bracket-multi enumerates index combinations via
apl-cartesian (helper) and produces sub-array. Scalar axes
collapse from result shape; vector / nil axes contribute their
length.

apl-run "M ← (3 3) ⍴ ⍳9 ⋄ M[2;2]"  → 5
apl-run "M ← (3 3) ⍴ ⍳9 ⋄ M[1;]"   → 1 2 3
apl-run "M ← (3 3) ⍴ ⍳9 ⋄ M[;2]"   → 2 5 8
apl-run "M ← (2 3) ⍴ ⍳6 ⋄ M[1 2;1 2]" → 2x2 sub-block
2026-05-07 17:56:24 +00:00
b13819c50c apl: named function definitions f ← {…} (+7 tests, 467/467)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 53s
Parser: apl-collect-fn-bindings pre-scans stmt-groups for
`name ← { ... }` patterns and populates apl-known-fn-names.
is-fn-tok? consults this list; collect-segments-loop emits
(:fn-name nm) for known names so they parse as functions.

Resolver: apl-resolve-{monadic,dyadic} handle :fn-name by
looking up env, asserting the binding is a dfn, returning
a closure that dispatches to apl-call-dfn{-m,}.

Recursion still works: `fact ← {0=⍵:1 ⋄ ⍵×∇⍵-1} ⋄ fact 5` → 120.
2026-05-07 17:33:41 +00:00
d9cf00f287 apl: quick-wins bundle — decimals + ⎕← + strings (+10 tests, 460/460)
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 49s
Three small unblockers in one iteration:
- tokenizer: read-digits! now consumes optional ".digits" suffix,
  so 3.7 and ¯2.5 are single number tokens.
- tokenizer: ⎕ followed by ← emits a single :name "⎕←" token
  (instead of splitting on the assign glyph).  Parser registers
  ⎕← in apl-quad-fn-names; apl-monadic-fn maps to apl-quad-print.
- eval-ast: :str AST nodes evaluate to char arrays.  Single-char
  strings become rank-0 scalars; multi-char become rank-1 vectors
  of single-char strings.
2026-05-07 17:26:37 +00:00
0c0ed0605a plans: Phase 8 — quick-wins, named fns, multi-axis brackets, .apl-as-tests, trains, perf
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 49s
2026-05-07 17:20:47 +00:00
112 changed files with 1245 additions and 8756 deletions

View File

@@ -25,8 +25,9 @@
; Glyph classification sets ; Glyph classification sets
; ============================================================ ; ============================================================
(define apl-parse-op-glyphs (define
(list "/" "\\" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@")) apl-parse-op-glyphs
(list "/" "⌿" "\\" "⍀" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@"))
(define (define
apl-parse-fn-glyphs apl-parse-fn-glyphs
@@ -82,22 +83,48 @@
"⍎" "⍎"
"⍕")) "⍕"))
(define apl-quad-fn-names (list "⎕FMT")) (define apl-quad-fn-names (list "⎕FMT" "⎕←"))
(define (define apl-known-fn-names (list))
apl-parse-op-glyph?
(fn (v) (some (fn (g) (= g v)) apl-parse-op-glyphs)))
; ============================================================ ; ============================================================
; Token accessors ; Token accessors
; ============================================================ ; ============================================================
(define
apl-collect-fn-bindings
(fn
(stmt-groups)
(set! apl-known-fn-names (list))
(for-each
(fn
(toks)
(when
(and
(>= (len toks) 3)
(= (tok-type (nth toks 0)) :name)
(= (tok-type (nth toks 1)) :assign)
(= (tok-type (nth toks 2)) :lbrace))
(set!
apl-known-fn-names
(cons (tok-val (nth toks 0)) apl-known-fn-names))))
stmt-groups)))
(define
apl-parse-op-glyph?
(fn (v) (some (fn (g) (= g v)) apl-parse-op-glyphs)))
(define (define
apl-parse-fn-glyph? apl-parse-fn-glyph?
(fn (v) (some (fn (g) (= g v)) apl-parse-fn-glyphs))) (fn (v) (some (fn (g) (= g v)) apl-parse-fn-glyphs)))
(define tok-type (fn (tok) (get tok :type))) (define tok-type (fn (tok) (get tok :type)))
; ============================================================
; Collect trailing operators starting at index i
; Returns {:ops (op ...) :end new-i}
; ============================================================
(define tok-val (fn (tok) (get tok :value))) (define tok-val (fn (tok) (get tok :value)))
(define (define
@@ -107,8 +134,8 @@
(and (= (tok-type tok) :glyph) (apl-parse-op-glyph? (tok-val tok))))) (and (= (tok-type tok) :glyph) (apl-parse-op-glyph? (tok-val tok)))))
; ============================================================ ; ============================================================
; Collect trailing operators starting at index i ; Build a derived-fn node by chaining operators left-to-right
; Returns {:ops (op ...) :end new-i} ; (+/¨ → (:derived-fn "¨" (:derived-fn "/" (:fn-glyph "+"))))
; ============================================================ ; ============================================================
(define (define
@@ -119,15 +146,17 @@
(and (= (tok-type tok) :glyph) (apl-parse-fn-glyph? (tok-val tok))) (and (= (tok-type tok) :glyph) (apl-parse-fn-glyph? (tok-val tok)))
(and (and
(= (tok-type tok) :name) (= (tok-type tok) :name)
(some (fn (q) (= q (tok-val tok))) apl-quad-fn-names))))) (or
(some (fn (q) (= q (tok-val tok))) apl-quad-fn-names)
(some (fn (q) (= q (tok-val tok))) apl-known-fn-names))))))
; ============================================================
; Find matching close bracket/paren/brace
; Returns the index of the matching close token
; ============================================================
(define collect-ops (fn (tokens i) (collect-ops-loop tokens i (list)))) (define collect-ops (fn (tokens i) (collect-ops-loop tokens i (list))))
; ============================================================
; Build a derived-fn node by chaining operators left-to-right
; (+/¨ → (:derived-fn "¨" (:derived-fn "/" (:fn-glyph "+"))))
; ============================================================
(define (define
collect-ops-loop collect-ops-loop
(fn (fn
@@ -143,8 +172,10 @@
{:end i :ops acc}))))) {:end i :ops acc})))))
; ============================================================ ; ============================================================
; Find matching close bracket/paren/brace ; Segment collection: scan tokens left-to-right, building
; Returns the index of the matching close token ; a list of {:kind "val"/"fn" :node ast} segments.
; Operators following function glyphs are merged into
; derived-fn nodes during this pass.
; ============================================================ ; ============================================================
(define (define
@@ -163,12 +194,20 @@
(find-matching-close-loop tokens start open-type close-type 1))) (find-matching-close-loop tokens start open-type close-type 1)))
; ============================================================ ; ============================================================
; Segment collection: scan tokens left-to-right, building ; Build tree from segment list
; a list of {:kind "val"/"fn" :node ast} segments. ;
; Operators following function glyphs are merged into ; The segments are in left-to-right order.
; derived-fn nodes during this pass. ; APL evaluates right-to-left, so the LEFTMOST function is
; the outermost (last-evaluated) node.
;
; Patterns:
; [val] → val node
; [fn val ...] → (:monad fn (build-tree rest))
; [val fn val ...] → (:dyad fn val (build-tree rest))
; [val val ...] → (:vec val1 val2 ...) — strand
; ============================================================ ; ============================================================
; Find the index of the first function segment (returns -1 if none)
(define (define
find-matching-close-loop find-matching-close-loop
(fn (fn
@@ -208,21 +247,9 @@
collect-segments collect-segments
(fn (tokens) (collect-segments-loop tokens 0 (list)))) (fn (tokens) (collect-segments-loop tokens 0 (list))))
; ============================================================ ; Build an array node from 0..n value segments
; Build tree from segment list ; If n=1 → return that segment's node
; ; If n>1 → return (:vec node1 node2 ...)
; The segments are in left-to-right order.
; APL evaluates right-to-left, so the LEFTMOST function is
; the outermost (last-evaluated) node.
;
; Patterns:
; [val] → val node
; [fn val ...] → (:monad fn (build-tree rest))
; [val fn val ...] → (:dyad fn val (build-tree rest))
; [val val ...] → (:vec val1 val2 ...) — strand
; ============================================================
; Find the index of the first function segment (returns -1 if none)
(define (define
collect-segments-loop collect-segments-loop
(fn (fn
@@ -242,24 +269,38 @@
((= tt :str) ((= tt :str)
(collect-segments-loop tokens (+ i 1) (append acc {:kind "val" :node (list :str tv)}))) (collect-segments-loop tokens (+ i 1) (append acc {:kind "val" :node (list :str tv)})))
((= tt :name) ((= tt :name)
(if (cond
(some (fn (q) (= q tv)) apl-quad-fn-names) ((some (fn (q) (= q tv)) apl-quad-fn-names)
(let
((op-result (collect-ops tokens (+ i 1))))
(let (let
((ops (get op-result :ops)) (ni (get op-result :end))) ((op-result (collect-ops tokens (+ i 1))))
(let (let
((fn-node (build-derived-fn (list :fn-glyph tv) ops))) ((ops (get op-result :ops))
(collect-segments-loop (ni (get op-result :end)))
tokens (let
ni ((fn-node (build-derived-fn (list :fn-glyph tv) ops)))
(append acc {:kind "fn" :node fn-node}))))) (collect-segments-loop
(let tokens
((br (maybe-bracket (list :name tv) tokens (+ i 1)))) ni
(collect-segments-loop (append acc {:kind "fn" :node fn-node}))))))
tokens ((some (fn (q) (= q tv)) apl-known-fn-names)
(nth br 1) (let
(append acc {:kind "val" :node (nth br 0)}))))) ((op-result (collect-ops tokens (+ i 1))))
(let
((ops (get op-result :ops))
(ni (get op-result :end)))
(let
((fn-node (build-derived-fn (list :fn-name tv) ops)))
(collect-segments-loop
tokens
ni
(append acc {:kind "fn" :node fn-node}))))))
(else
(let
((br (maybe-bracket (list :name tv) tokens (+ i 1))))
(collect-segments-loop
tokens
(nth br 1)
(append acc {:kind "val" :node (nth br 0)}))))))
((= tt :lparen) ((= tt :lparen)
(let (let
((end (find-matching-close tokens (+ i 1) :lparen :rparen))) ((end (find-matching-close tokens (+ i 1) :lparen :rparen)))
@@ -267,11 +308,23 @@
((inner-tokens (slice tokens (+ i 1) end)) ((inner-tokens (slice tokens (+ i 1) end))
(after (+ end 1))) (after (+ end 1)))
(let (let
((br (maybe-bracket (parse-apl-expr inner-tokens) tokens after))) ((inner-segs (collect-segments inner-tokens)))
(collect-segments-loop (if
tokens (and
(nth br 1) (>= (len inner-segs) 2)
(append acc {:kind "val" :node (nth br 0)})))))) (every? (fn (s) (= (get s :kind) "fn")) inner-segs))
(let
((train-node (cons :train (map (fn (s) (get s :node)) inner-segs))))
(collect-segments-loop
tokens
after
(append acc {:kind "fn" :node train-node})))
(let
((br (maybe-bracket (parse-apl-expr inner-tokens) tokens after)))
(collect-segments-loop
tokens
(nth br 1)
(append acc {:kind "val" :node (nth br 0)}))))))))
((= tt :lbrace) ((= tt :lbrace)
(let (let
((end (find-matching-close tokens (+ i 1) :lbrace :rbrace))) ((end (find-matching-close tokens (+ i 1) :lbrace :rbrace)))
@@ -346,9 +399,12 @@
(define find-first-fn (fn (segs) (find-first-fn-loop segs 0))) (define find-first-fn (fn (segs) (find-first-fn-loop segs 0)))
; Build an array node from 0..n value segments
; If n=1 → return that segment's node ; ============================================================
; If n>1 → return (:vec node1 node2 ...) ; Split token list on statement separators (diamond / newline)
; Only splits at depth 0 (ignores separators inside { } or ( ) )
; ============================================================
(define (define
find-first-fn-loop find-first-fn-loop
(fn (fn
@@ -370,10 +426,9 @@
(get (first segs) :node) (get (first segs) :node)
(cons :vec (map (fn (s) (get s :node)) segs))))) (cons :vec (map (fn (s) (get s :node)) segs)))))
; ============================================================ ; ============================================================
; Split token list on statement separators (diamond / newline) ; Parse a dfn body (tokens between { and })
; Only splits at depth 0 (ignores separators inside { } or ( ) ) ; Handles guard expressions: cond : expr
; ============================================================ ; ============================================================
(define (define
@@ -408,11 +463,6 @@
split-statements split-statements
(fn (tokens) (split-statements-loop tokens (list) (list) 0))) (fn (tokens) (split-statements-loop tokens (list) (list) 0)))
; ============================================================
; Parse a dfn body (tokens between { and })
; Handles guard expressions: cond : expr
; ============================================================
(define (define
split-statements-loop split-statements-loop
(fn (fn
@@ -467,6 +517,10 @@
((stmt-groups (split-statements tokens))) ((stmt-groups (split-statements tokens)))
(let ((stmts (map parse-dfn-stmt stmt-groups))) (cons :dfn stmts))))) (let ((stmts (map parse-dfn-stmt stmt-groups))) (cons :dfn stmts)))))
; ============================================================
; Parse a single statement (assignment or expression)
; ============================================================
(define (define
parse-dfn-stmt parse-dfn-stmt
(fn (fn
@@ -483,12 +537,17 @@
(parse-apl-expr body-tokens))) (parse-apl-expr body-tokens)))
(parse-stmt tokens))))) (parse-stmt tokens)))))
; ============================================================
; Parse an expression from a flat token list
; ============================================================
(define (define
find-top-level-colon find-top-level-colon
(fn (tokens i) (find-top-level-colon-loop tokens i 0))) (fn (tokens i) (find-top-level-colon-loop tokens i 0)))
; ============================================================ ; ============================================================
; Parse a single statement (assignment or expression) ; Main entry point
; parse-apl: string → AST
; ============================================================ ; ============================================================
(define (define
@@ -508,10 +567,6 @@
((and (= tt :colon) (= depth 0)) i) ((and (= tt :colon) (= depth 0)) i)
(true (find-top-level-colon-loop tokens (+ i 1) depth))))))) (true (find-top-level-colon-loop tokens (+ i 1) depth)))))))
; ============================================================
; Parse an expression from a flat token list
; ============================================================
(define (define
parse-stmt parse-stmt
(fn (fn
@@ -526,11 +581,6 @@
(parse-apl-expr (slice tokens 2))) (parse-apl-expr (slice tokens 2)))
(parse-apl-expr tokens)))) (parse-apl-expr tokens))))
; ============================================================
; Main entry point
; parse-apl: string → AST
; ============================================================
(define (define
parse-apl-expr parse-apl-expr
(fn (fn
@@ -547,13 +597,52 @@
((tokens (apl-tokenize src))) ((tokens (apl-tokenize src)))
(let (let
((stmt-groups (split-statements tokens))) ((stmt-groups (split-statements tokens)))
(if (begin
(= (len stmt-groups) 0) (apl-collect-fn-bindings stmt-groups)
nil
(if (if
(= (len stmt-groups) 1) (= (len stmt-groups) 0)
(parse-stmt (first stmt-groups)) nil
(cons :program (map parse-stmt stmt-groups)))))))) (if
(= (len stmt-groups) 1)
(parse-stmt (first stmt-groups))
(cons :program (map parse-stmt stmt-groups)))))))))
(define
split-bracket-loop
(fn
(tokens current acc depth)
(if
(= (len tokens) 0)
(append acc (list current))
(let
((tok (first tokens)) (more (rest tokens)))
(let
((tt (tok-type tok)))
(cond
((or (= tt :lparen) (= tt :lbrace) (= tt :lbracket))
(split-bracket-loop
more
(append current (list tok))
acc
(+ depth 1)))
((or (= tt :rparen) (= tt :rbrace) (= tt :rbracket))
(split-bracket-loop
more
(append current (list tok))
acc
(- depth 1)))
((and (= tt :semi) (= depth 0))
(split-bracket-loop
more
(list)
(append acc (list current))
depth))
(else
(split-bracket-loop more (append current (list tok)) acc depth))))))))
(define
split-bracket-content
(fn (tokens) (split-bracket-loop tokens (list) (list) 0)))
(define (define
maybe-bracket maybe-bracket
@@ -569,8 +658,17 @@
((inner-tokens (slice tokens (+ after 1) end)) ((inner-tokens (slice tokens (+ after 1) end))
(next-after (+ end 1))) (next-after (+ end 1)))
(let (let
((idx-expr (parse-apl-expr inner-tokens))) ((sections (split-bracket-content inner-tokens)))
(let (if
((indexed (list :dyad (list :fn-glyph "⌷") idx-expr val-node))) (= (len sections) 1)
(maybe-bracket indexed tokens next-after))))) (let
((idx-expr (parse-apl-expr inner-tokens)))
(let
((indexed (list :dyad (list :fn-glyph "⌷") idx-expr val-node)))
(maybe-bracket indexed tokens next-after)))
(let
((axis-exprs (map (fn (toks) (if (= (len toks) 0) :all (parse-apl-expr toks))) sections)))
(let
((indexed (cons :bracket (cons val-node axis-exprs))))
(maybe-bracket indexed tokens next-after)))))))
(list val-node after)))) (list val-node after))))

View File

@@ -883,7 +883,7 @@
(let (let
((sub (apl-permutations (- n 1)))) ((sub (apl-permutations (- n 1))))
(reduce (reduce
(fn (acc p) (append acc (apl-insert-everywhere n p))) (fn (acc p) (append (apl-insert-everywhere n p) acc))
(list) (list)
sub))))) sub)))))
@@ -985,6 +985,38 @@
(some (fn (c) (= c 0)) codes) (some (fn (c) (= c 0)) codes)
(some (fn (c) (= c (nth e 1))) codes))))) (some (fn (c) (= c (nth e 1))) codes)))))
(define
apl-cartesian
(fn
(lists)
(if
(= (len lists) 0)
(list (list))
(let
((rest-prods (apl-cartesian (rest lists))))
(reduce
(fn (acc x) (append acc (map (fn (p) (cons x p)) rest-prods)))
(list)
(first lists))))))
(define
apl-bracket-multi
(fn
(axes arr)
(let
((shape (get arr :shape)) (ravel (get arr :ravel)))
(let
((rank (len shape)) (strides (apl-strides shape)))
(let
((axis-info (map (fn (i) (let ((a (nth axes i))) (cond ((= a nil) {:idxs (range 0 (nth shape i)) :scalar? false}) ((= (len (get a :shape)) 0) {:idxs (list (- (first (get a :ravel)) apl-io)) :scalar? true}) (else {:idxs (map (fn (x) (- x apl-io)) (get a :ravel)) :scalar? false})))) (range 0 rank))))
(let
((cells (apl-cartesian (map (fn (a) (get a :idxs)) axis-info))))
(let
((result-ravel (map (fn (cell) (let ((flat (reduce + 0 (map (fn (i) (* (nth cell i) (nth strides i))) (range 0 rank))))) (nth ravel flat))) cells)))
(let
((result-shape (filter (fn (x) (>= x 0)) (map (fn (i) (let ((a (nth axis-info i))) (if (get a :scalar?) -1 (len (get a :idxs))))) (range 0 rank)))))
(make-array result-shape result-ravel)))))))))
(define (define
apl-reduce apl-reduce
(fn (fn

View File

@@ -39,6 +39,7 @@ cat > "$TMPFILE" << 'EPOCHS'
(load "lib/apl/tests/idioms.sx") (load "lib/apl/tests/idioms.sx")
(load "lib/apl/tests/eval-ops.sx") (load "lib/apl/tests/eval-ops.sx")
(load "lib/apl/tests/pipeline.sx") (load "lib/apl/tests/pipeline.sx")
(load "lib/apl/tests/programs-e2e.sx")
(epoch 4) (epoch 4)
(eval "(list apl-test-pass apl-test-fail)") (eval "(list apl-test-pass apl-test-fail)")
EPOCHS EPOCHS

View File

@@ -178,3 +178,137 @@
"apl-run \"(5)[3] × 7\" → 21" "apl-run \"(5)[3] × 7\" → 21"
(mkrv (apl-run "(5)[3] × 7")) (mkrv (apl-run "(5)[3] × 7"))
(list 21)) (list 21))
(apl-test "decimal: 3.7 → 3.7" (mkrv (apl-run "3.7")) (list 3.7))
(apl-test "decimal: ¯2.5 → -2.5" (mkrv (apl-run "¯2.5")) (list -2.5))
(apl-test "decimal: 1.5 + 2.5 → 4" (mkrv (apl-run "1.5 + 2.5")) (list 4))
(apl-test "decimal: ⌊3.7 → 3" (mkrv (apl-run "⌊ 3.7")) (list 3))
(apl-test "decimal: ⌈3.7 → 4" (mkrv (apl-run "⌈ 3.7")) (list 4))
(apl-test
"⎕← scalar passthrough"
(mkrv (apl-run "⎕← 42"))
(list 42))
(apl-test
"⎕← vector passthrough"
(mkrv (apl-run "⎕← 1 2 3"))
(list 1 2 3))
(apl-test
"string: 'abc' → 3-char vector"
(mkrv (apl-run "'abc'"))
(list "a" "b" "c"))
(apl-test "string: 'a' is rank-0 scalar" (mksh (apl-run "'a'")) (list))
(apl-test "string: 'hello' shape (5)" (mksh (apl-run "'hello'")) (list 5))
(apl-test
"named-fn: f ← {+⍵} ⋄ 3 f 4 → 7"
(mkrv (apl-run "f ← {+⍵} ⋄ 3 f 4"))
(list 7))
(apl-test
"named-fn monadic: sq ← {⍵×⍵} ⋄ sq 7 → 49"
(mkrv (apl-run "sq ← {⍵×⍵} ⋄ sq 7"))
(list 49))
(apl-test
"named-fn dyadic: hyp ← {((×)+⍵×⍵)} ⋄ 3 hyp 4 → 25"
(mkrv (apl-run "hyp ← {((×)+⍵×⍵)} ⋄ 3 hyp 4"))
(list 25))
(apl-test
"named-fn: dbl ← {⍵+⍵} ⋄ dbl 5"
(mkrv (apl-run "dbl ← {⍵+⍵} ⋄ dbl 5"))
(list 2 4 6 8 10))
(apl-test
"named-fn factorial via ∇ recursion"
(mkrv (apl-run "fact ← {0=⍵:1 ⋄ ⍵×∇⍵-1} ⋄ fact 5"))
(list 120))
(apl-test
"named-fn used twice in expr: dbl ← {⍵+⍵} ⋄ (dbl 3) + dbl 4"
(mkrv (apl-run "dbl ← {⍵+⍵} ⋄ (dbl 3) + dbl 4"))
(list 14))
(apl-test
"named-fn with vector arg: neg ← {-⍵} ⋄ neg 1 2 3"
(mkrv (apl-run "neg ← {-⍵} ⋄ neg 1 2 3"))
(list -1 -2 -3))
(apl-test
"multi-axis: M[2;2] → center"
(mkrv (apl-run "M ← (3 3) 9 ⋄ M[2;2]"))
(list 5))
(apl-test
"multi-axis: M[1;] → first row"
(mkrv (apl-run "M ← (3 3) 9 ⋄ M[1;]"))
(list 1 2 3))
(apl-test
"multi-axis: M[;2] → second column"
(mkrv (apl-run "M ← (3 3) 9 ⋄ M[;2]"))
(list 2 5 8))
(apl-test
"multi-axis: M[1 2;1 2] → 2x2 block"
(mkrv (apl-run "M ← (2 3) 6 ⋄ M[1 2;1 2]"))
(list 1 2 4 5))
(apl-test
"multi-axis: M[1 2;1 2] shape (2 2)"
(mksh (apl-run "M ← (2 3) 6 ⋄ M[1 2;1 2]"))
(list 2 2))
(apl-test
"multi-axis: M[;] full matrix"
(mkrv (apl-run "M ← (2 2) 10 20 30 40 ⋄ M[;]"))
(list 10 20 30 40))
(apl-test
"multi-axis: M[1;] shape collapsed"
(mksh (apl-run "M ← (3 3) 9 ⋄ M[1;]"))
(list 3))
(apl-test
"multi-axis: select all rows of column 3"
(mkrv (apl-run "M ← (4 3) 1 2 3 4 5 6 7 8 9 10 11 12 ⋄ M[;3]"))
(list 3 6 9 12))
(apl-test
"train: mean = (+/÷≢) on 1..5"
(mkrv (apl-run "(+/÷≢) 1 2 3 4 5"))
(list 3))
(apl-test
"train: mean of 2 4 6 8 10"
(mkrv (apl-run "(+/÷≢) 2 4 6 8 10"))
(list 6))
(apl-test
"train 2-atop: (- ⌊) 5 → -5"
(mkrv (apl-run "(- ⌊) 5"))
(list -5))
(apl-test
"train 3-fork dyadic: 2(+×-)5 → -21"
(mkrv (apl-run "2 (+ × -) 5"))
(list -21))
(apl-test
"train: range = (⌈/-⌊/) on vector"
(mkrv (apl-run "(⌈/-⌊/) 3 1 4 1 5 9 2 6"))
(list 8))
(apl-test
"train: mean of 10 has shape ()"
(mksh (apl-run "(+/÷≢) 10"))
(list))

View File

@@ -0,0 +1,96 @@
; End-to-end tests of the classic-program archetypes — running APL
; source through the full pipeline (tokenize → parse → eval-ast → runtime).
;
; These mirror the algorithms documented in lib/apl/tests/programs/*.apl
; but use forms our pipeline supports today (named functions instead of
; the inline ⍵← rebinding idiom; multi-stmt over single one-liners).
(define mkrv (fn (arr) (get arr :ravel)))
(define mksh (fn (arr) (get arr :shape)))
; ---------- factorial via ∇ recursion (cf. n-queens style) ----------
(apl-test
"e2e: factorial 5! = 120"
(mkrv (apl-run "fact ← {0=⍵:1 ⋄ ⍵×∇⍵-1} ⋄ fact 5"))
(list 120))
(apl-test
"e2e: factorial 7! = 5040"
(mkrv (apl-run "fact ← {0=⍵:1 ⋄ ⍵×∇⍵-1} ⋄ fact 7"))
(list 5040))
(apl-test
"e2e: factorial via ×/N (no recursion)"
(mkrv (apl-run "fact ← {×/⍳⍵} ⋄ fact 6"))
(list 720))
; ---------- sum / triangular numbers (sum-1..N) ----------
(apl-test
"e2e: triangular(10) = 55"
(mkrv (apl-run "tri ← {+/⍳⍵} ⋄ tri 10"))
(list 55))
(apl-test
"e2e: triangular(100) = 5050"
(mkrv (apl-run "tri ← {+/⍳⍵} ⋄ tri 100"))
(list 5050))
; ---------- sum of squares ----------
(apl-test
"e2e: sum-of-squares 1..5 = 55"
(mkrv (apl-run "ss ← {+/⍵×⍵} ⋄ ss 5"))
(list 55))
(apl-test
"e2e: sum-of-squares 1..10 = 385"
(mkrv (apl-run "ss ← {+/⍵×⍵} ⋄ ss 10"))
(list 385))
; ---------- divisor-counting (prime-sieve building blocks) ----------
(apl-test
"e2e: divisor counts 1..5 via outer mod"
(mkrv (apl-run "P ← 5 ⋄ +⌿ 0 = P ∘.| P"))
(list 1 2 2 3 2))
(apl-test
"e2e: divisor counts 1..10"
(mkrv (apl-run "P ← 10 ⋄ +⌿ 0 = P ∘.| P"))
(list 1 2 2 3 2 4 2 4 3 4))
(apl-test
"e2e: prime-mask 1..10 (count==2)"
(mkrv (apl-run "P ← 10 ⋄ 2 = +⌿ 0 = P ∘.| P"))
(list 0 1 1 0 1 0 1 0 0 0))
; ---------- monadic primitives chained ----------
(apl-test
"e2e: sum of |abs| = 15"
(mkrv (apl-run "+/|¯1 ¯2 ¯3 ¯4 ¯5"))
(list 15))
(apl-test
"e2e: max of squares 1..6"
(mkrv (apl-run "⌈/(6)×6"))
(list 36))
; ---------- nested named functions ----------
(apl-test
"e2e: compose dbl and sq via two named fns"
(mkrv (apl-run "dbl ← {⍵+⍵} ⋄ sq ← {⍵×⍵} ⋄ sq dbl 3"))
(list 36))
(apl-test
"e2e: max-of-two as named dyadic fn"
(mkrv (apl-run "mx ← {⍺⌈⍵} ⋄ 5 mx 3"))
(list 5))
(apl-test
"e2e: sqrt-via-newton 1 step from 1 → 2.5"
(mkrv (apl-run "step ← {(⍵+⍺÷⍵)÷2} ⋄ 4 step 1"))
(list 2.5))

View File

@@ -252,6 +252,8 @@
(apl-test "queens 7 → 40 solutions" (mkrv (apl-queens 7)) (list 40)) (apl-test "queens 7 → 40 solutions" (mkrv (apl-queens 7)) (list 40))
(apl-test "queens 8 → 92 solutions" (mkrv (apl-queens 8)) (list 92))
(apl-test "permutations of 3 has 6" (len (apl-permutations 3)) 6) (apl-test "permutations of 3 has 6" (len (apl-permutations 3)) 6)
(apl-test "permutations of 4 has 24" (len (apl-permutations 4)) 24) (apl-test "permutations of 4 has 24" (len (apl-permutations 4)) 24)

View File

@@ -2,7 +2,7 @@
(list "+" "-" "×" "÷" "*" "⍟" "⌈" "⌊" "|" "!" "?" "○" "~" "<" "≤" "=" "≥" ">" "≠" (list "+" "-" "×" "÷" "*" "⍟" "⌈" "⌊" "|" "!" "?" "○" "~" "<" "≤" "=" "≥" ">" "≠"
"≢" "≡" "∊" "∧" "" "⍱" "⍲" "," "⍪" "" "⌽" "⊖" "⍉" "↑" "↓" "⊂" "⊃" "⊆" "≢" "≡" "∊" "∧" "" "⍱" "⍲" "," "⍪" "" "⌽" "⊖" "⍉" "↑" "↓" "⊂" "⊃" "⊆"
"" "∩" "" "⍸" "⌷" "⍋" "⍒" "⊥" "" "⊣" "⊢" "⍎" "⍕" "" "∩" "" "⍸" "⌷" "⍋" "⍒" "⊥" "" "⊣" "⊢" "⍎" "⍕"
"" "⍵" "∇" "/" "\\" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@" "¯")) "" "⍵" "∇" "/" "⌿" "\\" "⍀" "¨" "⍨" "∘" "." "⍣" "⍤" "⍥" "@" "¯"))
(define apl-glyph? (define apl-glyph?
(fn (ch) (fn (ch)
@@ -138,12 +138,22 @@
(begin (begin
(consume! "¯") (consume! "¯")
(let ((digits (read-digits! ""))) (let ((digits (read-digits! "")))
(tok-push! :num (- 0 (parse-int digits 0)))) (if (and (< pos src-len) (= (cur-byte) ".")
(< (+ pos 1) src-len) (apl-digit? (nth source (+ pos 1))))
(begin (advance!)
(let ((frac (read-digits! "")))
(tok-push! :num (- 0 (string->number (str digits "." frac))))))
(tok-push! :num (- 0 (parse-int digits 0)))))
(scan!))) (scan!)))
((apl-digit? ch) ((apl-digit? ch)
(begin (begin
(let ((digits (read-digits! ""))) (let ((digits (read-digits! "")))
(tok-push! :num (parse-int digits 0))) (if (and (< pos src-len) (= (cur-byte) ".")
(< (+ pos 1) src-len) (apl-digit? (nth source (+ pos 1))))
(begin (advance!)
(let ((frac (read-digits! "")))
(tok-push! :num (string->number (str digits "." frac)))))
(tok-push! :num (parse-int digits 0))))
(scan!))) (scan!)))
((= ch "'") ((= ch "'")
(begin (begin
@@ -155,7 +165,9 @@
(let ((start pos)) (let ((start pos))
(begin (begin
(if (cur-sw? "⎕") (consume! "⎕") (advance!)) (if (cur-sw? "⎕") (consume! "⎕") (advance!))
(read-ident-cont!) (if (and (< pos src-len) (cur-sw? "←"))
(consume! "←")
(read-ident-cont!))
(tok-push! :name (slice source start pos)) (tok-push! :name (slice source start pos))
(scan!)))) (scan!))))
(true (true

View File

@@ -40,6 +40,7 @@
((= g "⍋") apl-grade-up) ((= g "⍋") apl-grade-up)
((= g "⍒") apl-grade-down) ((= g "⍒") apl-grade-down)
((= g "⎕FMT") apl-quad-fmt) ((= g "⎕FMT") apl-quad-fmt)
((= g "⎕←") apl-quad-print)
(else (error "no monadic fn for glyph"))))) (else (error "no monadic fn for glyph")))))
(define (define
@@ -97,6 +98,15 @@
((tag (first node))) ((tag (first node)))
(cond (cond
((= tag :num) (apl-scalar (nth node 1))) ((= tag :num) (apl-scalar (nth node 1)))
((= tag :str)
(let
((s (nth node 1)))
(if
(= (len s) 1)
(apl-scalar s)
(make-array
(list (len s))
(map (fn (i) (slice s i (+ i 1))) (range 0 (len s)))))))
((= tag :vec) ((= tag :vec)
(let (let
((items (rest node))) ((items (rest node)))
@@ -139,6 +149,16 @@
(apl-eval-ast rhs env))))) (apl-eval-ast rhs env)))))
((= tag :program) (apl-eval-stmts (rest node) env)) ((= tag :program) (apl-eval-stmts (rest node) env))
((= tag :dfn) node) ((= tag :dfn) node)
((= tag :bracket)
(let
((arr-expr (nth node 1)) (axis-exprs (rest (rest node))))
(let
((arr (apl-eval-ast arr-expr env))
(axes
(map
(fn (a) (if (= a :all) nil (apl-eval-ast a env)))
axis-exprs)))
(apl-bracket-multi axes arr))))
(else (error (list "apl-eval-ast: unknown node tag" tag node))))))) (else (error (list "apl-eval-ast: unknown node tag" tag node)))))))
(define (define
@@ -419,6 +439,36 @@
((f (apl-resolve-dyadic inner env))) ((f (apl-resolve-dyadic inner env)))
(fn (arr) (apl-commute f arr)))) (fn (arr) (apl-commute f arr))))
(else (error "apl-resolve-monadic: unsupported op"))))) (else (error "apl-resolve-monadic: unsupported op")))))
((= tag :fn-name)
(let
((nm (nth fn-node 1)))
(let
((bound (get env nm)))
(if
(and
(list? bound)
(> (len bound) 0)
(= (first bound) :dfn))
(fn (arg) (apl-call-dfn-m bound arg))
(error "apl-resolve-monadic: name not bound to dfn")))))
((= tag :train)
(let
((fns (rest fn-node)))
(let
((n (len fns)))
(cond
((= n 2)
(let
((g (apl-resolve-monadic (nth fns 0) env))
(h (apl-resolve-monadic (nth fns 1) env)))
(fn (arg) (g (h arg)))))
((= n 3)
(let
((f (apl-resolve-monadic (nth fns 0) env))
(g (apl-resolve-dyadic (nth fns 1) env))
(h (apl-resolve-monadic (nth fns 2) env)))
(fn (arg) (g (f arg) (h arg)))))
(else (error "monadic train arity not 2 or 3"))))))
(else (error "apl-resolve-monadic: unknown fn-node tag")))))) (else (error "apl-resolve-monadic: unknown fn-node tag"))))))
(define (define
@@ -442,6 +492,18 @@
((f (apl-resolve-dyadic inner env))) ((f (apl-resolve-dyadic inner env)))
(fn (a b) (apl-commute-dyadic f a b)))) (fn (a b) (apl-commute-dyadic f a b))))
(else (error "apl-resolve-dyadic: unsupported op"))))) (else (error "apl-resolve-dyadic: unsupported op")))))
((= tag :fn-name)
(let
((nm (nth fn-node 1)))
(let
((bound (get env nm)))
(if
(and
(list? bound)
(> (len bound) 0)
(= (first bound) :dfn))
(fn (a b) (apl-call-dfn bound a b))
(error "apl-resolve-dyadic: name not bound to dfn")))))
((= tag :outer) ((= tag :outer)
(let (let
((inner (nth fn-node 2))) ((inner (nth fn-node 2)))
@@ -455,6 +517,24 @@
((f (apl-resolve-dyadic f-node env)) ((f (apl-resolve-dyadic f-node env))
(g (apl-resolve-dyadic g-node env))) (g (apl-resolve-dyadic g-node env)))
(fn (a b) (apl-inner f g a b))))) (fn (a b) (apl-inner f g a b)))))
((= tag :train)
(let
((fns (rest fn-node)))
(let
((n (len fns)))
(cond
((= n 2)
(let
((g (apl-resolve-monadic (nth fns 0) env))
(h (apl-resolve-dyadic (nth fns 1) env)))
(fn (a b) (g (h a b)))))
((= n 3)
(let
((f (apl-resolve-dyadic (nth fns 0) env))
(g (apl-resolve-dyadic (nth fns 1) env))
(h (apl-resolve-dyadic (nth fns 2) env)))
(fn (a b) (g (f a b) (h a b)))))
(else (error "dyadic train arity not 2 or 3"))))))
(else (error "apl-resolve-dyadic: unknown fn-node tag")))))) (else (error "apl-resolve-dyadic: unknown fn-node tag"))))))
(define apl-run (fn (src) (apl-eval-ast (parse-apl src) {}))) (define apl-run (fn (src) (apl-eval-ast (parse-apl src) {})))

180
lib/guest/hm.sx Normal file
View File

@@ -0,0 +1,180 @@
;; lib/guest/hm.sx — Hindley-Milner type-inference foundations.
;;
;; Builds on lib/guest/match.sx (terms + unify) and ast.sx (canonical
;; AST shapes). This file ships the ALGEBRA — types, schemes, free
;; type-vars, generalize / instantiate, substitution composition — so a
;; full Algorithm W (or J) can be assembled on top either inside this
;; file or in a host-specific consumer (haskell/infer.sx,
;; lib/ocaml/types.sx, …).
;;
;; Per the brief the second consumer for this step is OCaml-on-SX
;; Phase 5 (paired sequencing). Until that lands, the algebra is the
;; deliverable; the host-flavoured assembly (lambda / app / let
;; inference rules with substitution threading) lives in the host.
;;
;; Types
;; -----
;; A type is a canonical match.sx term — type variables use mk-var,
;; type constructors use mk-ctor:
;; (hm-tv NAME) type variable
;; (hm-arrow A B) A -> B
;; (hm-con NAME ARGS) named n-ary constructor
;; (hm-int) / (hm-bool) / (hm-string) primitive constructors
;;
;; Schemes
;; -------
;; (hm-scheme VARS TYPE) ∀ VARS . TYPE
;; (hm-monotype TYPE) empty quantifier
;; (hm-scheme? S) (hm-scheme-vars S) (hm-scheme-type S)
;;
;; Free type variables
;; -------------------
;; (hm-ftv TYPE) names occurring in TYPE
;; (hm-ftv-scheme S) free names (minus quantifiers)
;; (hm-ftv-env ENV) free across an env (name -> scheme)
;;
;; Substitution
;; ------------
;; (hm-apply SUBST TYPE) substitute through a type
;; (hm-apply-scheme SUBST S) leaves bound vars alone
;; (hm-apply-env SUBST ENV)
;; (hm-compose S2 S1) apply S1 then S2
;;
;; Generalize / Instantiate
;; ------------------------
;; (hm-generalize TYPE ENV) → scheme over ftv(t) - ftv(env)
;; (hm-instantiate SCHEME COUNTER) → fresh-var instance
;; (hm-fresh-tv COUNTER) → (:var "tN"), bumps COUNTER
;;
;; Inference (literal only — the rest of Algorithm W lives in the host)
;; --------------------------------------------------------------------
;; (hm-infer-literal EXPR) → {:subst {} :type T}
;;
;; A complete Algorithm W consumes this kit by assembling lambda / app
;; / let rules in the host language file.
(define hm-tv (fn (name) (list :var name)))
(define hm-con (fn (name args) (list :ctor name args)))
(define hm-arrow (fn (a b) (hm-con "->" (list a b))))
(define hm-int (fn () (hm-con "Int" (list))))
(define hm-bool (fn () (hm-con "Bool" (list))))
(define hm-string (fn () (hm-con "String" (list))))
(define hm-scheme (fn (vars t) (list :scheme vars t)))
(define hm-monotype (fn (t) (hm-scheme (list) t)))
(define hm-scheme? (fn (s) (and (list? s) (not (empty? s)) (= (first s) :scheme))))
(define hm-scheme-vars (fn (s) (nth s 1)))
(define hm-scheme-type (fn (s) (nth s 2)))
(define
hm-fresh-tv
(fn (counter)
(let ((n (first counter)))
(begin
(set-nth! counter 0 (+ n 1))
(hm-tv (str "t" (+ n 1)))))))
(define
hm-ftv-acc
(fn (t acc)
(cond
((is-var? t)
(if (some (fn (n) (= n (var-name t))) acc) acc (cons (var-name t) acc)))
((is-ctor? t)
(let ((a acc))
(begin
(for-each (fn (x) (set! a (hm-ftv-acc x a))) (ctor-args t))
a)))
(:else acc))))
(define hm-ftv (fn (t) (hm-ftv-acc t (list))))
(define
hm-ftv-scheme
(fn (s)
(let ((qs (hm-scheme-vars s))
(all (hm-ftv (hm-scheme-type s))))
(filter (fn (n) (not (some (fn (q) (= q n)) qs))) all))))
(define
hm-ftv-env
(fn (env)
(let ((acc (list)))
(begin
(for-each
(fn (k)
(for-each
(fn (n)
(when (not (some (fn (m) (= m n)) acc))
(set! acc (cons n acc))))
(hm-ftv-scheme (get env k))))
(keys env))
acc))))
(define hm-apply (fn (subst t) (walk* t subst)))
(define
hm-apply-scheme
(fn (subst s)
(let ((qs (hm-scheme-vars s))
(d {}))
(begin
(for-each
(fn (k)
(when (not (some (fn (q) (= q k)) qs))
(dict-set! d k (get subst k))))
(keys subst))
(hm-scheme qs (walk* (hm-scheme-type s) d))))))
(define
hm-apply-env
(fn (subst env)
(let ((d {}))
(begin
(for-each
(fn (k) (dict-set! d k (hm-apply-scheme subst (get env k))))
(keys env))
d))))
(define
hm-compose
(fn (s2 s1)
(let ((d {}))
(begin
(for-each (fn (k) (dict-set! d k (walk* (get s1 k) s2))) (keys s1))
(for-each
(fn (k) (when (not (has-key? d k)) (dict-set! d k (get s2 k))))
(keys s2))
d))))
(define
hm-generalize
(fn (t env)
(let ((tvars (hm-ftv t))
(evars (hm-ftv-env env)))
(let ((qs (filter (fn (n) (not (some (fn (m) (= m n)) evars))) tvars)))
(hm-scheme qs t)))))
(define
hm-instantiate
(fn (s counter)
(let ((qs (hm-scheme-vars s))
(subst {}))
(begin
(for-each
(fn (q) (set! subst (assoc subst q (hm-fresh-tv counter))))
qs)
(walk* (hm-scheme-type s) subst)))))
;; Literal inference — the only AST kind whose typing rule is closed
;; in the kit. Lambda / app / let live in host code so the host's own
;; AST conventions stay untouched.
(define
hm-infer-literal
(fn (expr)
(let ((v (ast-literal-value expr)))
(cond
((number? v) {:subst {} :type (hm-int)})
((string? v) {:subst {} :type (hm-string)})
((boolean? v) {:subst {} :type (hm-bool)})
(:else (error (str "hm-infer-literal: unknown kind: " v)))))))

145
lib/guest/layout.sx Normal file
View File

@@ -0,0 +1,145 @@
;; lib/guest/layout.sx — configurable off-side / layout-sensitive lexer.
;;
;; Inserts virtual open / close / separator tokens based on indentation.
;; Configurable enough to encode either the Haskell 98 layout rule (let /
;; where / do / of opens a virtual brace at the next token's column) or
;; a Python-ish indent / dedent rule (a colon at the end of a line opens
;; a block at the next non-blank line's column).
;;
;; Token shape (input + output)
;; ----------------------------
;; Each token is a dict {:type :value :line :col …}. The kit reads
;; only :type / :value / :line / :col and passes everything else
;; through. The input stream MUST be free of newline filler tokens
;; (preprocess them away with your tokenizer) — line breaks are detected
;; by comparing :line of consecutive tokens.
;;
;; Config
;; ------
;; :open-keywords list of strings; a token whose :value matches
;; opens a new layout block at the next token's
;; column (Haskell: let/where/do/of).
;; :open-trailing-fn (fn (tok) -> bool) — alternative trigger that
;; fires AFTER the token is emitted. Use for
;; Python-style trailing `:`.
;; :open-token / :close-token / :sep-token
;; templates {:type :value} merged with :line and
;; :col when virtual tokens are emitted.
;; :explicit-open? (fn (tok) -> bool) — if the next token after a
;; trigger satisfies this, suppress virtual layout
;; for that block (Haskell: `{`).
;; :module-prelude? if true, wrap whole input in an implicit block
;; at the first token's column (Haskell yes,
;; Python no).
;;
;; Public entry
;; ------------
;; (layout-pass cfg tokens) -> tokens with virtual layout inserted.
(define
layout-mk-virtual
(fn (template line col)
(assoc (assoc template :line line) :col col)))
(define
layout-is-open-kw?
(fn (tok open-kws)
(and (= (get tok :type) "reserved")
(some (fn (k) (= k (get tok :value))) open-kws))))
(define
layout-pass
(fn (cfg tokens)
(let ((open-kws (get cfg :open-keywords))
(trailing-fn (get cfg :open-trailing-fn))
(open-tmpl (get cfg :open-token))
(close-tmpl (get cfg :close-token))
(sep-tmpl (get cfg :sep-token))
(mod-prelude? (get cfg :module-prelude?))
(expl?-fn (get cfg :explicit-open?))
(out (list))
(stack (list))
(n (len tokens))
(i 0)
(prev-line -1)
(pending-open false)
(just-opened false))
(define
emit-closes-while-greater
(fn (col line)
(when (and (not (empty? stack)) (> (first stack) col))
(do
(append! out (layout-mk-virtual close-tmpl line col))
(set! stack (rest stack))
(emit-closes-while-greater col line)))))
(define
emit-pending-open
(fn (line col)
(do
(append! out (layout-mk-virtual open-tmpl line col))
(set! stack (cons col stack))
(set! pending-open false)
(set! just-opened true))))
(define
layout-step
(fn ()
(when (< i n)
(let ((tok (nth tokens i)))
(let ((line (get tok :line)) (col (get tok :col)))
(cond
(pending-open
(cond
((and (not (= expl?-fn nil)) (expl?-fn tok))
(do
(set! pending-open false)
(append! out tok)
(set! prev-line line)
(set! i (+ i 1))
(layout-step)))
(:else
(do
(emit-pending-open line col)
(layout-step)))))
(:else
(let ((on-fresh-line? (and (> prev-line 0) (> line prev-line))))
(do
(when on-fresh-line?
(let ((stack-before stack))
(begin
(emit-closes-while-greater col line)
(when (and (not (empty? stack))
(= (first stack) col)
(not just-opened)
;; suppress separator if a dedent fired
;; — the dedent is itself the separator
(= (len stack) (len stack-before)))
(append! out (layout-mk-virtual sep-tmpl line col))))))
(set! just-opened false)
(append! out tok)
(set! prev-line line)
(set! i (+ i 1))
(cond
((layout-is-open-kw? tok open-kws)
(set! pending-open true))
((and (not (= trailing-fn nil)) (trailing-fn tok))
(set! pending-open true)))
(layout-step))))))))))
(begin
;; Module prelude: implicit layout block at the first token's column.
(when (and mod-prelude? (> n 0))
(let ((tok (nth tokens 0)))
(do
(append! out (layout-mk-virtual open-tmpl (get tok :line) (get tok :col)))
(set! stack (cons (get tok :col) stack))
(set! just-opened true))))
(layout-step)
;; EOF: close every remaining block.
(define close-rest
(fn ()
(when (not (empty? stack))
(do
(append! out (layout-mk-virtual close-tmpl 0 0))
(set! stack (rest stack))
(close-rest)))))
(close-rest)
out))))

89
lib/guest/tests/hm.sx Normal file
View File

@@ -0,0 +1,89 @@
;; lib/guest/tests/hm.sx — exercises lib/guest/hm.sx algebra.
(define ghm-test-pass 0)
(define ghm-test-fail 0)
(define ghm-test-fails (list))
(define
ghm-test
(fn (name actual expected)
(if (= actual expected)
(set! ghm-test-pass (+ ghm-test-pass 1))
(begin
(set! ghm-test-fail (+ ghm-test-fail 1))
(append! ghm-test-fails {:name name :expected expected :actual actual})))))
;; ── Type constructors ─────────────────────────────────────────────
(ghm-test "tv" (hm-tv "a") (list :var "a"))
(ghm-test "int" (hm-int) (list :ctor "Int" (list)))
(ghm-test "arrow" (ctor-head (hm-arrow (hm-int) (hm-bool))) "->")
(ghm-test "arrow-args-len" (len (ctor-args (hm-arrow (hm-int) (hm-bool)))) 2)
;; ── Schemes ───────────────────────────────────────────────────────
(ghm-test "scheme-vars" (hm-scheme-vars (hm-scheme (list "a") (hm-tv "a"))) (list "a"))
(ghm-test "monotype-vars" (hm-scheme-vars (hm-monotype (hm-int))) (list))
(ghm-test "scheme?-yes" (hm-scheme? (hm-monotype (hm-int))) true)
(ghm-test "scheme?-no" (hm-scheme? (hm-int)) false)
;; ── Fresh tyvars ──────────────────────────────────────────────────
(ghm-test "fresh-1"
(let ((c (list 0))) (var-name (hm-fresh-tv c))) "t1")
(ghm-test "fresh-bumps"
(let ((c (list 5))) (begin (hm-fresh-tv c) (first c))) 6)
;; ── Free type variables ──────────────────────────────────────────
(ghm-test "ftv-int" (hm-ftv (hm-int)) (list))
(ghm-test "ftv-tv" (hm-ftv (hm-tv "a")) (list "a"))
(ghm-test "ftv-arrow"
(len (hm-ftv (hm-arrow (hm-tv "a") (hm-arrow (hm-tv "b") (hm-tv "a"))))) 2)
(ghm-test "ftv-scheme-quantified"
(hm-ftv-scheme (hm-scheme (list "a") (hm-arrow (hm-tv "a") (hm-tv "b")))) (list "b"))
(ghm-test "ftv-env"
(let ((env (assoc {} "f" (hm-monotype (hm-arrow (hm-tv "x") (hm-tv "y"))))))
(len (hm-ftv-env env))) 2)
;; ── Substitution / apply / compose ───────────────────────────────
(ghm-test "apply-tv"
(hm-apply (assoc {} "a" (hm-int)) (hm-tv "a")) (hm-int))
(ghm-test "apply-arrow"
(ctor-head
(hm-apply (assoc {} "a" (hm-int))
(hm-arrow (hm-tv "a") (hm-tv "b")))) "->")
(ghm-test "compose-1-then-2"
(var-name
(hm-apply
(hm-compose (assoc {} "b" (hm-tv "c")) (assoc {} "a" (hm-tv "b")))
(hm-tv "a"))) "c")
;; ── Generalize / Instantiate ─────────────────────────────────────
;; forall a. a -> a instantiated twice yields fresh vars each time
(ghm-test "generalize-id"
(len (hm-scheme-vars (hm-generalize (hm-arrow (hm-tv "a") (hm-tv "a")) {}))) 1)
(ghm-test "generalize-skips-env"
;; ftv(t)={a,b}, ftv(env)={a}, qs={b}
(let ((env (assoc {} "x" (hm-monotype (hm-tv "a")))))
(len (hm-scheme-vars
(hm-generalize (hm-arrow (hm-tv "a") (hm-tv "b")) env)))) 1)
(ghm-test "instantiate-fresh"
(let ((s (hm-scheme (list "a") (hm-arrow (hm-tv "a") (hm-tv "a"))))
(c (list 0)))
(let ((t1 (hm-instantiate s c)) (t2 (hm-instantiate s c)))
(not (= (var-name (first (ctor-args t1)))
(var-name (first (ctor-args t2)))))))
true)
;; ── Inference (literal only) ─────────────────────────────────────
(ghm-test "infer-int"
(ctor-head (get (hm-infer-literal (ast-literal 42)) :type)) "Int")
(ghm-test "infer-string"
(ctor-head (get (hm-infer-literal (ast-literal "hi")) :type)) "String")
(ghm-test "infer-bool"
(ctor-head (get (hm-infer-literal (ast-literal true)) :type)) "Bool")
(define ghm-tests-run!
(fn ()
{:passed ghm-test-pass
:failed ghm-test-fail
:total (+ ghm-test-pass ghm-test-fail)}))

180
lib/guest/tests/layout.sx Normal file
View File

@@ -0,0 +1,180 @@
;; lib/guest/tests/layout.sx — synthetic Python-ish off-side fixture.
;;
;; Exercises lib/guest/layout.sx with a config different from Haskell's
;; (no module-prelude, layout opens via trailing `:` not via reserved
;; keyword) to prove the kit isn't Haskell-shaped.
(define glayout-test-pass 0)
(define glayout-test-fail 0)
(define glayout-test-fails (list))
(define
glayout-test
(fn (name actual expected)
(if (= actual expected)
(set! glayout-test-pass (+ glayout-test-pass 1))
(begin
(set! glayout-test-fail (+ glayout-test-fail 1))
(append! glayout-test-fails {:name name :expected expected :actual actual})))))
;; Convenience: build a token from {type value line col}.
(define
glayout-tok
(fn (ty val line col)
{:type ty :value val :line line :col col}))
;; Project a token list to ((type value) ...) for compact comparison.
(define
glayout-shape
(fn (toks)
(map (fn (t) (list (get t :type) (get t :value))) toks)))
;; ── Haskell-flavour: keyword opens block ─────────────────────────
(define
glayout-haskell-cfg
{:open-keywords (list "let" "where" "do" "of")
:open-trailing-fn nil
:open-token {:type "vlbrace" :value "{"}
:close-token {:type "vrbrace" :value "}"}
:sep-token {:type "vsemi" :value ";"}
:module-prelude? false
:explicit-open? (fn (tok) (= (get tok :type) "lbrace"))})
;; do
;; a
;; b
;; c ← outside the do-block
(glayout-test "haskell-do-block"
(glayout-shape
(layout-pass
glayout-haskell-cfg
(list (glayout-tok "reserved" "do" 1 1)
(glayout-tok "ident" "a" 2 3)
(glayout-tok "ident" "b" 3 3)
(glayout-tok "ident" "c" 4 1))))
(list (list "reserved" "do")
(list "vlbrace" "{")
(list "ident" "a")
(list "vsemi" ";")
(list "ident" "b")
(list "vrbrace" "}")
(list "ident" "c")))
;; Explicit `{` after `do` suppresses virtual layout.
(glayout-test "haskell-explicit-brace"
(glayout-shape
(layout-pass
glayout-haskell-cfg
(list (glayout-tok "reserved" "do" 1 1)
(glayout-tok "lbrace" "{" 1 4)
(glayout-tok "ident" "a" 1 6)
(glayout-tok "rbrace" "}" 1 8))))
(list (list "reserved" "do")
(list "lbrace" "{")
(list "ident" "a")
(list "rbrace" "}")))
;; Single-statement do-block on the same line.
(glayout-test "haskell-do-inline"
(glayout-shape
(layout-pass
glayout-haskell-cfg
(list (glayout-tok "reserved" "do" 1 1)
(glayout-tok "ident" "a" 1 4))))
(list (list "reserved" "do")
(list "vlbrace" "{")
(list "ident" "a")
(list "vrbrace" "}")))
;; Module-prelude: wrap whole input in implicit layout block at first
;; tok's column.
(glayout-test "haskell-module-prelude"
(glayout-shape
(layout-pass
(assoc glayout-haskell-cfg :module-prelude? true)
(list (glayout-tok "ident" "x" 1 1)
(glayout-tok "ident" "y" 2 1)
(glayout-tok "ident" "z" 3 1))))
(list (list "vlbrace" "{")
(list "ident" "x")
(list "vsemi" ";")
(list "ident" "y")
(list "vsemi" ";")
(list "ident" "z")
(list "vrbrace" "}")))
;; ── Python-flavour: trailing `:` opens block ─────────────────────
(define
glayout-python-cfg
{:open-keywords (list)
:open-trailing-fn (fn (tok) (and (= (get tok :type) "punct")
(= (get tok :value) ":")))
:open-token {:type "indent" :value "INDENT"}
:close-token {:type "dedent" :value "DEDENT"}
:sep-token {:type "newline" :value "NEWLINE"}
:module-prelude? false
:explicit-open? nil})
;; if x:
;; a
;; b
;; c
(glayout-test "python-if-block"
(glayout-shape
(layout-pass
glayout-python-cfg
(list (glayout-tok "reserved" "if" 1 1)
(glayout-tok "ident" "x" 1 4)
(glayout-tok "punct" ":" 1 5)
(glayout-tok "ident" "a" 2 5)
(glayout-tok "ident" "b" 3 5)
(glayout-tok "ident" "c" 4 1))))
(list (list "reserved" "if")
(list "ident" "x")
(list "punct" ":")
(list "indent" "INDENT")
(list "ident" "a")
(list "newline" "NEWLINE")
(list "ident" "b")
(list "dedent" "DEDENT")
(list "ident" "c")))
;; Nested Python-style blocks.
;; def f():
;; if x:
;; a
;; b
(glayout-test "python-nested"
(glayout-shape
(layout-pass
glayout-python-cfg
(list (glayout-tok "reserved" "def" 1 1)
(glayout-tok "ident" "f" 1 5)
(glayout-tok "punct" "(" 1 6)
(glayout-tok "punct" ")" 1 7)
(glayout-tok "punct" ":" 1 8)
(glayout-tok "reserved" "if" 2 5)
(glayout-tok "ident" "x" 2 8)
(glayout-tok "punct" ":" 2 9)
(glayout-tok "ident" "a" 3 9)
(glayout-tok "ident" "b" 4 5))))
(list (list "reserved" "def")
(list "ident" "f")
(list "punct" "(")
(list "punct" ")")
(list "punct" ":")
(list "indent" "INDENT")
(list "reserved" "if")
(list "ident" "x")
(list "punct" ":")
(list "indent" "INDENT")
(list "ident" "a")
(list "dedent" "DEDENT")
(list "ident" "b")
(list "dedent" "DEDENT")))
(define glayout-tests-run!
(fn ()
{:passed glayout-test-pass
:failed glayout-test-fail
:total (+ glayout-test-pass glayout-test-fail)}))

View File

@@ -1,858 +0,0 @@
;; lib/minikanren/clpfd.sx — Phase 6: native CLP(FD) on miniKanren.
;;
;; The substitution dict carries an extra reserved key "_fd" that holds a
;; constraint-store record:
;;
;; {:domains {var-name -> sorted-int-list}
;; :constraints (... pending constraint closures ...)}
;;
;; Domains are sorted SX lists of ints (no duplicates).
;; Constraints are functions s -> s-or-nil that propagate / re-check.
;; They are re-fired after every label binding via fd-fire-store.
(define fd-key "_fd")
;; --- domain primitives ---
(define
fd-dom-rev
(fn
(xs acc)
(cond
((empty? xs) acc)
(:else (fd-dom-rev (rest xs) (cons (first xs) acc))))))
(define
fd-dom-insert
(fn
(x desc)
(cond
((empty? desc) (list x))
((= x (first desc)) desc)
((> x (first desc)) (cons x desc))
(:else (cons (first desc) (fd-dom-insert x (rest desc)))))))
(define
fd-dom-sort-dedupe
(fn
(xs acc)
(cond
((empty? xs) (fd-dom-rev acc (list)))
(:else (fd-dom-sort-dedupe (rest xs) (fd-dom-insert (first xs) acc))))))
(define fd-dom-from-list (fn (xs) (fd-dom-sort-dedupe xs (list))))
(define fd-dom-empty? (fn (d) (empty? d)))
(define
fd-dom-singleton?
(fn (d) (and (not (empty? d)) (empty? (rest d)))))
(define fd-dom-min (fn (d) (first d)))
(define
fd-dom-last
(fn
(d)
(cond ((empty? (rest d)) (first d)) (:else (fd-dom-last (rest d))))))
(define fd-dom-max (fn (d) (fd-dom-last d)))
(define fd-dom-member? (fn (x d) (some (fn (y) (= x y)) d)))
(define
fd-dom-intersect
(fn
(a b)
(cond
((empty? a) (list))
((empty? b) (list))
((= (first a) (first b))
(cons (first a) (fd-dom-intersect (rest a) (rest b))))
((< (first a) (first b)) (fd-dom-intersect (rest a) b))
(:else (fd-dom-intersect a (rest b))))))
(define
fd-dom-without
(fn
(x d)
(cond
((empty? d) (list))
((= (first d) x) (rest d))
((> (first d) x) d)
(:else (cons (first d) (fd-dom-without x (rest d)))))))
(define
fd-dom-range
(fn
(lo hi)
(cond
((> lo hi) (list))
(:else (cons lo (fd-dom-range (+ lo 1) hi))))))
;; --- constraint store accessors ---
(define fd-store-empty (fn () {:domains {} :constraints (list)}))
(define
fd-store-of
(fn
(s)
(cond ((has-key? s fd-key) (get s fd-key)) (:else (fd-store-empty)))))
(define fd-domains-of (fn (s) (get (fd-store-of s) :domains)))
(define fd-with-store (fn (s store) (assoc s fd-key store)))
(define
fd-domain-of
(fn
(s var-name)
(let
((doms (fd-domains-of s)))
(cond ((has-key? doms var-name) (get doms var-name)) (:else nil)))))
(define
fd-set-domain
(fn
(s var-name d)
(cond
((fd-dom-empty? d) nil)
(:else
(let
((store (fd-store-of s)))
(let
((doms-prime (assoc (get store :domains) var-name d)))
(let
((store-prime (assoc store :domains doms-prime)))
(fd-with-store s store-prime))))))))
(define
fd-add-constraint
(fn
(s c)
(let
((store (fd-store-of s)))
(let
((cs-prime (cons c (get store :constraints))))
(let
((store-prime (assoc store :constraints cs-prime)))
(fd-with-store s store-prime))))))
(define
fd-fire-list
(fn
(cs s)
(cond
((empty? cs) s)
(:else
(let
((s2 ((first cs) s)))
(cond ((= s2 nil) nil) (:else (fd-fire-list (rest cs) s2))))))))
(define
fd-store-signature
(fn
(s)
(let
((doms (fd-domains-of s)))
(let
((dom-sizes (reduce (fn (acc k) (+ acc (len (get doms k)))) 0 (keys doms))))
(+ dom-sizes (len (keys s)))))))
(define
fd-fire-store
(fn
(s)
(let
((s2 (fd-fire-list (get (fd-store-of s) :constraints) s)))
(cond
((= s2 nil) nil)
((= (fd-store-signature s) (fd-store-signature s2)) s2)
(:else (fd-fire-store s2))))))
;; --- user-facing goals ---
(define
fd-in
(fn
(x dom-list)
(fn
(s)
(let
((new-dom (fd-dom-from-list dom-list)))
(let
((wx (mk-walk x s)))
(cond
((number? wx)
(cond ((fd-dom-member? wx new-dom) (unit s)) (:else mzero)))
((is-var? wx)
(let
((existing (fd-domain-of s (var-name wx))))
(let
((narrowed (cond ((= existing nil) new-dom) (:else (fd-dom-intersect existing new-dom)))))
(let
((s2 (fd-set-domain s (var-name wx) narrowed)))
(cond ((= s2 nil) mzero) (:else (unit s2)))))))
(:else mzero)))))))
;; --- fd-neq ---
(define
fd-neq-prop
(fn
(x y s)
(let
((wx (mk-walk x s)) (wy (mk-walk y s)))
(cond
((and (number? wx) (number? wy))
(cond ((= wx wy) nil) (:else s)))
((and (number? wx) (is-var? wy))
(let
((y-dom (fd-domain-of s (var-name wy))))
(cond
((= y-dom nil) s)
(:else
(fd-set-domain s (var-name wy) (fd-dom-without wx y-dom))))))
((and (number? wy) (is-var? wx))
(let
((x-dom (fd-domain-of s (var-name wx))))
(cond
((= x-dom nil) s)
(:else
(fd-set-domain s (var-name wx) (fd-dom-without wy x-dom))))))
(:else s)))))
(define
fd-neq
(fn
(x y)
(fn
(s)
(let
((c (fn (s-prime) (fd-neq-prop x y s-prime))))
(let
((s2 (fd-add-constraint s c)))
(let
((s2-or-nil (c s2)))
(let
((s3 (cond ((= s2-or-nil nil) nil) (:else (fd-fire-store s2-or-nil)))))
(cond ((= s3 nil) mzero) (:else (unit s3))))))))))
;; --- fd-lt ---
(define
fd-lt-prop
(fn
(x y s)
(let
((wx (mk-walk x s)) (wy (mk-walk y s)))
(cond
((and (number? wx) (number? wy))
(cond ((< wx wy) s) (:else nil)))
((and (number? wx) (is-var? wy))
(let
((yd (fd-domain-of s (var-name wy))))
(cond
((= yd nil) s)
(:else
(fd-set-domain
s
(var-name wy)
(filter (fn (v) (> v wx)) yd))))))
((and (is-var? wx) (number? wy))
(let
((xd (fd-domain-of s (var-name wx))))
(cond
((= xd nil) s)
(:else
(fd-set-domain
s
(var-name wx)
(filter (fn (v) (< v wy)) xd))))))
((and (is-var? wx) (is-var? wy))
(let
((xd (fd-domain-of s (var-name wx)))
(yd (fd-domain-of s (var-name wy))))
(cond
((or (= xd nil) (= yd nil)) s)
(:else
(let
((xd-prime (filter (fn (v) (< v (fd-dom-max yd))) xd)))
(let
((s2 (fd-set-domain s (var-name wx) xd-prime)))
(cond
((= s2 nil) nil)
(:else
(let
((yd-prime (filter (fn (v) (> v (fd-dom-min xd-prime))) yd)))
(fd-set-domain s2 (var-name wy) yd-prime))))))))))
(:else s)))))
(define
fd-lt
(fn
(x y)
(fn
(s)
(let
((c (fn (sp) (fd-lt-prop x y sp))))
(let
((s2 (fd-add-constraint s c)))
(let
((s2-or-nil (c s2)))
(let
((s3 (cond ((= s2-or-nil nil) nil) (:else (fd-fire-store s2-or-nil)))))
(cond ((= s3 nil) mzero) (:else (unit s3))))))))))
;; --- fd-lte ---
(define
fd-lte-prop
(fn
(x y s)
(let
((wx (mk-walk x s)) (wy (mk-walk y s)))
(cond
((and (number? wx) (number? wy))
(cond ((<= wx wy) s) (:else nil)))
((and (number? wx) (is-var? wy))
(let
((yd (fd-domain-of s (var-name wy))))
(cond
((= yd nil) s)
(:else
(fd-set-domain
s
(var-name wy)
(filter (fn (v) (>= v wx)) yd))))))
((and (is-var? wx) (number? wy))
(let
((xd (fd-domain-of s (var-name wx))))
(cond
((= xd nil) s)
(:else
(fd-set-domain
s
(var-name wx)
(filter (fn (v) (<= v wy)) xd))))))
((and (is-var? wx) (is-var? wy))
(let
((xd (fd-domain-of s (var-name wx)))
(yd (fd-domain-of s (var-name wy))))
(cond
((or (= xd nil) (= yd nil)) s)
(:else
(let
((xd-prime (filter (fn (v) (<= v (fd-dom-max yd))) xd)))
(let
((s2 (fd-set-domain s (var-name wx) xd-prime)))
(cond
((= s2 nil) nil)
(:else
(let
((yd-prime (filter (fn (v) (>= v (fd-dom-min xd-prime))) yd)))
(fd-set-domain s2 (var-name wy) yd-prime))))))))))
(:else s)))))
(define
fd-lte
(fn
(x y)
(fn
(s)
(let
((c (fn (sp) (fd-lte-prop x y sp))))
(let
((s2 (fd-add-constraint s c)))
(let
((s2-or-nil (c s2)))
(let
((s3 (cond ((= s2-or-nil nil) nil) (:else (fd-fire-store s2-or-nil)))))
(cond ((= s3 nil) mzero) (:else (unit s3))))))))))
;; --- fd-eq ---
(define
fd-eq-prop
(fn
(x y s)
(let
((wx (mk-walk x s)) (wy (mk-walk y s)))
(cond
((and (number? wx) (number? wy))
(cond ((= wx wy) s) (:else nil)))
((and (number? wx) (is-var? wy))
(let
((yd (fd-domain-of s (var-name wy))))
(cond
((and (not (= yd nil)) (not (fd-dom-member? wx yd))) nil)
(:else
(let
((s2 (mk-unify wy wx s)))
(cond ((= s2 nil) nil) (:else s2)))))))
((and (is-var? wx) (number? wy))
(let
((xd (fd-domain-of s (var-name wx))))
(cond
((and (not (= xd nil)) (not (fd-dom-member? wy xd))) nil)
(:else
(let
((s2 (mk-unify wx wy s)))
(cond ((= s2 nil) nil) (:else s2)))))))
((and (is-var? wx) (is-var? wy))
(let
((xd (fd-domain-of s (var-name wx)))
(yd (fd-domain-of s (var-name wy))))
(cond
((and (= xd nil) (= yd nil))
(let
((s2 (mk-unify wx wy s)))
(cond ((= s2 nil) nil) (:else s2))))
(:else
(let
((shared (cond ((= xd nil) yd) ((= yd nil) xd) (:else (fd-dom-intersect xd yd)))))
(cond
((fd-dom-empty? shared) nil)
(:else
(let
((s2 (fd-set-domain s (var-name wx) shared)))
(cond
((= s2 nil) nil)
(:else
(let
((s3 (fd-set-domain s2 (var-name wy) shared)))
(cond
((= s3 nil) nil)
(:else (mk-unify wx wy s3))))))))))))))
(:else s)))))
(define
fd-eq
(fn
(x y)
(fn
(s)
(let
((c (fn (sp) (fd-eq-prop x y sp))))
(let
((s2 (fd-add-constraint s c)))
(let
((s2-or-nil (c s2)))
(let
((s3 (cond ((= s2-or-nil nil) nil) (:else (fd-fire-store s2-or-nil)))))
(cond ((= s3 nil) mzero) (:else (unit s3))))))))))
;; --- labelling ---
(define
fd-try-each-value
(fn
(x dom s)
(cond
((empty? dom) mzero)
(:else
(let
((s2 (mk-unify x (first dom) s)))
(let
((s3 (cond ((= s2 nil) nil) (:else (fd-fire-store s2)))))
(let
((this-stream (cond ((= s3 nil) mzero) (:else (unit s3))))
(rest-stream (fd-try-each-value x (rest dom) s)))
(mk-mplus this-stream rest-stream))))))))
(define
fd-label-one
(fn
(x)
(fn
(s)
(let
((wx (mk-walk x s)))
(cond
((number? wx) (unit s))
((is-var? wx)
(let
((dom (fd-domain-of s (var-name wx))))
(cond
((= dom nil) mzero)
(:else (fd-try-each-value wx dom s)))))
(:else mzero))))))
(define
fd-label
(fn
(vars)
(cond
((empty? vars) succeed)
(:else (mk-conj (fd-label-one (first vars)) (fd-label (rest vars)))))))
;; --- fd-distinct (pairwise distinct via fd-neq) ---
(define
fd-distinct-from-head
(fn
(x others)
(cond
((empty? others) succeed)
(:else
(mk-conj
(fd-neq x (first others))
(fd-distinct-from-head x (rest others)))))))
(define
fd-distinct
(fn
(vars)
(cond
((empty? vars) succeed)
((empty? (rest vars)) succeed)
(:else
(mk-conj
(fd-distinct-from-head (first vars) (rest vars))
(fd-distinct (rest vars)))))))
;; --- fd-plus (x + y = z, ground-cases propagator) ---
(define
fd-bind-or-narrow
(fn
(w target s)
(cond
((number? w) (cond ((= w target) s) (:else nil)))
((is-var? w)
(let
((wd (fd-domain-of s (var-name w))))
(cond
((and (not (= wd nil)) (not (fd-dom-member? target wd))) nil)
(:else
(let
((s2 (mk-unify w target s)))
(cond ((= s2 nil) nil) (:else s2)))))))
(:else nil))))
(define
fd-narrow-or-skip
(fn
(s var-key d lo hi)
(cond
((= d nil) s)
(:else
(fd-set-domain
s
var-key
(filter (fn (v) (and (>= v lo) (<= v hi))) d))))))
(define
fd-plus-prop-vvn
(fn
(wx wy wz s)
(let
((xd (fd-domain-of s (var-name wx)))
(yd (fd-domain-of s (var-name wy))))
(cond
((or (= xd nil) (= yd nil)) s)
(:else
(let
((s1 (fd-narrow-or-skip s (var-name wx) xd (- wz (fd-dom-max yd)) (- wz (fd-dom-min yd)))))
(cond
((= s1 nil) nil)
(:else
(let
((xd2 (fd-domain-of s1 (var-name wx))))
(fd-narrow-or-skip
s1
(var-name wy)
yd
(- wz (fd-dom-max xd2))
(- wz (fd-dom-min xd2))))))))))))
(define
fd-plus-prop-nvv
(fn
(wx wy wz s)
(let
((yd (fd-domain-of s (var-name wy)))
(zd (fd-domain-of s (var-name wz))))
(cond
((or (= yd nil) (= zd nil)) s)
(:else
(let
((s1 (fd-narrow-or-skip s (var-name wy) yd (- (fd-dom-min zd) wx) (- (fd-dom-max zd) wx))))
(cond
((= s1 nil) nil)
(:else
(let
((yd2 (fd-domain-of s1 (var-name wy))))
(fd-narrow-or-skip
s1
(var-name wz)
zd
(+ wx (fd-dom-min yd2))
(+ wx (fd-dom-max yd2))))))))))))
(define
fd-plus-prop-vnv
(fn
(wx wy wz s)
(let
((xd (fd-domain-of s (var-name wx)))
(zd (fd-domain-of s (var-name wz))))
(cond
((or (= xd nil) (= zd nil)) s)
(:else
(let
((s1 (fd-narrow-or-skip s (var-name wx) xd (- (fd-dom-min zd) wy) (- (fd-dom-max zd) wy))))
(cond
((= s1 nil) nil)
(:else
(let
((xd2 (fd-domain-of s1 (var-name wx))))
(fd-narrow-or-skip
s1
(var-name wz)
zd
(+ (fd-dom-min xd2) wy)
(+ (fd-dom-max xd2) wy)))))))))))
(define
fd-plus-prop-vvv
(fn
(wx wy wz s)
(let
((xd (fd-domain-of s (var-name wx)))
(yd (fd-domain-of s (var-name wy)))
(zd (fd-domain-of s (var-name wz))))
(cond
((or (= xd nil) (or (= yd nil) (= zd nil))) s)
(:else
(let
((s1 (fd-narrow-or-skip s (var-name wx) xd (- (fd-dom-min zd) (fd-dom-max yd)) (- (fd-dom-max zd) (fd-dom-min yd)))))
(cond
((= s1 nil) nil)
(:else
(let
((s2 (fd-narrow-or-skip s1 (var-name wy) yd (- (fd-dom-min zd) (fd-dom-max xd)) (- (fd-dom-max zd) (fd-dom-min xd)))))
(cond
((= s2 nil) nil)
(:else
(fd-narrow-or-skip
s2
(var-name wz)
zd
(+ (fd-dom-min xd) (fd-dom-min yd))
(+ (fd-dom-max xd) (fd-dom-max yd))))))))))))))
(define
fd-plus-prop
(fn
(x y z s)
(let
((wx (mk-walk x s)) (wy (mk-walk y s)) (wz (mk-walk z s)))
(cond
((and (number? wx) (number? wy) (number? wz))
(cond ((= (+ wx wy) wz) s) (:else nil)))
((and (number? wx) (number? wy))
(fd-bind-or-narrow wz (+ wx wy) s))
((and (number? wx) (number? wz))
(fd-bind-or-narrow wy (- wz wx) s))
((and (number? wy) (number? wz))
(fd-bind-or-narrow wx (- wz wy) s))
((and (is-var? wx) (is-var? wy) (number? wz))
(fd-plus-prop-vvn wx wy wz s))
((and (number? wx) (is-var? wy) (is-var? wz))
(fd-plus-prop-nvv wx wy wz s))
((and (is-var? wx) (number? wy) (is-var? wz))
(fd-plus-prop-vnv wx wy wz s))
((and (is-var? wx) (is-var? wy) (is-var? wz))
(fd-plus-prop-vvv wx wy wz s))
(:else s)))))
(define
fd-plus
(fn
(x y z)
(fn
(s)
(let
((c (fn (sp) (fd-plus-prop x y z sp))))
(let
((s2 (fd-add-constraint s c)))
(let
((s2-or-nil (c s2)))
(let
((s3 (cond ((= s2-or-nil nil) nil) (:else (fd-fire-store s2-or-nil)))))
(cond ((= s3 nil) mzero) (:else (unit s3))))))))))
;; --- fd-times (x * y = z, ground-cases propagator) ---
(define
fd-int-ceil-div
(fn
(a b)
(cond
((= (mod a b) 0) (/ a b))
(:else (+ (fd-int-floor-div a b) 1)))))
(define fd-int-floor-div (fn (a b) (/ (- a (mod a b)) b)))
(define
fd-dom-positive?
(fn
(d)
(cond ((empty? d) false) (:else (>= (fd-dom-min d) 1)))))
(define
fd-times-prop-vvv
(fn
(wx wy wz s)
(let
((xd (fd-domain-of s (var-name wx)))
(yd (fd-domain-of s (var-name wy)))
(zd (fd-domain-of s (var-name wz))))
(cond
((or (= xd nil) (or (= yd nil) (= zd nil))) s)
((not (and (fd-dom-positive? xd) (and (fd-dom-positive? yd) (fd-dom-positive? zd))))
s)
(:else
(let
((s1 (fd-narrow-or-skip s (var-name wx) xd (fd-int-ceil-div (fd-dom-min zd) (fd-dom-max yd)) (fd-int-floor-div (fd-dom-max zd) (fd-dom-min yd)))))
(cond
((= s1 nil) nil)
(:else
(let
((s2 (fd-narrow-or-skip s1 (var-name wy) yd (fd-int-ceil-div (fd-dom-min zd) (fd-dom-max xd)) (fd-int-floor-div (fd-dom-max zd) (fd-dom-min xd)))))
(cond
((= s2 nil) nil)
(:else
(fd-narrow-or-skip
s2
(var-name wz)
zd
(* (fd-dom-min xd) (fd-dom-min yd))
(* (fd-dom-max xd) (fd-dom-max yd))))))))))))))
(define
fd-times-prop-vvn
(fn
(wx wy wz s)
(let
((xd (fd-domain-of s (var-name wx)))
(yd (fd-domain-of s (var-name wy))))
(cond
((or (= xd nil) (= yd nil)) s)
((not (and (fd-dom-positive? xd) (fd-dom-positive? yd))) s)
((<= wz 0) s)
(:else
(let
((s1 (fd-narrow-or-skip s (var-name wx) xd (fd-int-ceil-div wz (fd-dom-max yd)) (fd-int-floor-div wz (fd-dom-min yd)))))
(cond
((= s1 nil) nil)
(:else
(let
((xd2 (fd-domain-of s1 (var-name wx))))
(fd-narrow-or-skip
s1
(var-name wy)
yd
(fd-int-ceil-div wz (fd-dom-max xd2))
(fd-int-floor-div wz (fd-dom-min xd2))))))))))))
(define
fd-times-prop-nvv
(fn
(wx wy wz s)
(cond
((<= wx 0) s)
(:else
(let
((yd (fd-domain-of s (var-name wy)))
(zd (fd-domain-of s (var-name wz))))
(cond
((or (= yd nil) (= zd nil)) s)
((not (and (fd-dom-positive? yd) (fd-dom-positive? zd))) s)
(:else
(let
((s1 (fd-narrow-or-skip s (var-name wy) yd (fd-int-ceil-div (fd-dom-min zd) wx) (fd-int-floor-div (fd-dom-max zd) wx))))
(cond
((= s1 nil) nil)
(:else
(let
((yd2 (fd-domain-of s1 (var-name wy))))
(fd-narrow-or-skip
s1
(var-name wz)
zd
(* wx (fd-dom-min yd2))
(* wx (fd-dom-max yd2))))))))))))))
(define
fd-times-prop-vnv
(fn
(wx wy wz s)
(cond
((<= wy 0) s)
(:else
(let
((xd (fd-domain-of s (var-name wx)))
(zd (fd-domain-of s (var-name wz))))
(cond
((or (= xd nil) (= zd nil)) s)
((not (and (fd-dom-positive? xd) (fd-dom-positive? zd))) s)
(:else
(let
((s1 (fd-narrow-or-skip s (var-name wx) xd (fd-int-ceil-div (fd-dom-min zd) wy) (fd-int-floor-div (fd-dom-max zd) wy))))
(cond
((= s1 nil) nil)
(:else
(let
((xd2 (fd-domain-of s1 (var-name wx))))
(fd-narrow-or-skip
s1
(var-name wz)
zd
(* (fd-dom-min xd2) wy)
(* (fd-dom-max xd2) wy)))))))))))))
(define
fd-times-prop
(fn
(x y z s)
(let
((wx (mk-walk x s)) (wy (mk-walk y s)) (wz (mk-walk z s)))
(cond
((and (number? wx) (number? wy) (number? wz))
(cond ((= (* wx wy) wz) s) (:else nil)))
((and (number? wx) (number? wy))
(fd-bind-or-narrow wz (* wx wy) s))
((and (number? wx) (number? wz))
(cond
((= wx 0) (cond ((= wz 0) s) (:else nil)))
((not (= (mod wz wx) 0)) nil)
(:else (fd-bind-or-narrow wy (/ wz wx) s))))
((and (number? wy) (number? wz))
(cond
((= wy 0) (cond ((= wz 0) s) (:else nil)))
((not (= (mod wz wy) 0)) nil)
(:else (fd-bind-or-narrow wx (/ wz wy) s))))
((and (is-var? wx) (is-var? wy) (number? wz))
(fd-times-prop-vvn wx wy wz s))
((and (number? wx) (is-var? wy) (is-var? wz))
(fd-times-prop-nvv wx wy wz s))
((and (is-var? wx) (number? wy) (is-var? wz))
(fd-times-prop-vnv wx wy wz s))
((and (is-var? wx) (is-var? wy) (is-var? wz))
(fd-times-prop-vvv wx wy wz s))
(:else s)))))
(define
fd-times
(fn
(x y z)
(fn
(s)
(let
((c (fn (sp) (fd-times-prop x y z sp))))
(let
((s2 (fd-add-constraint s c)))
(let
((s2-or-nil (c s2)))
(let
((s3 (cond ((= s2-or-nil nil) nil) (:else (fd-fire-store s2-or-nil)))))
(cond ((= s3 nil) mzero) (:else (unit s3))))))))))

View File

@@ -1,42 +0,0 @@
;; lib/minikanren/conda.sx — Phase 5 piece A: `conda`, the soft-cut.
;;
;; (conda (g0 g ...) (h0 h ...) ...)
;; — first clause whose head g0 produces ANY answer wins; ALL of g0's
;; answers are then conj'd with the rest of that clause; later
;; clauses are NOT tried.
;; — differs from condu only in not wrapping g0 in onceo: condu
;; commits to the SINGLE first answer, conda lets the head's full
;; answer-set flow into the rest of the clause.
;; (Reasoned Schemer chapter 10; Byrd 5.3.)
(define
conda-try
(fn
(clauses s)
(cond
((empty? clauses) mzero)
(:else
(let
((cl (first clauses)))
(let
((head-goal (first cl)) (rest-goals (rest cl)))
(let
((peek (stream-take 1 (head-goal s))))
(if
(empty? peek)
(conda-try (rest clauses) s)
(mk-bind (head-goal s) (mk-conj-list rest-goals))))))))))
(defmacro
conda
(&rest clauses)
(quasiquote
(fn
(s)
(conda-try
(list
(splice-unquote
(map
(fn (cl) (quasiquote (list (splice-unquote cl))))
clauses)))
s))))

View File

@@ -1,39 +0,0 @@
;; lib/minikanren/conde.sx — Phase 2 piece C: `conde`, the canonical
;; miniKanren and-or form, with implicit Zzz inverse-eta delay so recursive
;; relations like appendo terminate.
;;
;; (conde (g1a g1b ...) (g2a g2b ...) ...)
;; ≡ (mk-disj (Zzz (mk-conj g1a g1b ...))
;; (Zzz (mk-conj g2a g2b ...)) ...)
;;
;; `Zzz g` wraps a goal expression in (fn (S) (fn () (g S))) so that
;; `g`'s body isn't constructed until the surrounding fn is applied to a
;; substitution AND the returned thunk is forced. This is what gives
;; miniKanren its laziness — recursive goal definitions can be `(conde
;; ... (... (recur ...)))` without infinite descent at construction time.
;;
;; Hygiene: the substitution parameter is gensym'd so that user goal
;; expressions which themselves bind `s` (e.g. `(appendo l s ls)`) keep
;; their lexical `s` and don't accidentally reference the wrapper's
;; substitution. Without gensym, miniKanren relations that follow the
;; common (l s ls) parameter convention are silently miscompiled.
(defmacro
Zzz
(g)
(let
((s-sym (gensym "zzz-s-")))
(quasiquote
(fn ((unquote s-sym)) (fn () ((unquote g) (unquote s-sym)))))))
(defmacro
conde
(&rest clauses)
(quasiquote
(mk-disj
(splice-unquote
(map
(fn
(clause)
(quasiquote (Zzz (mk-conj (splice-unquote clause)))))
clauses)))))

View File

@@ -1,58 +0,0 @@
;; lib/minikanren/condu.sx — Phase 2 piece D: `condu` and `onceo`.
;;
;; Both are commitment forms (no backtracking into discarded options):
;;
;; (onceo g) — succeeds at most once: takes the first answer
;; stream-take produces from (g s).
;;
;; (condu (g0 g ...) (h0 h ...) ...)
;; — first clause whose head goal succeeds wins; only
;; the first answer of the head is propagated to the
;; rest of that clause; later clauses are not tried.
;; (Reasoned Schemer chapter 10; Byrd 5.4.)
(define
onceo
(fn
(g)
(fn
(s)
(let
((peek (stream-take 1 (g s))))
(if (empty? peek) mzero (unit (first peek)))))))
;; condu-try — runtime walker over a list of clauses (each clause a list of
;; goals). Forces the head with stream-take 1; if head fails, recurse to
;; the next clause; if head succeeds, commits its single answer through
;; the rest of the clause.
(define
condu-try
(fn
(clauses s)
(cond
((empty? clauses) mzero)
(:else
(let
((cl (first clauses)))
(let
((head-goal (first cl)) (rest-goals (rest cl)))
(let
((peek (stream-take 1 (head-goal s))))
(if
(empty? peek)
(condu-try (rest clauses) s)
((mk-conj-list rest-goals) (first peek))))))))))
(defmacro
condu
(&rest clauses)
(quasiquote
(fn
(s)
(condu-try
(list
(splice-unquote
(map
(fn (cl) (quasiquote (list (splice-unquote cl))))
clauses)))
s))))

View File

@@ -1,25 +0,0 @@
;; lib/minikanren/defrel.sx — Prolog-style defrel macro.
;;
;; (defrel (NAME ARG1 ARG2 ...)
;; (CLAUSE1 ...)
;; (CLAUSE2 ...)
;; ...)
;;
;; expands to
;;
;; (define NAME (fn (ARG1 ARG2 ...) (conde (CLAUSE1 ...) (CLAUSE2 ...))))
;;
;; This puts each clause's goals immediately after the head, mirroring
;; Prolog's `name(Args) :- goals.` shape. Clauses are conde-conjoined
;; goals — `Zzz`-wrapping is automatic via `conde`, so recursive
;; relations terminate on partial answers.
(defmacro
defrel
(head &rest clauses)
(let
((name (first head)) (args (rest head)))
(list
(quote define)
name
(list (quote fn) args (cons (quote conde) clauses)))))

View File

@@ -1,71 +0,0 @@
;; lib/minikanren/diseq.sx — Phase 5 polish: =/= disequality with a
;; constraint store, generalising nafc / fd-neq to logic terms.
;;
;; The constraint store lives under the same `_fd` reserved key as the
;; CLP(FD) propagators (a disequality is just another constraint
;; closure that the existing fd-fire-store machinery re-runs).
;;
;; =/= semantics:
;; - If u and v walk to ground non-unifiable terms, succeed (drop).
;; - If they walk to terms that COULD become equal under a future
;; binding, store the constraint; re-check after each binding.
;; - If they're already equal (unify with no new bindings), fail.
;;
;; Implementation: each =/= test attempts (mk-unify wu wv s).
;; nil — distinct, keep s, drop the constraint (return s).
;; subst eq — equal, fail (return nil).
;; subst > — partially unifiable; keep the constraint, return s.
;;
;; "Substitution equal to s" is detected via key-count: mk-unify only
;; ever extends a substitution, never removes from it, so equal
;; key-count means no new bindings were needed.
(define
=/=-prop
(fn
(u v s)
(let
((s-after (mk-unify u v s)))
(cond
((= s-after nil) s)
((= (len (keys s)) (len (keys s-after))) nil)
(:else s)))))
(define
=/=
(fn
(u v)
(fn
(s)
(let
((c (fn (sp) (=/=-prop u v sp))))
(let
((s2 (fd-add-constraint s c)))
(let
((s2-or-nil (c s2)))
(let
((s3 (cond ((= s2-or-nil nil) nil) (:else (fd-fire-store s2-or-nil)))))
(cond ((= s3 nil) mzero) (:else (unit s3))))))))))
;; --- constraint-aware == ---
;;
;; Plain `==` doesn't fire the constraint store, so a binding that
;; should violate a pending =/= goes undetected. `==-cs` is the
;; drop-in replacement that fires fd-fire-store after each binding.
;; Use ==-cs in any program that mixes =/= (or fd-* goals that should
;; re-check after non-FD bindings) with regular unification.
(define
==-cs
(fn
(u v)
(fn
(s)
(let
((s2 (mk-unify u v s)))
(cond
((= s2 nil) mzero)
(:else
(let
((s3 (fd-fire-store s2)))
(cond ((= s3 nil) mzero) (:else (unit s3))))))))))

View File

@@ -1,25 +0,0 @@
;; lib/minikanren/fd.sx — Phase 6 piece A: minimal finite-domain helpers.
;;
;; A full CLP(FD) engine (arc consistency, native integer domains, fd-plus
;; etc.) is Phase 6 proper. For now we expose two small relations layered
;; on the existing list machinery — they're sufficient for permutation
;; puzzles, the N-queens-style core of constraint solving:
;;
;; (ino x dom) — x is a member of dom (alias for membero with the
;; constraint-store-friendly argument order).
;; (all-distincto l) — all elements of l are pairwise distinct.
;;
;; all-distincto uses nafc + membero on the tail — it requires the head
;; element of each recursive step to be ground enough for membero to be
;; finitary, so order matters: prefer (in x dom) goals BEFORE
;; (all-distincto (list x ...)) so values get committed first.
(define ino (fn (x dom) (membero x dom)))
(define
all-distincto
(fn
(l)
(conde
((nullo l))
((fresh (a d) (conso a d l) (nafc (membero a d)) (all-distincto d))))))

View File

@@ -1,23 +0,0 @@
;; lib/minikanren/fresh.sx — Phase 2 piece B: `fresh` for introducing
;; logic variables inside a goal body.
;;
;; (fresh (x y z) goal1 goal2 ...)
;; ≡ (let ((x (make-var)) (y (make-var)) (z (make-var)))
;; (mk-conj goal1 goal2 ...))
;;
;; A macro rather than a function so user-named vars are real lexical
;; bindings — which is also what miniKanren convention expects.
;; The empty-vars form (fresh () goal ...) is just a goal grouping.
(defmacro
fresh
(vars &rest goals)
(quasiquote
(let
(unquote (map (fn (v) (list v (list (quote make-var)))) vars))
(mk-conj (splice-unquote goals)))))
;; call-fresh — functional alternative for code that builds goals
;; programmatically:
;; ((call-fresh (fn (x) (== x 7))) empty-s) → ({:_.N 7})
(define call-fresh (fn (f) (fn (s) ((f (make-var)) s))))

View File

@@ -1,58 +0,0 @@
;; lib/minikanren/goals.sx — Phase 2 piece B: core goals.
;;
;; A goal is a function (fn (s) → stream-of-substitutions).
;; Goals built here:
;; succeed — always returns (unit s)
;; fail — always returns mzero
;; == — unifies two terms; succeeds with a singleton, else fails
;; ==-check — opt-in occurs-checked equality
;; conj2 / mk-conj — sequential conjunction of goals
;; disj2 / mk-disj — interleaved disjunction of goals (raw — `conde` adds
;; the implicit-conj-per-clause sugar in a later commit)
(define succeed (fn (s) (unit s)))
(define fail (fn (s) mzero))
(define
==
(fn
(u v)
(fn
(s)
(let ((s2 (mk-unify u v s))) (if (= s2 nil) mzero (unit s2))))))
(define
==-check
(fn
(u v)
(fn
(s)
(let ((s2 (mk-unify-check u v s))) (if (= s2 nil) mzero (unit s2))))))
(define conj2 (fn (g1 g2) (fn (s) (mk-bind (g1 s) g2))))
(define disj2 (fn (g1 g2) (fn (s) (mk-mplus (g1 s) (g2 s)))))
;; Fold goals in a list. (mk-conj-list ()) ≡ succeed; (mk-disj-list ()) ≡ fail.
(define
mk-conj-list
(fn
(gs)
(cond
((empty? gs) succeed)
((empty? (rest gs)) (first gs))
(:else (conj2 (first gs) (mk-conj-list (rest gs)))))))
(define
mk-disj-list
(fn
(gs)
(cond
((empty? gs) fail)
((empty? (rest gs)) (first gs))
(:else (disj2 (first gs) (mk-disj-list (rest gs)))))))
(define mk-conj (fn (&rest gs) (mk-conj-list gs)))
(define mk-disj (fn (&rest gs) (mk-disj-list gs)))

View File

@@ -1,151 +0,0 @@
;; lib/minikanren/intarith.sx — fast integer arithmetic via project.
;;
;; These are ground-only escapes into host arithmetic. They run at native
;; speed (host ints) but require their arguments to walk to actual numbers
;; — they are not relational the way `pluso` (Peano) is. Use them when
;; the puzzle size makes Peano impractical.
;;
;; Naming: `-i` suffix marks "integer-only" goals.
(define
pluso-i
(fn
(a b c)
(project
(a b)
(if (and (number? a) (number? b)) (== c (+ a b)) fail))))
(define
minuso-i
(fn
(a b c)
(project
(a b)
(if (and (number? a) (number? b)) (== c (- a b)) fail))))
(define
*o-i
(fn
(a b c)
(project
(a b)
(if (and (number? a) (number? b)) (== c (* a b)) fail))))
(define
lto-i
(fn
(a b)
(project
(a b)
(if (and (number? a) (and (number? b) (< a b))) succeed fail))))
(define
lteo-i
(fn
(a b)
(project
(a b)
(if (and (number? a) (and (number? b) (<= a b))) succeed fail))))
(define
neqo-i
(fn
(a b)
(project
(a b)
(if (and (number? a) (and (number? b) (not (= a b)))) succeed fail))))
(define numbero (fn (x) (project (x) (if (number? x) succeed fail))))
(define stringo (fn (x) (project (x) (if (string? x) succeed fail))))
(define symbolo (fn (x) (project (x) (if (symbol? x) succeed fail))))
(define
even-i
(fn (n) (project (n) (if (and (number? n) (even? n)) succeed fail))))
(define
odd-i
(fn (n) (project (n) (if (and (number? n) (odd? n)) succeed fail))))
(define
sortedo
(fn
(l)
(conde
((nullo l))
((fresh (a) (== l (list a))))
((fresh (a b rest mid) (conso a mid l) (conso b rest mid) (lteo-i a b) (sortedo mid))))))
(define
mino
(fn
(l m)
(conde
((fresh (a) (== l (list a)) (== m a)))
((fresh (a d rest-min) (conso a d l) (mino d rest-min) (conde ((lteo-i a rest-min) (== m a)) ((lto-i rest-min a) (== m rest-min))))))))
(define
maxo
(fn
(l m)
(conde
((fresh (a) (== l (list a)) (== m a)))
((fresh (a d rest-max) (conso a d l) (maxo d rest-max) (conde ((lteo-i rest-max a) (== m a)) ((lto-i a rest-max) (== m rest-max))))))))
(define
sumo
(fn
(l total)
(conde
((nullo l) (== total 0))
((fresh (a d rest-sum) (conso a d l) (sumo d rest-sum) (pluso-i a rest-sum total))))))
(define
producto
(fn
(l total)
(conde
((nullo l) (== total 1))
((fresh (a d rest-prod) (conso a d l) (producto d rest-prod) (*o-i a rest-prod total))))))
(define
lengtho-i
(fn
(l n)
(conde
((nullo l) (== n 0))
((fresh (a d n-1) (conso a d l) (lengtho-i d n-1) (pluso-i 1 n-1 n))))))
(define
enumerate-from-i
(fn
(start l result)
(conde
((nullo l) (nullo result))
((fresh (a d r-rest start-prime) (conso a d l) (conso (list start a) r-rest result) (pluso-i 1 start start-prime) (enumerate-from-i start-prime d r-rest))))))
(define enumerate-i (fn (l result) (enumerate-from-i 0 l result)))
(define
counto
(fn
(x l n)
(conde
((nullo l) (== n 0))
((fresh (a d n-rest) (conso a d l) (conde ((== a x) (counto x d n-rest) (pluso-i 1 n-rest n)) ((nafc (== a x)) (counto x d n))))))))
(define
mk-arith-prog
(fn
(start step len)
(cond
((= len 0) (list))
(:else (cons start (mk-arith-prog (+ start step) step (- len 1)))))))
(define
arith-progo
(fn
(start step len result)
(project (start step len) (== result (mk-arith-prog start step len)))))

View File

@@ -1,76 +0,0 @@
;; lib/minikanren/matche.sx — Phase 5 piece D: pattern matching over terms.
;;
;; (matche TARGET
;; (PATTERN1 g1 g2 ...)
;; (PATTERN2 g1 ...)
;; ...)
;;
;; Pattern grammar:
;; _ wildcard — fresh anonymous var
;; x plain symbol — fresh var, bind by name
;; ATOM literal (number, string, boolean) — must equal
;; :keyword keyword literal — emitted bare (keywords self-evaluate
;; to their string name in SX, so quoting them changes
;; their type from string to keyword)
;; () empty list — must equal
;; (p1 p2 ... pn) list pattern — recurse on each element
;;
;; The macro expands to a `conde` whose clauses are
;; `((fresh (vars-in-pat) (== target pat-expr) body...))`.
;;
;; Repeated symbol names within a pattern produce the same fresh var, so
;; they unify by `==`. Fixed-length list patterns only — head/tail
;; destructuring uses `(fresh (a d) (conso a d target) body)` directly.
;;
;; Note: the macro builds the expansion via `cons` / `list` rather than a
;; quasiquote — quasiquote does not recurse into nested lambda bodies in
;; SX, so `\`(matche-clause (quote ,target) cl)` left literal
;; `(unquote target)` in the output.
(define matche-symbol-var? (fn (s) (symbol? s)))
(define
matche-collect-vars-acc
(fn
(pat acc)
(cond
((matche-symbol-var? pat)
(if (some (fn (s) (= s pat)) acc) acc (append acc (list pat))))
((and (list? pat) (not (empty? pat)))
(reduce (fn (a p) (matche-collect-vars-acc p a)) acc pat))
(:else acc))))
(define
matche-collect-vars
(fn (pat) (matche-collect-vars-acc pat (list))))
(define
matche-pattern->expr
(fn
(pat)
(cond
((matche-symbol-var? pat) pat)
((and (list? pat) (empty? pat)) (list (quote list)))
((list? pat) (cons (quote list) (map matche-pattern->expr pat)))
((keyword? pat) pat)
(:else (list (quote quote) pat)))))
(define
matche-clause
(fn
(target cl)
(let
((pat (first cl)) (body (rest cl)))
(let
((vars (matche-collect-vars pat)))
(let
((pat-expr (matche-pattern->expr pat)))
(list
(cons
(quote fresh)
(cons vars (cons (list (quote ==) target pat-expr) body)))))))))
(defmacro
matche
(target &rest clauses)
(cons (quote conde) (map (fn (cl) (matche-clause target cl)) clauses)))

View File

@@ -1,24 +0,0 @@
;; lib/minikanren/nafc.sx — Phase 5 piece C: negation as finite failure.
;;
;; (nafc g)
;; succeeds (yields the input substitution) if g has zero answers
;; against that substitution; fails (mzero) if g has at least one.
;;
;; Caveat: `nafc` is unsound under the open-world assumption. It only
;; makes sense for goals over fully-ground terms, or with the explicit
;; understanding that adding more facts could flip the answer. Use
;; `(project (...) ...)` to ensure the relevant vars are ground first.
;;
;; Caveat 2: stream-take forces g for at least one answer; if g is
;; infinitely-ground (say, a divergent search over an unbound list),
;; nafc itself will diverge. Standard miniKanren limitation.
(define
nafc
(fn
(g)
(fn
(s)
(let
((peek (stream-take 1 (g s))))
(if (empty? peek) (unit s) mzero)))))

View File

@@ -1,51 +0,0 @@
;; lib/minikanren/peano.sx — Peano-encoded natural-number relations.
;;
;; Same encoding as `lengtho`: zero is the keyword `:z`; successors are
;; `(:s n)`. So 3 = `(:s (:s (:s :z)))`. `(:z)` and `(:s ...)` are normal
;; SX values that unify positionally — no special primitives needed.
;;
;; Peano arithmetic is the canonical miniKanren way to test addition /
;; multiplication / less-than relationally without an FD constraint store.
;; (CLP(FD) integers come in Phase 6.)
(define zeroo (fn (n) (== n :z)))
(define succ-of (fn (n m) (== m (list :s n))))
(define
pluso
(fn
(a b c)
(conde
((== a :z) (== b c))
((fresh (a-1 c-1) (== a (list :s a-1)) (== c (list :s c-1)) (pluso a-1 b c-1))))))
(define minuso (fn (a b c) (pluso b c a)))
(define lteo (fn (a b) (fresh (k) (pluso a k b))))
(define lto (fn (a b) (fresh (sa) (succ-of a sa) (lteo sa b))))
(define
eveno
(fn
(n)
(conde
((== n :z))
((fresh (m) (== n (list :s (list :s m))) (eveno m))))))
(define
oddo
(fn
(n)
(conde
((== n (list :s :z)))
((fresh (m) (== n (list :s (list :s m))) (oddo m))))))
(define
*o
(fn
(a b c)
(conde
((== a :z) (== c :z))
((fresh (a-1 ab-1) (== a (list :s a-1)) (*o a-1 b ab-1) (pluso b ab-1 c))))))

View File

@@ -1,25 +0,0 @@
;; lib/minikanren/project.sx — Phase 5 piece B: `project`.
;;
;; (project (x y) g1 g2 ...)
;; — rebinds each named var to (mk-walk* var s) within the body's
;; lexical scope, then runs the conjunction of the body goals on
;; the same substitution. Use to escape into regular SX (arithmetic,
;; string ops, host predicates) when you need a ground value.
;;
;; If any of the projected vars is still unbound at this point, the body
;; sees the raw `(:var NAME)` term — that is intentional and lets you
;; mix project with `(== ground? var)` patterns or with conda guards.
;;
;; Hygiene: substitution parameter is gensym'd so it doesn't capture user
;; vars (`s` is a popular relation parameter name).
(defmacro
project
(vars &rest goals)
(let
((s-sym (gensym "proj-s-")))
(quasiquote
(fn
((unquote s-sym))
((let (unquote (map (fn (v) (list v (list (quote mk-walk*) v s-sym))) vars)) (mk-conj (splice-unquote goals)))
(unquote s-sym))))))

View File

@@ -1,67 +0,0 @@
;; lib/minikanren/queens.sx — N-queens via ino + all-distincto + project.
;;
;; Encoding: q = (c1 c2 ... cn) where ci is the column of the queen in
;; row i. Each ci ∈ {1..n}; all distinct (no two queens share a column);
;; no two queens on the same diagonal (|ci - cj| ≠ |i - j| for i ≠ j).
;;
;; The diagonal check uses `project` to escape into host arithmetic
;; once both column values are ground.
(define
safe-diag
(fn
(a b dist)
(project (a b) (if (= (abs (- a b)) dist) fail succeed))))
(define
safe-cell-vs-rest
(fn
(c c-row others next-row)
(cond
((empty? others) succeed)
(:else
(mk-conj
(safe-diag c (first others) (- next-row c-row))
(safe-cell-vs-rest c c-row (rest others) (+ next-row 1)))))))
(define
all-cells-safe
(fn
(cols start-row)
(cond
((empty? cols) succeed)
(:else
(mk-conj
(safe-cell-vs-rest
(first cols)
start-row
(rest cols)
(+ start-row 1))
(all-cells-safe (rest cols) (+ start-row 1)))))))
(define
range-1-to-n
(fn
(n)
(cond
((= n 0) (list))
(:else (append (range-1-to-n (- n 1)) (list n))))))
(define
ino-each
(fn
(cols dom)
(cond
((empty? cols) succeed)
(:else (mk-conj (ino (first cols) dom) (ino-each (rest cols) dom))))))
(define
queens-cols
(fn
(cols n)
(let
((dom (range-1-to-n n)))
(mk-conj
(ino-each cols dom)
(all-distincto cols)
(all-cells-safe cols 1)))))

View File

@@ -1,361 +0,0 @@
;; lib/minikanren/relations.sx — Phase 4 standard relations.
;;
;; Programs use native SX lists as data. Relations decompose lists via the
;; tagged cons-cell shape `(:cons h t)` because SX has no improper pairs;
;; the unifier treats `(:cons h t)` and the native list `(h . t)` as
;; equivalent, and `mk-walk*` flattens cons cells back to flat lists for
;; reification.
;; --- pair / list shape relations ---
(define nullo (fn (l) (== l (list))))
(define pairo (fn (p) (fresh (a d) (== p (mk-cons a d)))))
(define caro (fn (p a) (fresh (d) (== p (mk-cons a d)))))
(define cdro (fn (p d) (fresh (a) (== p (mk-cons a d)))))
(define conso (fn (a d p) (== p (mk-cons a d))))
(define firsto caro)
(define resto cdro)
(define
listo
(fn (l) (conde ((nullo l)) ((fresh (a d) (conso a d l) (listo d))))))
;; --- appendo: the canary ---
;;
;; (appendo l s ls) — `ls` is the concatenation of `l` and `s`.
;; Runs forwards (l, s known → ls), backwards (ls known → all (l, s) pairs),
;; and bidirectionally (mix of bound + unbound).
(define
appendo
(fn
(l s ls)
(conde
((nullo l) (== s ls))
((fresh (a d res) (conso a d l) (conso a res ls) (appendo d s res))))))
;; --- membero ---
;; (membero x l) — x appears (at least once) in l.
(define
appendo3
(fn
(l1 l2 l3 result)
(fresh (l12) (appendo l1 l2 l12) (appendo l12 l3 result))))
(define
partitiono
(fn
(pred l yes no)
(conde
((nullo l) (nullo yes) (nullo no))
((fresh (a d y-rest n-rest) (conso a d l) (conde ((pred a) (conso a y-rest yes) (== no n-rest) (partitiono pred d y-rest n-rest)) ((nafc (pred a)) (== yes y-rest) (conso a n-rest no) (partitiono pred d y-rest n-rest))))))))
(define
foldr-o
(fn
(rel l acc result)
(conde
((nullo l) (== result acc))
((fresh (a d r-rest) (conso a d l) (foldr-o rel d acc r-rest) (rel a r-rest result))))))
(define
foldl-o
(fn
(rel l acc result)
(conde
((nullo l) (== result acc))
((fresh (a d new-acc) (conso a d l) (rel acc a new-acc) (foldl-o rel d new-acc result))))))
(define
flat-mapo
(fn
(rel l result)
(conde
((nullo l) (nullo result))
((fresh (a d a-result rest-result) (conso a d l) (rel a a-result) (flat-mapo rel d rest-result) (appendo a-result rest-result result))))))
(define
nub-o
(fn
(l result)
(conde
((nullo l) (nullo result))
((fresh (a d r-rest) (conso a d l) (conde ((membero a d) (nub-o d result)) ((nafc (membero a d)) (conso a r-rest result) (nub-o d r-rest))))))))
(define
take-while-o
(fn
(pred l result)
(conde
((nullo l) (nullo result))
((fresh (a d r-rest) (conso a d l) (conde ((pred a) (conso a r-rest result) (take-while-o pred d r-rest)) ((nafc (pred a)) (== result (list)))))))))
(define
drop-while-o
(fn
(pred l result)
(conde
((nullo l) (nullo result))
((fresh (a d) (conso a d l) (conde ((pred a) (drop-while-o pred d result)) ((nafc (pred a)) (== result l))))))))
(define
membero
(fn
(x l)
(conde
((fresh (d) (conso x d l)))
((fresh (a d) (conso a d l) (membero x d))))))
(define
not-membero
(fn
(x l)
(conde
((nullo l))
((fresh (a d) (conso a d l) (nafc (== a x)) (not-membero x d))))))
(define
subseto
(fn
(l1 l2)
(conde
((nullo l1))
((fresh (a d) (conso a d l1) (membero a l2) (subseto d l2))))))
(define
reverseo
(fn
(l r)
(conde
((nullo l) (nullo r))
((fresh (a d res-rev) (conso a d l) (reverseo d res-rev) (appendo res-rev (list a) r))))))
(define
rev-acco
(fn
(l acc result)
(conde
((nullo l) (== result acc))
((fresh (a d acc-prime) (conso a d l) (conso a acc acc-prime) (rev-acco d acc-prime result))))))
(define rev-2o (fn (l result) (rev-acco l (list) result)))
(define palindromeo (fn (l) (fresh (rev) (reverseo l rev) (== l rev))))
(define prefixo (fn (p l) (fresh (rest) (appendo p rest l))))
(define suffixo (fn (s l) (fresh (front) (appendo front s l))))
(define
subo
(fn
(s l)
(fresh
(front-and-s back front)
(appendo front-and-s back l)
(appendo front s front-and-s))))
(define
selecto
(fn
(x rest l)
(conde
((conso x rest l))
((fresh (a d r) (conso a d l) (conso a r rest) (selecto x r d))))))
(define
lengtho
(fn
(l n)
(conde
((nullo l) (== n :z))
((fresh (a d n-1) (conso a d l) (== n (list :s n-1)) (lengtho d n-1))))))
(define
inserto
(fn
(a l p)
(conde
((conso a l p))
((fresh (h t pt) (conso h t l) (conso h pt p) (inserto a t pt))))))
(define
permuteo
(fn
(l p)
(conde
((nullo l) (nullo p))
((fresh (a d perm-d) (conso a d l) (permuteo d perm-d) (inserto a perm-d p))))))
(define
flatteno
(fn
(tree flat)
(conde
((nullo tree) (nullo flat))
((pairo tree)
(fresh
(h t hf tf)
(conso h t tree)
(flatteno h hf)
(flatteno t tf)
(appendo hf tf flat)))
((nafc (nullo tree)) (nafc (pairo tree)) (== flat (list tree))))))
(define
rembero
(fn
(x l out)
(conde
((nullo l) (nullo out))
((fresh (a d) (conso a d l) (== a x) (== out d)))
((fresh (a d res) (conso a d l) (nafc (== a x)) (conso a res out) (rembero x d res))))))
(define
removeo-allo
(fn
(x l result)
(conde
((nullo l) (nullo result))
((fresh (a d) (conso a d l) (== a x) (removeo-allo x d result)))
((fresh (a d r-rest) (conso a d l) (nafc (== a x)) (conso a r-rest result) (removeo-allo x d r-rest))))))
(define
assoco
(fn
(key pairs val)
(fresh
(rest)
(conde
((conso (list key val) rest pairs))
((fresh (other) (conso other rest pairs) (assoco key rest val)))))))
(define
nth-o
(fn
(n l elem)
(conde
((== n :z) (fresh (d) (conso elem d l)))
((fresh (n-1 a d) (== n (list :s n-1)) (conso a d l) (nth-o n-1 d elem))))))
(define
samelengtho
(fn
(l1 l2)
(conde
((nullo l1) (nullo l2))
((fresh (a d a-prime d-prime) (conso a d l1) (conso a-prime d-prime l2) (samelengtho d d-prime))))))
(define
mapo
(fn
(rel l1 l2)
(conde
((nullo l1) (nullo l2))
((fresh (a d a-prime d-prime) (conso a d l1) (conso a-prime d-prime l2) (rel a a-prime) (mapo rel d d-prime))))))
(define
iterate-no
(fn
(rel n x result)
(conde
((== n :z) (== result x))
((fresh (n-1 mid) (== n (list :s n-1)) (rel x mid) (iterate-no rel n-1 mid result))))))
(define
pairlisto
(fn
(l1 l2 pairs)
(conde
((nullo l1) (nullo l2) (nullo pairs))
((fresh (a1 d1 a2 d2 d-pairs) (conso a1 d1 l1) (conso a2 d2 l2) (conso (list a1 a2) d-pairs pairs) (pairlisto d1 d2 d-pairs))))))
(define
zip-with-o
(fn
(rel l1 l2 result)
(conde
((nullo l1) (nullo l2) (nullo result))
((fresh (a1 d1 a2 d2 a-result d-result) (conso a1 d1 l1) (conso a2 d2 l2) (rel a1 a2 a-result) (conso a-result d-result result) (zip-with-o rel d1 d2 d-result))))))
(define
swap-firsto
(fn
(l result)
(fresh
(a b rest mid-l mid-r)
(conso a mid-l l)
(conso b rest mid-l)
(conso b mid-r result)
(conso a rest mid-r))))
(define
everyo
(fn
(rel l)
(conde
((nullo l))
((fresh (a d) (conso a d l) (rel a) (everyo rel d))))))
(define
someo
(fn
(rel l)
(conde
((fresh (a d) (conso a d l) (rel a)))
((fresh (a d) (conso a d l) (someo rel d))))))
(define
lasto
(fn
(l x)
(conde
((conso x (list) l))
((fresh (a d) (conso a d l) (lasto d x))))))
(define
init-o
(fn
(l init)
(conde
((fresh (x) (conso x (list) l) (== init (list))))
((fresh (a d d-init) (conso a d l) (conso a d-init init) (init-o d d-init))))))
(define
tako
(fn
(n l prefix)
(conde
((== n :z) (== prefix (list)))
((fresh (n-1 a d p-rest) (== n (list :s n-1)) (conso a d l) (conso a p-rest prefix) (tako n-1 d p-rest))))))
(define
dropo
(fn
(n l suffix)
(conde
((== n :z) (== suffix l))
((fresh (n-1 a d) (== n (list :s n-1)) (conso a d l) (dropo n-1 d suffix))))))
(define
repeato
(fn
(x n result)
(conde
((== n :z) (== result (list)))
((fresh (n-1 r-rest) (== n (list :s n-1)) (conso x r-rest result) (repeato x n-1 r-rest))))))
(define
concato
(fn
(lists result)
(conde
((nullo lists) (nullo result))
((fresh (h t r-rest) (conso h t lists) (appendo h r-rest result) (concato t r-rest))))))

View File

@@ -1,56 +0,0 @@
;; lib/minikanren/run.sx — Phase 3: drive a goal + reify the query var.
;;
;; reify-name N — make the canonical "_.N" reified symbol.
;; reify-s term rs — walk term in rs, add a mapping from each fresh
;; unbound var to its _.N name (left-to-right order).
;; reify q s — walk* q in s, build reify-s, walk* again to
;; substitute reified names in.
;; run-n n q-name g... — defmacro: bind q-name to a fresh var, conj goals,
;; take ≤ n answers from the stream, reify each
;; through q-name. n = -1 takes all (used by run*).
;; run* — defmacro: (run* q g...) ≡ (run-n -1 q g...)
;; run — defmacro: (run n q g...) ≡ (run-n n q g...)
;; The two-segment form is the standard TRS API.
(define reify-name (fn (n) (make-symbol (str "_." n))))
(define
reify-s
(fn
(term rs)
(let
((w (mk-walk term rs)))
(cond
((is-var? w) (extend (var-name w) (reify-name (len rs)) rs))
((mk-list-pair? w) (reduce (fn (acc a) (reify-s a acc)) rs w))
(:else rs)))))
(define
reify
(fn
(term s)
(let
((w (mk-walk* term s)))
(let ((rs (reify-s w (empty-subst)))) (mk-walk* w rs)))))
(defmacro
run-n
(n q-name &rest goals)
(quasiquote
(let
(((unquote q-name) (make-var)))
(map
(fn (s) (reify (unquote q-name) s))
(stream-take
(unquote n)
((mk-conj (splice-unquote goals)) empty-s))))))
(defmacro
run*
(q-name &rest goals)
(quasiquote (run-n -1 (unquote q-name) (splice-unquote goals))))
(defmacro
run
(n q-name &rest goals)
(quasiquote (run-n (unquote n) (unquote q-name) (splice-unquote goals))))

View File

@@ -1,66 +0,0 @@
;; lib/minikanren/stream.sx — Phase 2 piece A: lazy streams of substitutions.
;;
;; SX has no improper pairs (cons requires a list cdr), so we use a
;; tagged stream-cell shape for mature stream elements:
;;
;; stream ::= mzero empty (the SX empty list)
;; | (:s HEAD TAIL) mature cell, TAIL is a stream
;; | thunk (fn () ...) → stream when forced
;;
;; HEAD is a substitution dict. TAIL is again a stream (possibly a thunk),
;; which is what gives us laziness — mk-mplus can return a mature head with
;; a thunk in the tail, deferring the rest of the search.
(define mzero (list))
(define s-cons (fn (h t) (list :s h t)))
(define
s-cons?
(fn (s) (and (list? s) (not (empty? s)) (= (first s) :s))))
(define s-car (fn (s) (nth s 1)))
(define s-cdr (fn (s) (nth s 2)))
(define unit (fn (s) (s-cons s mzero)))
(define stream-pause? (fn (s) (and (not (list? s)) (callable? s))))
;; mk-mplus — interleave two streams. If s1 is paused we suspend and
;; swap (Reasoned Schemer "interleave"); otherwise mature-cons head with
;; mk-mplus of the rest.
(define
mk-mplus
(fn
(s1 s2)
(cond
((empty? s1) s2)
((stream-pause? s1) (fn () (mk-mplus s2 (s1))))
(:else (s-cons (s-car s1) (mk-mplus (s-cdr s1) s2))))))
;; mk-bind — apply goal g to every substitution in stream s, mk-mplus-ing.
(define
mk-bind
(fn
(s g)
(cond
((empty? s) mzero)
((stream-pause? s) (fn () (mk-bind (s) g)))
(:else (mk-mplus (g (s-car s)) (mk-bind (s-cdr s) g))))))
;; stream-take — force up to n results out of a (possibly lazy) stream
;; into a flat SX list of substitutions. n = -1 means take all.
(define
stream-take
(fn
(n s)
(cond
((= n 0) (list))
((empty? s) (list))
((stream-pause? s) (stream-take n (s)))
(:else
(cons
(s-car s)
(stream-take
(if (= n -1) -1 (- n 1))
(s-cdr s)))))))

View File

@@ -1,94 +0,0 @@
;; lib/minikanren/tabling-slg.sx — Phase 7 piece A: SLG-style tabling.
;;
;; Naive memoization (table-1/2/3 in tabling.sx) drains the body's
;; answer stream eagerly, then caches. Recursive tabled calls with the
;; SAME ground key see an empty cache (the in-progress entry doesn't
;; exist), so they recurse and the host overflows on cyclic relations.
;;
;; This module ships the in-progress-sentinel piece of SLG resolution:
;; before evaluating the body, mark the cache entry as :in-progress;
;; any recursive call to the same key sees the sentinel and returns
;; mzero (no answers yet). Outer recursion thus terminates on cycles.
;; Limitation: a single pass — answers found by cycle-dependent
;; recursive calls are NOT discovered. Full SLG with fixed-point
;; iteration (re-running until no new answers) is left for follow-up.
(define
table-2-slg-iter
(fn
(rel-fn input output s key prev-vals)
(begin
(mk-tab-store! key prev-vals)
(let
((all-substs (stream-take -1 ((rel-fn input output) s))))
(let
((vals (map (fn (s2) (mk-walk* output s2)) all-substs)))
(cond
((= (len vals) (len prev-vals))
(begin
(mk-tab-store! key vals)
(mk-tab-replay-vals vals output s)))
(:else (table-2-slg-iter rel-fn input output s key vals))))))))
(define
table-2-slg
(fn
(name rel-fn)
(fn
(input output)
(fn
(s)
(let
((winput (mk-walk* input s)))
(cond
((mk-tab-ground-term? winput)
(let
((key (str name "/slg/" winput)))
(let
((cached (mk-tab-lookup key)))
(cond
((not (= cached :miss))
(mk-tab-replay-vals cached output s))
(:else
(table-2-slg-iter rel-fn input output s key (list)))))))
(:else ((rel-fn input output) s))))))))
(define
table-3-slg-iter
(fn
(rel-fn i1 i2 output s key prev-vals)
(begin
(mk-tab-store! key prev-vals)
(let
((all-substs (stream-take -1 ((rel-fn i1 i2 output) s))))
(let
((vals (map (fn (s2) (mk-walk* output s2)) all-substs)))
(cond
((= (len vals) (len prev-vals))
(begin
(mk-tab-store! key vals)
(mk-tab-replay-vals vals output s)))
(:else (table-3-slg-iter rel-fn i1 i2 output s key vals))))))))
(define
table-3-slg
(fn
(name rel-fn)
(fn
(i1 i2 output)
(fn
(s)
(let
((wi1 (mk-walk* i1 s)) (wi2 (mk-walk* i2 s)))
(cond
((and (mk-tab-ground-term? wi1) (mk-tab-ground-term? wi2))
(let
((key (str name "/slg3/" wi1 "/" wi2)))
(let
((cached (mk-tab-lookup key)))
(cond
((not (= cached :miss))
(mk-tab-replay-vals cached output s))
(:else
(table-3-slg-iter rel-fn i1 i2 output s key (list)))))))
(:else ((rel-fn i1 i2 output) s))))))))

View File

@@ -1,157 +0,0 @@
;; lib/minikanren/tabling.sx — Phase 7 piece A: naive memoization.
;;
;; A `table-2` wrapper for 2-arg relations (input, output). Caches by
;; ground input (walked at call time). On hit, replays the cached output
;; values; on miss, runs the relation, collects all output values from
;; the answer stream, stores, then replays.
;;
;; Limitations of naive memoization (vs proper SLG / producer-consumer
;; tabling):
;; - Each call must terminate before its result enters the cache —
;; so cyclic recursive calls with the SAME ground input would still
;; diverge (not addressed here).
;; - Caching by full ground walk only; partially-ground args fall
;; through to the underlying relation.
;;
;; Despite the limitations, naive memoization is enough for the
;; canonical demo: Fibonacci goes from exponential to linear because
;; each fib(k) result is computed at most once.
;;
;; Cache lifetime: a single global mk-tab-cache. Use `(mk-tab-clear!)`
;; between independent queries.
(define mk-tab-cache {})
(define mk-tab-clear! (fn () (set! mk-tab-cache {})))
(define
mk-tab-lookup
(fn
(key)
(cond
((has-key? mk-tab-cache key) (get mk-tab-cache key))
(:else :miss))))
(define
mk-tab-store!
(fn (key vals) (set! mk-tab-cache (assoc mk-tab-cache key vals))))
(define
mk-tab-ground-term?
(fn
(t)
(cond
((is-var? t) false)
((mk-cons-cell? t)
(and
(mk-tab-ground-term? (mk-cons-head t))
(mk-tab-ground-term? (mk-cons-tail t))))
((mk-list-pair? t) (every? mk-tab-ground-term? t))
(:else true))))
(define
mk-tab-replay-vals
(fn
(vals output s)
(cond
((empty? vals) mzero)
(:else
(let
((sp (mk-unify output (first vals) s)))
(let
((this-stream (cond ((= sp nil) mzero) (:else (unit sp)))))
(mk-mplus this-stream (mk-tab-replay-vals (rest vals) output s))))))))
(define
table-2
(fn
(name rel-fn)
(fn
(input output)
(fn
(s)
(let
((winput (mk-walk* input s)))
(cond
((mk-tab-ground-term? winput)
(let
((key (str name "@" winput)))
(let
((cached (mk-tab-lookup key)))
(cond
((= cached :miss)
(let
((all-substs (stream-take -1 ((rel-fn input output) s))))
(let
((vals (map (fn (s2) (mk-walk* output s2)) all-substs)))
(begin
(mk-tab-store! key vals)
(mk-tab-replay-vals vals output s)))))
(:else (mk-tab-replay-vals cached output s))))))
(:else ((rel-fn input output) s))))))))
;; --- table-1: 1-arg relation (one input, no output to cache) ---
;; The relation is a predicate `(p input)` that succeeds or fails.
;; Cache stores either :ok or :no.
(define
table-1
(fn
(name rel-fn)
(fn
(input)
(fn
(s)
(let
((winput (mk-walk* input s)))
(cond
((mk-tab-ground-term? winput)
(let
((key (str name "@1@" winput)))
(let
((cached (mk-tab-lookup key)))
(cond
((= cached :miss)
(let
((stream ((rel-fn input) s)))
(let
((peek (stream-take 1 stream)))
(cond
((empty? peek)
(begin (mk-tab-store! key :no) mzero))
(:else (begin (mk-tab-store! key :ok) stream))))))
((= cached :ok) (unit s))
((= cached :no) mzero)
(:else mzero)))))
(:else ((rel-fn input) s))))))))
;; --- table-3: 3-arg relation (input1 input2 output) ---
;; Cache keyed by (input1, input2). Output values cached as a list.
(define
table-3
(fn
(name rel-fn)
(fn
(i1 i2 output)
(fn
(s)
(let
((wi1 (mk-walk* i1 s)) (wi2 (mk-walk* i2 s)))
(cond
((and (mk-tab-ground-term? wi1) (mk-tab-ground-term? wi2))
(let
((key (str name "@3@" wi1 "/" wi2)))
(let
((cached (mk-tab-lookup key)))
(cond
((= cached :miss)
(let
((all-substs (stream-take -1 ((rel-fn i1 i2 output) s))))
(let
((vals (map (fn (s2) (mk-walk* output s2)) all-substs)))
(begin
(mk-tab-store! key vals)
(mk-tab-replay-vals vals output s)))))
(:else (mk-tab-replay-vals cached output s))))))
(:else ((rel-fn i1 i2 output) s))))))))

View File

@@ -1,49 +0,0 @@
;; lib/minikanren/tests/appendo3.sx — 3-list append.
(mk-test
"appendo3-forward"
(run*
q
(appendo3
(list 1 2)
(list 3 4)
(list 5 6)
q))
(list
(list 1 2 3 4 5 6)))
(mk-test
"appendo3-empty-everything"
(run* q (appendo3 (list) (list) (list) q))
(list (list)))
(mk-test
"appendo3-recover-middle"
(run*
q
(appendo3
(list 1 2)
q
(list 5 6)
(list 1 2 3 4 5 6)))
(list (list 3 4)))
(mk-test
"appendo3-empty-middle"
(run*
q
(appendo3
(list 1 2)
(list)
(list 3 4)
q))
(list (list 1 2 3 4)))
(mk-test
"appendo3-empty-first-and-last"
(run*
q
(appendo3 (list) (list 1 2 3) (list) q))
(list (list 1 2 3)))
(mk-tests-run!)

View File

@@ -1,33 +0,0 @@
;; lib/minikanren/tests/arith-prog.sx — arithmetic progression generation.
(mk-test
"arith-progo-zero-len"
(run* q (arith-progo 5 1 0 q))
(list (list)))
(mk-test
"arith-progo-1-to-5"
(run* q (arith-progo 1 1 5 q))
(list (list 1 2 3 4 5)))
(mk-test
"arith-progo-evens-from-0"
(run* q (arith-progo 0 2 5 q))
(list (list 0 2 4 6 8)))
(mk-test
"arith-progo-descending"
(run* q (arith-progo 10 -1 4 q))
(list (list 10 9 8 7)))
(mk-test
"arith-progo-zero-step"
(run* q (arith-progo 7 0 3 q))
(list (list 7 7 7)))
(mk-test
"arith-progo-negative-start"
(run* q (arith-progo -3 2 4 q))
(list (list -3 -1 1 3)))
(mk-tests-run!)

View File

@@ -1,54 +0,0 @@
;; lib/minikanren/tests/btree-walko.sx — walk a leaves-of-binary-tree relation
;; using matche dispatch on (:leaf v) and (:node left right) patterns.
(define
btree-walko
(fn
(tree v)
(matche
tree
((:leaf x) (== v x))
((:node l r) (conde ((btree-walko l v)) ((btree-walko r v)))))))
;; A small test tree: ((1 2) (3 (4 5))).
(define
test-btree
(list
:node (list :node (list :leaf 1) (list :leaf 2))
(list
:node (list :leaf 3)
(list :node (list :leaf 4) (list :leaf 5)))))
(mk-test
"btree-walko-enumerates-all-leaves"
(let
((leaves (run* q (btree-walko test-btree q))))
(and
(= (len leaves) 5)
(and
(some (fn (l) (= l 1)) leaves)
(and
(some (fn (l) (= l 2)) leaves)
(and
(some (fn (l) (= l 3)) leaves)
(and
(some (fn (l) (= l 4)) leaves)
(some (fn (l) (= l 5)) leaves)))))))
true)
(mk-test
"btree-walko-find-3-membership"
(run 1 q (btree-walko test-btree 3))
(list (make-symbol "_.0")))
(mk-test
"btree-walko-find-99-not-present"
(run* q (btree-walko test-btree 99))
(list))
(mk-test
"btree-walko-leaf-only"
(run* q (btree-walko (list :leaf 42) q))
(list 42))
(mk-tests-run!)

View File

@@ -1,87 +0,0 @@
;; lib/minikanren/tests/classics.sx — small classic-style puzzles that
;; exercise the full system end to end (relations + conde + matche +
;; fresh + run*). Each test is a self-contained miniKanren program.
;; -----------------------------------------------------------------------
;; Pet puzzle (3 friends, 3 pets, 1-each).
;; -----------------------------------------------------------------------
(mk-test
"classics-pet-puzzle"
(run*
q
(fresh
(a b c)
(== q (list a b c))
(permuteo (list :dog :cat :fish) (list a b c))
(== b :fish)
(conde ((== a :cat)) ((== a :fish)))))
(list (list :cat :fish :dog)))
;; -----------------------------------------------------------------------
;; Family-relations puzzle (uses membero on a fact list).
;; -----------------------------------------------------------------------
(define
parent-facts
(list
(list "alice" "bob")
(list "alice" "carol")
(list "bob" "dave")
(list "carol" "eve")
(list "dave" "frank")))
(define parento (fn (x y) (membero (list x y) parent-facts)))
(define grandparento (fn (x z) (fresh (y) (parento x y) (parento y z))))
(mk-test
"classics-grandparents-of-frank"
(run* q (grandparento q "frank"))
(list "bob"))
(mk-test
"classics-grandchildren-of-alice"
(run* q (grandparento "alice" q))
(list "dave" "eve"))
;; -----------------------------------------------------------------------
;; Symbolic differentiation, matche-driven.
;; Variable :x: d/dx x = 1
;; Sum (:+ a b): d/dx (a+b) = (da + db)
;; Product (:* a b): d/dx (a*b) = (da*b + a*db)
;; -----------------------------------------------------------------------
(define
diffo
(fn
(expr var d)
(matche
expr
(:x (== d 1))
((:+ a b)
(fresh
(da db)
(== d (list :+ da db))
(diffo a var da)
(diffo b var db)))
((:* a b)
(fresh
(da db)
(== d (list :+ (list :* da b) (list :* a db)))
(diffo a var da)
(diffo b var db))))))
(mk-test "classics-diff-of-x" (run* q (diffo :x :x q)) (list 1))
(mk-test
"classics-diff-of-x-plus-x"
(run* q (diffo (list :+ :x :x) :x q))
(list (list :+ 1 1)))
(mk-test
"classics-diff-of-x-times-x"
(run* q (diffo (list :* :x :x) :x q))
(list (list :+ (list :* 1 :x) (list :* :x 1))))
(mk-tests-run!)

View File

@@ -1,316 +0,0 @@
;; lib/minikanren/tests/clpfd-bounds.sx — Phase 6 piece B: bounds-consistency
;; for fd-plus and fd-times in the partial- and all-domain cases.
;;
;; We probe domains directly (peek at the FD store) before any labelling
;; happens. This isolates the propagator's narrowing behaviour from the
;; search engine.
(define
probe-dom
(fn
(goal var-key)
(let
((s (first (stream-take 1 (goal empty-s)))))
(cond ((= s nil) :no-subst) (:else (fd-domain-of s var-key))))))
;; --- fd-plus partial-domain narrowing ---
(mk-test
"fd-plus-vvn-narrows-x"
(let
((x (mk-var "x")) (y (mk-var "y")))
(probe-dom
(mk-conj
(fd-in
x
(list
1
2
3
4
5
6
7
8
9
10))
(fd-in y (list 1 2 3))
(fd-plus x y 10))
"x"))
(list 7 8 9))
(mk-test
"fd-plus-vvn-narrows-y"
(let
((x (mk-var "x")) (y (mk-var "y")))
(probe-dom
(mk-conj
(fd-in
x
(list
1
2
3
4
5
6
7
8
9
10))
(fd-in y (list 1 2 3))
(fd-plus x y 10))
"y"))
(list 1 2 3))
(mk-test
"fd-plus-nvv-narrows"
(let
((y (mk-var "y")) (z (mk-var "z")))
(probe-dom
(mk-conj
(fd-in y (list 1 2 3))
(fd-in
z
(list
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20))
(fd-plus 5 y z))
"z"))
(list 6 7 8))
(mk-test
"fd-plus-vvv-narrows-z"
(let
((x (mk-var "x")) (y (mk-var "y")) (z (mk-var "z")))
(probe-dom
(mk-conj
(fd-in x (list 1 2 3))
(fd-in y (list 1 2 3))
(fd-in
z
(list
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20))
(fd-plus x y z))
"z"))
(list 2 3 4 5 6))
(mk-test
"fd-plus-vvv-narrows-x"
(let
((x (mk-var "x")) (y (mk-var "y")) (z (mk-var "z")))
(probe-dom
(mk-conj
(fd-in
x
(list
1
2
3
4
5
6
7
8
9
10))
(fd-in y (list 1 2 3))
(fd-in z (list 5 6 7))
(fd-plus x y z))
"x"))
(list 2 3 4 5 6))
;; --- fd-times partial-domain narrowing (positive domains) ---
(mk-test
"fd-times-vvn-narrows"
(let
((x (mk-var "x")) (y (mk-var "y")))
(probe-dom
(mk-conj
(fd-in
x
(list
1
2
3
4
5
6))
(fd-in
y
(list
1
2
3
4
5
6))
(fd-times x y 12))
"x"))
(list 2 3 4 5 6))
(mk-test
"fd-times-nvv-narrows"
(let
((y (mk-var "y")) (z (mk-var "z")))
(probe-dom
(mk-conj
(fd-in y (list 1 2 3 4))
(fd-in
z
(list
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20))
(fd-times 3 y z))
"z"))
(list
3
4
5
6
7
8
9
10
11
12))
(mk-test
"fd-times-vvv-narrows"
(let
((x (mk-var "x")) (y (mk-var "y")) (z (mk-var "z")))
(probe-dom
(mk-conj
(fd-in x (list 1 2 3))
(fd-in y (list 1 2 3))
(fd-in
z
(list
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20))
(fd-times x y z))
"z"))
(list
1
2
3
4
5
6
7
8
9))
;; --- bounds force impossible branches to fail early ---
(mk-test
"fd-plus-impossible-via-bounds"
(let
((x (mk-var "x")) (y (mk-var "y")))
(probe-dom
(mk-conj
(fd-in
x
(list
1
2
3
4
5
6
7
8
9
10))
(fd-in
y
(list
1
2
3
4
5
6
7
8
9
10))
(fd-plus x y 100))
"x"))
:no-subst)
(mk-tests-run!)

View File

@@ -1,52 +0,0 @@
;; lib/minikanren/tests/clpfd-distinct.sx — fd-distinct (alldifferent).
(mk-test
"fd-distinct-empty"
(run* q (fd-distinct (list)))
(list (make-symbol "_.0")))
(mk-test
"fd-distinct-singleton"
(run* q (fd-distinct (list 5)))
(list (make-symbol "_.0")))
(mk-test
"fd-distinct-pair-distinct"
(run* q (fd-distinct (list 1 2)))
(list (make-symbol "_.0")))
(mk-test
"fd-distinct-pair-equal-fails"
(run* q (fd-distinct (list 5 5)))
(list))
(mk-test
"fd-distinct-3-perms-of-3"
(let
((res (run* q (fresh (a b c) (fd-in a (list 1 2 3)) (fd-in b (list 1 2 3)) (fd-in c (list 1 2 3)) (fd-distinct (list a b c)) (fd-label (list a b c)) (== q (list a b c))))))
(= (len res) 6))
true)
(mk-test
"fd-distinct-4-perms-of-4-count"
(let
((res (run* q (fresh (a b c d) (fd-in a (list 1 2 3 4)) (fd-in b (list 1 2 3 4)) (fd-in c (list 1 2 3 4)) (fd-in d (list 1 2 3 4)) (fd-distinct (list a b c d)) (fd-label (list a b c d)) (== q (list a b c d))))))
(= (len res) 24))
true)
(mk-test
"fd-distinct-pigeonhole-fails"
(run*
q
(fresh
(a b c d)
(fd-in a (list 1 2 3))
(fd-in b (list 1 2 3))
(fd-in c (list 1 2 3))
(fd-in d (list 1 2 3))
(fd-distinct (list a b c d))
(fd-label (list a b c d))
(== q (list a b c d))))
(list))
(mk-tests-run!)

View File

@@ -1,133 +0,0 @@
;; lib/minikanren/tests/clpfd-domains.sx — Phase 6 piece B: domain primitives.
;; --- domain construction ---
(mk-test
"fd-dom-from-list-sorts"
(fd-dom-from-list
(list 3 1 2 1 5))
(list 1 2 3 5))
(mk-test "fd-dom-from-list-empty" (fd-dom-from-list (list)) (list))
(mk-test
"fd-dom-from-list-single"
(fd-dom-from-list (list 7))
(list 7))
(mk-test
"fd-dom-range-1-5"
(fd-dom-range 1 5)
(list 1 2 3 4 5))
(mk-test "fd-dom-range-empty" (fd-dom-range 5 1) (list))
;; --- predicates ---
(mk-test "fd-dom-empty-yes" (fd-dom-empty? (list)) true)
(mk-test "fd-dom-empty-no" (fd-dom-empty? (list 1)) false)
(mk-test "fd-dom-singleton-yes" (fd-dom-singleton? (list 5)) true)
(mk-test
"fd-dom-singleton-multi"
(fd-dom-singleton? (list 1 2))
false)
(mk-test "fd-dom-singleton-empty" (fd-dom-singleton? (list)) false)
(mk-test
"fd-dom-min"
(fd-dom-min (list 3 7 9))
3)
(mk-test
"fd-dom-max"
(fd-dom-max (list 3 7 9))
9)
(mk-test
"fd-dom-member-yes"
(fd-dom-member?
3
(list 1 2 3 4))
true)
(mk-test
"fd-dom-member-no"
(fd-dom-member?
9
(list 1 2 3 4))
false)
;; --- intersect / without ---
(mk-test
"fd-dom-intersect"
(fd-dom-intersect
(list 1 2 3 4 5)
(list 2 4 6))
(list 2 4))
(mk-test
"fd-dom-intersect-disjoint"
(fd-dom-intersect
(list 1 2 3)
(list 4 5 6))
(list))
(mk-test
"fd-dom-intersect-empty"
(fd-dom-intersect (list) (list 1 2 3))
(list))
(mk-test
"fd-dom-intersect-equal"
(fd-dom-intersect
(list 1 2 3)
(list 1 2 3))
(list 1 2 3))
(mk-test
"fd-dom-without-mid"
(fd-dom-without
3
(list 1 2 3 4 5))
(list 1 2 4 5))
(mk-test
"fd-dom-without-missing"
(fd-dom-without 9 (list 1 2 3))
(list 1 2 3))
(mk-test
"fd-dom-without-min"
(fd-dom-without 1 (list 1 2 3))
(list 2 3))
;; --- store accessors ---
(mk-test "fd-domain-of-unset" (fd-domain-of {} "x") nil)
(mk-test
"fd-domain-of-set"
(let
((s (fd-set-domain {} "x" (list 1 2 3))))
(fd-domain-of s "x"))
(list 1 2 3))
(mk-test
"fd-set-domain-empty-fails"
(fd-set-domain {} "x" (list))
nil)
(mk-test
"fd-set-domain-overrides"
(let
((s (fd-set-domain {} "x" (list 1 2 3))))
(fd-domain-of (fd-set-domain s "x" (list 5)) "x"))
(list 5))
(mk-test
"fd-set-domain-multiple-vars"
(let
((s (fd-set-domain (fd-set-domain {} "x" (list 1)) "y" (list 2 3))))
(list (fd-domain-of s "x") (fd-domain-of s "y")))
(list (list 1) (list 2 3)))
(mk-tests-run!)

View File

@@ -1,120 +0,0 @@
;; lib/minikanren/tests/clpfd-in-label.sx — fd-in (domain narrowing) + fd-label.
;; --- fd-in: domain narrowing ---
(mk-test
"fd-in-bare-label"
(run*
q
(fresh
(x)
(fd-in x (list 1 2 3 4 5))
(fd-label (list x))
(== q x)))
(list 1 2 3 4 5))
(mk-test
"fd-in-intersection"
(run*
q
(fresh
(x)
(fd-in x (list 1 2 3 4 5))
(fd-in x (list 3 4 5 6 7))
(fd-label (list x))
(== q x)))
(list 3 4 5))
(mk-test
"fd-in-disjoint-empty"
(run*
q
(fresh
(x)
(fd-in x (list 1 2 3))
(fd-in x (list 7 8 9))
(fd-label (list x))
(== q x)))
(list))
(mk-test
"fd-in-singleton-domain"
(run*
q
(fresh (x) (fd-in x (list 5)) (fd-label (list x)) (== q x)))
(list 5))
;; --- ground value checks the domain ---
(mk-test
"fd-in-ground-in-domain"
(run*
q
(fresh
(x)
(== x 3)
(fd-in x (list 1 2 3 4 5))
(== q x)))
(list 3))
(mk-test
"fd-in-ground-not-in-domain"
(run*
q
(fresh
(x)
(== x 9)
(fd-in x (list 1 2 3 4 5))
(== q x)))
(list))
;; --- fd-label across multiple vars ---
(mk-test
"fd-label-multiple-vars"
(let
((res (run* q (fresh (a b) (fd-in a (list 1 2 3)) (fd-in b (list 10 20)) (fd-label (list a b)) (== q (list a b))))))
(= (len res) 6))
true)
(mk-test
"fd-label-empty-vars"
(run* q (fd-label (list)))
(list (make-symbol "_.0")))
;; --- composition with regular goals ---
(mk-test
"fd-in-with-membero-style-filtering"
(run*
q
(fresh
(x)
(fd-in
x
(list
1
2
3
4
5
6
7
8
9
10))
(fd-label (list x))
(== q x)))
(list
1
2
3
4
5
6
7
8
9
10))
(mk-tests-run!)

View File

@@ -1,82 +0,0 @@
;; lib/minikanren/tests/clpfd-neq.sx — fd-neq with constraint propagation.
;; --- ground / domain interaction ---
(mk-test
"fd-neq-ground-distinct"
(run*
q
(fresh
(x)
(fd-neq x 5)
(fd-in x (list 4 5 6))
(fd-label (list x))
(== q x)))
(list 4 6))
(mk-test
"fd-neq-ground-equal-fails"
(run* q (fresh (x) (== x 5) (fd-neq x 5) (== q x)))
(list))
(mk-test
"fd-neq-symmetric"
(run*
q
(fresh
(x)
(fd-neq 7 x)
(fd-in x (list 5 6 7 8 9))
(fd-label (list x))
(== q x)))
(list 5 6 8 9))
;; --- two vars with overlapping domains ---
(mk-test
"fd-neq-pair-from-3"
(let
((res (run* q (fresh (x y) (fd-in x (list 1 2 3)) (fd-in y (list 1 2 3)) (fd-neq x y) (fd-label (list x y)) (== q (list x y))))))
(= (len res) 6))
true)
(mk-test
"fd-all-distinct-3-of-3"
(let
((res (run* q (fresh (a b c) (fd-in a (list 1 2 3)) (fd-in b (list 1 2 3)) (fd-in c (list 1 2 3)) (fd-neq a b) (fd-neq a c) (fd-neq b c) (fd-label (list a b c)) (== q (list a b c))))))
(= (len res) 6))
true)
(mk-test
"fd-pigeonhole-fails"
(run*
q
(fresh
(a b c)
(fd-in a (list 1 2))
(fd-in b (list 1 2))
(fd-in c (list 1 2))
(fd-neq a b)
(fd-neq a c)
(fd-neq b c)
(fd-label (list a b c))
(== q (list a b c))))
(list))
;; --- propagation when one side becomes ground ---
(mk-test
"fd-neq-propagates-after-ground"
(run*
q
(fresh
(x y)
(fd-in x (list 1 2 3))
(fd-in y (list 1 2 3))
(fd-neq x y)
(== x 2)
(fd-label (list y))
(== q y)))
(list 1 3))
(mk-tests-run!)

View File

@@ -1,128 +0,0 @@
;; lib/minikanren/tests/clpfd-ord.sx — fd-lt / fd-lte / fd-eq.
;; --- fd-lt ---
(mk-test
"fd-lt-narrows-x-against-num"
(run*
q
(fresh
(x)
(fd-in x (list 1 2 3 4 5))
(fd-lt x 3)
(fd-label (list x))
(== q x)))
(list 1 2))
(mk-test
"fd-lt-narrows-x-against-num-symmetric"
(run*
q
(fresh
(x)
(fd-in x (list 1 2 3 4 5))
(fd-lt 3 x)
(fd-label (list x))
(== q x)))
(list 4 5))
(mk-test
"fd-lt-pair-ordered"
(let
((res (run* q (fresh (x y) (fd-in x (list 1 2 3 4)) (fd-in y (list 1 2 3 4)) (fd-lt x y) (fd-label (list x y)) (== q (list x y))))))
(= (len res) 6))
true)
(mk-test
"fd-lt-impossible-fails"
(run*
q
(fresh
(x)
(fd-in x (list 5 6 7))
(fd-lt x 3)
(fd-label (list x))
(== q x)))
(list))
;; --- fd-lte ---
(mk-test
"fd-lte-includes-equal"
(run*
q
(fresh
(x)
(fd-in x (list 1 2 3 4 5))
(fd-lte x 3)
(fd-label (list x))
(== q x)))
(list 1 2 3))
(mk-test
"fd-lte-equal-bound"
(run*
q
(fresh
(x)
(fd-in x (list 1 2 3 4 5))
(fd-lte 3 x)
(fd-label (list x))
(== q x)))
(list 3 4 5))
;; --- fd-eq ---
(mk-test
"fd-eq-bind"
(run*
q
(fresh
(x)
(fd-in x (list 1 2 3 4 5))
(fd-eq x 3)
(== q x)))
(list 3))
(mk-test
"fd-eq-out-of-domain-fails"
(run*
q
(fresh
(x)
(fd-in x (list 1 2 3))
(fd-eq x 5)
(== q x)))
(list))
(mk-test
"fd-eq-two-vars-share-domain"
(run*
q
(fresh
(x y)
(fd-in x (list 1 2 3))
(fd-in y (list 2 3 4))
(fd-eq x y)
(fd-label (list x y))
(== q (list x y))))
(list (list 2 2) (list 3 3)))
;; --- combine fd-lt + fd-neq for "between" puzzle ---
(mk-test
"fd-lt-neq-combined"
(run*
q
(fresh
(x y z)
(fd-in x (list 1 2 3))
(fd-in y (list 1 2 3))
(fd-in z (list 1 2 3))
(fd-lt x y)
(fd-lt y z)
(fd-label (list x y z))
(== q (list x y z))))
(list (list 1 2 3)))
(mk-tests-run!)

View File

@@ -1,62 +0,0 @@
;; lib/minikanren/tests/clpfd-plus.sx — fd-plus (x + y = z).
(mk-test
"fd-plus-all-ground"
(run* q (fresh (z) (fd-plus 2 3 z) (== q z)))
(list 5))
(mk-test
"fd-plus-recover-x"
(run* q (fresh (x) (fd-plus x 3 5) (== q x)))
(list 2))
(mk-test
"fd-plus-recover-y"
(run* q (fresh (y) (fd-plus 2 y 5) (== q y)))
(list 3))
(mk-test
"fd-plus-impossible-fails"
(run*
q
(fresh
(z)
(fd-plus 2 3 z)
(== z 99)
(== q z)))
(list))
(mk-test
"fd-plus-domain-check"
(run*
q
(fresh
(x)
(fd-in x (list 3 4 5))
(fd-plus x 3 5)
(== q x)))
(list))
(mk-test
"fd-plus-pairs-summing-to-5"
(run*
q
(fresh
(x y)
(fd-in x (list 1 2 3 4))
(fd-in y (list 1 2 3 4))
(fd-plus x y 5)
(fd-label (list x y))
(== q (list x y))))
(list
(list 1 4)
(list 2 3)
(list 3 2)
(list 4 1)))
(mk-test
"fd-plus-z-derived"
(run* q (fresh (z) (fd-plus 7 8 z) (== q z)))
(list 15))
(mk-tests-run!)

View File

@@ -1,85 +0,0 @@
;; lib/minikanren/tests/clpfd-times.sx — fd-times (x * y = z).
(mk-test
"fd-times-3-4"
(run* q (fresh (z) (fd-times 3 4 z) (== q z)))
(list 12))
(mk-test
"fd-times-recover-divisor"
(run* q (fresh (x) (fd-times x 5 30) (== q x)))
(list 6))
(mk-test
"fd-times-non-divisible-fails"
(run* q (fresh (x) (fd-times x 5 31) (== q x)))
(list))
(mk-test
"fd-times-by-zero"
(run* q (fresh (z) (fd-times 0 99 z) (== q z)))
(list 0))
(mk-test
"fd-times-zero-by-anything-zero"
(run*
q
(fresh
(x)
(fd-in x (list 1 2 3))
(fd-times x 0 0)
(fd-label (list x))
(== q x)))
(list 1 2 3))
(mk-test
"fd-times-12-divisor-pairs"
(run*
q
(fresh
(x y)
(fd-in
x
(list
1
2
3
4
5
6))
(fd-in
y
(list
1
2
3
4
5
6))
(fd-times x y 12)
(fd-label (list x y))
(== q (list x y))))
(list
(list 2 6)
(list 3 4)
(list 4 3)
(list 6 2)))
(mk-test
"fd-times-square-of-each"
(run*
q
(fresh
(x z)
(fd-in x (list 1 2 3 4 5))
(fd-times x x z)
(fd-label (list x))
(== q (list x z))))
(list
(list 1 1)
(list 2 4)
(list 3 9)
(list 4 16)
(list 5 25)))
(mk-tests-run!)

View File

@@ -1,75 +0,0 @@
;; lib/minikanren/tests/conda.sx — Phase 5 piece A tests for `conda`.
;; --- conda commits to first non-failing head, keeps ALL its answers ---
(mk-test
"conda-first-clause-keeps-all"
(run*
q
(conda
((mk-disj (== q 1) (== q 2)))
((== q 100))))
(list 1 2))
(mk-test
"conda-skips-failing-head"
(run*
q
(conda
((== 1 2))
((mk-disj (== q 10) (== q 20)))))
(list 10 20))
(mk-test
"conda-all-fail"
(run*
q
(conda ((== 1 2)) ((== 3 4))))
(list))
(mk-test "conda-no-clauses" (run* q (conda)) (list))
;; --- conda DIFFERS from condu: conda keeps all head answers ---
(mk-test
"conda-vs-condu-divergence"
(list
(run*
q
(conda
((mk-disj (== q 1) (== q 2)))
((== q 100))))
(run*
q
(condu
((mk-disj (== q 1) (== q 2)))
((== q 100)))))
(list (list 1 2) (list 1)))
;; --- conda head's rest-goals run on every head answer ---
(mk-test
"conda-rest-goals-run-on-all-answers"
(run*
q
(fresh
(x r)
(conda
((mk-disj (== x 1) (== x 2))
(== r (list :tag x))))
(== q r)))
(list (list :tag 1) (list :tag 2)))
;; --- if rest-goals fail on a head answer, that head answer is filtered;
;; the clause does not fall through to next clauses (per soft-cut). ---
(mk-test
"conda-rest-fails-no-fallthrough"
(run*
q
(conda
((mk-disj (== q 1) (== q 2)) (== q 99))
((== q 200))))
(list))
(mk-tests-run!)

View File

@@ -1,89 +0,0 @@
;; lib/minikanren/tests/conde.sx — Phase 2 piece C tests for `conde`.
;;
;; Note on ordering: conde clauses are wrapped in Zzz (inverse-eta delay),
;; so applying the conde goal to a substitution returns thunks. mk-mplus
;; suspends-and-swaps when its left operand is paused, giving fair
;; interleaving — this is exactly what makes recursive relations work,
;; but it does mean conde answers can interleave rather than appear in
;; strict left-to-right clause order.
;; --- single-clause conde ≡ conj of clause body ---
(mk-test
"conde-one-clause"
(let ((q (mk-var "q"))) (run* q (conde ((== q 7)))))
(list 7))
(mk-test
"conde-one-clause-multi-goals"
(let
((q (mk-var "q")))
(run* q (conde ((fresh (x) (== x 5) (== q (list x x)))))))
(list (list 5 5)))
;; --- multi-clause: produces one row per clause (interleaved) ---
(mk-test
"conde-three-clauses-as-set"
(let
((qs (run* q (conde ((== q 1)) ((== q 2)) ((== q 3))))))
(and
(= (len qs) 3)
(and
(some (fn (x) (= x 1)) qs)
(and
(some (fn (x) (= x 2)) qs)
(some (fn (x) (= x 3)) qs)))))
true)
(mk-test
"conde-mixed-success-failure-as-set"
(let
((qs (run* q (conde ((== q "a")) ((== 1 2)) ((== q "b"))))))
(and
(= (len qs) 2)
(and (some (fn (x) (= x "a")) qs) (some (fn (x) (= x "b")) qs))))
true)
;; --- conde with conjuncts inside clauses ---
(mk-test
"conde-clause-conj-as-set"
(let
((rows (run* q (fresh (x y) (conde ((== x 1) (== y 10)) ((== x 2) (== y 20))) (== q (list x y))))))
(and
(= (len rows) 2)
(and
(some (fn (r) (= r (list 1 10))) rows)
(some (fn (r) (= r (list 2 20))) rows))))
true)
;; --- nested conde ---
(mk-test
"conde-nested-yields-three"
(let
((qs (run* q (conde ((conde ((== q 1)) ((== q 2)))) ((== q 3))))))
(and
(= (len qs) 3)
(and
(some (fn (x) (= x 1)) qs)
(and
(some (fn (x) (= x 2)) qs)
(some (fn (x) (= x 3)) qs)))))
true)
;; --- conde all clauses fail → empty stream ---
(mk-test
"conde-all-fail"
(run*
q
(conde ((== 1 2)) ((== 3 4))))
(list))
;; --- empty conde: no clauses ⇒ fail ---
(mk-test "conde-no-clauses" (run* q (conde)) (list))
(mk-tests-run!)

View File

@@ -1,86 +0,0 @@
;; lib/minikanren/tests/condu.sx — Phase 2 piece D tests for `onceo` and `condu`.
;; --- onceo: at most one answer ---
(mk-test
"onceo-single-success-passes-through"
(let
((q (mk-var "q")))
(let
((res (stream-take 5 ((onceo (== q 7)) empty-s))))
(map (fn (s) (mk-walk q s)) res)))
(list 7))
(mk-test
"onceo-multi-success-trimmed-to-one"
(let
((q (mk-var "q")))
(let
((res (stream-take 5 ((onceo (mk-disj (== q 1) (== q 2) (== q 3))) empty-s))))
(map (fn (s) (mk-walk q s)) res)))
(list 1))
(mk-test
"onceo-failure-stays-failure"
((onceo (== 1 2)) empty-s)
(list))
(mk-test
"onceo-conde-trimmed"
(let
((q (mk-var "q")))
(let
((res (stream-take 5 ((onceo (conde ((== q "a")) ((== q "b")))) empty-s))))
(map (fn (s) (mk-walk q s)) res)))
(list "a"))
;; --- condu: first clause with successful head wins ---
(mk-test
"condu-first-clause-wins"
(let
((q (mk-var "q")))
(let
((res (stream-take 10 ((condu ((== q 1)) ((== q 2))) empty-s))))
(map (fn (s) (mk-walk q s)) res)))
(list 1))
(mk-test
"condu-skips-failing-head"
(let
((q (mk-var "q")))
(let
((res (stream-take 10 ((condu ((== 1 2)) ((== q 100)) ((== q 200))) empty-s))))
(map (fn (s) (mk-walk q s)) res)))
(list 100))
(mk-test
"condu-all-fail-empty"
((condu ((== 1 2)) ((== 3 4)))
empty-s)
(list))
(mk-test "condu-empty-clauses-fail" ((condu) empty-s) (list))
;; --- condu commits head's first answer; rest-goals can still backtrack
;; within that committed substitution but cannot revisit other heads. ---
(mk-test
"condu-head-onceo-rest-runs"
(let
((q (mk-var "q")) (r (mk-var "r")))
(let
((res (stream-take 10 ((condu ((mk-disj (== q 1) (== q 2)) (== r 99))) empty-s))))
(map (fn (s) (list (mk-walk q s) (mk-walk r s))) res)))
(list (list 1 99)))
(mk-test
"condu-rest-goals-can-fail-the-clause"
(let
((q (mk-var "q")))
(let
((res (stream-take 10 ((condu ((== q 1) (== 2 3)) ((== q 99))) empty-s))))
(map (fn (s) (mk-walk q s)) res)))
(list))
(mk-tests-run!)

View File

@@ -1,35 +0,0 @@
;; lib/minikanren/tests/counto.sx — count occurrences of x in l (intarith).
(mk-test
"counto-empty"
(run* q (counto 1 (list) q))
(list 0))
(mk-test
"counto-not-found"
(run* q (counto 99 (list 1 2 3) q))
(list 0))
(mk-test
"counto-once"
(run* q (counto 2 (list 1 2 3) q))
(list 1))
(mk-test
"counto-thrice"
(run*
q
(counto
1
(list 1 2 1 3 1)
q))
(list 3))
(mk-test
"counto-all-same"
(run*
q
(counto 7 (list 7 7 7 7) q))
(list 4))
(mk-test
"counto-string"
(run* q (counto "x" (list "x" "y" "x") q))
(list 2))
(mk-tests-run!)

View File

@@ -1,48 +0,0 @@
;; lib/minikanren/tests/cyclic-graph.sx — demonstrates the naive-patho
;; behaviour on a cyclic graph. Without Phase-7 tabling/SLG, the search
;; produces ever-longer paths revisiting the cycle. `run n` truncates;
;; `run*` would diverge.
(define cyclic-edges (list (list :a :b) (list :b :a) (list :b :c)))
(define cyclic-edgeo (fn (x y) (membero (list x y) cyclic-edges)))
(define
cyclic-patho
(fn
(x y path)
(conde
((cyclic-edgeo x y) (== path (list x y)))
((fresh (z mid) (cyclic-edgeo x z) (cyclic-patho z y mid) (conso x mid path))))))
;; --- direct edge ---
(mk-test
"cyclic-direct"
(run 1 q (cyclic-patho :a :b q))
(list (list :a :b)))
;; --- runs first 5 paths from a to b: bare edge, then increasing
;; numbers of cycle traversals (a->b->a->b, etc.) ---
(mk-test
"cyclic-enumerates-prefix-via-run-n"
(let
((paths (run 5 q (cyclic-patho :a :b q))))
(and
(= (len paths) 5)
(and
(every? (fn (p) (= (first p) :a)) paths)
(every? (fn (p) (= (last p) :b)) paths))))
true)
(mk-test
"cyclic-finds-c-via-cycle-or-direct"
(let
((paths (run 3 q (cyclic-patho :a :c q))))
(and
(>= (len paths) 1)
(some (fn (p) (= p (list :a :b :c))) paths)))
true)
(mk-tests-run!)

View File

@@ -1,40 +0,0 @@
;; lib/minikanren/tests/defrel.sx — Prolog-style relation definition macro.
(defrel
(my-membero x l)
((fresh (d) (conso x d l)))
((fresh (a d) (conso a d l) (my-membero x d))))
(mk-test
"defrel-defines-membero"
(run* q (my-membero q (list 1 2 3)))
(list 1 2 3))
(defrel
(my-listo l)
((nullo l))
((fresh (a d) (conso a d l) (my-listo d))))
(mk-test
"defrel-listo-bounded"
(run 3 q (my-listo q))
(list
(list)
(list (make-symbol "_.0"))
(list (make-symbol "_.0") (make-symbol "_.1"))))
;; Multi-arg relation with arithmetic.
(defrel
(my-pluso a b c)
((== a :z) (== b c))
((fresh (a-1 c-1) (== a (list :s a-1)) (== c (list :s c-1)) (my-pluso a-1 b c-1))))
(mk-test
"defrel-pluso-2-3"
(run*
q
(my-pluso (list :s (list :s :z)) (list :s (list :s (list :s :z))) q))
(list (list :s (list :s (list :s (list :s (list :s :z)))))))
(mk-tests-run!)

View File

@@ -1,83 +0,0 @@
;; lib/minikanren/tests/diseq.sx — Phase 5 polish: =/= disequality.
;; --- ground cases ---
(mk-test
"=/=-ground-distinct"
(run* q (=/= 1 2))
(list (make-symbol "_.0")))
(mk-test "=/=-ground-equal" (run* q (=/= 1 1)) (list))
(mk-test
"=/=-ground-strings"
(run* q (=/= "a" "b"))
(list (make-symbol "_.0")))
(mk-test "=/=-ground-strings-eq" (run* q (=/= "a" "a")) (list))
;; --- structural ---
(mk-test
"=/=-pair-distinct"
(run* q (=/= (list 1 2) (list 1 3)))
(list (make-symbol "_.0")))
(mk-test
"=/=-pair-equal"
(run* q (=/= (list 1 2) (list 1 2)))
(list))
(mk-test
"=/=-pair-vs-atom"
(run* q (=/= (list 1) 1))
(list (make-symbol "_.0")))
;; --- partial / late binding ---
;;
;; ==-cs is required to wake up the constraint store after a binding;
;; plain == doesn't fire constraints.
(mk-test
"=/=-late-bind-violates"
(run* q (fresh (x) (=/= x 5) (==-cs x 5) (== q x)))
(list))
(mk-test
"=/=-late-bind-ok"
(run* q (fresh (x) (=/= x 5) (==-cs x 7) (== q x)))
(list 7))
(mk-test
"=/=-two-vars-equal-late-fails"
(run*
q
(fresh
(x y)
(=/= x y)
(==-cs x 1)
(==-cs y 1)
(== q (list x y))))
(list))
(mk-test
"=/=-two-vars-distinct-late"
(run*
q
(fresh
(x y)
(=/= x y)
(==-cs x 1)
(==-cs y 2)
(== q (list x y))))
(list (list 1 2)))
;; --- compose with conde / fresh ---
(mk-test
"=/=-with-membero-filter"
(run*
q
(fresh
(x)
(membero x (list 1 2 3))
(=/= x 2)
(== q x)))
(list 1 3))
(mk-tests-run!)

View File

@@ -1,31 +0,0 @@
;; lib/minikanren/tests/enumerate.sx — index-each-element relation.
(mk-test
"enumerate-i-empty"
(run* q (enumerate-i (list) q))
(list (list)))
(mk-test
"enumerate-i-three"
(run* q (enumerate-i (list :a :b :c) q))
(list
(list (list 0 :a) (list 1 :b) (list 2 :c))))
(mk-test
"enumerate-i-strings"
(run* q (enumerate-i (list "x" "y" "z") q))
(list
(list (list 0 "x") (list 1 "y") (list 2 "z"))))
(mk-test
"enumerate-from-i-100"
(run* q (enumerate-from-i 100 (list :x :y :z) q))
(list
(list (list 100 :x) (list 101 :y) (list 102 :z))))
(mk-test
"enumerate-from-i-singleton"
(run* q (enumerate-from-i 0 (list :only) q))
(list (list (list 0 :only))))
(mk-tests-run!)

View File

@@ -1,75 +0,0 @@
;; lib/minikanren/tests/fd.sx — Phase 6 piece A: ino + all-distincto.
;; --- ino ---
(mk-test
"ino-element-in-domain"
(run* q (ino q (list 1 2 3)))
(list 1 2 3))
(mk-test "ino-empty-domain" (run* q (ino q (list))) (list))
(mk-test
"ino-singleton-domain"
(run* q (ino q (list 42)))
(list 42))
;; --- all-distincto ---
(mk-test
"all-distincto-empty"
(run* q (all-distincto (list)))
(list (make-symbol "_.0")))
(mk-test
"all-distincto-singleton"
(run* q (all-distincto (list 1)))
(list (make-symbol "_.0")))
(mk-test
"all-distincto-distinct-three"
(run* q (all-distincto (list 1 2 3)))
(list (make-symbol "_.0")))
(mk-test
"all-distincto-duplicate-fails"
(run* q (all-distincto (list 1 2 1)))
(list))
(mk-test
"all-distincto-adjacent-duplicate-fails"
(run* q (all-distincto (list 1 1 2)))
(list))
;; --- ino + all-distincto: classic enumerate-all-permutations ---
(mk-test
"fd-puzzle-three-distinct-from-domain"
(let
((perms (run* q (fresh (a b c) (== q (list a b c)) (ino a (list 1 2 3)) (ino b (list 1 2 3)) (ino c (list 1 2 3)) (all-distincto (list a b c))))))
(and
(= (len perms) 6)
(and
(some (fn (p) (= p (list 1 2 3))) perms)
(and
(some
(fn (p) (= p (list 1 3 2)))
perms)
(and
(some
(fn (p) (= p (list 2 1 3)))
perms)
(and
(some
(fn (p) (= p (list 2 3 1)))
perms)
(and
(some
(fn (p) (= p (list 3 1 2)))
perms)
(some
(fn (p) (= p (list 3 2 1)))
perms))))))))
true)
(mk-tests-run!)

View File

@@ -1,39 +0,0 @@
;; lib/minikanren/tests/flat-mapo.sx — concatMap-style relation.
(mk-test
"flat-mapo-empty"
(run* q (flat-mapo (fn (x r) (== r (list x x))) (list) q))
(list (list)))
(mk-test
"flat-mapo-duplicate-each"
(run*
q
(flat-mapo
(fn (x r) (== r (list x x)))
(list 1 2 3)
q))
(list
(list 1 1 2 2 3 3)))
(mk-test
"flat-mapo-empty-from-each"
(run* q (flat-mapo (fn (x r) (== r (list))) (list :a :b :c) q))
(list (list)))
(mk-test
"flat-mapo-singleton-from-each-is-identity"
(run* q (flat-mapo (fn (x r) (== r (list x))) (list :a :b :c) q))
(list (list :a :b :c)))
(mk-test
"flat-mapo-tag-each"
(run*
q
(flat-mapo
(fn (x r) (== r (list :tag x)))
(list 1 2)
q))
(list (list :tag 1 :tag 2)))
(mk-tests-run!)

View File

@@ -1,42 +0,0 @@
(mk-test "flatteno-empty" (run* q (flatteno (list) q)) (list (list)))
(mk-test
"flatteno-atom"
(run* q (flatteno 5 q))
(list (list 5)))
(mk-test
"flatteno-flat-list"
(run* q (flatteno (list 1 2 3) q))
(list (list 1 2 3)))
(mk-test
"flatteno-singleton"
(run* q (flatteno (list 1) q))
(list (list 1)))
(mk-test
"flatteno-nested-once"
(run*
q
(flatteno (list 1 (list 2 3) 4) q))
(list (list 1 2 3 4)))
(mk-test
"flatteno-nested-twice"
(run*
q
(flatteno
(list
1
(list 2 (list 3 4))
5)
q))
(list (list 1 2 3 4 5)))
(mk-test
"flatteno-keywords"
(run* q (flatteno (list :a (list :b :c) :d) q))
(list (list :a :b :c :d)))
(mk-tests-run!)

View File

@@ -1,48 +0,0 @@
;; lib/minikanren/tests/foldl-o.sx — relational left fold.
(mk-test
"foldl-o-empty"
(run* q (foldl-o pluso-i (list) 42 q))
(list 42))
(mk-test
"foldl-o-sum"
(run*
q
(foldl-o
pluso-i
(list 1 2 3 4 5)
0
q))
(list 15))
(mk-test
"foldl-o-product"
(run*
q
(foldl-o
*o-i
(list 1 2 3 4)
1
q))
(list 24))
(mk-test
"foldl-o-reverse-via-flip-conso"
(run*
q
(foldl-o
(fn (acc x r) (conso x acc r))
(list 1 2 3 4)
(list)
q))
(list (list 4 3 2 1)))
(mk-test
"foldl-o-with-init"
(run*
q
(foldl-o pluso-i (list 1 2 3) 100 q))
(list 106))
(mk-tests-run!)

View File

@@ -1,38 +0,0 @@
;; lib/minikanren/tests/foldr-o.sx — relational right fold.
(mk-test
"foldr-o-empty"
(run* q (foldr-o conso (list) (list 99) q))
(list (list 99)))
(mk-test
"foldr-o-conso-rebuilds-list"
(run* q (foldr-o conso (list 1 2 3) (list) q))
(list (list 1 2 3)))
(mk-test
"foldr-o-appendo-flattens"
(run*
q
(foldr-o
appendo
(list
(list 1 2)
(list 3)
(list 4 5))
(list)
q))
(list (list 1 2 3 4 5)))
(mk-test
"foldr-o-with-acc-init"
(run*
q
(foldr-o
conso
(list 1 2)
(list 9 9)
q))
(list (list 1 2 9 9)))
(mk-tests-run!)

View File

@@ -1,101 +0,0 @@
;; lib/minikanren/tests/fresh.sx — Phase 2 piece B tests for `fresh`.
;; --- empty fresh: pure goal grouping ---
(mk-test
"fresh-empty-vars-equiv-conj"
(stream-take 5 ((fresh () (== 1 1)) empty-s))
(list empty-s))
(mk-test
"fresh-empty-vars-no-goals-is-succeed"
(stream-take 5 ((fresh ()) empty-s))
(list empty-s))
;; --- single var ---
(mk-test
"fresh-one-var-bound"
(let
((s (first (stream-take 5 ((fresh (x) (== x 7)) empty-s)))))
(first (vals s)))
7)
;; --- multiple vars + multiple goals ---
(mk-test
"fresh-two-vars-three-goals"
(let
((q (mk-var "q"))
(g
(fresh
(x y)
(== x 10)
(== y 20)
(== q (list x y)))))
(mk-walk* q (first (stream-take 5 (g empty-s)))))
(list 10 20))
(mk-test
"fresh-three-vars"
(let
((q (mk-var "q"))
(g
(fresh
(a b c)
(== a 1)
(== b 2)
(== c 3)
(== q (list a b c)))))
(mk-walk* q (first (stream-take 5 (g empty-s)))))
(list 1 2 3))
;; --- fresh interacts with disj ---
(mk-test
"fresh-with-disj"
(let
((q (mk-var "q")))
(let
((g (fresh (x) (mk-disj (== x 1) (== x 2)) (== q x))))
(let
((res (stream-take 5 (g empty-s))))
(map (fn (s) (mk-walk q s)) res))))
(list 1 2))
;; --- nested fresh ---
(mk-test
"fresh-nested"
(let
((q (mk-var "q"))
(g
(fresh
(x)
(fresh
(y)
(== x 1)
(== y 2)
(== q (list x y))))))
(mk-walk* q (first (stream-take 5 (g empty-s)))))
(list 1 2))
;; --- call-fresh (functional alternative) ---
(mk-test
"call-fresh-binds-and-walks"
(let
((s (first (stream-take 5 ((call-fresh (fn (x) (== x 99))) empty-s)))))
(first (vals s)))
99)
(mk-test
"call-fresh-distinct-from-outer-vars"
(let
((q (mk-var "q")))
(let
((g (call-fresh (fn (x) (mk-conj (== x 5) (== q (list x x)))))))
(mk-walk* q (first (stream-take 5 (g empty-s))))))
(list 5 5))
(mk-tests-run!)

View File

@@ -1,260 +0,0 @@
;; lib/minikanren/tests/goals.sx — Phase 2 tests for stream.sx + goals.sx.
;;
;; Streams use a tagged shape internally (`(:s head tail)`) so that mature
;; cells can have thunk tails — SX has no improper pairs. Test assertions
;; therefore stream-take into a plain SX list, or check goal effects via
;; mk-walk on the resulting subst, instead of inspecting raw streams.
;; --- stream-take base cases (input streams use s-cons / mzero) ---
(mk-test
"stream-take-zero-from-mature"
(stream-take 0 (s-cons (empty-subst) mzero))
(list))
(mk-test "stream-take-from-mzero" (stream-take 5 mzero) (list))
(mk-test
"stream-take-mature-pair"
(stream-take 5 (s-cons :a (s-cons :b mzero)))
(list :a :b))
(mk-test
"stream-take-fewer-than-available"
(stream-take 1 (s-cons :a (s-cons :b mzero)))
(list :a))
(mk-test
"stream-take-all-with-neg-1"
(stream-take -1 (s-cons :a (s-cons :b (s-cons :c mzero))))
(list :a :b :c))
;; --- stream-take forces immature thunks ---
(mk-test
"stream-take-forces-thunk"
(stream-take 5 (fn () (s-cons :x mzero)))
(list :x))
(mk-test
"stream-take-forces-nested-thunks"
(stream-take 5 (fn () (fn () (s-cons :y mzero))))
(list :y))
;; --- mk-mplus interleaves ---
(mk-test
"mplus-empty-left"
(stream-take 5 (mk-mplus mzero (s-cons :r mzero)))
(list :r))
(mk-test
"mplus-empty-right"
(stream-take 5 (mk-mplus (s-cons :l mzero) mzero))
(list :l))
(mk-test
"mplus-mature-mature"
(stream-take
5
(mk-mplus (s-cons :a (s-cons :b mzero)) (s-cons :c (s-cons :d mzero))))
(list :a :b :c :d))
(mk-test
"mplus-with-paused-left-swaps"
(stream-take
5
(mk-mplus
(fn () (s-cons :a (s-cons :b mzero)))
(s-cons :c (s-cons :d mzero))))
(list :c :d :a :b))
;; --- mk-bind ---
(mk-test
"bind-empty-stream"
(stream-take 5 (mk-bind mzero (fn (s) (unit s))))
(list))
(mk-test
"bind-singleton-identity"
(stream-take
5
(mk-bind (s-cons 5 mzero) (fn (x) (unit x))))
(list 5))
(mk-test
"bind-flat-multi"
(stream-take
10
(mk-bind
(s-cons 1 (s-cons 2 mzero))
(fn (x) (s-cons x (s-cons (* x 10) mzero)))))
(list 1 10 2 20))
(mk-test
"bind-fail-prunes-some"
(stream-take
10
(mk-bind
(s-cons 1 (s-cons 2 (s-cons 3 mzero)))
(fn (x) (if (= x 2) mzero (unit x)))))
(list 1 3))
;; --- core goals: succeed / fail ---
(mk-test
"succeed-yields-singleton"
(stream-take 5 (succeed empty-s))
(list empty-s))
(mk-test "fail-yields-mzero" (stream-take 5 (fail empty-s)) (list))
;; --- == ---
(mk-test
"eq-ground-success"
(stream-take 5 ((== 1 1) empty-s))
(list empty-s))
(mk-test
"eq-ground-failure"
(stream-take 5 ((== 1 2) empty-s))
(list))
(mk-test
"eq-binds-var"
(let
((x (mk-var "x")))
(mk-walk
x
(first (stream-take 5 ((== x 7) empty-s)))))
7)
(mk-test
"eq-list-success"
(let
((x (mk-var "x")))
(mk-walk
x
(first
(stream-take
5
((== x (list 1 2)) empty-s)))))
(list 1 2))
(mk-test
"eq-list-mismatch-fails"
(stream-take
5
((== (list 1 2) (list 1 3)) empty-s))
(list))
;; --- conj2 / mk-conj ---
(mk-test
"conj2-both-bind"
(let
((x (mk-var "x")) (y (mk-var "y")))
(let
((s (first (stream-take 5 ((conj2 (== x 1) (== y 2)) empty-s)))))
(list (mk-walk x s) (mk-walk y s))))
(list 1 2))
(mk-test
"conj2-conflict-empty"
(let
((x (mk-var "x")))
(stream-take
5
((conj2 (== x 1) (== x 2)) empty-s)))
(list))
(mk-test
"conj-empty-is-succeed"
(stream-take 5 ((mk-conj) empty-s))
(list empty-s))
(mk-test
"conj-single-is-goal"
(let
((x (mk-var "x")))
(mk-walk
x
(first
(stream-take 5 ((mk-conj (== x 99)) empty-s)))))
99)
(mk-test
"conj-three-bindings"
(let
((x (mk-var "x")) (y (mk-var "y")) (z (mk-var "z")))
(let
((s (first (stream-take 5 ((mk-conj (== x 1) (== y 2) (== z 3)) empty-s)))))
(list (mk-walk x s) (mk-walk y s) (mk-walk z s))))
(list 1 2 3))
;; --- disj2 / mk-disj ---
(mk-test
"disj2-both-succeed"
(let
((q (mk-var "q")))
(let
((res (stream-take 5 ((disj2 (== q 1) (== q 2)) empty-s))))
(map (fn (s) (mk-walk q s)) res)))
(list 1 2))
(mk-test
"disj2-fail-or-succeed"
(let
((q (mk-var "q")))
(let
((res (stream-take 5 ((disj2 fail (== q 5)) empty-s))))
(map (fn (s) (mk-walk q s)) res)))
(list 5))
(mk-test
"disj-empty-is-fail"
(stream-take 5 ((mk-disj) empty-s))
(list))
(mk-test
"disj-three-clauses"
(let
((q (mk-var "q")))
(let
((res (stream-take 5 ((mk-disj (== q "a") (== q "b") (== q "c")) empty-s))))
(map (fn (s) (mk-walk q s)) res)))
(list "a" "b" "c"))
;; --- conj/disj nesting ---
(mk-test
"disj-of-conj"
(let
((x (mk-var "x")) (y (mk-var "y")))
(let
((res (stream-take 5 ((mk-disj (mk-conj (== x 1) (== y 2)) (mk-conj (== x 3) (== y 4))) empty-s))))
(map (fn (s) (list (mk-walk x s) (mk-walk y s))) res)))
(list (list 1 2) (list 3 4)))
;; --- ==-check ---
(mk-test
"eq-check-no-occurs-fails"
(let
((x (mk-var "x")))
(stream-take 5 ((==-check x (list 1 x)) empty-s)))
(list))
(mk-test
"eq-check-no-occurs-non-occurring-succeeds"
(let
((x (mk-var "x")))
(mk-walk
x
(first (stream-take 5 ((==-check x 5) empty-s)))))
5)
(mk-tests-run!)

View File

@@ -1,70 +0,0 @@
;; lib/minikanren/tests/graph.sx — directed-graph reachability via patho.
(define
test-edges
(list (list :a :b) (list :b :c) (list :c :d) (list :a :c) (list :d :e)))
(define edgeo (fn (from to) (membero (list from to) test-edges)))
(define
patho
(fn
(x y path)
(conde
((edgeo x y) (== path (list x y)))
((fresh (z mid-path) (edgeo x z) (patho z y mid-path) (conso x mid-path path))))))
;; --- direct edges ---
(mk-test "patho-direct" (run* q (patho :a :b q)) (list (list :a :b)))
(mk-test "patho-no-direct-edge" (run* q (patho :e :a q)) (list))
;; --- indirect ---
(mk-test
"patho-multi-hop"
(let
((paths (run* q (patho :a :d q))))
(and
(= (len paths) 2)
(and
(some (fn (p) (= p (list :a :b :c :d))) paths)
(some (fn (p) (= p (list :a :c :d))) paths))))
true)
(mk-test
"patho-to-leaf"
(let
((paths (run* q (patho :a :e q))))
(and
(= (len paths) 2)
(and
(some (fn (p) (= p (list :a :b :c :d :e))) paths)
(some (fn (p) (= p (list :a :c :d :e))) paths))))
true)
;; --- enumeration with multiplicity ---
;; Each path contributes one tuple, so reachable nodes can repeat. Here
;; targets are: b (1 path), c (2 paths), d (2 paths), e (2 paths) = 7.
(mk-test
"patho-enumerate-from-a-with-multiplicity"
(let
((targets (run* q (fresh (path) (patho :a q path)))))
(and
(= (len targets) 7)
(and
(some (fn (t) (= t :b)) targets)
(and
(some (fn (t) (= t :c)) targets)
(and
(some (fn (t) (= t :d)) targets)
(some (fn (t) (= t :e)) targets))))))
true)
;; --- unreachable target ---
(mk-test "patho-unreachable" (run* q (patho :a :z q)) (list))
(mk-tests-run!)

View File

@@ -1,103 +0,0 @@
;; lib/minikanren/tests/intarith.sx — ground-only integer arithmetic
;; goals that escape into host operations via project.
;; --- pluso-i ---
(mk-test
"pluso-i-forward"
(run* q (pluso-i 7 8 q))
(list 15))
(mk-test
"pluso-i-zero"
(run* q (pluso-i 0 0 q))
(list 0))
(mk-test
"pluso-i-negatives"
(run* q (pluso-i -5 3 q))
(list -2))
(mk-test
"pluso-i-non-ground-fails"
(run* q (fresh (a) (pluso-i a 3 5)))
(list))
;; --- minuso-i ---
(mk-test
"minuso-i-forward"
(run* q (minuso-i 10 4 q))
(list 6))
(mk-test
"minuso-i-zero"
(run* q (minuso-i 5 5 q))
(list 0))
;; --- *o-i ---
(mk-test
"times-i-forward"
(run* q (*o-i 6 7 q))
(list 42))
(mk-test
"times-i-by-zero"
(run* q (*o-i 0 99 q))
(list 0))
(mk-test
"times-i-by-one"
(run* q (*o-i 1 17 q))
(list 17))
;; --- comparisons ---
(mk-test
"lto-i-true"
(run 1 q (lto-i 2 5))
(list (make-symbol "_.0")))
(mk-test "lto-i-false" (run* q (lto-i 5 2)) (list))
(mk-test "lto-i-equal-false" (run* q (lto-i 3 3)) (list))
(mk-test
"lteo-i-equal"
(run 1 q (lteo-i 4 4))
(list (make-symbol "_.0")))
(mk-test
"lteo-i-less"
(run 1 q (lteo-i 1 4))
(list (make-symbol "_.0")))
(mk-test "lteo-i-more" (run* q (lteo-i 9 4)) (list))
(mk-test
"neqo-i-different"
(run 1 q (neqo-i 3 5))
(list (make-symbol "_.0")))
(mk-test "neqo-i-same" (run* q (neqo-i 3 3)) (list))
;; --- composition with relational vars ---
(mk-test
"intarith-with-membero"
(run*
q
(fresh
(x)
(membero
x
(list 1 2 3 4 5))
(lto-i x 3)
(== q x)))
(list 1 2))
(mk-test "even-i-pos" (run* q (even-i 4)) (list (make-symbol "_.0")))
(mk-test "even-i-neg" (run* q (even-i 5)) (list))
(mk-test "odd-i-pos" (run* q (odd-i 7)) (list (make-symbol "_.0")))
(mk-test "odd-i-neg" (run* q (odd-i 4)) (list))
(mk-test
"even-i-filter"
(run* q (fresh (x) (membero x (list 1 2 3 4 5 6)) (even-i x) (== q x)))
(list 2 4 6))
(mk-tests-run!)

View File

@@ -1,38 +0,0 @@
;; lib/minikanren/tests/iterate-no.sx — iterated relation application.
(define
mk-nat
(fn (n) (if (= n 0) :z (list :s (mk-nat (- n 1))))))
(mk-test
"iterate-no-zero"
(run*
q
(iterate-no
(fn (a b) (== b (list :wrap a)))
(mk-nat 0)
:seed q))
(list :seed))
(mk-test
"iterate-no-three-wraps"
(run*
q
(iterate-no (fn (a b) (== b (list :wrap a))) (mk-nat 3) :x q))
(list (list :wrap (list :wrap (list :wrap :x)))))
(mk-test
"iterate-no-succ-three-times"
(run*
q
(iterate-no (fn (a b) (== b (list :s a))) (mk-nat 3) :z q))
(list (mk-nat 3)))
(mk-test
"iterate-no-with-list-cons"
(run*
q
(iterate-no (fn (a b) (conso :a a b)) (mk-nat 4) (list) q))
(list (list :a :a :a :a)))
(mk-tests-run!)

View File

@@ -1,38 +0,0 @@
;; lib/minikanren/tests/lasto.sx — last-element + init-without-last.
(mk-test
"lasto-singleton"
(run* q (lasto (list 5) q))
(list 5))
(mk-test
"lasto-multi"
(run* q (lasto (list 1 2 3 4) q))
(list 4))
(mk-test "lasto-empty" (run* q (lasto (list) q)) (list))
(mk-test "lasto-strings" (run* q (lasto (list "a" "b" "c") q)) (list "c"))
(mk-test
"init-o-multi"
(run* q (init-o (list 1 2 3 4) q))
(list (list 1 2 3)))
(mk-test
"init-o-singleton"
(run* q (init-o (list 7) q))
(list (list)))
(mk-test "init-o-empty" (run* q (init-o (list) q)) (list))
(mk-test
"lasto-init-o-roundtrip"
(run*
q
(fresh
(init last)
(lasto (list 1 2 3 4) last)
(init-o (list 1 2 3 4) init)
(appendo init (list last) q)))
(list (list 1 2 3 4)))
(mk-tests-run!)

View File

@@ -1,61 +0,0 @@
;; lib/minikanren/tests/latin.sx — 2x2 Latin square via ino + all-distincto.
;;
;; A 2x2 Latin square has 2 distinct fillings:
;; ((1 2) (2 1)) and ((2 1) (1 2)).
;; The 3x3 version has 12 fillings but takes minutes under naive search;
;; full CLP(FD) (Phase 6 proper) would handle it in milliseconds.
(define
latin-2x2
(fn
(cells)
(let
((c11 (nth cells 0))
(c12 (nth cells 1))
(c21 (nth cells 2))
(c22 (nth cells 3))
(dom (list 1 2)))
(mk-conj
(ino c11 dom)
(ino c12 dom)
(ino c21 dom)
(ino c22 dom)
(all-distincto (list c11 c12))
(all-distincto (list c21 c22))
(all-distincto (list c11 c21))
(all-distincto (list c12 c22)))))) ;; col 2
(mk-test
"latin-2x2-count"
(let
((squares (run* q (fresh (a b c d) (== q (list a b c d)) (latin-2x2 (list a b c d))))))
(len squares))
2)
(mk-test
"latin-2x2-as-set"
(let
((squares (run* q (fresh (a b c d) (== q (list a b c d)) (latin-2x2 (list a b c d))))))
(and
(= (len squares) 2)
(and
(some
(fn (s) (= s (list 1 2 2 1)))
squares)
(some
(fn (s) (= s (list 2 1 1 2)))
squares))))
true)
(mk-test
"latin-2x2-with-clue"
(run*
q
(fresh
(a b c d)
(== a 1)
(== q (list a b c d))
(latin-2x2 (list a b c d))))
(list (list 1 2 2 1)))
(mk-tests-run!)

View File

@@ -1,77 +0,0 @@
;; lib/minikanren/tests/laziness.sx — verify Zzz wrapping (in conde)
;; lets infinitely-recursive relations produce finite prefixes via run-n.
;; --- a relation that has no base case but conde-protects via Zzz ---
(define
listo-aux
(fn
(l)
(conde ((nullo l)) ((fresh (a d) (conso a d l) (listo-aux d))))))
(mk-test
"infinite-relation-truncates-via-run-n"
(run 4 q (listo-aux q))
(list
(list)
(list (make-symbol "_.0"))
(list (make-symbol "_.0") (make-symbol "_.1"))
(list (make-symbol "_.0") (make-symbol "_.1") (make-symbol "_.2"))))
;; --- two infinite generators interleaved via mk-disj must both produce
;; answers (no starvation) — the fairness test ---
(define
ones-gen
(fn
(l)
(conde
((== l (list)))
((fresh (d) (conso 1 d l) (ones-gen d))))))
(define
twos-gen
(fn
(l)
(conde
((== l (list)))
((fresh (d) (conso 2 d l) (twos-gen d))))))
(mk-test
"interleaving-keeps-both-streams-alive"
(let
((res (run 4 q (mk-disj (ones-gen q) (twos-gen q)))))
(and
(= (len res) 4)
(and
(some
(fn
(x)
(and
(list? x)
(and (not (empty? x)) (= (first x) 1))))
res)
(some
(fn
(x)
(and
(list? x)
(and (not (empty? x)) (= (first x) 2))))
res))))
true)
;; --- run* terminates on a relation whose conde has finite base case
;; reached from any starting point ---
(mk-test
"run-star-terminates-on-bounded-relation"
(run*
q
(fresh
(l)
(== l (list 1 2 3))
(listo l)
(== q :ok)))
(list :ok))
(mk-tests-run!)

View File

@@ -1,28 +0,0 @@
;; lib/minikanren/tests/lengtho-i.sx — integer-indexed length (fast).
(mk-test "lengtho-i-empty" (run* q (lengtho-i (list) q)) (list 0))
(mk-test
"lengtho-i-singleton"
(run* q (lengtho-i (list :a) q))
(list 1))
(mk-test
"lengtho-i-three"
(run* q (lengtho-i (list 1 2 3) q))
(list 3))
(mk-test
"lengtho-i-five"
(run*
q
(lengtho-i
(list 1 2 3 4 5)
q))
(list 5))
(mk-test
"lengtho-i-mixed-types"
(run*
q
(lengtho-i (list 1 "two" :three (list 4 5)) q))
(list 4))
(mk-tests-run!)

View File

@@ -1,126 +0,0 @@
;; lib/minikanren/tests/list-relations.sx — rembero, assoco, nth-o, samelengtho.
;; --- rembero (remove first occurrence) ---
(mk-test
"rembero-element-present"
(run*
q
(rembero 2 (list 1 2 3 2) q))
(list (list 1 3 2)))
(mk-test
"rembero-element-not-present"
(run* q (rembero 99 (list 1 2 3) q))
(list (list 1 2 3)))
(mk-test
"rembero-empty"
(run* q (rembero 1 (list) q))
(list (list)))
(mk-test
"rembero-only-element"
(run* q (rembero 5 (list 5) q))
(list (list)))
(mk-test
"rembero-first-of-many"
(run*
q
(rembero 1 (list 1 2 3 4) q))
(list (list 2 3 4)))
;; --- assoco (alist lookup) ---
(define
test-pairs
(list
(list "alice" 30)
(list "bob" 25)
(list "carol" 35)))
(mk-test
"assoco-found"
(run* q (assoco "bob" test-pairs q))
(list 25))
(mk-test
"assoco-first"
(run* q (assoco "alice" test-pairs q))
(list 30))
(mk-test "assoco-missing" (run* q (assoco "dave" test-pairs q)) (list))
(mk-test
"assoco-find-keys-with-value"
(run* q (assoco q test-pairs 25))
(list "bob"))
;; --- nth-o (Peano-indexed access) ---
(mk-test
"nth-o-zero"
(run* q (nth-o :z (list 10 20 30) q))
(list 10))
(mk-test
"nth-o-one"
(run* q (nth-o (list :s :z) (list 10 20 30) q))
(list 20))
(mk-test
"nth-o-two"
(run*
q
(nth-o (list :s (list :s :z)) (list 10 20 30) q))
(list 30))
(mk-test
"nth-o-out-of-range"
(run*
q
(nth-o
(list :s (list :s (list :s :z)))
(list 10 20 30)
q))
(list))
;; --- samelengtho ---
(mk-test
"samelengtho-equal"
(run*
q
(samelengtho (list 1 2 3) (list :a :b :c)))
(list (make-symbol "_.0")))
(mk-test
"samelengtho-different-fails"
(run* q (samelengtho (list 1 2) (list :a :b :c)))
(list))
(mk-test
"samelengtho-empty-equal"
(run* q (samelengtho (list) (list)))
(list (make-symbol "_.0")))
(mk-test
"samelengtho-builds-vars"
(run 1 q (samelengtho (list 1 2 3) q))
(list (list (make-symbol "_.0") (make-symbol "_.1") (make-symbol "_.2"))))
(mk-test
"samelengtho-enumerates-pairs"
(run
3
q
(fresh (l1 l2) (samelengtho l1 l2) (== q (list l1 l2))))
(list
(list (list) (list))
(list (list (make-symbol "_.0")) (list (make-symbol "_.1")))
(list
(list (make-symbol "_.0") (make-symbol "_.1"))
(list (make-symbol "_.2") (make-symbol "_.3")))))
(mk-tests-run!)

View File

@@ -1,62 +0,0 @@
;; lib/minikanren/tests/mapo.sx — relational map.
(mk-test
"mapo-identity"
(run*
q
(mapo (fn (a b) (== a b)) (list 1 2 3) q))
(list (list 1 2 3)))
(mk-test
"mapo-tag-each"
(run*
q
(mapo
(fn (a b) (== b (list :tag a)))
(list 1 2 3)
q))
(list
(list
(list :tag 1)
(list :tag 2)
(list :tag 3))))
(mk-test
"mapo-backward"
(run*
q
(mapo (fn (a b) (== a b)) q (list 1 2 3)))
(list (list 1 2 3)))
(mk-test
"mapo-empty"
(run* q (mapo (fn (a b) (== a b)) (list) q))
(list (list)))
(mk-test
"mapo-duplicate"
(run* q (mapo (fn (a b) (== b (list a a))) (list :x :y) q))
(list (list (list :x :x) (list :y :y))))
(mk-test
"mapo-different-length-fails"
(run*
q
(mapo
(fn (a b) (== a b))
(list 1 2)
(list 1 2 3)))
(list))
;; mapo + arithmetic via intarith
(mk-test
"mapo-square-each"
(run*
q
(mapo
(fn (a b) (*o-i a a b))
(list 1 2 3 4)
q))
(list (list 1 4 9 16)))
(mk-tests-run!)

View File

@@ -1,138 +0,0 @@
;; lib/minikanren/tests/matche.sx — Phase 5 piece D tests for `matche`.
;; --- literal patterns ---
(mk-test
"matche-literal-number"
(run* q (matche q (1 (== q 1))))
(list 1))
(mk-test
"matche-literal-string"
(run* q (matche q ("hello" (== q "hello"))))
(list "hello"))
(mk-test
"matche-literal-no-clause-matches"
(run*
q
(matche 7 (1 (== q :a)) (2 (== q :b))))
(list))
;; --- variable patterns ---
(mk-test
"matche-symbol-pattern"
(run* q (fresh (x) (== x 99) (matche x (a (== q a)))))
(list 99))
(mk-test
"matche-wildcard"
(run* q (fresh (x) (== x 7) (matche x (_ (== q :any)))))
(list :any))
;; --- list patterns ---
(mk-test
"matche-empty-list"
(run* q (matche (list) (() (== q :ok))))
(list :ok))
(mk-test
"matche-pair-binds"
(run*
q
(fresh
(x)
(== x (list 1 2))
(matche x ((a b) (== q (list b a))))))
(list (list 2 1)))
(mk-test
"matche-triple-binds"
(run*
q
(fresh
(x)
(== x (list 1 2 3))
(matche x ((a b c) (== q (list :sum a b c))))))
(list (list :sum 1 2 3)))
(mk-test
"matche-mixed-literal-and-var"
(run*
q
(fresh
(x)
(== x (list 1 99 3))
(matche x ((1 m 3) (== q m)))))
(list 99))
;; --- multi-clause dispatch ---
(mk-test
"matche-multi-clause-shape"
(run*
q
(fresh
(x)
(== x (list 5 6))
(matche
x
(() (== q :empty))
((a) (== q (list :one a)))
((a b) (== q (list :two a b))))))
(list (list :two 5 6)))
(mk-test
"matche-three-shapes-via-fresh"
(run*
q
(fresh
(x)
(matche
x
(() (== q :empty))
((a) (== q (list :one a)))
((a b) (== q (list :two a b))))))
(list
:empty (list :one (make-symbol "_.0"))
(list :two (make-symbol "_.0") (make-symbol "_.1"))))
;; --- nested patterns ---
(mk-test
"matche-nested"
(run*
q
(fresh
(x)
(==
x
(list (list 1 2) (list 3 4)))
(matche x (((a b) (c d)) (== q (list a b c d))))))
(list (list 1 2 3 4)))
;; --- repeated var names create the same fresh var → must unify ---
(mk-test
"matche-repeated-var-implies-equality"
(run*
q
(fresh
(x)
(== x (list 7 7))
(matche x ((a a) (== q a)))))
(list 7))
(mk-test
"matche-repeated-var-mismatch-fails"
(run*
q
(fresh
(x)
(== x (list 7 8))
(matche x ((a a) (== q a)))))
(list))
(mk-tests-run!)

View File

@@ -1,49 +0,0 @@
;; lib/minikanren/tests/minmax.sx — mino + maxo via intarith.
(mk-test
"mino-singleton"
(run* q (mino (list 7) q))
(list 7))
(mk-test
"mino-of-3"
(run* q (mino (list 5 1 3) q))
(list 1))
(mk-test
"mino-of-5"
(run*
q
(mino (list 5 1 3 2 4) q))
(list 1))
(mk-test
"mino-with-dups"
(run* q (mino (list 3 3 3) q))
(list 3))
(mk-test "mino-empty-fails" (run* q (mino (list) q)) (list))
(mk-test
"maxo-singleton"
(run* q (maxo (list 7) q))
(list 7))
(mk-test
"maxo-of-5"
(run*
q
(maxo (list 5 1 3 2 4) q))
(list 5))
(mk-test
"maxo-of-negs"
(run* q (maxo (list -5 -1 -3) q))
(list -1))
(mk-test
"min-and-max-of-list"
(run*
q
(fresh
(mn mx)
(mino (list 5 1 3 2 4) mn)
(maxo (list 5 1 3 2 4) mx)
(== q (list mn mx))))
(list (list 1 5)))
(mk-tests-run!)

View File

@@ -1,50 +0,0 @@
;; lib/minikanren/tests/nafc.sx — Phase 5 piece C tests for `nafc`.
(mk-test
"nafc-failed-goal-succeeds"
(run* q (nafc (== 1 2)))
(list (make-symbol "_.0")))
(mk-test
"nafc-successful-goal-fails"
(run* q (nafc (== 1 1)))
(list))
(mk-test
"nafc-double-negation"
(run* q (nafc (nafc (== 1 1))))
(list (make-symbol "_.0")))
(mk-test
"nafc-with-conde-no-clauses-succeed"
(run*
q
(nafc
(conde ((== 1 2)) ((== 3 4)))))
(list (make-symbol "_.0")))
(mk-test
"nafc-with-conde-some-clause-succeeds-fails"
(run*
q
(nafc
(conde ((== 1 1)) ((== 3 4)))))
(list))
;; --- composing nafc with == as a guard ---
(mk-test
"nafc-as-guard"
(run*
q
(fresh (x) (== x 5) (nafc (== x 99)) (== q x)))
(list 5))
(mk-test
"nafc-guard-blocking"
(run*
q
(fresh (x) (== x 5) (nafc (== x 5)) (== q x)))
(list))
(mk-tests-run!)

View File

@@ -1,29 +0,0 @@
;; lib/minikanren/tests/not-membero.sx — relational "not in list".
(mk-test
"not-membero-absent"
(run* q (not-membero 99 (list 1 2 3)))
(list (make-symbol "_.0")))
(mk-test
"not-membero-present"
(run* q (not-membero 2 (list 1 2 3)))
(list))
(mk-test
"not-membero-empty"
(run* q (not-membero 1 (list)))
(list (make-symbol "_.0")))
(mk-test
"not-membero-as-filter"
(run*
q
(fresh
(x)
(membero
x
(list 1 2 3 4 5))
(not-membero x (list 2 4))
(== q x)))
(list 1 3 5))
(mk-tests-run!)

View File

@@ -1,31 +0,0 @@
;; lib/minikanren/tests/nub-o.sx — relational dedupe (keep last occurrence).
(mk-test "nub-o-empty" (run* q (nub-o (list) q)) (list (list)))
(mk-test
"nub-o-no-duplicates"
(run* q (nub-o (list 1 2 3) q))
(list (list 1 2 3)))
(mk-test
"nub-o-with-duplicates"
(run*
q
(nub-o
(list 1 2 1 3 2 4)
q))
(list (list 1 3 2 4)))
(mk-test
"nub-o-all-same"
(let
((res (run* q (nub-o (list 1 1 1) q))))
(every? (fn (r) (= r (list 1))) res))
true)
(mk-test
"nub-o-keeps-last"
(run* q (nub-o (list 1 2 1) q))
(list (list 2 1)))
(mk-tests-run!)

View File

@@ -1,41 +0,0 @@
;; lib/minikanren/tests/pairlisto.sx — zip two lists into pair list.
(mk-test
"pairlisto-empty"
(run* q (pairlisto (list) (list) q))
(list (list)))
(mk-test
"pairlisto-equal-lengths"
(run*
q
(pairlisto (list 1 2 3) (list :a :b :c) q))
(list
(list (list 1 :a) (list 2 :b) (list 3 :c))))
(mk-test
"pairlisto-recover-l1"
(run*
q
(pairlisto
q
(list :a :b :c)
(list (list 10 :a) (list 20 :b) (list 30 :c))))
(list (list 10 20 30)))
(mk-test
"pairlisto-recover-l2"
(run*
q
(pairlisto
(list 1 2 3)
q
(list (list 1 :x) (list 2 :y) (list 3 :z))))
(list (list :x :y :z)))
(mk-test
"pairlisto-different-lengths-fails"
(run* q (pairlisto (list 1 2) (list :a :b :c) q))
(list))
(mk-tests-run!)

View File

@@ -1,44 +0,0 @@
;; lib/minikanren/tests/palindromeo.sx — palindromic list relation.
(mk-test
"palindromeo-empty"
(run* q (palindromeo (list)))
(list (make-symbol "_.0")))
(mk-test
"palindromeo-singleton"
(run* q (palindromeo (list :a)))
(list (make-symbol "_.0")))
(mk-test
"palindromeo-pair-equal"
(run* q (palindromeo (list 1 1)))
(list (make-symbol "_.0")))
(mk-test
"palindromeo-pair-unequal-fails"
(run* q (palindromeo (list 1 2)))
(list))
(mk-test
"palindromeo-five-yes"
(run*
q
(palindromeo
(list 1 2 3 2 1)))
(list (make-symbol "_.0")))
(mk-test
"palindromeo-five-no"
(run*
q
(palindromeo
(list 1 2 3 4 5)))
(list))
(mk-test
"palindromeo-strings"
(run* q (palindromeo (list "a" "b" "a")))
(list (make-symbol "_.0")))
(mk-tests-run!)

View File

@@ -1,58 +0,0 @@
;; lib/minikanren/tests/parity.sx — eveno + oddo Peano predicates.
(define
mk-nat
(fn (n) (if (= n 0) :z (list :s (mk-nat (- n 1))))))
(mk-test "eveno-zero" (run* q (eveno :z)) (list (make-symbol "_.0")))
(mk-test
"eveno-2"
(run* q (eveno (mk-nat 2)))
(list (make-symbol "_.0")))
(mk-test
"eveno-4"
(run* q (eveno (mk-nat 4)))
(list (make-symbol "_.0")))
(mk-test "eveno-1-fails" (run* q (eveno (mk-nat 1))) (list))
(mk-test "eveno-3-fails" (run* q (eveno (mk-nat 3))) (list))
(mk-test
"oddo-1"
(run* q (oddo (mk-nat 1)))
(list (make-symbol "_.0")))
(mk-test
"oddo-3"
(run* q (oddo (mk-nat 3)))
(list (make-symbol "_.0")))
(mk-test "oddo-zero-fails" (run* q (oddo :z)) (list))
(mk-test "oddo-2-fails" (run* q (oddo (mk-nat 2))) (list))
;; Enumerate small evens.
(mk-test
"eveno-enumerates"
(run 4 q (eveno q))
(list
(mk-nat 0)
(mk-nat 2)
(mk-nat 4)
(mk-nat 6)))
;; Enumerate small odds.
(mk-test
"oddo-enumerates"
(run 4 q (oddo q))
(list
(mk-nat 1)
(mk-nat 3)
(mk-nat 5)
(mk-nat 7)))
;; A number is even XOR odd (no overlap).
(mk-test
"even-odd-no-overlap"
(run*
q
(mk-conj (eveno (mk-nat 4)) (oddo (mk-nat 4))))
(list))
(mk-tests-run!)

View File

@@ -1,75 +0,0 @@
;; lib/minikanren/tests/partitiono.sx — partition list by predicate.
(mk-test
"partitiono-empty"
(run*
q
(fresh
(yes no)
(partitiono (fn (x) (== x 1)) (list) yes no)
(== q (list yes no))))
(list (list (list) (list))))
(mk-test
"partitiono-by-equality"
(run*
q
(fresh
(yes no)
(partitiono
(fn (x) (== x 2))
(list 1 2 3 2 4)
yes
no)
(== q (list yes no))))
(list
(list
(list 2 2)
(list 1 3 4))))
(mk-test
"partitiono-by-numeric-pred"
(run*
q
(fresh
(yes no)
(partitiono
(fn (x) (lto-i x 5))
(list 1 7 2 8 3)
yes
no)
(== q (list yes no))))
(list
(list
(list 1 2 3)
(list 7 8))))
(mk-test
"partitiono-all-yes"
(run*
q
(fresh
(yes no)
(partitiono
(fn (x) (lto-i x 100))
(list 1 2 3)
yes
no)
(== q (list yes no))))
(list (list (list 1 2 3) (list))))
(mk-test
"partitiono-all-no"
(run*
q
(fresh
(yes no)
(partitiono
(fn (x) (lto-i 100 x))
(list 1 2 3)
yes
no)
(== q (list yes no))))
(list (list (list) (list 1 2 3))))
(mk-tests-run!)

View File

@@ -1,40 +0,0 @@
;; lib/minikanren/tests/path-cycle-free.sx — cycle-free reachability search.
;;
;; Threads a "visited" accumulator through the recursion, using nafc +
;; membero to prevent revisiting nodes. Demonstrates how to make the
;; cyclic-graph divergence problem (see tests/cyclic-graph.sx) tractable
;; for graphs with cycles, without invoking Phase-7 tabling.
(define
cf-edges
(list (list :a :b) (list :b :a) (list :b :c) (list :c :d) (list :d :a))) ; another cycle
(define cf-edgeo (fn (from to) (membero (list from to) cf-edges)))
(define
patho-no-cycles
(fn
(x y visited path)
(conde
((cf-edgeo x y) (nafc (membero y visited)) (== path (list x y)))
((fresh (z mid v-prime) (cf-edgeo x z) (nafc (membero z visited)) (conso z visited v-prime) (patho-no-cycles z y v-prime mid) (conso x mid path))))))
(define cf-patho (fn (x y path) (patho-no-cycles x y (list x) path)))
(mk-test
"cycle-free-finds-finitely"
(let
((paths (run* q (cf-patho :a :d q))))
(and
(>= (len paths) 1)
(every? (fn (p) (and (= (first p) :a) (= (last p) :d))) paths)))
true)
(mk-test
"cycle-free-direct-edge"
(run* q (cf-patho :a :b q))
(list (list :a :b)))
(mk-test "cycle-free-no-self-loop" (run* q (cf-patho :a :a q)) (list))
(mk-tests-run!)

View File

@@ -1,119 +0,0 @@
;; lib/minikanren/tests/peano.sx — Peano arithmetic.
;;
;; Builds Peano numbers via a host-side helper so tests stay readable.
;; (mk-nat 3) → (:s (:s (:s :z))).
(define
mk-nat
(fn (n) (if (= n 0) :z (list :s (mk-nat (- n 1))))))
;; --- zeroo ---
(mk-test
"zeroo-zero-succeeds"
(run* q (zeroo :z))
(list (make-symbol "_.0")))
(mk-test
"zeroo-non-zero-fails"
(run* q (zeroo (mk-nat 1)))
(list))
;; --- pluso forward ---
(mk-test
"pluso-forward-2-3"
(run* q (pluso (mk-nat 2) (mk-nat 3) q))
(list (mk-nat 5)))
(mk-test "pluso-forward-zero-zero" (run* q (pluso :z :z q)) (list :z))
(mk-test
"pluso-forward-zero-n"
(run* q (pluso :z (mk-nat 4) q))
(list (mk-nat 4)))
(mk-test
"pluso-forward-n-zero"
(run* q (pluso (mk-nat 4) :z q))
(list (mk-nat 4)))
;; --- pluso backward ---
(mk-test
"pluso-recover-augend"
(run* q (pluso q (mk-nat 2) (mk-nat 5)))
(list (mk-nat 3)))
(mk-test
"pluso-recover-addend"
(run* q (pluso (mk-nat 2) q (mk-nat 5)))
(list (mk-nat 3)))
(mk-test
"pluso-enumerate-pairs-summing-to-3"
(run*
q
(fresh (a b) (pluso a b (mk-nat 3)) (== q (list a b))))
(list
(list :z (mk-nat 3))
(list (mk-nat 1) (mk-nat 2))
(list (mk-nat 2) (mk-nat 1))
(list (mk-nat 3) :z)))
;; --- minuso ---
(mk-test
"minuso-5-2-3"
(run* q (minuso (mk-nat 5) (mk-nat 2) q))
(list (mk-nat 3)))
(mk-test
"minuso-n-n-zero"
(run* q (minuso (mk-nat 7) (mk-nat 7) q))
(list :z))
;; --- *o ---
(mk-test
"times-2-3"
(run* q (*o (mk-nat 2) (mk-nat 3) q))
(list (mk-nat 6)))
(mk-test
"times-zero-anything-zero"
(run* q (*o :z (mk-nat 99) q))
(list :z))
(mk-test
"times-3-4"
(run* q (*o (mk-nat 3) (mk-nat 4) q))
(list (mk-nat 12)))
;; --- lteo / lto ---
(mk-test
"lteo-success"
(run 1 q (lteo (mk-nat 2) (mk-nat 5)))
(list (make-symbol "_.0")))
(mk-test
"lteo-equal-success"
(run 1 q (lteo (mk-nat 3) (mk-nat 3)))
(list (make-symbol "_.0")))
(mk-test
"lteo-greater-fails"
(run* q (lteo (mk-nat 5) (mk-nat 2)))
(list))
(mk-test
"lto-strict-success"
(run 1 q (lto (mk-nat 2) (mk-nat 5)))
(list (make-symbol "_.0")))
(mk-test
"lto-equal-fails"
(run* q (lto (mk-nat 3) (mk-nat 3)))
(list))
(mk-tests-run!)

View File

@@ -1,87 +0,0 @@
;; lib/minikanren/tests/predicates.sx — everyo, someo.
;; --- everyo ---
(mk-test
"everyo-empty-trivially-true"
(run* q (everyo (fn (x) (== x 1)) (list)))
(list (make-symbol "_.0")))
(mk-test
"everyo-all-match"
(run*
q
(everyo
(fn (x) (== x 1))
(list 1 1 1)))
(list (make-symbol "_.0")))
(mk-test
"everyo-some-mismatch"
(run*
q
(everyo
(fn (x) (== x 1))
(list 1 2 1)))
(list))
(mk-test
"everyo-with-intarith"
(run*
q
(everyo
(fn (x) (lto-i x 10))
(list 1 5 9)))
(list (make-symbol "_.0")))
(mk-test
"everyo-with-intarith-fail"
(run*
q
(everyo
(fn (x) (lto-i x 5))
(list 1 5 9)))
(list))
;; --- someo ---
(mk-test
"someo-finds-element"
(run*
q
(someo
(fn (x) (== x 2))
(list 1 2 3)))
(list (make-symbol "_.0")))
(mk-test
"someo-not-found"
(run*
q
(someo
(fn (x) (== x 99))
(list 1 2 3)))
(list))
(mk-test
"someo-empty-fails"
(run* q (someo (fn (x) (== x 1)) (list)))
(list))
(mk-test
"someo-multiple-matches-yields-multiple"
(let
((res (run* q (fresh (x) (someo (fn (y) (== y x)) (list 1 2 1)) (== q x)))))
(len res))
3)
(mk-test
"someo-with-intarith"
(run*
q
(someo
(fn (x) (lto-i 100 x))
(list 5 50 200)))
(list (make-symbol "_.0")))
(mk-tests-run!)

View File

@@ -1,76 +0,0 @@
;; lib/minikanren/tests/prefix-suffix.sx — appendo-derived sublist relations.
(mk-test
"prefixo-empty"
(run* q (prefixo (list) (list 1 2 3)))
(list (make-symbol "_.0")))
(mk-test
"prefixo-full"
(run*
q
(prefixo
(list 1 2 3)
(list 1 2 3)))
(list (make-symbol "_.0")))
(mk-test
"prefixo-partial"
(run*
q
(prefixo
(list 1 2)
(list 1 2 3 4)))
(list (make-symbol "_.0")))
(mk-test
"prefixo-mismatch-fails"
(run*
q
(prefixo
(list 1 3)
(list 1 2 3)))
(list))
(mk-test
"prefixo-enumerates-all"
(run* q (prefixo q (list 1 2 3)))
(list
(list)
(list 1)
(list 1 2)
(list 1 2 3)))
(mk-test
"suffixo-empty"
(run* q (suffixo (list) (list 1 2 3)))
(list (make-symbol "_.0")))
(mk-test
"suffixo-full"
(run*
q
(suffixo
(list 1 2 3)
(list 1 2 3)))
(list (make-symbol "_.0")))
(mk-test
"suffixo-partial"
(run*
q
(suffixo
(list 2 3)
(list 1 2 3)))
(list (make-symbol "_.0")))
(mk-test
"suffixo-enumerates-all"
(run* q (suffixo q (list 1 2 3)))
(list
(list 1 2 3)
(list 2 3)
(list 3)
(list)))
(mk-tests-run!)

View File

@@ -1,60 +0,0 @@
;; lib/minikanren/tests/project.sx — Phase 5 piece B tests for `project`.
;; --- project rebinds vars to ground values for SX use ---
(mk-test
"project-square-via-host"
(run* q (fresh (n) (== n 5) (project (n) (== q (* n n)))))
(list 25))
(mk-test
"project-multi-vars"
(run*
q
(fresh
(a b)
(== a 3)
(== b 4)
(project (a b) (== q (+ a b)))))
(list 7))
(mk-test
"project-with-string-host-op"
(run* q (fresh (s) (== s "hello") (project (s) (== q (str s "!")))))
(list "hello!"))
;; --- project nested inside conde ---
(mk-test
"project-inside-conde"
(run*
q
(fresh
(n)
(conde ((== n 3)) ((== n 4)))
(project (n) (== q (* n 10)))))
(list 30 40))
;; --- project body can be multiple goals (mk-conj'd) ---
(mk-test
"project-multi-goal-body"
(run*
q
(fresh
(n)
(== n 7)
(project (n) (== q (+ n 1)) (== q (+ n 1)))))
(list 8))
(mk-test
"project-multi-goal-body-conflict"
(run*
q
(fresh
(n)
(== n 7)
(project (n) (== q (+ n 1)) (== q (+ n 2)))))
(list))
(mk-tests-run!)

View File

@@ -1,36 +0,0 @@
;; lib/minikanren/tests/pythag.sx — Pythagorean triple search.
;;
;; Uses ino + intarith goals to find triples (a, b, c) with
;; a, b, c ∈ [1..N], a ≤ b, a² + b² = c². With intarith escapes
;; the search runs at host-arithmetic speed.
(define
digits-1-10
(list
1
2
3
4
5
6
7
8
9
10))
(mk-test
"pythag-triples-1-to-10"
(let
((triples (run* q (fresh (a b c a-sq b-sq sum c-sq) (ino a digits-1-10) (ino b digits-1-10) (ino c digits-1-10) (lteo-i a b) (*o-i a a a-sq) (*o-i b b b-sq) (*o-i c c c-sq) (pluso-i a-sq b-sq sum) (== sum c-sq) (== q (list a b c))))))
(and
(= (len triples) 2)
(and
(some
(fn (t) (= t (list 3 4 5)))
triples)
(some
(fn (t) (= t (list 6 8 10)))
triples))))
true)
(mk-tests-run!)

View File

@@ -1,97 +0,0 @@
;; lib/minikanren/tests/queens-fd.sx — N-queens via CLP(FD).
;;
;; Native FD propagation makes N-queens tractable: 4-queens finds both
;; solutions instantly; 5-queens finds all 10 in seconds. Compare with
;; the naive enumerate-then-filter version in queens.sx, which struggles
;; past N=4.
(define
fd-no-diag
(fn
(ci cj k)
(fresh
(a b)
(fd-plus cj k a)
(fd-plus ci k b)
(fd-neq ci a)
(fd-neq cj b))))
(define
n-queens-4-fd
(fn
(cs)
(let
((c1 (nth cs 0))
(c2 (nth cs 1))
(c3 (nth cs 2))
(c4 (nth cs 3)))
(mk-conj
(fd-in c1 (list 1 2 3 4))
(fd-in c2 (list 1 2 3 4))
(fd-in c3 (list 1 2 3 4))
(fd-in c4 (list 1 2 3 4))
(fd-distinct cs)
(fd-no-diag c1 c2 1)
(fd-no-diag c1 c3 2)
(fd-no-diag c1 c4 3)
(fd-no-diag c2 c3 1)
(fd-no-diag c2 c4 2)
(fd-no-diag c3 c4 1)
(fd-label cs)))))
(define
n-queens-5-fd
(fn
(cs)
(let
((c1 (nth cs 0))
(c2 (nth cs 1))
(c3 (nth cs 2))
(c4 (nth cs 3))
(c5 (nth cs 4)))
(mk-conj
(fd-in
c1
(list 1 2 3 4 5))
(fd-in
c2
(list 1 2 3 4 5))
(fd-in
c3
(list 1 2 3 4 5))
(fd-in
c4
(list 1 2 3 4 5))
(fd-in
c5
(list 1 2 3 4 5))
(fd-distinct cs)
(fd-no-diag c1 c2 1)
(fd-no-diag c1 c3 2)
(fd-no-diag c1 c4 3)
(fd-no-diag c1 c5 4)
(fd-no-diag c2 c3 1)
(fd-no-diag c2 c4 2)
(fd-no-diag c2 c5 3)
(fd-no-diag c3 c4 1)
(fd-no-diag c3 c5 2)
(fd-no-diag c4 c5 1)
(fd-label cs)))))
(mk-test
"n-queens-4-fd-two-solutions"
(run*
q
(fresh (a b c d) (== q (list a b c d)) (n-queens-4-fd (list a b c d))))
(list
(list 2 4 1 3)
(list 3 1 4 2)))
(mk-test
"n-queens-5-fd-ten-solutions"
(let
((sols (run* q (fresh (a b c d e) (== q (list a b c d e)) (n-queens-5-fd (list a b c d e))))))
(= (len sols) 10))
true)
(mk-tests-run!)

View File

@@ -1,45 +0,0 @@
;; lib/minikanren/tests/queens.sx — N-queens, the classic miniKanren benchmark.
;; --- safe-diag (helper) ---
(mk-test
"safe-diag-different-cols-different-distance"
(run* q (safe-diag 1 4 2))
(list (make-symbol "_.0")))
(mk-test
"safe-diag-same-distance-fails"
(run* q (safe-diag 1 4 3))
(list))
(mk-test
"safe-diag-same-distance-other-direction-fails"
(run* q (safe-diag 4 1 3))
(list))
;; --- ino-each / range ---
(mk-test
"range-1-to-4"
(range-1-to-n 4)
(list 1 2 3 4))
(mk-test "range-empty" (range-1-to-n 0) (list))
;; --- 4-queens: two solutions ---
(mk-test
"queens-4"
(let
((sols (run* q (fresh (a b c d) (== q (list a b c d)) (queens-cols (list a b c d) 4)))))
(and
(= (len sols) 2)
(and
(some
(fn (s) (= s (list 2 4 1 3)))
sols)
(some
(fn (s) (= s (list 3 1 4 2)))
sols))))
true)
(mk-tests-run!)

View File

@@ -1,90 +0,0 @@
;; lib/minikanren/tests/rdb.sx — relational database queries.
;;
;; Demonstrates how miniKanren can serve as a Datalog-style query engine
;; over fact tables. Tables are SX lists of tuples; the relation just
;; wraps `membero` over the table.
(define
rdb-employees
(list
(list "alice" "engineering" 100000)
(list "bob" "marketing" 80000)
(list "carol" "engineering" 90000)
(list "dave" "engineering" 85000)
(list "eve" "sales" 75000)))
(define
rdb-projects
(list
(list "alice" "compiler")
(list "carol" "compiler")
(list "dave" "runtime")
(list "alice" "runtime")
(list "eve" "outreach")))
;; Relation views over the tables.
(define
employees
(fn (name dept salary) (membero (list name dept salary) rdb-employees)))
(define
on-project
(fn (name project) (membero (list name project) rdb-projects)))
;; --- queries ---
(mk-test
"rdb-engineering-staff"
(let
((res (run* q (fresh (n s) (employees n "engineering" s) (== q n)))))
(and
(= (len res) 3)
(and
(some (fn (n) (= n "alice")) res)
(and
(some (fn (n) (= n "carol")) res)
(some (fn (n) (= n "dave")) res)))))
true)
(mk-test
"rdb-high-salary"
(let
((res (run* q (fresh (n d s) (employees n d s) (lto-i 85000 s) (== q (list n s))))))
(and
(= (len res) 2)
(and
(some (fn (r) (= r (list "alice" 100000))) res)
(some (fn (r) (= r (list "carol" 90000))) res))))
true)
(mk-test
"rdb-join-employee-project"
(let
((res (run* q (fresh (n d s) (employees n d s) (on-project n "compiler") (== q n)))))
(and
(= (len res) 2)
(and
(some (fn (n) (= n "alice")) res)
(some (fn (n) (= n "carol")) res))))
true)
(mk-test
"rdb-engineers-on-runtime"
(let
((res (run* q (fresh (n s) (employees n "engineering" s) (on-project n "runtime") (== q n)))))
(and
(= (len res) 2)
(and
(some (fn (n) (= n "alice")) res)
(some (fn (n) (= n "dave")) res))))
true)
(mk-test
"rdb-people-on-multiple-projects"
(let
((res (run* q (fresh (n p1 p2) (on-project n p1) (on-project n p2) (nafc (== p1 p2)) (== q n)))))
(some (fn (n) (= n "alice")) res))
true)
(mk-tests-run!)

View File

@@ -1,291 +0,0 @@
;; lib/minikanren/tests/relations.sx — Phase 4 standard relations.
;;
;; Includes the classic miniKanren canaries: appendo forwards / backwards /
;; bidirectionally, membero, listo enumeration.
;; --- nullo / pairo ---
(mk-test
"nullo-empty-succeeds"
(run* q (nullo (list)))
(list (make-symbol "_.0")))
(mk-test "nullo-non-empty-fails" (run* q (nullo (list 1))) (list))
(mk-test
"pairo-non-empty-succeeds"
(run* q (pairo (list 1 2)))
(list (make-symbol "_.0")))
(mk-test "pairo-empty-fails" (run* q (pairo (list))) (list))
;; --- caro / cdro / firsto / resto ---
(mk-test
"caro-extracts-head"
(run* q (caro (list 1 2 3) q))
(list 1))
(mk-test
"cdro-extracts-tail"
(run* q (cdro (list 1 2 3) q))
(list (list 2 3)))
(mk-test
"firsto-alias-of-caro"
(run* q (firsto (list 10 20) q))
(list 10))
(mk-test
"resto-alias-of-cdro"
(run* q (resto (list 10 20) q))
(list (list 20)))
(mk-test
"caro-cdro-build"
(run*
q
(fresh
(h t)
(caro (list 1 2 3) h)
(cdro (list 1 2 3) t)
(== q (list h t))))
(list (list 1 (list 2 3))))
;; --- conso ---
(mk-test
"conso-forward"
(run* q (conso 0 (list 1 2 3) q))
(list (list 0 1 2 3)))
(mk-test
"conso-extract-head"
(run*
q
(conso
q
(list 2 3)
(list 1 2 3)))
(list 1))
(mk-test
"conso-extract-tail"
(run* q (conso 1 q (list 1 2 3)))
(list (list 2 3)))
;; --- listo ---
(mk-test
"listo-empty-succeeds"
(run* q (listo (list)))
(list (make-symbol "_.0")))
(mk-test
"listo-finite-list-succeeds"
(run* q (listo (list 1 2 3)))
(list (make-symbol "_.0")))
(mk-test
"listo-enumerates-shapes"
(run 3 q (listo q))
(list
(list)
(list (make-symbol "_.0"))
(list (make-symbol "_.0") (make-symbol "_.1"))))
;; --- appendo: the canary ---
(mk-test
"appendo-forward-simple"
(run*
q
(appendo (list 1 2) (list 3 4) q))
(list (list 1 2 3 4)))
(mk-test
"appendo-forward-empty-l"
(run* q (appendo (list) (list 3 4) q))
(list (list 3 4)))
(mk-test
"appendo-forward-empty-s"
(run* q (appendo (list 1 2) (list) q))
(list (list 1 2)))
(mk-test
"appendo-recovers-tail"
(run*
q
(appendo
(list 1 2)
q
(list 1 2 3 4)))
(list (list 3 4)))
(mk-test
"appendo-recovers-prefix"
(run*
q
(appendo
q
(list 3 4)
(list 1 2 3 4)))
(list (list 1 2)))
(mk-test
"appendo-backward-all-splits"
(run*
q
(fresh
(l s)
(appendo l s (list 1 2 3))
(== q (list l s))))
(list
(list (list) (list 1 2 3))
(list (list 1) (list 2 3))
(list (list 1 2) (list 3))
(list (list 1 2 3) (list))))
(mk-test
"appendo-empty-empty-empty"
(run* q (appendo (list) (list) q))
(list (list)))
;; --- membero ---
(mk-test
"membero-element-present"
(run
1
q
(membero 2 (list 1 2 3)))
(list (make-symbol "_.0")))
(mk-test
"membero-element-absent-empty"
(run* q (membero 99 (list 1 2 3)))
(list))
(mk-test
"membero-enumerates"
(run* q (membero q (list "a" "b" "c")))
(list "a" "b" "c"))
;; --- reverseo ---
(mk-test
"reverseo-forward"
(run* q (reverseo (list 1 2 3) q))
(list (list 3 2 1)))
(mk-test "reverseo-empty" (run* q (reverseo (list) q)) (list (list)))
(mk-test
"reverseo-singleton"
(run* q (reverseo (list 42) q))
(list (list 42)))
(mk-test
"reverseo-five"
(run*
q
(reverseo (list 1 2 3 4 5) q))
(list (list 5 4 3 2 1)))
(mk-test
"reverseo-backward-one"
(run 1 q (reverseo q (list 1 2 3)))
(list (list 3 2 1)))
(mk-test
"reverseo-round-trip"
(run*
q
(fresh (mid) (reverseo (list "a" "b" "c") mid) (reverseo mid q)))
(list (list "a" "b" "c")))
;; --- lengtho (Peano-style) ---
(mk-test "lengtho-empty-is-z" (run* q (lengtho (list) q)) (list :z))
(mk-test
"lengtho-of-3"
(run* q (lengtho (list "a" "b" "c") q))
(list (list :s (list :s (list :s :z)))))
(mk-test
"lengtho-empty-from-zero"
(run 1 q (lengtho q :z))
(list (list)))
(mk-test
"lengtho-enumerates-of-length-2"
(run 1 q (lengtho q (list :s (list :s :z))))
(list (list (make-symbol "_.0") (make-symbol "_.1"))))
;; --- inserto ---
(mk-test
"inserto-front"
(run* q (inserto 0 (list 1 2 3) q))
(list
(list 0 1 2 3)
(list 1 0 2 3)
(list 1 2 0 3)
(list 1 2 3 0)))
(mk-test
"inserto-empty"
(run* q (inserto 0 (list) q))
(list (list 0)))
;; --- permuteo ---
(mk-test "permuteo-empty" (run* q (permuteo (list) q)) (list (list)))
(mk-test
"permuteo-singleton"
(run* q (permuteo (list 42) q))
(list (list 42)))
(mk-test
"permuteo-two"
(run* q (permuteo (list 1 2) q))
(list (list 1 2) (list 2 1)))
(mk-test
"permuteo-three-as-set"
(let
((perms (run* q (permuteo (list 1 2 3) q))))
(and
(= (len perms) 6)
(and
(some (fn (p) (= p (list 1 2 3))) perms)
(and
(some
(fn (p) (= p (list 2 1 3)))
perms)
(and
(some
(fn (p) (= p (list 1 3 2)))
perms)
(and
(some
(fn (p) (= p (list 2 3 1)))
perms)
(and
(some
(fn (p) (= p (list 3 1 2)))
perms)
(some
(fn (p) (= p (list 3 2 1)))
perms))))))))
true)
(mk-test
"permuteo-backward-finds-input"
(run 1 q (permuteo q (list "a" "b" "c")))
(list (list "a" "b" "c")))
(mk-tests-run!)

View File

@@ -1,39 +0,0 @@
;; lib/minikanren/tests/removeo-allo.sx — remove every occurrence of x.
(mk-test
"removeo-allo-multi"
(run*
q
(removeo-allo
2
(list 1 2 3 2 4 2)
q))
(list (list 1 3 4)))
(mk-test
"removeo-allo-single"
(run*
q
(removeo-allo 2 (list 1 2 3) q))
(list (list 1 3)))
(mk-test
"removeo-allo-no-match"
(run*
q
(removeo-allo 99 (list 1 2 3) q))
(list (list 1 2 3)))
(mk-test
"removeo-allo-everything"
(run*
q
(removeo-allo 1 (list 1 1 1) q))
(list (list)))
(mk-test
"removeo-allo-empty"
(run* q (removeo-allo 1 (list) q))
(list (list)))
(mk-tests-run!)

View File

@@ -1,69 +0,0 @@
;; lib/minikanren/tests/repeato-concato.sx — repeat element n times +
;; concatenate a list of lists.
(define
mk-nat
(fn (n) (if (= n 0) :z (list :s (mk-nat (- n 1))))))
;; --- repeato ---
(mk-test
"repeato-zero"
(run* q (repeato :a (mk-nat 0) q))
(list (list)))
(mk-test
"repeato-one"
(run* q (repeato :a (mk-nat 1) q))
(list (list :a)))
(mk-test
"repeato-three"
(run* q (repeato :a (mk-nat 3) q))
(list (list :a :a :a)))
(mk-test
"repeato-numeric"
(run* q (repeato 7 (mk-nat 4) q))
(list (list 7 7 7 7)))
(mk-test
"repeato-recover-count"
(run* q (repeato :x q (list :x :x :x :x)))
(list (mk-nat 4)))
;; --- concato ---
(mk-test "concato-empty" (run* q (concato (list) q)) (list (list)))
(mk-test
"concato-single"
(run* q (concato (list (list 1 2 3)) q))
(list (list 1 2 3)))
(mk-test
"concato-multi"
(run*
q
(concato
(list
(list 1 2)
(list 3)
(list 4 5 6))
q))
(list
(list 1 2 3 4 5 6)))
(mk-test
"concato-all-empty"
(run* q (concato (list (list) (list) (list)) q))
(list (list)))
(mk-test
"concato-mixed-empty"
(run*
q
(concato
(list (list 1) (list) (list 2 3))
q))
(list (list 1 2 3)))
(mk-tests-run!)

View File

@@ -1,46 +0,0 @@
;; lib/minikanren/tests/rev-acco.sx — accumulator-style reverse.
;;
;; Faster than reverseo for forward queries (no quadratic appendos).
;; Trade-off: rev-acco is asymmetric (acc=initial-empty for the public
;; interface), so it does not cleanly run backwards in run* the way
;; reverseo does.
(mk-test "rev-2o-empty" (run* q (rev-2o (list) q)) (list (list)))
(mk-test
"rev-2o-singleton"
(run* q (rev-2o (list 7) q))
(list (list 7)))
(mk-test
"rev-2o-three"
(run* q (rev-2o (list 1 2 3) q))
(list (list 3 2 1)))
(mk-test
"rev-2o-five"
(run*
q
(rev-2o (list 1 2 3 4 5) q))
(list (list 5 4 3 2 1)))
(mk-test
"rev-2o-strings"
(run* q (rev-2o (list "a" "b" "c") q))
(list (list "c" "b" "a")))
(mk-test
"rev-2o-reverseo-agree"
(let
((via-reverseo (first (run* q (reverseo (list 1 2 3 4 5) q))))
(via-rev-2o
(first
(run*
q
(rev-2o
(list 1 2 3 4 5)
q)))))
(= via-reverseo via-rev-2o))
true)
(mk-tests-run!)

View File

@@ -1,114 +0,0 @@
;; lib/minikanren/tests/run.sx — Phase 3 tests for run* / run / reify.
;; --- canonical TRS one-liners ---
(mk-test "run*-eq-one" (run* q (== q 1)) (list 1))
(mk-test "run*-eq-string" (run* q (== q "hello")) (list "hello"))
(mk-test "run*-eq-symbol" (run* q (== q (quote sym))) (list (quote sym)))
(mk-test "run*-fail-empty" (run* q (== 1 2)) (list))
;; --- run with a count ---
(mk-test
"run-3-of-many"
(run
3
q
(conde
((== q 1))
((== q 2))
((== q 3))
((== q 4))
((== q 5))))
(list 1 2 3))
(mk-test "run-zero-empty" (run 0 q (== q 1)) (list))
(mk-test
"run-1-takes-one"
(run 1 q (conde ((== q "a")) ((== q "b"))))
(list "a"))
;; --- reification: unbound vars get _.N left-to-right ---
(mk-test
"reify-single-unbound"
(run* q (fresh (x) (== q x)))
(list (make-symbol "_.0")))
(mk-test
"reify-pair-unbound"
(run* q (fresh (x y) (== q (list x y))))
(list (list (make-symbol "_.0") (make-symbol "_.1"))))
(mk-test
"reify-mixed-bound-unbound"
(run* q (fresh (x y) (== q (list 1 x 2 y))))
(list
(list 1 (make-symbol "_.0") 2 (make-symbol "_.1"))))
(mk-test
"reify-shared-unbound-same-name"
(run* q (fresh (x) (== q (list x x))))
(list (list (make-symbol "_.0") (make-symbol "_.0"))))
(mk-test
"reify-distinct-unbound-distinct-names"
(run* q (fresh (x y) (== q (list x y x y))))
(list
(list
(make-symbol "_.0")
(make-symbol "_.1")
(make-symbol "_.0")
(make-symbol "_.1"))))
;; --- conde + run* ---
(mk-test
"run*-conde-three"
(run*
q
(conde ((== q 1)) ((== q 2)) ((== q 3))))
(list 1 2 3))
(mk-test
"run*-conde-fresh-mix"
(run*
q
(conde ((fresh (x) (== q (list 1 x)))) ((== q "ground"))))
(list (list 1 (make-symbol "_.0")) "ground"))
;; --- run* + conjunction ---
(mk-test
"run*-conj-binds-q"
(run* q (fresh (x) (== x 5) (== q (list x x))))
(list (list 5 5)))
;; --- run* + condu ---
(mk-test
"run*-condu-first-wins"
(run* q (condu ((== q 1)) ((== q 2))))
(list 1))
(mk-test
"run*-onceo-trim"
(run* q (onceo (conde ((== q "a")) ((== q "b")))))
(list "a"))
;; --- multi-goal run ---
(mk-test
"run*-three-goals"
(run*
q
(fresh
(x y z)
(== x 1)
(== y 2)
(== z 3)
(== q (list x y z))))
(list (list 1 2 3)))
(mk-tests-run!)

View File

@@ -1,46 +0,0 @@
;; lib/minikanren/tests/selecto.sx — choose an element + rest of list.
(mk-test
"selecto-enumerate"
(run*
q
(fresh
(x r)
(selecto x r (list 1 2 3))
(== q (list x r))))
(list
(list 1 (list 2 3))
(list 2 (list 1 3))
(list 3 (list 1 2))))
(mk-test
"selecto-find-rest"
(run* q (selecto 2 q (list 1 2 3)))
(list (list 1 3)))
(mk-test
"selecto-find-element"
(run*
q
(selecto
q
(list 1 3)
(list 1 2 3)))
(list 2))
(mk-test
"selecto-element-not-present-fails"
(run* q (selecto 99 q (list 1 2 3)))
(list))
(mk-test
"selecto-empty-list-fails"
(run* q (selecto q (list) (list)))
(list))
(mk-test
"selecto-singleton"
(run* q (fresh (x r) (selecto x r (list :only)) (== q (list x r))))
(list (list :only (list))))
(mk-tests-run!)

View File

@@ -1,97 +0,0 @@
;; lib/minikanren/tests/send-more-money.sx — classic cryptarithmetic
;;
;; S E N D
;; + M O R E
;; ---------
;; M O N E Y
;;
;; Column-by-column encoding with carries c1, c2, c3, and the
;; leftmost column produces a carry which equals M (the result is 5 digits).
;; All 8 letters distinct; S ≠ 0, M ≠ 0.
;; Unique solution: S=9, E=5, N=6, D=7, M=1, O=0, R=8, Y=2.
;;
;; Note: the full search labelling 11 variables from {0..9} is too slow
;; for naive labelling order (10^11 combinations naively, even with
;; bounds-consistency the branching factor dominates). Real CLP(FD)
;; systems use first-fail heuristics. Here we only verify the encoding
;; against the known answer.
(define
digits-0-9
(list
0
1
2
3
4
5
6
7
8
9))
(define
digits-1-9
(list
1
2
3
4
5
6
7
8
9))
(define
smm-col-with-carry
(fn
(x y carry-in z carry-out)
(fresh
(xy xyc ten-cout z-plus-ten-cout)
(fd-plus x y xy)
(fd-plus xy carry-in xyc)
(fd-times 10 carry-out ten-cout)
(fd-plus z ten-cout z-plus-ten-cout)
(fd-eq xyc z-plus-ten-cout))))
(define
send-more-money
(fn
(S E N D M O R Y)
(fresh
(c1 c2 c3)
(mk-conj
(fd-in S digits-1-9)
(fd-in M digits-1-9)
(fd-in E digits-0-9)
(fd-in N digits-0-9)
(fd-in D digits-0-9)
(fd-in O digits-0-9)
(fd-in R digits-0-9)
(fd-in Y digits-0-9)
(fd-in c1 (list 0 1))
(fd-in c2 (list 0 1))
(fd-in c3 (list 0 1))
(fd-distinct (list S E N D M O R Y))
(smm-col-with-carry D E 0 Y c1)
(smm-col-with-carry N R c1 E c2)
(smm-col-with-carry E O c2 N c3)
(smm-col-with-carry S M c3 O M)
(fd-label (list S E N D M O R Y c1 c2 c3))))))
(mk-test
"send-more-money-verify-known-solution"
(run*
q
(send-more-money
9
5
6
7
1
0
8
2))
(list (make-symbol "_.0")))
(mk-tests-run!)

View File

@@ -1,47 +0,0 @@
;; lib/minikanren/tests/simplifyo.sx — algebraic expression simplifier
;; demo using conda for first-match-wins dispatch.
(define
simplify-step-o
(fn
(expr result)
(conda
((fresh (x) (== expr (list :+ 0 x)) (== result x)))
((fresh (x) (== expr (list :+ x 0)) (== result x)))
((fresh (y) (== expr (list :* 0 y)) (== result 0)))
((fresh (x) (== expr (list :* x 0)) (== result 0)))
((fresh (x) (== expr (list :* 1 x)) (== result x)))
((fresh (x) (== expr (list :* x 1)) (== result x)))
((== result expr))))) ;; default: unchanged
(mk-test
"simplify-zero-plus"
(run* q (simplify-step-o (list :+ 0 :y) q))
(list :y))
(mk-test
"simplify-plus-zero"
(run* q (simplify-step-o (list :+ :x 0) q))
(list :x))
(mk-test
"simplify-zero-times"
(run* q (simplify-step-o (list :* 0 :y) q))
(list 0))
(mk-test
"simplify-one-times"
(run* q (simplify-step-o (list :* 1 :y) q))
(list :y))
(mk-test
"simplify-no-rule-applies"
(run* q (simplify-step-o (list :+ :x :y) q))
(list (list :+ :x :y)))
(mk-test
"simplify-non-identity-form"
(run* q (simplify-step-o (list :+ 5 7) q))
(list (list :+ 5 7)))
(mk-tests-run!)

View File

@@ -1,40 +0,0 @@
;; lib/minikanren/tests/sortedo.sx — checks list is non-decreasing.
(mk-test
"sortedo-empty"
(run* q (sortedo (list)))
(list (make-symbol "_.0")))
(mk-test
"sortedo-singleton"
(run* q (sortedo (list 42)))
(list (make-symbol "_.0")))
(mk-test
"sortedo-ascending"
(run* q (sortedo (list 1 2 3 4)))
(list (make-symbol "_.0")))
(mk-test
"sortedo-with-equal-adjacent"
(run*
q
(sortedo (list 1 1 2 2 3)))
(list (make-symbol "_.0")))
(mk-test
"sortedo-out-of-order-fails"
(run* q (sortedo (list 1 3 2)))
(list))
(mk-test
"sortedo-descending-fails"
(run* q (sortedo (list 3 2 1)))
(list))
(mk-test
"sortedo-pair-equal"
(run* q (sortedo (list 5 5)))
(list (make-symbol "_.0")))
(mk-tests-run!)

View File

@@ -1,60 +0,0 @@
;; lib/minikanren/tests/subo.sx — contiguous-sublist relation.
(mk-test
"subo-simple-found"
(run*
q
(subo
(list 2 3)
(list 1 2 3 4)))
(list (make-symbol "_.0")))
(mk-test
"subo-not-contiguous-fails"
(run*
q
(subo
(list 2 4)
(list 1 2 3 4)))
(list))
(mk-test
"subo-full-list-found"
(run*
q
(subo
(list 1 2 3)
(list 1 2 3)))
(list (make-symbol "_.0")))
(mk-test
"subo-empty-list-found"
(let
((res (run* q (subo (list) (list 1 2 3)))))
(= (len res) 4))
true)
(mk-test
"subo-prefix"
(run*
q
(subo
(list 1 2)
(list 1 2 3 4)))
(list (make-symbol "_.0")))
(mk-test
"subo-suffix"
(run*
q
(subo
(list 3 4)
(list 1 2 3 4)))
(list (make-symbol "_.0")))
(mk-test
"subo-strings"
(run* q (subo (list "b" "c") (list "a" "b" "c" "d")))
(list (make-symbol "_.0")))
(mk-tests-run!)

View File

@@ -1,62 +0,0 @@
;; lib/minikanren/tests/subseto.sx — every element of l1 is in l2.
(mk-test
"subseto-empty"
(run* q (subseto (list) (list 1 2 3)))
(list (make-symbol "_.0")))
(mk-test
"subseto-singleton-yes"
(run*
q
(subseto (list 2) (list 1 2 3)))
(list (make-symbol "_.0")))
(mk-test
"subseto-singleton-no"
(run*
q
(subseto (list 99) (list 1 2 3)))
(list))
(mk-test
"subseto-multi-yes"
(run
1
q
(subseto
(list 1 3)
(list 1 2 3 4)))
(list (make-symbol "_.0")))
(mk-test
"subseto-multi-no"
(run*
q
(subseto
(list 1 99)
(list 1 2 3)))
(list))
(mk-test
"subseto-equal-sets"
(run
1
q
(subseto
(list 1 2 3)
(list 1 2 3)))
(list (make-symbol "_.0")))
;; allow duplicates in l1 — each just needs membership in l2.
(mk-test
"subseto-duplicates-allowed"
(run
1
q
(subseto
(list 1 1 2)
(list 1 2 3)))
(list (make-symbol "_.0")))
(mk-tests-run!)

View File

@@ -1,89 +0,0 @@
;; lib/minikanren/tests/sudoku-4x4.sx — Sudoku 4×4 via CLP(FD).
;;
;; Grid in row-major order:
;;
;; c0 c1 | c2 c3
;; c4 c5 | c6 c7
;; ------+------
;; c8 c9 | cA cB
;; cC cD | cE cF
;;
;; Each cell ∈ {1, 2, 3, 4}. 4 rows + 4 cols + 4 2x2 boxes are each a
;; distinct permutation.
(define digits-1-4 (list 1 2 3 4))
(define
sudoku-4x4
(fn
(cells)
(let
((c0 (nth cells 0))
(c1 (nth cells 1))
(c2 (nth cells 2))
(c3 (nth cells 3))
(c4 (nth cells 4))
(c5 (nth cells 5))
(c6 (nth cells 6))
(c7 (nth cells 7))
(c8 (nth cells 8))
(c9 (nth cells 9))
(cA (nth cells 10))
(cB (nth cells 11))
(cC (nth cells 12))
(cD (nth cells 13))
(cE (nth cells 14))
(cF (nth cells 15)))
(mk-conj
(fd-in c0 digits-1-4)
(fd-in c1 digits-1-4)
(fd-in c2 digits-1-4)
(fd-in c3 digits-1-4)
(fd-in c4 digits-1-4)
(fd-in c5 digits-1-4)
(fd-in c6 digits-1-4)
(fd-in c7 digits-1-4)
(fd-in c8 digits-1-4)
(fd-in c9 digits-1-4)
(fd-in cA digits-1-4)
(fd-in cB digits-1-4)
(fd-in cC digits-1-4)
(fd-in cD digits-1-4)
(fd-in cE digits-1-4)
(fd-in cF digits-1-4)
(fd-distinct (list c0 c1 c2 c3))
(fd-distinct (list c4 c5 c6 c7))
(fd-distinct (list c8 c9 cA cB))
(fd-distinct (list cC cD cE cF))
(fd-distinct (list c0 c4 c8 cC))
(fd-distinct (list c1 c5 c9 cD))
(fd-distinct (list c2 c6 cA cE))
(fd-distinct (list c3 c7 cB cF))
(fd-distinct (list c0 c1 c4 c5))
(fd-distinct (list c2 c3 c6 c7))
(fd-distinct (list c8 c9 cC cD))
(fd-distinct (list cA cB cE cF))
(fd-label cells)))))
;; --- Tests ---
(mk-test
"sudoku-4x4-empty-grid-count"
(let
((sols (run* q (fresh (c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 cA cB cC cD cE cF) (== q (list c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 cA cB cC cD cE cF)) (sudoku-4x4 (list c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 cA cB cC cD cE cF))))))
(len sols))
288)
(mk-test
"sudoku-4x4-impossible-clue-empty"
(run*
q
(fresh
(c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 cA cB cC cD cE cF)
(== c0 1)
(== c1 1)
(== q (list c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 cA cB cC cD cE cF))
(sudoku-4x4 (list c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 cA cB cC cD cE cF))))
(list))
(mk-tests-run!)

View File

@@ -1,44 +0,0 @@
;; lib/minikanren/tests/sum-product.sx — fold list to integer.
(mk-test "sumo-empty" (run* q (sumo (list) q)) (list 0))
(mk-test
"sumo-1-to-5"
(run*
q
(sumo (list 1 2 3 4 5) q))
(list 15))
(mk-test
"sumo-zeros"
(run* q (sumo (list 0 0 0) q))
(list 0))
(mk-test
"sumo-negs"
(run* q (sumo (list 5 -3 8) q))
(list 10))
(mk-test "producto-empty" (run* q (producto (list) q)) (list 1))
(mk-test
"producto-1-to-4"
(run* q (producto (list 1 2 3 4) q))
(list 24))
(mk-test
"producto-with-0"
(run* q (producto (list 5 0 7) q))
(list 0))
(mk-test
"producto-of-1s"
(run* q (producto (list 1 1 1) q))
(list 1))
(mk-test
"sum-product-pythagorean-square"
(run*
q
(fresh
(s sq2)
(sumo (list 3 4 5) s)
(producto (list 3 3) sq2)
(== q (list s sq2))))
(list (list 12 9)))
(mk-tests-run!)

View File

@@ -1,32 +0,0 @@
;; lib/minikanren/tests/swap-firsto.sx — swap first two elements.
(mk-test
"swap-firsto-pair"
(run* q (swap-firsto (list 1 2) q))
(list (list 2 1)))
(mk-test
"swap-firsto-with-tail"
(run* q (swap-firsto (list 1 2 3 4) q))
(list (list 2 1 3 4)))
(mk-test
"swap-firsto-singleton-fails"
(run* q (swap-firsto (list 1) q))
(list))
(mk-test "swap-firsto-empty-fails" (run* q (swap-firsto (list) q)) (list))
(mk-test
"swap-firsto-self-inverse"
(run*
q
(fresh (mid) (swap-firsto (list :a :b :c :d) mid) (swap-firsto mid q)))
(list (list :a :b :c :d)))
(mk-test
"swap-firsto-backward"
(run* q (swap-firsto q (list :y :x :z)))
(list (list :x :y :z)))
(mk-tests-run!)

Some files were not shown because too many files have changed in this diff Show More