Recover agent-loop progress: lua/prolog/forth/erlang/haskell phases 1-2

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.
This commit is contained in:
2026-04-24 16:03:00 +00:00
parent e274878052
commit 99753580b4
32 changed files with 7803 additions and 36 deletions

215
lib/prolog/tests/parse.sx Normal file
View File

@@ -0,0 +1,215 @@
;; 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}))