(import scheme brev-separate (chicken base) (chicken file) (chicken load) (chicken pathname) (chicken process-context) (chicken string) fmt matchable reader srfi-1 utils) (define +persistent-module-paths+ (list)) ;;; for guarding against multiple imports (sort of mandatory #pragma ;;; once) (define +imported-modules+ (list)) (define (get-modules-public-forms module-list) ;; Module list is a list of symbols ;; How Sex handles modules: ;; For each module in a list, construct path, find module by path in ;; module path directories, extract public definitions from the ;; module, paste them in current one in emulation of C include ;; directives. (fold-right append (list) (map (fn (import-module (symbol->string x))) module-list))) (define (import-module name) (let ((module-path (locate-module name))) (assert module-path (fmt #f "Failed to find module " name " in " (get-module-paths))) (if (member module-path +imported-modules+) (list) (begin (set! +imported-modules+ (cons module-path +imported-modules+)) (read-public-interface module-path))))) (define (get-module-paths) (cons (current-directory) +persistent-module-paths+)) (define (locate-module name) ;; Module locations: relative to file being compiled, or in what was ;; in SEX_MODULE_PATH env var at the start of the process (see ;; load-persistent-module-paths function) (let ((search-paths (get-module-paths))) (let loop ((paths search-paths)) (if (null? paths) #f (or (module-exists? name (car paths)) (loop (cdr paths))))))) (define (module-exists? name module-dir) ;; returns absolute path to module, if it exists (and (directory-exists? module-dir) (let ((module-path (make-absolute-pathname module-dir name "sex"))) (and (file-exists? module-path) (file-readable? module-path) module-path)))) (define (read-public-interface module-path) ;; pub fns are reduced to prototypes, other pub forms are just pasted (let ((raw-forms (read-from-file module-path))) (fold process-public-interface-form (list) raw-forms))) (define (public-fn-interface raw-form) ;; (pub fn name args ret . body) -> prototype, keeping a docstring so ;; the importer can emit it above the declaration (let ((form (strip-header-comments raw-form 5))) (match form (('pub 'fn name args ret) form) (('pub 'fn name args ret ('comment . _) . rest) (public-fn-interface `(pub fn ,name ,args ,ret ,@rest))) (('pub 'fn name args ret (? string? doc) . _) `(pub fn ,name ,args ,ret ,doc)) (('pub 'fn name args ret . _) `(pub fn ,name ,args ,ret))))) ;;; TODO: use semen facilities to analyze modules (define (process-public-interface-form form acc) (match form ;; Reduced to a prototype, still `pub', so the importer declares it ;; with external linkage (('pub 'fn . _) (cons (copy-form-source! form (public-fn-interface form)) acc)) (('pub 'var . _) (match-let ((('pub 'var name type . _) (strip-header-comments form 4))) (cons (copy-form-source! form `(extern var ,name ,type)) acc))) (('pub (or 'define 'defmacro 'enum 'import 'include 'struct 'typedef 'union) . _) (cons (copy-form-source! form (cdr form)) acc)) (('pub . _) (sex-error form "pub must be followed by a definition" form)) (_ acc))) (define (load-persistent-module-paths) (let ((sex-module-path-env-var (get-env-var "SEX_MODULE_PATH"))) (when sex-module-path-env-var (set! +persistent-module-paths+ (map (lambda (p) (make-absolute-pathname p #f #f)) (string-split sex-module-path-env-var ":"))))))