;; lib/scheme/tests/records.sx — define-record-type.

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

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

(define
  scm-rec
  (fn (src) (scheme-eval (scheme-parse src) (scheme-standard-env))))

(define
  scm-rec-all
  (fn
    (src)
    (scheme-eval-program (scheme-parse-all src) (scheme-standard-env))))

;; ── Basic record: point ─────────────────────────────────────────

(scm-rec-test
  "point: constructor + predicate"
  (scm-rec-all
    "(define-record-type point\n       (make-point x y) point?\n       (x point-x) (y point-y))\n     (point? (make-point 3 4))")
  true)
(scm-rec-test
  "point: accessor x"
  (scm-rec-all
    "(define-record-type point\n       (make-point x y) point?\n       (x point-x) (y point-y))\n     (point-x (make-point 3 4))")
  3)
(scm-rec-test
  "point: accessor y"
  (scm-rec-all
    "(define-record-type point\n       (make-point x y) point?\n       (x point-x) (y point-y))\n     (point-y (make-point 3 4))")
  4)
(scm-rec-test
  "point: predicate false on number"
  (scm-rec-all
    "(define-record-type point\n       (make-point x y) point?\n       (x point-x) (y point-y))\n     (point? 42)")
  false)

;; ── Mutator ─────────────────────────────────────────────────────

(scm-rec-test
  "point: mutator"
  (scm-rec-all
    "(define-record-type point\n       (make-point x y) point?\n       (x point-x) (y point-y set-point-y!))\n     (define p (make-point 3 4))\n     (set-point-y! p 99)\n     (point-y p)")
  99)

;; ── Multiple record types are distinct ──────────────────────────

(scm-rec-test
  "distinct types: point? false on circle"
  (scm-rec-all
    "(define-record-type point\n       (make-point x y) point? (x point-x) (y point-y))\n     (define-record-type circle\n       (make-circle r) circle? (r circle-r))\n     (point? (make-circle 5))")
  false)
(scm-rec-test
  "distinct types: circle? true on circle"
  (scm-rec-all
    "(define-record-type point\n       (make-point x y) point? (x point-x) (y point-y))\n     (define-record-type circle\n       (make-circle r) circle? (r circle-r))\n     (circle? (make-circle 5))")
  true)

;; ── Records as first-class values ───────────────────────────────

(scm-rec-test
  "record in a list"
  (scm-rec-all
    "(define-record-type box\n       (make-box v) box? (v box-v))\n     (map box-v (list (make-box 1) (make-box 2) (make-box 3)))")
  (list 1 2 3))

;; ── Records via map/filter ──────────────────────────────────────

(scm-rec-test
  "filter records by predicate"
  (scm-rec-all
    "(define-record-type box\n       (make-box v) box? (v box-v))\n     (length\n       (filter (lambda (b) (> (box-v b) 5))\n               (list (make-box 1) (make-box 7) (make-box 3) (make-box 10)))))")
  2)

;; ── Constructor arity errors ────────────────────────────────────

(scm-rec-test
  "ctor: wrong arity errors"
  (scm-rec-all
    "(define-record-type point (make-point x y) point? (x point-x) (y point-y))\n     (guard (e (else 'arity-err)) (make-point 1))")
  "arity-err")

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