;; lib/prolog/tests/cross_validate.sx
;; Verifies that the compiled solver produces the same solution counts as the
;; interpreter for each classic program + built-in exercise.
;; Interpreter is the reference: if they disagree, the compiler is wrong.

(define pl-xv-test-count 0)
(define pl-xv-test-pass 0)
(define pl-xv-test-fail 0)
(define pl-xv-test-failures (list))

(define
  pl-xv-test!
  (fn
    (name got expected)
    (set! pl-xv-test-count (+ pl-xv-test-count 1))
    (if
      (= got expected)
      (set! pl-xv-test-pass (+ pl-xv-test-pass 1))
      (begin
        (set! pl-xv-test-fail (+ pl-xv-test-fail 1))
        (append! pl-xv-test-failures name)))))

;; Shorthand: assert compiled result matches interpreter.
(define
  pl-xv-match!
  (fn
    (name src goal)
    (pl-xv-test! name (pl-compiled-matches-interp? src goal) true)))

;; ── 1. append/3 ─────────────────────────────────────────────────

(define
  pl-xv-append
  "append([], L, L). append([H|T], L, [H|R]) :- append(T, L, R).")

(pl-xv-match! "append build 2+2" pl-xv-append "append([1,2],[3,4],X)")
(pl-xv-match! "append split [a,b,c]" pl-xv-append "append(X, Y, [a,b,c])")
(pl-xv-match! "append member-mode" pl-xv-append "append(_, [3], [1,2,3])")

;; ── 2. member/2 ─────────────────────────────────────────────────

(define pl-xv-member "member(X, [X|_]). member(X, [_|T]) :- member(X, T).")

(pl-xv-match! "member check hit" pl-xv-member "member(b, [a,b,c])")
(pl-xv-match! "member count" pl-xv-member "member(X, [a,b,c])")
(pl-xv-match! "member empty" pl-xv-member "member(X, [])")

;; ── 3. facts + transitive rules ─────────────────────────────────

(define
  pl-xv-ancestor
  (str
    "parent(a,b). parent(b,c). parent(c,d). parent(a,c)."
    "ancestor(X,Y) :- parent(X,Y)."
    "ancestor(X,Y) :- parent(X,Z), ancestor(Z,Y)."))

(pl-xv-match! "ancestor direct" pl-xv-ancestor "ancestor(a,b)")
(pl-xv-match! "ancestor transitive" pl-xv-ancestor "ancestor(a,d)")
(pl-xv-match! "ancestor all from a" pl-xv-ancestor "ancestor(a,Y)")

;; ── 4. cut semantics ────────────────────────────────────────────

(define pl-xv-cut "first(X,[X|_]) :- !. first(X,[_|T]) :- first(X,T).")

(pl-xv-match! "cut one solution" pl-xv-cut "first(X,[a,b,c])")
(pl-xv-match! "cut empty list" pl-xv-cut "first(X,[])")

;; ── 5. arithmetic ───────────────────────────────────────────────

(define pl-xv-arith "sq(X,Y) :- Y is X * X. even(X) :- 0 is X mod 2.")

(pl-xv-match! "sq(3,Y) count" pl-xv-arith "sq(3,Y)")
(pl-xv-match! "sq(3,9) check" pl-xv-arith "sq(3,9)")
(pl-xv-match! "even(4) check" pl-xv-arith "even(4)")
(pl-xv-match! "even(3) check" pl-xv-arith "even(3)")

;; ── 6. if-then-else ─────────────────────────────────────────────

(define pl-xv-ite "classify(X, pos) :- X > 0, !. classify(_, nonpos).")

(pl-xv-match! "classify positive" pl-xv-ite "classify(5, C)")
(pl-xv-match! "classify zero" pl-xv-ite "classify(0, C)")

;; ── Runner ───────────────────────────────────────────────────────

(define pl-cross-validate-tests-run! (fn () {:failed pl-xv-test-fail :passed pl-xv-test-pass :total pl-xv-test-count :failures pl-xv-test-failures}))
