;; lib/prolog/tests/operators.sx — operator-table parsing + comparison built-ins. (define pl-op-test-count 0) (define pl-op-test-pass 0) (define pl-op-test-fail 0) (define pl-op-test-failures (list)) (define pl-op-test! (fn (name got expected) (begin (set! pl-op-test-count (+ pl-op-test-count 1)) (if (= got expected) (set! pl-op-test-pass (+ pl-op-test-pass 1)) (begin (set! pl-op-test-fail (+ pl-op-test-fail 1)) (append! pl-op-test-failures (str name "\n expected: " expected "\n got: " got))))))) (define pl-op-empty-db (pl-mk-db)) (define pl-op-body (fn (src) (nth (first (pl-parse (str "g :- " src "."))) 2))) (define pl-op-goal (fn (src env) (pl-instantiate (pl-op-body src) env))) ;; ── parsing tests ── (pl-op-test! "infix +" (pl-op-body "a + b") (list "compound" "+" (list (list "atom" "a") (list "atom" "b")))) (pl-op-test! "infix * tighter than +" (pl-op-body "a + b * c") (list "compound" "+" (list (list "atom" "a") (list "compound" "*" (list (list "atom" "b") (list "atom" "c")))))) (pl-op-test! "parens override precedence" (pl-op-body "(a + b) * c") (list "compound" "*" (list (list "compound" "+" (list (list "atom" "a") (list "atom" "b"))) (list "atom" "c")))) (pl-op-test! "+ is yfx (left-assoc)" (pl-op-body "a + b + c") (list "compound" "+" (list (list "compound" "+" (list (list "atom" "a") (list "atom" "b"))) (list "atom" "c")))) (pl-op-test! "; is xfy (right-assoc)" (pl-op-body "a ; b ; c") (list "compound" ";" (list (list "atom" "a") (list "compound" ";" (list (list "atom" "b") (list "atom" "c")))))) (pl-op-test! "= folds at 700" (pl-op-body "X = 5") (list "compound" "=" (list (list "var" "X") (list "num" 5)))) (pl-op-test! "is + nests via 700>500>400" (pl-op-body "X is 2 + 3 * 4") (list "compound" "is" (list (list "var" "X") (list "compound" "+" (list (list "num" 2) (list "compound" "*" (list (list "num" 3) (list "num" 4)))))))) (pl-op-test! "< parses at 700" (pl-op-body "2 < 3") (list "compound" "<" (list (list "num" 2) (list "num" 3)))) (pl-op-test! "mod parses as yfx 400" (pl-op-body "10 mod 3") (list "compound" "mod" (list (list "num" 10) (list "num" 3)))) (pl-op-test! "comma in body folds right-assoc" (pl-op-body "a, b, c") (list "compound" "," (list (list "atom" "a") (list "compound" "," (list (list "atom" "b") (list "atom" "c")))))) ;; ── solver tests via infix ── (pl-op-test! "X is 2 + 3 binds X = 5" (let ((env {}) (trail (pl-mk-trail))) (begin (pl-solve-once! pl-op-empty-db (pl-op-goal "X is 2 + 3" env) trail) (pl-num-val (pl-walk-deep (dict-get env "X"))))) 5) (pl-op-test! "infix conjunction parses + solves" (pl-solve-once! pl-op-empty-db (pl-op-goal "X = 5, X = 5" {}) (pl-mk-trail)) true) (pl-op-test! "infix mismatch fails" (pl-solve-once! pl-op-empty-db (pl-op-goal "X = 5, X = 6" {}) (pl-mk-trail)) false) (pl-op-test! "infix disjunction picks left" (pl-solve-once! pl-op-empty-db (pl-op-goal "true ; fail" {}) (pl-mk-trail)) true) (pl-op-test! "2 < 5 succeeds" (pl-solve-once! pl-op-empty-db (pl-op-goal "2 < 5" {}) (pl-mk-trail)) true) (pl-op-test! "5 < 2 fails" (pl-solve-once! pl-op-empty-db (pl-op-goal "5 < 2" {}) (pl-mk-trail)) false) (pl-op-test! "5 >= 5 succeeds" (pl-solve-once! pl-op-empty-db (pl-op-goal "5 >= 5" {}) (pl-mk-trail)) true) (pl-op-test! "3 =< 5 succeeds" (pl-solve-once! pl-op-empty-db (pl-op-goal "3 =< 5" {}) (pl-mk-trail)) true) (pl-op-test! "infix < with arithmetic both sides" (pl-solve-once! pl-op-empty-db (pl-op-goal "1 + 2 < 2 * 3" {}) (pl-mk-trail)) true) (define pl-operators-tests-run! (fn () {:failed pl-op-test-fail :passed pl-op-test-pass :total pl-op-test-count :failures pl-op-test-failures}))