;; R7RS library support
;;      Copyright (C) 2020, 2021, 2023 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))

(include-from-path "scheme/features.scm")

(define-syntax define-library
  (lambda (stx)
    (define (r7rs-module-name->r6rs-module-name name)
      ;; This is a hack to support (srfi N x ...) modules in R7RS.  The
      ;; longer term solution would be to add support at the level of
      ;; resolve-interface (bug #40371).
      (define (n? stx)
        (let ((n (syntax->datum stx)))
          (and (exact-integer? n)
               (not (negative? n)))))

      (define (srfi-name? stx)
        (syntax-case stx (srfi)
          ((srfi n rest ...)
           (n? #'n))
          (_ #f)))

      (define (make-srfi-n context n)
        (datum->syntax
         context
         (string->symbol
          (string-append
           "srfi-"
           (let ((n (syntax->datum n)))
             (number->string n))))))

      (syntax-case name (srfi)
        ;; (srfi n ...) -> (srfi srfi-n ...)
        ((srfi n rest ...) (srfi-name? #'(srfi n rest ...))
         #`(srfi #,(make-srfi-n #'srfi #'n) rest ...))
        (_ name)))

    (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
            (false-if-exception
             (resolve-r6rs-interface
              (syntax->datum #'lib-name)))))
          (id
           (identifier? #'id)
           (memq (syntax->datum #'id) (features)))))
      (syntax-case clauses (else)
        (() #'())  ; R7RS says this is not specified :-/
        (((else decl ...))
         #'(decl ...))
        (((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))))))

    (define (r7rs-export->r6rs-export export-spec)
      (syntax-case export-spec (rename)
        ((rename from-identifier to-identifier)
         #'(rename (from-identifier to-identifier)))
        (identifier #'identifier)))

    (define (r7rs-import->r6rs-import import-set)
      ;; Normalize SRFI names.
      (syntax-case import-set (only except prefix rename)
        ((only import-set identifier ...)
         #`(only #,(r7rs-import->r6rs-import #'import-set) identifier ...))
        ((except import-set identifier ...)
         #`(except #,(r7rs-import->r6rs-import #'import-set) identifier ...))
        ((prefix import-set identifier ...)
         #`(prefix #,(r7rs-import->r6rs-import #'import-set) identifier ...))
        ((rename import-set (from-identifier to-identifier) ...)
         #`(rename #,(r7rs-import->r6rs-import #'import-set)
                   (from-identifier to-identifier) ...))
        (_ (r7rs-module-name->r6rs-module-name import-set))))

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