;; 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!)
