Files
sex/sex-modules.scm
Pavel Kulyov 78862cfe4f
All checks were successful
Sex CI / build-linux (pull_request) Successful in 4m45s
TMP
2026-09-18 18:42:52 +03:00

111 lines
3.7 KiB
Scheme

(import
scheme
brev-separate
(chicken base)
(chicken file)
(chicken load)
(chicken pathname)
(chicken process-context)
(chicken string)
fmt
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 form)
;; (pub fn name args ret . body) -> prototype, keeping a docstring so
;; the importer can emit it above the declaration
(let ((header (take form 5)))
(let loop ((body (drop form 5)))
(cond
((null? body) header)
((and (pair? (car body)) (eq? (caar body) 'comment))
(loop (cdr body)))
((string? (car body)) (append header (list (car body))))
(else header)))))
;;; TODO: use semen facilities to analyze modules
(define (process-public-interface-form form acc)
(case (car form)
((pub)
(case (cadr form)
;; A function is reduced to a prototype and keeps its `pub', so
;; the importing unit declares it with external linkage
((fn)
(cons (copy-form-source! form (public-fn-interface form)) acc))
;; A variable becomes an `extern' declaration
((var)
(cons (copy-form-source! form (cons 'extern (take (cdr form) 3))) acc))
((define defmacro enum import include struct typedef union)
(cons (copy-form-source! form (cdr form)) acc))
(else (sex-error form "pub must be followed by a definition" form))))
(else 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 ":"))))))