Salvaged from worktree-agent-* branches killed during sx-tree MCP outage: - lua: tokenizer + parser + phase-2 transpile (~157 tests) - prolog: tokenizer + parser + unification (72 tests, plan update lost to WIP) - forth: phase-1 reader/interpreter + phase-2 colon/VARIABLE (134 tests) - erlang: tokenizer + parser (114 tests) - haskell: tokenizer + parse tests (43 tests) Cherry-picked file contents only, not branch history, to avoid pulling in unrelated ocaml-vm merge commits that were in those branches' bases.
216 lines
5.1 KiB
Plaintext
216 lines
5.1 KiB
Plaintext
;; lib/prolog/tests/parse.sx — parser unit tests
|
|
;;
|
|
;; Run: bash lib/prolog/tests/run-parse.sh
|
|
;; Or via sx-server: (load "lib/prolog/tokenizer.sx") (load "lib/prolog/parser.sx")
|
|
;; (load "lib/prolog/tests/parse.sx") (pl-parse-tests-run!)
|
|
|
|
(define pl-test-count 0)
|
|
(define pl-test-pass 0)
|
|
(define pl-test-fail 0)
|
|
(define pl-test-failures (list))
|
|
|
|
(define
|
|
pl-test!
|
|
(fn
|
|
(name got expected)
|
|
(do
|
|
(set! pl-test-count (+ pl-test-count 1))
|
|
(if
|
|
(= got expected)
|
|
(set! pl-test-pass (+ pl-test-pass 1))
|
|
(do
|
|
(set! pl-test-fail (+ pl-test-fail 1))
|
|
(append!
|
|
pl-test-failures
|
|
(str name "\n expected: " expected "\n got: " got)))))))
|
|
|
|
;; Atoms & variables
|
|
(pl-test!
|
|
"atom fact"
|
|
(pl-parse "foo.")
|
|
(list (list "clause" (list "atom" "foo") (list "atom" "true"))))
|
|
|
|
(pl-test! "number literal" (pl-parse-goal "42") (list "num" 42))
|
|
|
|
(pl-test!
|
|
"negative number — not supported yet (parsed as op atom + num)"
|
|
(pl-parse-goal "-5")
|
|
(list "atom" "-"))
|
|
|
|
(pl-test! "variable" (pl-parse-goal "X") (list "var" "X"))
|
|
|
|
(pl-test!
|
|
"underscore variable"
|
|
(pl-parse-goal "_Ignored")
|
|
(list "var" "_Ignored"))
|
|
|
|
(pl-test! "anonymous variable" (pl-parse-goal "_") (list "var" "_"))
|
|
|
|
(pl-test!
|
|
"compound 1-arg"
|
|
(pl-parse-goal "foo(a)")
|
|
(list "compound" "foo" (list (list "atom" "a"))))
|
|
|
|
(pl-test!
|
|
"compound 3-args mixed"
|
|
(pl-parse-goal "p(X, 1, hello)")
|
|
(list
|
|
"compound"
|
|
"p"
|
|
(list (list "var" "X") (list "num" 1) (list "atom" "hello"))))
|
|
|
|
(pl-test!
|
|
"nested compound"
|
|
(pl-parse-goal "f(g(X), h(Y, Z))")
|
|
(list
|
|
"compound"
|
|
"f"
|
|
(list
|
|
(list "compound" "g" (list (list "var" "X")))
|
|
(list "compound" "h" (list (list "var" "Y") (list "var" "Z"))))))
|
|
|
|
;; Lists
|
|
(pl-test! "empty list" (pl-parse-goal "[]") (list "atom" "[]"))
|
|
|
|
(pl-test!
|
|
"single-element list"
|
|
(pl-parse-goal "[a]")
|
|
(list "compound" "." (list (list "atom" "a") (list "atom" "[]"))))
|
|
|
|
(pl-test!
|
|
"three-element list"
|
|
(pl-parse-goal "[1, 2, 3]")
|
|
(list
|
|
"compound"
|
|
"."
|
|
(list
|
|
(list "num" 1)
|
|
(list
|
|
"compound"
|
|
"."
|
|
(list
|
|
(list "num" 2)
|
|
(list "compound" "." (list (list "num" 3) (list "atom" "[]"))))))))
|
|
|
|
(pl-test!
|
|
"head-tail list"
|
|
(pl-parse-goal "[H|T]")
|
|
(list "compound" "." (list (list "var" "H") (list "var" "T"))))
|
|
|
|
(pl-test!
|
|
"two-head-tail list"
|
|
(pl-parse-goal "[A, B|T]")
|
|
(list
|
|
"compound"
|
|
"."
|
|
(list
|
|
(list "var" "A")
|
|
(list "compound" "." (list (list "var" "B") (list "var" "T"))))))
|
|
|
|
;; Clauses
|
|
(pl-test!
|
|
"fact"
|
|
(pl-parse "parent(tom, bob).")
|
|
(list
|
|
(list
|
|
"clause"
|
|
(list
|
|
"compound"
|
|
"parent"
|
|
(list (list "atom" "tom") (list "atom" "bob")))
|
|
(list "atom" "true"))))
|
|
|
|
(pl-test!
|
|
"rule with single-goal body"
|
|
(pl-parse "q(X) :- p(X).")
|
|
(list
|
|
(list
|
|
"clause"
|
|
(list "compound" "q" (list (list "var" "X")))
|
|
(list "compound" "p" (list (list "var" "X"))))))
|
|
|
|
(pl-test!
|
|
"rule with conjunctive body"
|
|
(pl-parse "r(X, Y) :- p(X), q(Y).")
|
|
(list
|
|
(list
|
|
"clause"
|
|
(list "compound" "r" (list (list "var" "X") (list "var" "Y")))
|
|
(list
|
|
"compound"
|
|
","
|
|
(list
|
|
(list "compound" "p" (list (list "var" "X")))
|
|
(list "compound" "q" (list (list "var" "Y"))))))))
|
|
|
|
;; Cut in body
|
|
(pl-test!
|
|
"cut in body"
|
|
(pl-parse "foo(X) :- p(X), !, q(X).")
|
|
(list
|
|
(list
|
|
"clause"
|
|
(list "compound" "foo" (list (list "var" "X")))
|
|
(list
|
|
"compound"
|
|
","
|
|
(list
|
|
(list "compound" "p" (list (list "var" "X")))
|
|
(list
|
|
"compound"
|
|
","
|
|
(list
|
|
(list "cut")
|
|
(list "compound" "q" (list (list "var" "X"))))))))))
|
|
|
|
;; Symbolic-atom compound terms (phase 1 form)
|
|
(pl-test!
|
|
"= as compound"
|
|
(pl-parse-goal "=(X, 5)")
|
|
(list "compound" "=" (list (list "var" "X") (list "num" 5))))
|
|
|
|
(pl-test!
|
|
"is with +"
|
|
(pl-parse-goal "is(Y, +(X, 1))")
|
|
(list
|
|
"compound"
|
|
"is"
|
|
(list
|
|
(list "var" "Y")
|
|
(list "compound" "+" (list (list "var" "X") (list "num" 1))))))
|
|
|
|
;; Strings
|
|
(pl-test!
|
|
"double-quoted string"
|
|
(pl-parse-goal "\"hello\"")
|
|
(list "str" "hello"))
|
|
|
|
;; Single-quoted atom
|
|
(pl-test!
|
|
"quoted atom"
|
|
(pl-parse-goal "'Hello World'")
|
|
(list "atom" "Hello World"))
|
|
|
|
;; Multi-clause program
|
|
(pl-test!
|
|
"append program"
|
|
(len
|
|
(pl-parse "append([], L, L).\nappend([H|T], L, [H|R]) :- append(T, L, R).\n"))
|
|
2)
|
|
|
|
;; Comments
|
|
(pl-test!
|
|
"line comment ignored"
|
|
(pl-parse "foo.\n% this is a comment\nbar.")
|
|
(list
|
|
(list "clause" (list "atom" "foo") (list "atom" "true"))
|
|
(list "clause" (list "atom" "bar") (list "atom" "true"))))
|
|
|
|
(pl-test!
|
|
"block comment ignored"
|
|
(pl-parse "/* hello */\nfoo.")
|
|
(list (list "clause" (list "atom" "foo") (list "atom" "true"))))
|
|
|
|
;; ── Runner ───────────────────────────────────────────────────────
|
|
(define pl-parse-tests-run! (fn () {:failed pl-test-fail :passed pl-test-pass :total pl-test-count :failures pl-test-failures}))
|