test/test-modular.rkt
#lang racket/base

(require "../private/planet.rkt"
         "../proof/proof.rkt"
         "../proof/syntax.rkt"
         "../lang/acl2-module-v.rkt"
         "program-tests.rkt")
(require rackunit)

(provide test-modular)

(define (proof->sexp proof)
  (for/list ([name (proof-parts proof)])
    (part->sexp (proof-part proof name))))

(define (part->sexp part)
  (for/list ([i (in-range (part-length part))])
    (term-sexp (part-nth part i))))

(define (program defns [repl null])
  (make-module-program/mred modular-acl2-module-v defns repl))

(define (test/modular name defns repl result proof)
  (let* ([prog (program defns repl)])
    (test-suite name
      (test-case "proof"
        (check-equal?
         (proof->sexp (get-proof (car (check-expand-success prog))))
         proof))
      (test-case "program"
        (check-equal? (check-eval-success prog) result)))))

(define (test/syntax-error name defns rxs)
  (test-case name
    (define prog (program defns '()))
    (define (err? x)
      (and (exn:fail:syntax? x)
        (for/and {[rx (in-list rxs)]}
          (regexp-match? rx
            (exn-message x)))))
    (check-pred err?
      (check-expand-failure prog))))

(define test-modular
  (test-suite "Modular"
    (test/modular "top-level" '[] '[(integerp (+ 1 2 3))] 't '[])

    (test/modular
     "interface + module + invoke"
     '[(interface IIdentity (sig id (x)) (con id-equal (equal (id x) x)))
       (module MIdentity (defun id (x) x) (export IIdentity))
       (invoke MIdentity)]
     '[(id 1)]
     1
     '[[(defun id (x) x)
        (defthm id-equal (equal (id x) x))]])

    (test/modular
     "link + invoke"
     '[(interface IInc (sig inc (x)) (con inc-+ (equal (inc x) (+ x 1))))
       (interface ISkip (sig skip (x)) (con skip-+ (equal (skip x) (+ x 2))))
       (module MInc (defun inc (x) (+ 1 x)) (export IInc))
       (module MInc-Skip
         (import IInc)
         (defun skip (x) (inc (inc x)))
         (export ISkip))
       (link MSkip (MInc MInc-Skip))
       (invoke MSkip)]
     '[(skip 1)]
     3
     '[[(defun inc (x) (+ 1 x))
        (defthm inc-+ (equal (inc x) (+ x 1)))]
       [(progn (defstub inc (x) t)
               (defaxiom inc-+ (equal (inc x) (+ x 1))))
        (defun skip (x) (inc (inc x)))
        (defthm skip-+ (equal (skip x) (+ x 2)))]])

    (test/modular
     "rename + link + invoke"
     '[(interface IInc (sig inc (x)) (con inc-+ (equal (inc x) (+ x 1))))
       (interface ISkip (sig skip (x)) (con skip-+ (equal (skip x) (+ x 2))))
       (module MInc (defun add1 (x) (+ 1 x)) (export IInc (inc add1)))
       (module MInc-Skip
         (import IInc (inc hop))
         (defun skip (x) (hop (hop x)))
         (export ISkip))
       (link MSkip (MInc MInc-Skip))
       (invoke MSkip)]
     '[(skip 1)]
     3
     '[[(defun add1 (x) (+ 1 x))
        (defthm inc-+ (equal (add1 x) (+ x 1)))]
       [(progn (defstub hop (x) t) (defaxiom inc-+ (equal (hop x) (+ x 1))))
        (defun skip (x) (hop (hop x)))
        (defthm skip-+ (equal (skip x) (+ x 2)))]])

    (test/modular
     "include + rename"
     '[(interface ONE (sig f (x)))
       (interface TWO (include ONE) (sig g (x)) (con f=g (equal (f x) (g x))))
       (module M (import ONE (f it)) (export TWO (g it)))]
     '['dummy]
     'dummy
     '[[(defstub it (x) t)
        (defthm f=g (equal (it x) (it x)))]])

    (test/modular
     "induction (concrete function)"
     '[(interface I (fun f (x) (if (endp x) 0 (1+ (f (cdr x))))))
       (module A (defun f (x) (if (endp x) 0 (1+ (f (cdr x))))) (export I))
       (module B (import I))
       (invoke A)]
     '[(f (list 1 2 3))]
     3
     '[[(defun f (x) (if (endp x) 0 (1+ (f (cdr x)))))
        (defun f (x) (if (endp x) 0 (1+ (f (cdr x)))))]
       [(skip-proofs (defun f (x) (if (endp x) 0 (1+ (f (cdr x))))))]])

    (test/modular
     "multiple returns in signatures"
     '[(interface I (sig f (a b) (c d)))
       (module A
         (defun f (x y)
           (mv (+ x y) (- x y)))
         (export I))
       (module B
         (import I))
       (invoke A)]
     '[(mv-let (a b) (f 3 4) (list a b))]
     (list 7 -1)
     '[[(defun f (x y) (mv (+ x y) (- x y))) (progn)]
       [(defstub f (a b) (mv c d))]])

    (test/modular
     "include-book / teachpack"
     '[(interface I
         (sig replicate (n x))
         (con replicate/list (true-listp (replicate n x))))
       (module LI
         (include-book "list-utilities" :dir :teachpacks)
         (export I))
       (invoke LI)]
     '[(replicate 5 'x)]
     (list 'x 'x 'x 'x 'x)
     '[[(include-book "list-utilities" :dir :teachpacks)
        (defthm replicate/list (true-listp (replicate n x)))]])

    (test/modular
     "include-book / system"
     '[(interface I (sig id (x)) (con ident (equal (id x) x)))
       (module M
         (include-book "arithmetic-3/extra/top-ext" :dir :system)
         (defun id (x) x)
         (export I))
       (invoke M)]
     '[(id 7)]
     7
     '[[(include-book "arithmetic-3/extra/top-ext" :dir :system)
        (defun id (x) x)
        (defthm ident (equal (id x) x))]])

    (test/modular
      "preserve proof obligation order"
      '[(interface I (con true t) (sig f (x)))
        (interface J (con false nil) (sig g (x)))
        (module M
          (defun g (x) (1+ x))
          (export J)
          (include-book "arithmetic-5/top" :dir :system)
          (import I))]
      '[t]
      't
      '[[(defun g (x) (1+ x))
         (defthm false nil)
         (include-book "arithmetic-5/top" :dir :system)
         (progn
           (defaxiom true t)
           (defstub f (x) t))]])

    (test/modular
      "restrict exports / expand imports on link"
      '[(interface I (sig f (x)))
        (interface J (sig g (x)))
        (module M (defun f (x) (cons 'M x)) (export I))
        (module N (import I) (defun g (x) (f (cons 'N x))) (export J))
        (link O (import) (export J) (M N))
        (invoke O)
        (module M* (defun f (x) (cons 'M* x)) (export I))
        (invoke M*)]
      '[(list (f '()) (g '()))]
      '((M*) (M N))
      '[[(defun f (x) (cons 'M x)) (progn)]
        [(defstub f (x) t) (defun g (x) (f (cons 'N x))) (progn)]
        [(defun f (x) (cons 'M* x)) (progn)]])

    (test/syntax-error
      "link in wrong order"
      '[(interface the-interface (sig f (x)))
        (module the-producer (defun f (x) x) (export the-interface))
        (module the-consumer (import the-interface) (defun g (x) (f x)))
        (link the-link (the-consumer the-producer))]
      '[#px"link"
        #px"import"
        #px"export"
        #px"the-interface"])

    (test/syntax-error
      "hide necessary imports"
      '[(interface the-interface (sig f (x)))
        (module the-module (import the-interface))
        (link the-link (import) (export) (the-module))]
      '[#px"link"
        #px"import"
        #px"the-interface"
        #px"the-link"])

    ))