;; R7RS library support
;;      Copyright (C) 2020 Free Software Foundation, Inc.
;;
;; This library is free software; you can redistribute it and/or
;; modify it under the terms of the GNU Lesser General Public
;; License as published by the Free Software Foundation; either
;; version 3 of the License, or (at your option) any later version.
;;
;; This library is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; Lesser General Public License for more details.
;;
;; You should have received a copy of the GNU Lesser General Public
;; License along with this library; if not, write to the Free Software
;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA


;; This file is included from boot-9.scm and assumes the existence of (and
;; expands into) procedures and syntactic forms defined therein.

(define-syntax include-library-declarations
  (lambda (x)
    (syntax-violation
     'include-library-declarations
     "use of 'include-library-declarations' outside define-library" x x)))

;; FIXME: Implement properly!
(define-syntax-rule (include-ci filename)
  (include filename))

(define-syntax define-library
  (lambda (stx)
    (define (handle-includes filenames)
      (syntax-case filenames ()
        (() #'())
        ((filename . filenames)
         (append (call-with-include-port
                  #'filename
                  (lambda (p)
                    (let lp ()
                      (let ((x (read p)))
                        (if (eof-object? x)
                            #'()
                            (cons (datum->syntax #'filename x) (lp)))))))
                 (handle-includes #'filenames)))))

    (define (handle-cond-expand clauses)
      (define (has-req? req)
        (syntax-case req (and or not library)
          ((and req ...)
           (and-map has-req? #'(req ...)))
          ((or req ...)
           (or-map has-req? #'(req ...)))
          ((not req)
           (not (has-req? #'req)))
          ((library lib-name)
           (->bool (resolve-interface (syntax->datum #'lib-name))))
          (id
           (identifier? #'id)
           ;; FIXME: R7RS (features) isn't quite the same as
           ;; %cond-expand-features; see scheme/base.scm.
           (memq (syntax->datum #'id) %cond-expand-features))))
      (syntax-case clauses ()
        (() #'())  ; R7RS says this is not specified :-/
        (((test decl ...) . clauses)
         (if (has-req? #'test)
             #'(decl ...)
             (handle-cond-expand #'clauses)))))

    (define (partition-decls decls exports imports code)
      (syntax-case decls (export import begin include include-ci
                                 include-library-declarations cond-expand)
        (() (values exports imports (reverse code)))
        (((export clause ...) . decls)
         (partition-decls #'decls (append exports #'(clause ...)) imports code))
        (((import clause ...) . decls)
         (partition-decls #'decls exports (append imports #'(clause ...)) code))
        (((begin expr ...) . decls)
         (partition-decls #'decls exports imports
                          (cons #'(begin expr ...) code)))
        (((include filename ...) . decls)
         (partition-decls #'decls exports imports
                          (cons #'(begin (include filename) ...) code)))
        (((include-ci filename ...) . decls)
         (partition-decls #'decls exports imports
                          (cons #'(begin (include-ci filename) ...) code)))
        (((include-library-declarations filename ...) . decls)
         (syntax-case (handle-includes #'(filename ...)) ()
           ((decl ...)
            (partition-decls #'(decl ... decls) exports imports code))))
        (((cond-expand clause ...) . decls)
         (syntax-case (handle-cond-expand #'(clause ...)) ()
           ((decl ...)
            (partition-decls #'(decl ... decls) exports imports code))))))

    (syntax-case stx ()
      ((_ name decl ...)
       (call-with-values (lambda ()
                           (partition-decls #'(decl ...) '() '() '()))
         (lambda (exports imports code)
           #`(library name
               (export . #,exports)
               (import . #,imports)
               . #,code)))))))
