(module moddep mzscheme (require (lib "etc.ss") (lib "port.ss") (lib "resolver.ss" "planet")) (provide moddep-current-open-input-file) (define moddep-current-open-input-file (make-parameter open-input-file)) (provide with-module-reading-parameterization) (define (with-module-reading-parameterization thunk) (parameterize ((read-case-sensitive #t) (read-square-bracket-as-paren #t) (read-curly-brace-as-paren #t) (read-accept-box #t) (read-accept-compiled #t) (read-accept-bar-quote #t) (read-accept-graph #t) (read-decimal-as-inexact #t) (read-accept-dot #t) (read-accept-quasiquote #t) (read-accept-reader #t) (current-readtable #f)) (thunk))) (define (raise-wrong-module-name filename expected-name name) (raise (make-exn:fail (string->immutable-string (format "load-handler: expected a `module' declaration for `~a' in ~s, found: ~a" expected-name filename name)) (current-continuation-marks)))) (define (check-module-form exp expected-module filename) (cond [(or (eof-object? exp) (eof-object? (syntax-e exp))) (and filename (raise (make-exn:fail (string->immutable-string (format "load-handler: expected a `module' declaration for `~a' in ~s, but found end-of-file" expected-module filename)) (current-continuation-marks))))] [(compiled-module-expression? (syntax-e exp)) (if (eq? (module-compiled-name (syntax-e exp)) expected-module) ;; It's fine: exp ;; Wrong name: (and filename (raise-wrong-module-name filename expected-module (module-compiled-name (syntax-e exp)))))] [(and (syntax? exp) (syntax-case exp () [(mod nm . _) (and (eq? (syntax-e (syntax mod)) 'module) (identifier? (syntax nm)))] [_else #f])) ;; It's ok; need to install a specific `module' binding: (with-syntax ([(mod nm . _) exp]) (unless (eq? (syntax-e (syntax nm)) expected-module) (raise-wrong-module-name filename expected-module (syntax-e (syntax nm)))) (datum->syntax-object exp (cons (syntax module) (cdr (syntax-e exp))) exp exp))] [else (and filename (raise (make-exn:fail (string->immutable-string (format "load-handler: expected a `module' declaration for `~a' in ~s, but found something else" expected-module filename)) (current-continuation-marks))))])) (define re:suffix #rx#"\\..*$") (define (resolve s) (if (complete-path? s) s (let ([d (current-load-relative-directory)]) (if d (path->complete-path s d) s)))) (define (date>=? a bm) (and a (let ([am (with-handlers ([exn:fail:filesystem? (lambda (x) #f)]) (file-or-directory-modify-seconds a))]) (or (and (not bm) am) (and am bm (>= am bm)))))) (define (read-one path src?) (let ([p ((moddep-current-open-input-file) path)]) (when src? (port-count-lines! p) (strip-shell-command-start p)) (dynamic-wind void (lambda () (let ([v (with-module-reading-parameterization (lambda () (read-syntax path p)))]) (when (eof-object? v) (error 'read-one "empty file; expected a module declaration in: ~a" path)) (let ([name (let-values ([(base name dir?) (split-path path)]) (string->symbol (bytes->string/utf-8 (path->bytes (path-replace-suffix name #"")) #\?)))]) (check-module-form v name path)) (unless (eof-object? (read p)) (error 'read-one "file has more than one expression; expected a module declaration only in: ~a" path)) (if (and (syntax? v) (compiled-expression? (syntax-e v))) (syntax-e v) v))) (lambda () (close-input-port p))))) (define-struct (exn:get-module-code exn) (path)) (define get-module-code (opt-lambda (path [sub-path "compiled"] [compiler compile] [extension-handler #f]) (unless (path-string? path) (raise-type-error 'get-module-code "path or string (sans nul)" path)) (let*-values ([(path) (resolve path)] [(base file dir?) (split-path path)] [(base) (if (eq? base 'relative) 'same base)] [(mode) (use-compiled-file-paths)]) (let* ([get-so (lambda (file) (build-path base sub-path "native" (system-library-subpath) (path-replace-suffix file (case (system-type) [(windows) #".dll"] [else #".so"]))))] [zo (build-path base sub-path (path-replace-suffix file #".zo"))] [so (get-so file)] [_loader-so (get-so (string->path "_loader.ss"))] [path-d (with-handlers ([exn:fail:filesystem? (lambda (x) #f)]) (file-or-directory-modify-seconds path))] [with-dir (lambda (t) (parameterize ([current-load-relative-directory (if (path? base) base (current-directory))]) (t)))]) (cond ;; Use .zo, if it's new enough [(date>=? zo path-d) (read-one zo #f)] ;; Otherwise, use source if it exists [path-d (with-dir (lambda () (compiler (read-one path #t))))] ;; No source --- maybe there's an .so? [(and (not path-d) (date>=? so path-d)) (if extension-handler (extension-handler so #f) (raise (make-exn:get-module-code (string->immutable-string (format "get-module-code: cannot use extension file; ~e" so)) (current-continuation-marks) so)))] ;; Or maybe even a _loader.so? [(and (not path-d) (date>=? _loader-so path-d) (let ([getter (load-extension _loader-so)]) (let-values ([(loader modname) (getter (string->symbol (bytes->string/latin-1 (path->bytes (path-replace-suffix file #"")))))]) loader))) => (lambda (loader) (if extension-handler (extension-handler loader #t) (raise (make-exn:get-module-code (string->immutable-string (format "get-module-code: cannot use _loader file: ~e" _loader-so)) (current-continuation-marks) loader))))] ;; Report a not-there error [else (raise (make-exn:get-module-code (string->immutable-string (format "get-module-code: no such file: ~e" path)) (current-continuation-marks) #f))]))))) (define re:dir #rx#"(.+?)/+(.*)") (define (force-relto relto dir?) (cond [(path-string? relto) (if dir? (let-values ([(base n d?) (split-path relto)]) (if (eq? base 'relative) (or (current-load-relative-directory) (current-directory)) base)) relto)] [(pair? relto) relto] [(not dir?) (error 'resolve-module-path-index "can't resolve \"self\" with just a relative directory ~e" relto)] [(procedure? relto) (relto)] [else (current-directory)])) (define resolve-module-path ;; relto should be a complete path, #f, or procedure that returns a complete path (lambda (s relto) (let ([get-dir (lambda () (force-relto relto #t))]) (cond [(string? s) ;; Parse Unix-style relative path string (let loop ([path (get-dir)][s (if (path? s) (path->bytes s) (string->bytes/utf-8 s))]) (let ([prefix (regexp-match re:dir s)]) (if prefix (loop (build-path path (let ([p (cadr prefix)]) (cond [(bytes=? p #".") 'same] [(bytes=? p #"..") 'up] [else (bytes->path p)]))) (caddr prefix)) (build-path path (bytes->path s)))))] [(and (or (not (pair? s)) (not (list? s))) (not (path? s))) #f] [(or (path? s) (eq? (car s) 'file)) (let ([p (if (path? s) s (cadr s))]) (path->complete-path p (let ([d (get-dir)]) (if (path-string? d) d (or (current-load-relative-directory) (current-directory))))))] [(eq? (car s) 'lib) (let ([cols (let ([len (length s)]) (if (= len 2) (list "mzlib") (cddr s)))]) (let ([p (apply collection-path cols)]) (build-path p (cadr s))))] [(eq? (car s) 'planet) (let ((module-id (cond [(path? relto) (string->symbol (string-append "," (path->string (path->complete-path relto))))] [else #f]))) (let-values ([(path pkg) (get-planet-module-path/pkg s module-id #f)]) path))] [else #f])))) (define (resolve-module-path-index mpi relto) ;; relto must be a complete path (let-values ([(path base) (module-path-index-split mpi)]) (if path (resolve-module-path path (resolve-possible-module-path-index base relto)) (force-relto relto #f)))) (define (resolve-possible-module-path-index base relto) (cond [(module-path-index? base) (resolve-module-path-index base relto)] [(symbol? base) (let ([s (symbol->string base)]) (if (and ((string-length s) . > . 0) (char=? #\, (string-ref s 0))) (substring s 1 (string-length s)) relto))] [relto (if (procedure? relto) (relto) relto)] [else #f])) (define re:path-only #rx#"^(.*)/[^/]*$") (define collapse-module-path ;; relto-mp should be a relative path, '(lib relative-path collection), or '(file path) ;; of a thunk that produces one of those (lambda (s relto-mp) (let ([combine-relative-elements (lambda (elements) (when (procedure? relto-mp) (set! relto-mp (relto-mp))) (cond [(path-string? relto-mp) (bytes->string/locale (apply bytes-append (let ([m (regexp-match re:path-only (if (path? relto-mp) (path->bytes relto-mp) (string->bytes/locale relto-mp)))]) (if m (cadr m) #".")) (map (lambda (e) (cond [(eq? e 'same) #"/."] [(eq? e 'up) #"/.."] [else (bytes-append #"/" (if (path? e) (path->bytes e) e))])) elements)))] [else (let ([path (path->string (apply build-path (let-values ([(base n d?) (split-path (cadr relto-mp))]) (if (eq? base 'relative) 'same base)) (map (lambda (i) (cond [(bytes? i) (bytes->path i)] [else i])) elements)))]) (if (eq? (car relto-mp) 'lib) `(lib ,path ,(caddr relto-mp)) `(file ,path)))]))]) (cond [(string? s) ;; Parse Unix-style relative path string (let loop ([elements null][s (string->bytes/utf-8 s)]) (let ([prefix (regexp-match re:dir s)]) (if prefix (loop (cons (let ([p (cadr prefix)]) (cond [(bytes=? p #".") 'same] [(bytes=? p #"..") 'up] [else (bytes->path p)])) elements) (caddr prefix)) (combine-relative-elements (reverse (cons s elements))))))] [(and (or (not (pair? s)) (not (list? s))) (not (path? s))) #f] [(or (path? s) (eq? (car s) 'file)) (let ([p (if (path? s) s (cadr s))]) (if (absolute-path? p) s (let loop ([p p][elements null]) (let-values ([(base name dir?) (split-path p)]) (cond [(eq? base 'relative) (combine-relative-elements (cons name elements))] [else (loop base (cons name elements))])))))] [(eq? (car s) 'lib) (let ([cols (let ([len (length s)]) (if (= len 2) (list "mzlib") (cddr s)))]) `(lib ,(path->string (build-path (if (null? (cdr cols)) 'same (apply build-path 'same (cdr cols))) (cadr s))) ,(car cols)))] [else #f])))) (define (collapse-module-path-index mpi relto-mp) (let-values ([(path base) (module-path-index-split mpi)]) (if path (collapse-module-path path (cond [(symbol? base) (let ([s (symbol->string base)]) (if (and ((string-length s) . > . 0) (char=? #\, (string-ref s 0))) `(file ,(substring s 1 (string-length s))) relto-mp))] [(module-path-index? base) (collapse-module-path-index base relto-mp)] [else relto-mp])) relto-mp))) (define (show-import-tree module-path) (let loop ([path (resolve-module-path module-path #f)][indent ""][fs ""]) (printf "~a~a~a~n" indent path fs) (let ([code (get-module-code path)]) (let-values ([(imports fs-imports ft-imports) (module-compiled-imports code)] [(mk-loop) (lambda (fs) (lambda (i) (unless (symbol? i) (loop (resolve-module-path-index i path) (format " ~a" indent) fs))))]) (for-each (mk-loop "") imports) (for-each (mk-loop " [for-syntax]") fs-imports) (for-each (mk-loop " [for-template]") ft-imports))))) (provide check-module-form get-module-code exn:get-module-code exn:get-module-code? exn:get-module-code-path make-exn:get-module-code resolve-module-path resolve-module-path-index collapse-module-path collapse-module-path-index show-import-tree))