#lang racket/base #| Show imports (symbols that come from requires) and exports (symbols that are provided) 1. How can I avoid showing imported symbols from the lang line? It would be nice to ignore all the symbols from racket/base if a file starts with #lang racket/base |# (require racket/match unstable/generics racket/pretty syntax/parse (for-syntax racket/struct-info racket/base syntax/parse racket/match)) (provide get-exports (struct-out provided)) (define module-name (compose resolved-module-path-name module-path-index-resolve)) (define-syntax (import-struct stx) (syntax-parse stx [(_ ([struct-name:identifier instance:identifier] more ...) body ...) (define (get-fields struct instance) ;; (printf "Import struct for ~a\n" #'struct-name) (let ([info (syntax-local-value struct (lambda () #f))]) (match (extract-struct-info info) [(list name init-field-count auto-field-count accessor-proc mutator-proc immutable-k-list) (begin ;; messing around with strings is bad, whats a better solution? (define (make-local-field field-stx) (let* ([field (substring (symbol->string (syntax->datum field-stx)) (- (string-length (string-append (symbol->string (syntax->datum name)) "-")) (string-length "struct:")))] [final (string->symbol (string-append (symbol->string (syntax->datum instance)) "." field))]) (datum->syntax instance final instance instance))) #; (apply printf "name: ~a init-field-count: ~a auto-field-count: ~a accessor-proc: ~a mutator-proc: ~a immutable-k-list: ~a\n" (list name init-field-count auto-field-count (map syntax->datum accessor-proc) mutator-proc immutable-k-list)) (with-syntax ([(field ...) (map make-local-field accessor-proc)] [(setter! ...) mutator-proc] [instance instance] [(accessor ...) accessor-proc]) #| (printf "bind: ~a\n" (map syntax->datum (syntax->list #'(field ...)))) (printf "setter: ~a\n" (map syntax->datum (syntax->list #'(setter! ...)))) |# (begin #;syntax-local-introduce #; #'(let ([my-accessor]) let-syntax ([field (make-rename-transformer my-accessor)] ...) body) #; #'(let ([field (make-rename-transformer #'field (accessor instance))] ...) body) #'([field (make-set!-transformer (lambda (stx) (syntax-case stx (set!) [(set! id v) (if #'setter! #'(setter! instance v) #'(error 'with-struct "field ~a is not mutable so no set! is available" 'field))] [id #'(accessor instance)])))] ...) #; #'(let-syntax ([field (make-set!-transformer (lambda (stx) (syntax-case stx (set!) [(set! id v) (if #'setter! #'(setter! instance v) #'(error 'with-struct "field ~a is not mutable so no set! is available" 'field))] [id #'(accessor instance)])))] ...) body ...) #; #'(let-syntax ([field (lambda (stx) #'(accessor instance))] ...) body1 body ...))))]))) (with-syntax ([(field ...) (get-fields #'struct-name #'instance)]) ;; (printf "Final let syntax is ~a\n" (syntax->datum #'(let-syntax (field ...) body ...))) #'(let-syntax (field ...) (import-struct (more ...) body ...)))] [(_ () body ...) #'(begin body ...)])) (generics module-symbol (print module-symbol) (get-symbol module-symbol)) (provide print) (define-syntax-rule (define-module-symbol name (fields ...) rest ...) (define-struct name (fields ...) #:property prop:module-symbol rest ...)) (define-module-symbol symbol:normal (name) (define-methods module-symbol (define (get-symbol self) (symbol:normal-name self)) (define (print self) (import-struct ([symbol:normal self]) (format "~a" self.name))))) (define-module-symbol symbol:normal/contract (name contract) (define-methods module-symbol (define (get-symbol self) (symbol:normal-name self)) (define (print self) (import-struct ([symbol:normal/contract self]) (format "~a contract ~a" self.name self.contract))))) (define-module-symbol symbol:renamed (provided defined) (define-methods module-symbol (define (get-symbol self) (symbol:renamed-provided self)) (define (print self) (import-struct ([symbol:renamed self]) (format "~a as ~a" self.defined self.provided))))) (define-module-symbol symbol:module-exported (where) (define-methods module-symbol (define/generic symbol-print print) (define (get-symbol self) (raise 'get-symbol "Not defined")) (define (print self) (format "from ~a" (module-name (symbol:module-exported-where self)) )))) (define-module-symbol symbol:module-exported-from (original where) (define-methods module-symbol (define/generic symbol-print print) (define (get-symbol self) (raise 'get-symbol "Not defined")) (define (print self) (import-struct ([symbol:module-exported-from self]) (format "from ~a ~a" (module-name self.where) (symbol-print self.original)))))) (define-module-symbol symbol:module-exported-as (where phase-shift imported-name import-shift) (define-methods module-symbol (define/generic symbol-print print) (define (get-symbol self) (symbol:module-exported-as-imported-name self)) (define (print self) (import-struct ([symbol:module-exported-as self]) (format "from ~a as ~a" (module-name self.where) self.imported-name))))) (define-module-symbol symbol:multiple-modules (symbol modules) (define-methods module-symbol (define/generic symbol-print print) (define/generic symbol-get-symbol get-symbol) (define (get-symbol self) (symbol-get-symbol (symbol:multiple-modules-symbol self))) (define (print self) (import-struct ([symbol:multiple-modules self]) (format "~a ~a" (symbol-print self.symbol) (let ([modules self.modules]) (if (null? modules) "" (for/fold ([start (symbol-print (car modules))]) ([next (cdr modules)]) (format "~a and ~a" start (symbol-print next)))))))))) (struct provided (phase variables syntaxes)) (define get-namespace (let ([namespaces (make-hash)]) (lambda (file) (hash-ref namespaces file (lambda () (let ([new (make-base-namespace)]) (hash-set! namespaces file new) new)))))) (define (read-file file) (parameterize ([read-accept-reader #t]) (with-input-from-file file (lambda () (read))))) ;; extract the symbol from the module and call `contract-name' on its contract (define (get-contract symbol file) (parameterize ([current-namespace (get-namespace file) #; (make-base-namespace)]) ;; FIXME! it would be nice if we could pull multiple symbols out ;; in the same `dynamic-require' call (define has-contract? (dynamic-require 'racket/contract 'has-contract?)) (define value-contract (dynamic-require 'racket/contract 'value-contract)) (define contract-name (dynamic-require 'racket/contract 'contract-name)) ;; syntax expansion might fail, just ignore it (with-handlers ([exn:fail:syntax? (lambda (e) #f)]) (let ([result (dynamic-require file symbol (lambda () #f))]) #; (printf "Result is ~a\n" result) #; (printf "v is ~a\n" v) #; (printf "v has contract? ~a\n" (has-contract? v)) (if (has-contract? result) (contract-name (value-contract result)) #f))))) (define (make-symbol something file get-contract?) (define (populate-symbol symbol) (if (not get-contract?) (symbol:normal symbol) (let ([contract (get-contract symbol file)]) (if contract (symbol:normal/contract symbol contract) (symbol:normal symbol))))) (define (extract-module path) (match path [(and (? module-path-index?) module) (symbol:module-exported module)] [(list path phase-shift imported-name imported-phase) (symbol:module-exported-as path phase-shift imported-name imported-phase)])) (match something [(list exported (list paths ...)) (symbol:multiple-modules (populate-symbol exported) (map extract-module paths))])) (define (extract-base-module module-code) (syntax-parse module-code [(module name base . rest) (syntax->datum #'base)])) (define (module=? module1 module2) (define (resolve module) (cond [(symbol? module) (module-path-index-resolve (module-path-index-join module #f))] [(resolved-module-path? module) module] ;; [(module-path-index? module) ( [(module-path-index? module) (module-path-index-resolve module) #; (let-values ([(path base) (module-path-index-split module)]) (printf "Split module path ~a base ~a\n" path base) ((current-module-name-resolver) path))] [else (error 'module=? "Dont understand ~a" module)])) (define (raw-exports module) (parameterize ([current-namespace (get-namespace (resolved-module-path-name module)) #; (make-base-namespace)]) (dynamic-require (resolved-module-path-name module) #f) (call-with-values (lambda () (module->exports (resolved-module-path-name module))) (lambda v v)))) #; (printf "~a resolved ~a. ~a resolved ~a\n" module1 (resolve module1) module2 (resolve module2)) (eq? (resolve module1) (resolve module2)) #; (equal? (raw-exports (resolve module1)) (raw-exports (resolve module2))) #; (equal? (resolve module1) (resolve module2))) (define (get-imports file all?) (let ([imports (parameterize ([current-namespace (get-namespace file) #; (make-base-namespace)]) (dynamic-require file #f) (module->imports file))]) (define (combine-provides provides) ;; provides is guaranteed to have at least one thing or we wouldn't get here (for/fold ([all (car provides)]) ([provide (cdr provides)]) (provided (provided-phase all) (append (provided-variables all) (provided-variables provide)) (append (provided-syntaxes all) (provided-syntaxes provide))))) (define phase-imports (make-hash)) (define base-module (extract-base-module (read-file file))) (define (fixup-paths path exports) (for/list ([export exports]) (match export [(symbol:multiple-modules symbol modules) (symbol:multiple-modules symbol (if (null? modules) (list (symbol:module-exported path)) (map (lambda (module) (symbol:module-exported-from module path)) modules)))]))) (define (add-provide phase provide) (hash-set! phase-imports phase (cons provide (hash-ref phase-imports phase (lambda () (list)))))) ;; (printf "Base module is ~a ~a\n" base-module (make-resolved-module-path base-module)) (for ([import imports]) (match import [(list phase-shift paths ...) ;; (printf "Import at phase shift ~a\n" phase-shift) (for ([path paths]) ;; (printf " Module ~a\n" (module-name path)) (define module-path (let-values ([(module-path rest) (module-path-index-split path)]) ;; (printf "Module path is ~a. Rest is ~a\n" module-path rest) module-path)) ; (define resolved-module-path (module-path-index-resolve path)) ;; (define resolved-module-path (make-resolved-module-path module-path)) ;; (printf "base ~a = resolved ~a is ~a\n" base-module path (module=? base-module path)) (when (or all? (not (module=? path base-module))) (let ([exports (get-exports module-path #f)]) (for ([export exports]) (match export [(provided phase variables syntaxes) (add-provide (+ phase phase-shift) (provided (+ phase phase-shift) (fixup-paths path variables) (fixup-paths path syntaxes)))])))))])) (hash-map phase-imports (lambda (phase provides) (combine-provides provides))))) (define (get-exports file get-contracts?) (define (sort-symbols symbols) (sort symbols (lambda (a b) (define (get-symbol what) (match what [(list name rest ...) (symbol->string name)])) (stringexports file))]) #; (pretty-print (syntax->datum (parameterize ([current-namespace (make-base-namespace)]) (expand (read-file file))))) ; (printf "Expanded is ~a\n" expanded) ; (printf "Variables ~a\n" (syntax-property expanded 'module-variable-provides)) ; (printf "Syntaxes ~a\n" (syntax-property expanded 'module-syntax-provides)) (define exports (make-hash)) (for ([export exported-variables]) (match export [(list (and (? number?) phase) symbols ...) (hash-set! exports phase (provided phase (map make-symbol* (sort-symbols symbols)) '()))])) (for ([export exported-syntaxes]) (match export [(list (and (? number?) phase) symbols ...) (hash-set! exports phase (let ([existing (hash-ref exports phase (lambda () (provided phase '() '())))]) (provided phase (provided-variables existing) (map make-symbol* (sort-symbols symbols)))))])) (hash-map exports (lambda (a b) b)))) (define (phase-name phase) (case phase [(0) " (runtime)"] [(1) " (syntax)"] [(-1) " (template)"] [else ""])) #| (define (find-file provides category search) (struct levenshtein (name distance phase)) ;; find the levenshtein distance between the searched-for term and the name (define (fuzzy-search search name) (local-require (prefix-in neil: (planet neil/levenshtein:1:3/levenshtein))) ;; (printf "Name is ~a\n" name) (define real-name (symbol->string (get-symbol name))) (let ([search-in real-name]) (define distance (neil:string-levenshtein search search-in)) (levenshtein real-name distance 0))) (define (compare-levenshtein object1 object2) (< (levenshtein-distance object1) (levenshtein-distance object2))) (define (do-search export) (match export [(provided phase variables syntaxes) ;; replace the phase from the fuzzy-search with the phase from the export (define (update-phase stuff) (for/list ([object stuff]) (match object [(levenshtein name distance dont-care) (levenshtein name distance phase)]))) (let ([found-variables (map (lambda (variable) (fuzzy-search search variable)) variables)] [found-syntaxes (map (lambda (syntax) (fuzzy-search search syntax)) syntaxes)]) (append (update-phase found-variables) (update-phase found-syntaxes)))])) (let* ([exports provides] [found (apply append (map do-search exports))] [sorted (sort found compare-levenshtein)]) (if (null? sorted) (printf "No ~as available\n" category) (for ([i (in-range 1 6)] [found sorted]) (match found [(levenshtein name distance phase) (printf "~a. Found ~a `~a' at phase ~a\n" i category name phase)]))))) (define (find-file-export file search) (find-file (get-exports file #f) "export" search)) (define (find-file-import file search) (find-file (get-imports file #t) "import" search)) |# (define (find-defines file) (define defines (parameterize ([current-load-relative-directory (let-values ([(care a b) (split-path (path->complete-path (resolve-path (string->path file))))]) care)]) (let ([code (parameterize ([current-namespace (make-base-namespace)]) (expand (read-file file)))]) (syntax-case code (module) [(module name base (module-begin stuff ...)) (apply append (for/list ([top-level (syntax->list #'(stuff ...))]) (syntax-case top-level (define-values define-syntaxes) [(define-values (name ...) . body) (for/list ([name (syntax->list #'(name ...))]) (symbol->string (syntax->datum name)) #; (printf "~a\n" (syntax->datum name)))] [(define-syntaxes (name ...) . body) (for/list ([name (syntax->list #'(name ...))]) (symbol->string (syntax->datum name)) #; (printf "~a\n" (syntax->datum name)))] [else (list)])))])))) (for ([item (sort defines stringnumber phase))] [("--exports") "Only show exports" (show-imports #f)] [("--imports") "Only show imports" (show-exports #f)] [("--defines") "Only show defined identifiers" (mode 'defines)] [("--find-export") export "Do a fuzzy match for an export" (begin (mode 'find-export) (find-export export))] [("--find-import") import "Do a fuzzy match for an import" (begin (mode 'find-import) (find-import import))] #:args files files)) (for ([file (do-parse-command-line)]) (printf "Checking file ~a\n" file) (case (mode) [(show) (check-file (string->path file) (only-phase) (show-imports) (show-exports))] [(raw) (check-file/raw (string->path file) (only-phase) (show-imports) (show-exports))] [(defines) (find-defines file)] [(find-export) (find-file-export file (find-export))] [(find-import) (find-file-import file (find-import))])) |#