#lang eopl

(require "lang.rkt")
(require "static-data-structures.rkt")
(require "expand-type.rkt")
(require "renaming.rkt")

(provide <:-iface)

;; <:-iface : Iface * Iface * Tenv -> Bool
;; Page: 289, 323
(define <:-iface
  (lambda (iface1 iface2 tenv)
    (cases interface iface1
      (simple-iface (decls1)
                    (cases interface iface2
                      (simple-iface (decls2)
                                    (<:-decls decls1 decls2 tenv))
                      (else #f)))
      (proc-iface (param-name1 param-iface1 result-iface1)
                  (cases interface iface2
                    (proc-iface (param-name2 param-iface2 result-iface2)
                                ;; first we rename the param names to the same fresh name
                                (let ((new-name (fresh-module-name param-name1)))
                                  (let ((result-iface1
                                         (rename-in-iface
                                          result-iface1 param-name1 new-name)) 
                                        (result-iface2
                                         (rename-in-iface
                                          result-iface2 param-name2 new-name)))
                                    (and
                                     (<:-iface param-iface2 param-iface1 tenv)
                                     (<:-iface result-iface1 result-iface2 
                                               (extend-tenv-with-module
                                                new-name
                                                (expand-iface new-name param-iface1 tenv)
                                                tenv))))))
                    (else #f))))))

;; s1 <: s2 iff s1 has at least as much stuff as s2, and in the same
;; order.  We walk down s1 until we find a declaration that declares
;; the same name as the first component of s2.  If we run off the
;; end of s1, then we fail.  As we walk down s1, we record any type
;; bindings in the tenv

;; <:-decls : Listof(Decl) * Listof(Decl) * Tenv -> Bool
;; Page: 289, 305
(define <:-decls
  (lambda (decls1 decls2 tenv)
    (cond
      ;; if nothing in decls2, any decls1 will do
      ((null? decls2) #t)
      ;; nothing in decls1 to match, so false
      ((null? decls1) #f)
      (else
       ;; at this point we know both decls1 and decls2 are non-empty.
       (let ((name1 (decl->name (car decls1)))
             (name2 (decl->name (car decls2))))
         (if (eqv? name1 name2)
             ;; same name.  They'd better match
             (and
              (<:-decl (car decls1) (car decls2) tenv)
              (<:-decls (cdr decls1) (cdr decls2)
                        (extend-tenv-with-decl (car decls1) tenv)))
             ;; different names.  OK to skip, but record decl1 in the tenv. 
             (<:-decls (cdr decls1) decls2
                       (extend-tenv-with-decl (car decls1) tenv))))))))

;; extend-tenv-with-decl : Decl * Tenv -> Tenv
;; Page: 309
(define extend-tenv-with-decl
  (lambda (decl tenv)
    (cases declaration decl
      ;; don't need to record val declarations
      (val-decl (name ty) tenv)
      (transparent-type-decl (name ty)
                             (extend-tenv-with-type
                              name
                              (expand-type ty tenv)
                              tenv))
      (opaque-type-decl (name)
                        (extend-tenv-with-type
                         name
                         ;; the module name doesn't matter, since the only
                         ;; operation on qualified types is equal?
                         (qualified-type (fresh-module-name '%abstype) name)
                         tenv)))))

;; decl1 and decl2 are known to declare the same name.  There are
;; exactly four combinations that can succeed.

;; <:-decl : Decl * Decl * Tenv -> Bool
;; Page: 311
(define <:-decl 
  (lambda (decl1 decl2 tenv)
    (or
     (and
      (val-decl? decl1)
      (val-decl? decl2)
      (equiv-type? (decl->type decl1) (decl->type decl2) tenv))
     (and
      (transparent-type-decl? decl1)
      (transparent-type-decl? decl2)
      (equiv-type? (decl->type decl1) (decl->type decl2) tenv))
     (and 
      (transparent-type-decl? decl1)
      (opaque-type-decl? decl2))
     (and
      (opaque-type-decl? decl1)
      (opaque-type-decl? decl2))
     )))

;; equiv-type? : Type * Type * Tenv -> Bool
;; Page: 311
(define equiv-type?
  (lambda (ty1 ty2 tenv)
    (equal?
     (expand-type ty1 tenv)
     (expand-type ty2 tenv))))
