;;; r6rs-libraries.scm --- Support for the R6RS `library' and `import' forms ;; Copyright (C) 2010, 2019 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. ;; Note that we can't use top-level define for helpers here as it will ;; pollute the (guile) module. (define (resolve-r6rs-interface import-spec) (define (sym? stx) (symbol? (syntax->datum stx))) (define (n? stx) (let ((n (syntax->datum stx))) (and (exact-integer? n) (not (negative? n))))) (define (colon-n? x) (let ((sym (syntax->datum x))) (and (symbol? sym) (let ((str (symbol->string sym))) (and (string-prefix? ":" str) (let ((num (string->number (substring str 1)))) (and (exact-integer? num) (not (negative? num))))))))) (define (srfi-name? stx) (syntax-case stx (srfi) ((srfi n rest ...) (and (and-map sym? #'(rest ...)) (or (n? #'n) (colon-n? #'n)))) (_ #f))) (define (module-name? stx) (or (srfi-name? stx) (syntax-case stx () ((name name* ...) (and-map sym? #'(name name* ...))) (_ #f)))) (define (make-srfi-n context n) (datum->syntax context (string->symbol (string-append "srfi-" (let ((n (syntax->datum n))) (if (symbol? n) (substring (symbol->string n) 1) (number->string n))))))) (define (make-custom-interface mod) (let ((iface (make-module))) (set-module-kind! iface 'custom-interface) (set-module-name! iface (module-name mod)) iface)) (define (module-for-each/nonlocal f mod) (define (module-and-uses mod) (let lp ((in (list mod)) (out '())) (cond ((null? in) (reverse out)) ((memq (car in) out) (lp (cdr in) out)) (else (lp (append (module-uses (car in)) (cdr in)) (cons (car in) out)))))) (for-each (lambda (mod) (module-for-each f mod)) (module-and-uses mod))) (syntax-case import-spec (library only except prefix rename srfi) ;; (srfi :n ...) -> (srfi srfi-n ...) ;; (srfi n ...) -> (srfi srfi-n ...) ((library (srfi n rest ... (version ...))) (srfi-name? #'(srfi n rest ...)) (let ((srfi-n (make-srfi-n #'srfi #'n))) (resolve-r6rs-interface (syntax-case #'(rest ...) () (() #`(library (srfi #,srfi-n (version ...)))) ((name rest ...) ;; SRFI 97 says that the first identifier after the `n' ;; is used for the libraries name, so it must be ignored. #`(library (srfi #,srfi-n rest ... (version ...)))))))) ((library (name name* ... (version ...))) (and-map sym? #'(name name* ...)) (resolve-interface (syntax->datum #'(name name* ...)) #:version (syntax->datum #'(version ...)))) ((library (name name* ...)) (and-map sym? #'(name name* ...)) (resolve-r6rs-interface #'(library (name name* ... ())))) ((only import-set identifier ...) (and-map sym? #'(identifier ...)) (let* ((mod (resolve-r6rs-interface #'import-set)) (iface (make-custom-interface mod))) (for-each (lambda (sym) (module-add! iface sym (or (module-variable mod sym) (error "no binding `~A' in module ~A" sym mod))) (when (hashq-ref (module-replacements mod) sym) (hashq-set! (module-replacements iface) sym #t))) (syntax->datum #'(identifier ...))) iface)) ((except import-set identifier ...) (and-map sym? #'(identifier ...)) (let* ((mod (resolve-r6rs-interface #'import-set)) (iface (make-custom-interface mod))) (module-for-each/nonlocal (lambda (sym var) (module-add! iface sym var)) mod) (for-each (lambda (sym) (unless (module-local-variable iface sym) (error "no binding `~A' in module ~A" sym mod)) (module-remove! iface sym)) (syntax->datum #'(identifier ...))) iface)) ((prefix import-set identifier) (sym? #'identifier) (let* ((mod (resolve-r6rs-interface #'import-set)) (iface (make-custom-interface mod)) (pre (syntax->datum #'identifier))) (module-for-each/nonlocal (lambda (sym var) (let ((sym* (symbol-append pre sym))) (module-add! iface sym* var) (when (hashq-ref (module-replacements mod) sym) (hashq-set! (module-replacements iface) sym* #t)))) mod) iface)) ((rename import-set (from to) ...) (and (and-map sym? #'(from ...)) (and-map sym? #'(to ...))) (let* ((mod (resolve-r6rs-interface #'import-set)) (replacements (module-replacements mod)) (iface (make-custom-interface mod))) (module-for-each/nonlocal (lambda (sym var) (module-add! iface sym var)) mod) (let lp ((in (syntax->datum #'((from . to) ...))) (out '())) (cond ((null? in) (for-each (lambda (v) (let ((to (vector-ref v 0)) (replace? (vector-ref v 1)) (var (vector-ref v 2))) (when (module-local-variable iface to) (error "duplicate binding for `~A' in module ~A" to mod)) (module-add! iface to var) (when replace? (hashq-set! replacements to #t)))) out) iface) (else (let* ((from (caar in)) (to (cdar in)) (var (module-variable mod from)) (replace? (hashq-ref replacements from))) (unless var (error "no binding `~A' in module ~A" from mod)) (module-remove! iface from) (hashq-remove! replacements from) (lp (cdr in) (cons (vector to replace? var) out)))))))) ((name name* ... (version ...)) (module-name? #'(name name* ...)) (resolve-r6rs-interface #'(library (name name* ... (version ...))))) ((name name* ...) (module-name? #'(name name* ...)) (resolve-r6rs-interface #'(library (name name* ... ())))))) (define-syntax library (lambda (stx) (define (sym? stx) (symbol? (syntax->datum stx))) (define (n? stx) (let ((n (syntax->datum stx))) (and (exact-integer? n) (not (negative? n))))) (define (colon-n? x) (let ((sym (syntax->datum x))) (and (symbol? sym) (let ((str (symbol->string sym))) (and (string-prefix? ":" str) (let ((num (string->number (substring str 1)))) (and (exact-integer? num) (not (negative? num))))))))) (define (srfi-name? stx) (syntax-case stx (srfi) ((srfi n rest ...) (and (and-map sym? #'(rest ...)) (or (n? #'n) (colon-n? #'n)))) (_ #f))) (define (module-name? stx) (or (srfi-name? stx) (syntax-case stx () ((name name* ...) (and-map sym? #'(name name* ...))) (_ #f)))) (define (make-srfi-n context n) (datum->syntax context (string->symbol (string-append "srfi-" (let ((n (syntax->datum n))) (if (symbol? n) (substring (symbol->string n) 1) (number->string n))))))) (define (compute-exports ifaces specs) (define (re-export? sym) (or-map (lambda (iface) (module-variable iface sym)) ifaces)) (define (replace? sym) (module-variable the-scm-module sym)) (let lp ((specs specs) (e '()) (r '()) (x '())) (syntax-case specs (rename) (() (values e r x)) (((rename (from to) ...) . rest) (and (and-map identifier? #'(from ...)) (and-map identifier? #'(to ...))) (let lp2 ((in #'((from . to) ...)) (e e) (r r) (x x)) (syntax-case in () (() (lp #'rest e r x)) (((from . to) . in) (cond ((re-export? (syntax->datum #'from)) (lp2 #'in e (cons #'(from . to) r) x)) ((replace? (syntax->datum #'from)) (lp2 #'in e r (cons #'(from . to) x))) (else (lp2 #'in (cons #'(from . to) e) r x))))))) ((id . rest) (identifier? #'id) (let ((sym (syntax->datum #'id))) (cond ((re-export? sym) (lp #'rest e (cons #'id r) x)) ((replace? sym) (lp #'rest e r (cons #'id x))) (else (lp #'rest (cons #'id e) r x)))))))) (syntax-case stx (export import srfi) ((_ (name name* ...) (export espec ...) (import ispec ...) body ...) (module-name? #'(name name* ...)) ;; Add () as the version. #'(library (name name* ... ()) (export espec ...) (import ispec ...) body ...)) ((_ (srfi n rest ... (version ...)) (export espec ...) (import ispec ...) body ...) (srfi-name? #'(srfi n rest ...)) (let ((srfi-n (make-srfi-n #'srfi #'n))) #`(library (srfi #,srfi-n rest ... (version ...)) (export espec ...) (import ispec ...) body ...))) ((_ (name name* ... (version ...)) (export espec ...) (import ispec ...) body ...) (module-name? #'(name name* ...)) (call-with-values (lambda () (compute-exports (map (lambda (im) (syntax-case im (for) ((for import-set import-level ...) (resolve-r6rs-interface #'import-set)) (import-set (resolve-r6rs-interface #'import-set)))) #'(ispec ...)) #'(espec ...))) (lambda (exports re-exports replacements) (with-syntax (((e ...) exports) ((r ...) re-exports) ((x ...) replacements)) ;; It would be nice to push the module that was current before the ;; definition, and pop it after the library definition, but I ;; actually can't see a way to do that. Helper procedures perhaps, ;; around a fluid that is rebound in save-module-excursion? Patches ;; welcome! #'(begin (define-module (name name* ...) #:pure #:version (version ...)) (import ispec) ... (export e ...) (re-export r ...) (export! x ...) (@@ @@ (name name* ...) body) ...)))))))) (define-syntax import (lambda (stx) (define (strip-for import-set) (syntax-case import-set (for) ((for import-set import-level ...) #'import-set) (import-set #'import-set))) (syntax-case stx () ((_ import-set ...) (with-syntax (((library-reference ...) (map strip-for #'(import-set ...)))) #'(eval-when (expand load eval) (let ((iface (resolve-r6rs-interface 'library-reference))) (call-with-deferred-observers (lambda () (module-use-interfaces! (current-module) (list iface))))) ... (if #f #f)))))))