;; lib/scheme/tests/parse.sx — exercises lib/scheme/parser.sx.

(define scm-test-pass 0)
(define scm-test-fail 0)
(define scm-test-fails (list))

(define
  scm-test
  (fn
    (name actual expected)
    (if
      (= actual expected)
      (set! scm-test-pass (+ scm-test-pass 1))
      (begin
        (set! scm-test-fail (+ scm-test-fail 1))
        (append! scm-test-fails {:name name :actual actual :expected expected})))))

;; ── numbers ───────────────────────────────────────────────────────
(scm-test "num: integer" (scheme-parse "42") 42)
(scm-test "num: zero" (scheme-parse "0") 0)
(scm-test "num: negative" (scheme-parse "-17") -17)
(scm-test "num: float" (scheme-parse "3.14") 3.14)
(scm-test "num: exponent" (scheme-parse "1e3") 1000)
(scm-test "num: negative float" (scheme-parse "-2.5") -2.5)

;; ── booleans ──────────────────────────────────────────────────────
(scm-test "bool: #t" (scheme-parse "#t") true)
(scm-test "bool: #true" (scheme-parse "#true") true)
(scm-test "bool: #f" (scheme-parse "#f") false)
(scm-test "bool: #false" (scheme-parse "#false") false)

;; ── strings ───────────────────────────────────────────────────────
(scm-test "str: empty" (scheme-string-value (scheme-parse "\"\"")) "")
(scm-test
  "str: hello"
  (scheme-string-value (scheme-parse "\"hello\""))
  "hello")
(scm-test "str: predicate" (scheme-string? (scheme-parse "\"x\"")) true)
(scm-test "str: not symbol" (scheme-string? (scheme-parse "x")) false)
(scm-test
  "str: escape newline"
  (scheme-string-value (scheme-parse "\"a\\nb\""))
  "a\nb")
(scm-test
  "str: escape tab"
  (scheme-string-value (scheme-parse "\"a\\tb\""))
  "a\tb")
(scm-test
  "str: escape quote"
  (scheme-string-value (scheme-parse "\"a\\\"b\""))
  "a\"b")

;; ── symbols ───────────────────────────────────────────────────────
(scm-test "sym: word" (scheme-parse "foo") "foo")
(scm-test "sym: hyphenated" (scheme-parse "set-car!") "set-car!")
(scm-test "sym: question mark" (scheme-parse "null?") "null?")
(scm-test "sym: arrow" (scheme-parse "->") "->")
(scm-test "sym: lt-eq" (scheme-parse "<=") "<=")
(scm-test "sym: bare plus" (scheme-parse "+") "+")
(scm-test "sym: bare minus" (scheme-parse "-") "-")
(scm-test "sym: dot-prefixed" (scheme-parse ".foo") ".foo")

;; ── characters ────────────────────────────────────────────────────
(scm-test "char: single" (scheme-char-value (scheme-parse "#\\a")) "a")
(scm-test "char: space" (scheme-char-value (scheme-parse "#\\space")) " ")
(scm-test "char: newline" (scheme-char-value (scheme-parse "#\\newline")) "\n")
(scm-test "char: tab" (scheme-char-value (scheme-parse "#\\tab")) "\t")
(scm-test "char: predicate" (scheme-char? (scheme-parse "#\\x")) true)
(scm-test "char: digit" (scheme-char-value (scheme-parse "#\\5")) "5")

;; ── vectors ───────────────────────────────────────────────────────
(scm-test "vec: empty" (scheme-vector-elements (scheme-parse "#()")) (list))
(scm-test
  "vec: numbers"
  (scheme-vector-elements (scheme-parse "#(1 2 3)"))
  (list 1 2 3))
(scm-test "vec: predicate" (scheme-vector? (scheme-parse "#(1)")) true)
(scm-test "vec: not list" (scheme-vector? (scheme-parse "(1)")) false)
;; Nested vector: SX `=` doesn't deep-compare dicts-with-list-values
;; reliably under this CEK path, so check structure piecewise.
(scm-test "vec: nested first"
  (first (scheme-vector-elements (scheme-parse "#(a #(b c) d)"))) "a")
(scm-test "vec: nested second is vector"
  (scheme-vector?
    (nth (scheme-vector-elements (scheme-parse "#(a #(b c) d)")) 1))
  true)
(scm-test "vec: nested second elements"
  (scheme-vector-elements
    (nth (scheme-vector-elements (scheme-parse "#(a #(b c) d)")) 1))
  (list "b" "c"))

;; ── lists ─────────────────────────────────────────────────────────
(scm-test "list: empty" (scheme-parse "()") (list))
(scm-test "list: flat" (scheme-parse "(a b c)") (list "a" "b" "c"))
(scm-test
  "list: nested"
  (scheme-parse "(a (b c) d)")
  (list "a" (list "b" "c") "d"))
(scm-test
  "list: mixed atoms"
  (scheme-parse "(1 #t foo)")
  (list 1 true "foo"))

;; ── reader macros ─────────────────────────────────────────────────
(scm-test "quote: 'foo" (scheme-parse "'foo") (list "quote" "foo"))
(scm-test
  "quote: '(a b c)"
  (scheme-parse "'(a b c)")
  (list "quote" (list "a" "b" "c")))
(scm-test "quasiquote: `x" (scheme-parse "`x") (list "quasiquote" "x"))
(scm-test "unquote: ,x" (scheme-parse ",x") (list "unquote" "x"))
(scm-test
  "unquote-splicing: ,@x"
  (scheme-parse ",@x")
  (list "unquote-splicing" "x"))
(scm-test
  "qq mix"
  (scheme-parse "`(a ,b ,@c)")
  (list
    "quasiquote"
    (list "a" (list "unquote" "b") (list "unquote-splicing" "c"))))

;; ── comments ──────────────────────────────────────────────────────
(scm-test "comment: line" (scheme-parse "; nope\n42") 42)
(scm-test "comment: trailing" (scheme-parse "42 ; tail") 42)
(scm-test
  "comment: inside list"
  (scheme-parse "(a ; mid\n  b)")
  (list "a" "b"))
(scm-test "comment: block simple" (scheme-parse "#| skip |# 42") 42)
(scm-test
  "comment: block nested"
  (scheme-parse "#| outer #| inner |# done |# 42")
  42)
(scm-test "comment: datum #;" (scheme-parse "#;skipme 42") 42)
(scm-test
  "comment: datum skips list"
  (scheme-parse "#;(1 2 3) 42")
  42)

;; ── parse-all ─────────────────────────────────────────────────────
(scm-test "all: empty" (scheme-parse-all "") (list))
(scm-test
  "all: three forms"
  (scheme-parse-all "1 2 3")
  (list 1 2 3))
(scm-test
  "all: mixed"
  (scheme-parse-all "(if #t 1 2) foo")
  (list (list "if" true 1 2) "foo"))

;; ── classic Scheme idioms ─────────────────────────────────────────
(scm-test
  "classic: lambda"
  (scheme-parse "(lambda (x) (+ x 1))")
  (list "lambda" (list "x") (list "+" "x" 1)))
(scm-test
  "classic: define"
  (scheme-parse "(define (sq x) (* x x))")
  (list "define" (list "sq" "x") (list "*" "x" "x")))
(scm-test
  "classic: let"
  (scheme-parse "(let ((x 1) (y 2)) (+ x y))")
  (list
    "let"
    (list (list "x" 1) (list "y" 2))
    (list "+" "x" "y")))
(scm-test
  "classic: if"
  (scheme-parse "(if (zero? n) 1 (* n (fact (- n 1))))")
  (list
    "if"
    (list "zero?" "n")
    1
    (list "*" "n" (list "fact" (list "-" "n" 1)))))

(define scm-tests-run! (fn () {:total (+ scm-test-pass scm-test-fail) :passed scm-test-pass :failed scm-test-fail :fails scm-test-fails}))
