;; lib/common-lisp/tests/macros.sx — Phase 5: defmacro, gensym, LOOP tests
;;
;; Depends on: runtime.sx, eval.sx, loop.sx already loaded.
;; Tests via (ev "...") using the CL evaluator.

(define ev (fn (src) (cl-eval-str src (cl-make-env))))
(define evall (fn (src) (cl-eval-all-str src (cl-make-env))))

(define passed 0)
(define failed 0)
(define failures (list))

(define
  check
  (fn
    (label got expected)
    (if
      (= got expected)
      (set! passed (+ passed 1))
      (begin
        (set! failed (+ failed 1))
        (set!
          failures
          (append
            failures
            (list
              (str
                "FAIL ["
                label
                "]: got="
                (inspect got)
                " expected="
                (inspect expected)))))))))

;; ── defmacro basics ──────────────────────────────────────────────────────────

(check
  "defmacro returns name"
  (ev "(defmacro my-or (a b) (list 'if a a b))")
  "MY-OR")

(check
  "defmacro expansion works"
  (ev "(progn (defmacro my-inc (x) (list '+ x 1)) (my-inc 5))")
  6)

(check
  "defmacro with &rest"
  (ev "(progn (defmacro my-list (&rest xs) (cons 'list xs)) (my-list 1 2 3))")
  (list 1 2 3))

(check
  "nested macro expansion"
  (ev "(progn (defmacro sq (x) (list '* x x)) (sq 7))")
  49)

(check
  "macro in conditional"
  (ev
    "(progn (defmacro my-when (c &rest body) (list 'if c (cons 'progn body) nil)) (my-when t 10 20))")
  20)

(check
  "macro returns nil branch"
  (ev
    "(progn (defmacro my-when (c &rest body) (list 'if c (cons 'progn body) nil)) (my-when nil 42))")
  nil)

;; ── macroexpand ───────────────────────────────────────────────────────────────

(check
  "macroexpand returns expanded form"
  (ev "(progn (defmacro double (x) (list '+ x x)) (macroexpand '(double 5)))")
  (list "+" 5 5))

;; ── gensym ────────────────────────────────────────────────────────────────────

(check "gensym returns string" (ev "(stringp (gensym))") true)

(check
  "gensym prefix"
  (ev "(let ((g (gensym \"MY\"))) (not (= g nil)))")
  true)

(check "gensyms are unique" (ev "(not (= (gensym) (gensym)))") true)

;; ── swap! macro with gensym ───────────────────────────────────────────────────

(check
  "swap! macro"
  (evall
    "(defmacro swap! (a b) (let ((tmp (gensym))) (list 'let (list (list tmp a)) (list 'setq a b) (list 'setq b tmp)))) (defvar *a* 10) (defvar *b* 20) (swap! *a* *b*) (list *a* *b*)")
  (list 20 10))

;; ── LOOP: basic repeat and collect ────────────────────────────────────────────

(check
  "loop repeat collect"
  (ev "(loop repeat 3 collect 99)")
  (list 99 99 99))

(check
  "loop for-in collect"
  (ev "(loop for x in '(1 2 3) collect (* x x))")
  (list 1 4 9))

(check
  "loop for-from-to collect"
  (ev "(loop for i from 1 to 5 collect i)")
  (list 1 2 3 4 5))

(check
  "loop for-from-below collect"
  (ev "(loop for i from 0 below 4 collect i)")
  (list 0 1 2 3))

(check
  "loop for-downto collect"
  (ev "(loop for i from 5 downto 1 collect i)")
  (list 5 4 3 2 1))

(check
  "loop for-by collect"
  (ev "(loop for i from 0 to 10 by 2 collect i)")
  (list 0 2 4 6 8 10))

;; ── LOOP: sum, count, maximize, minimize ─────────────────────────────────────

(check "loop sum" (ev "(loop for i from 1 to 5 sum i)") 15)

(check
  "loop count"
  (ev "(loop for x in '(1 2 3 4 5) count (> x 3))")
  2)

(check
  "loop maximize"
  (ev "(loop for x in '(3 1 4 1 5 9 2 6) maximize x)")
  9)

(check
  "loop minimize"
  (ev "(loop for x in '(3 1 4 1 5 9 2 6) minimize x)")
  1)

;; ── LOOP: while and until ─────────────────────────────────────────────────────

(check
  "loop while"
  (ev "(loop for i from 1 to 10 while (< i 5) collect i)")
  (list 1 2 3 4))

(check
  "loop until"
  (ev "(loop for i from 1 to 10 until (= i 5) collect i)")
  (list 1 2 3 4))

;; ── LOOP: when / unless ───────────────────────────────────────────────────────

(check
  "loop when filter"
  (ev "(loop for i from 0 below 8 when (evenp i) collect i)")
  (list 0 2 4 6))

(check
  "loop unless filter"
  (ev "(loop for i from 0 below 8 unless (evenp i) collect i)")
  (list 1 3 5 7))

;; ── LOOP: append ─────────────────────────────────────────────────────────────

(check
  "loop append"
  (ev "(loop for x in '((1 2) (3 4) (5 6)) append x)")
  (list 1 2 3 4 5 6))

;; ── LOOP: always, never, thereis ─────────────────────────────────────────────

(check
  "loop always true"
  (ev "(loop for x in '(2 4 6) always (evenp x))")
  true)

(check
  "loop always false"
  (ev "(loop for x in '(2 3 6) always (evenp x))")
  false)

(check "loop never" (ev "(loop for x in '(1 3 5) never (evenp x))") true)

(check "loop thereis" (ev "(loop for x in '(1 2 3) thereis (> x 2))") true)

;; ── LOOP: for = then (general iteration) ─────────────────────────────────────

(check
  "loop for = then doubling"
  (ev "(loop repeat 5 for x = 1 then (* x 2) collect x)")
  (list 1 2 4 8 16))

;; ── summary ────────────────────────────────────────────────────────────────

(define macro-passed passed)
(define macro-failed failed)
(define macro-failures failures)
