;; lib/prolog/tests/compiler.sx — compiled clause dispatch tests

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

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

;; Load src, compile, return DB.
(define
  pl-cmp-mk
  (fn
    (src)
    (let
      ((db (pl-mk-db)))
      (pl-db-load! db (pl-parse src))
      (pl-compile-db! db)
      db)))

;; Run goal string against compiled DB; return bool (instantiates vars).
(define
  pl-cmp-once
  (fn
    (db src)
    (pl-solve-once!
      db
      (pl-instantiate (pl-parse-goal src) {})
      (pl-mk-trail))))

;; Count solutions for goal string against compiled DB.
(define
  pl-cmp-count
  (fn
    (db src)
    (pl-solve-count!
      db
      (pl-instantiate (pl-parse-goal src) {})
      (pl-mk-trail))))

;; ── 1. Simple facts ──────────────────────────────────────────────

(define pl-cmp-db1 (pl-cmp-mk "color(red). color(green). color(blue)."))

(pl-cmp-test! "compiled fact hit" (pl-cmp-once pl-cmp-db1 "color(red)") true)
(pl-cmp-test!
  "compiled fact miss"
  (pl-cmp-once pl-cmp-db1 "color(yellow)")
  false)
(pl-cmp-test! "compiled fact count" (pl-cmp-count pl-cmp-db1 "color(X)") 3)

;; ── 2. Recursive rule: append ────────────────────────────────────

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

(pl-cmp-test!
  "compiled append build"
  (pl-cmp-once pl-cmp-db2 "append([1,2],[3],[1,2,3])")
  true)
(pl-cmp-test!
  "compiled append fail"
  (pl-cmp-once pl-cmp-db2 "append([1,2],[3],[1,2])")
  false)
(pl-cmp-test!
  "compiled append split count"
  (pl-cmp-count pl-cmp-db2 "append(X, Y, [a,b])")
  3)

;; ── 3. Cut ───────────────────────────────────────────────────────

(define
  pl-cmp-db3
  (pl-cmp-mk "first(X, [X|_]) :- !. first(X, [_|T]) :- first(X, T)."))

(pl-cmp-test!
  "compiled cut: only one solution"
  (pl-cmp-count pl-cmp-db3 "first(X, [a,b,c])")
  1)

(let
  ((db pl-cmp-db3) (trail (pl-mk-trail)) (env {}))
  (let
    ((x (pl-mk-rt-var "X")))
    (dict-set! env "X" x)
    (pl-solve-once!
      db
      (pl-instantiate (pl-parse-goal "first(X, [a,b,c])") env)
      trail)
    (pl-cmp-test!
      "compiled cut: correct binding"
      (pl-atom-name (pl-walk x))
      "a")))

;; ── 4. member ────────────────────────────────────────────────────

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

(pl-cmp-test!
  "compiled member hit"
  (pl-cmp-once pl-cmp-db4 "member(b, [a,b,c])")
  true)
(pl-cmp-test!
  "compiled member miss"
  (pl-cmp-once pl-cmp-db4 "member(d, [a,b,c])")
  false)
(pl-cmp-test!
  "compiled member count"
  (pl-cmp-count pl-cmp-db4 "member(X, [a,b,c])")
  3)

;; ── 5. Arithmetic in body ────────────────────────────────────────

(define pl-cmp-db5 (pl-cmp-mk "double(X, Y) :- Y is X * 2."))

(let
  ((db pl-cmp-db5) (trail (pl-mk-trail)) (env {}))
  (let
    ((y (pl-mk-rt-var "Y")))
    (dict-set! env "Y" y)
    (pl-solve-once!
      db
      (pl-instantiate (pl-parse-goal "double(5, Y)") env)
      trail)
    (pl-cmp-test! "compiled arithmetic in body" (pl-num-val (pl-walk y)) 10)))

;; ── 6. Transitive ancestor ───────────────────────────────────────

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

(pl-cmp-test!
  "compiled ancestor direct"
  (pl-cmp-once pl-cmp-db6 "ancestor(a,b)")
  true)
(pl-cmp-test!
  "compiled ancestor 3-step"
  (pl-cmp-once pl-cmp-db6 "ancestor(a,d)")
  true)
(pl-cmp-test!
  "compiled ancestor fail"
  (pl-cmp-once pl-cmp-db6 "ancestor(d,a)")
  false)

;; ── 7. Fallback: uncompiled predicate calls compiled sub-predicate

(define
  pl-cmp-db7
  (let
    ((db (pl-mk-db)))
    (pl-db-load! db (pl-parse "q(1). q(2)."))
    (pl-compile-db! db)
    (pl-db-load! db (pl-parse "r(X) :- q(X)."))
    db))

(pl-cmp-test!
  "uncompiled predicate resolves"
  (pl-cmp-once pl-cmp-db7 "r(1)")
  true)
(pl-cmp-test!
  "uncompiled calls compiled sub-pred count"
  (pl-cmp-count pl-cmp-db7 "r(X)")
  2)

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

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