;;; sweep-doctrine/lib.lsp - shared harness for every doctrine reducer.
;;;
;;; Upstream lumbda hosts a generic helper layer: assertion macro,
;;; fresh-register builders, single-line verdict printer. Domain repos
;;; (foxhop ecdsa) extend with primitives that load domain substrate &
;;; baseline flag-set; those live under $LUMBDA_DOMAIN_DIR/tests/sweep-doctrine/.
;;;
;;; AGPLv3.

;; Each reducer prints exactly one line:
;;
;;   DOCTRINE <name> PASS got=<actual> expected=<expected>
;;   DOCTRINE <name> EXPECTED-FAIL got=<actual> expected=<expected>
;;   DOCTRINE <name> FAIL got=<actual> expected=<expected>
;;   DOCTRINE <name> UNEXPECTED-PASS got=<actual> expected=<expected>
;;
;; Runner gates on third-token verdicts only; data lines stay free.

(define (doctrine-report name expected-pass? got expected)
  "One-line summary. expected-pass? #t = positive control (PASS on match).
   #f = open defect alarm (EXPECTED-FAIL on mismatch, UNEXPECTED-PASS on match)."
  (let* ((match? (equal? got expected))
         (status (cond
                   ((and expected-pass? match?)       "PASS")
                   ((and expected-pass? (not match?)) "FAIL")
                   ((and (not expected-pass?) match?) "UNEXPECTED-PASS")
                   (else                              "EXPECTED-FAIL"))))
    (display "DOCTRINE ") (display name)
    (display " ") (display status)
    (display " got=") (display got)
    (display " expected=") (display expected)
    (newline)
    (or (and expected-pass? match?)
        (and (not expected-pass?) (not match?)))))

;; assertion shorthand - returns #t on equality, #f otherwise. Reducer authors
;; combine several `doctrine-assert-eq` results then call `doctrine-report` once.
(define (doctrine-assert-eq label got expected)
  "Print one data line per check; return #t on match. Use for multi-axis reducers
   where many rows fan out under one summary verdict."
  (let ((match? (equal? got expected)))
    (display "  ") (display label)
    (display " got=") (display got)
    (display " expected=") (display expected)
    (display " ") (display (if match? "OK" "MISS"))
    (newline)
    match?))

;; mod-expt - square-and-multiply over a prime.
;; Required because `(expt a e)` builds |a|^|e| BEFORE applying modulo;
;; at p near 2^31 & e = p-2 a literal expansion hangs lumbda. Six
;; reducer instances stacked indefinitely (oldest 13h 58m) until this
;; helper surfaced 2026-06-11.
(define (mod-expt base exp p)
  (let loop ((b (modulo base p)) (e exp) (acc 1))
    (cond
      ((= e 0) acc)
      ((odd? e) (loop (modulo (* b b) p) (quotient e 2) (modulo (* acc b) p)))
      (else     (loop (modulo (* b b) p) (quotient e 2) acc)))))

;; Domain substrate loader. If LUMBDA_DOMAIN_DIR points at a consumer
;; repo (foxhop ecdsa) AND that domain ships a `tests/sweep-doctrine/domain-lib.lsp`
;; file, load it for domain-specific helpers (baseline flag-sets,
;; circuit builders, expected-truth-table comparators).
;;
;; Pattern: upstream reducers stay primitive-level; domain reducers
;; live downstream & import upstream's lib + their own domain-lib.
;;
;; Note: getenv may or may not exist in every lumbda tier. Guard with
;; load-time check so a missing primitive never crashes a reducer that
;; ignores domain-lib entirely.
;; Disabled by default - domain reducers explicitly `(load "tests/sweep-doctrine/domain-lib.lsp")`
;; from their own $LUMBDA_DOMAIN_DIR cwd. Upstream lib.lsp stays minimal.
