modular/link.rkt
#lang racket/base

(require
  "../private/planet.rkt"
  (for-syntax
    racket/base
    racket/block
    syntax/parse
    (cce syntax)
    "static-rep.rkt"
    "syntax-meta.rkt"
    "list-set.rkt")
  "dynamic-rep.rkt"
  "keywords.rkt")

(provide link-macro)
(provide restrict-macro)

(define-for-syntax (restrict-module/static name stx mod imp-ids exp-ids)
  (define imps
    (for/list ([imp-id (in-list imp-ids)])
      (make-import/static
        imp-id
        (syntax->meta #:message "not an interface" imp-id)
        null
        null)))
  (for {[real-imp (in-list (module/static-imports mod))]}
    (unless (for/or {[spec-imp (in-list imps)]}
              (port/static-external=? spec-imp real-imp))
      (syntax-error stx
        "module ~s does not import required interface ~s"
        (syntax-e name)
        (syntax-e
          (interface/static-name
            (port/static-interface real-imp))))))
  (define exps
    (for/list ([exp-id (in-list exp-ids)])
      (make-export/static
        exp-id
        (syntax->meta #:message "not an interface" exp-id)
        null
        null)))
  (for {[spec-exp (in-list exps)]}
    (unless (for/or {[real-exp (in-list (module/static-exports mod))]}
              (port/static-external=? real-exp spec-exp))
      (syntax-error stx
        "module ~s exports unimplemented interface ~s"
        (syntax-e name)
        (syntax-e
          (interface/static-name
            (port/static-interface spec-exp))))))
  (make-module/static
    name
    (module/static-dynamic mod)
    stx
    (append imps exps)))

(define-syntax (restrict/derived stx)
  (syntax-parse stx
    [(_ original name full (import i ...) (export e ...))
     #'(define-syntax name
         (make-syntax-meta
           (restrict-module/static
             #'name
             (quote-syntax original)
             (syntax->meta #:message "not a module" #'full)
             (list #'i ...)
             (list #'e ...))
           (expand-keyword "cannot be used as an expression")))]))

(define-syntax (restrict-macro stx)
  (syntax-parse stx
    [(_ name full (import i ...) (export e ...))
     #`(restrict/derived #,stx name full (import i ...) (export e ...))]))

(define-for-syntax (expand-link stx)
  (parameterize ([current-syntax stx])
    (syntax-parse stx
      #:literals (import export)
      [(form name (import i ...) (export e ...) (mod ...))
       (with-syntax ([original stx])
         #'(begin
             (form full (mod ...))
             (restrict/derived original name full
               (import i ...) (export e ...))))]
      [(_ name ())
       (syntax-error stx "must link at least one module")]
      [(_ name (mod))
       (syntax/loc stx
         (define-syntax name (syntax-local-value #'mod)))]
      [(form name (one two . mods))
       (with-syntax ([original stx])
         (syntax/loc stx
           (begin
             (define-syntaxes (one-two one/impl two/impl)
               (block
                 (define one/static
                   (syntax->meta #:message "not a module" #'one))
                 (define one/dynamic (module/static-dynamic one/static))
                 (define one/imports (module/static-imports one/static))
                 (define one/exports (module/static-exports one/static))
                 (define two/static
                   (syntax->meta #:message "not a module" #'two))
                 (define two/dynamic (module/static-dynamic two/static))
                 (define two/imports (module/static-imports two/static))
                 (define two/exports (module/static-exports two/static))
                 (for* {[one/import (in-list one/imports)]
                        [two/export (in-list two/exports)]
                        #:when (port/static-external=? one/import two/export)}
                   (syntax-error (quote-syntax original)
                     (string-append
                       "constituents of link must be reordered; "
                       "import of interface ~s comes before "
                       "export of interface ~s")
                     (syntax-e
                       (interface/static-name
                         (port/static-interface one/import)))
                     (syntax-e
                       (interface/static-name
                         (port/static-interface two/export)))))
                 (define one-two/exports
                   (list-union #:compare port/static-external=?
                     one/exports two/exports))
                 (define one-two/imports
                   (list-union #:compare port/static-external=?
                     one/imports
                     (list-minus #:compare port/static-external=?
                       two/imports one/exports)))
                 (values
                   (make-syntax-meta
                     (make-module/static
                       #'one-two
                       #'dynamic
                       #'original
                       (append one-two/imports one-two/exports))
                     (expand-keyword "cannot be used as an expression"))
                   (make-rename-transformer one/dynamic)
                   (make-rename-transformer two/dynamic))))
             (define dynamic
               (make-module/dynamic
                (lambda (imp/dynamic)
                  (let* ([one/func (module/dynamic-implementation one/impl)]
                         [exp-one/dynamic (one/func imp/dynamic)]
                         [imp-two/dynamic
                          (interface/dynamic-join imp/dynamic exp-one/dynamic)]
                         [two/func (module/dynamic-implementation two/impl)]
                         [exp-two/dynamic (two/func imp-two/dynamic)]
                         [exp/dynamic
                          (interface/dynamic-join imp-two/dynamic
                                                  exp-two/dynamic)])
                    exp/dynamic))))
             (form name (one-two . mods)))))]
      [_
       (syntax-error
        stx
        "expected a name followed by a parenthesized list of one or more module names")])))

(define-syntax link-macro expand-link)