frisk 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. #!/bin/sh
  2. # aside from this initial boilerplate, this is actually -*- scheme -*- code
  3. main='(module-ref (resolve-module '\''(scripts frisk)) '\'main')'
  4. exec ${GUILE-guile} -l $0 -c "(apply $main (cdr (command-line)))" "$@"
  5. !#
  6. ;;; frisk --- Grok the module interfaces of a body of files
  7. ;; Copyright (C) 2002, 2006 Free Software Foundation, Inc.
  8. ;;
  9. ;; This program is free software; you can redistribute it and/or
  10. ;; modify it under the terms of the GNU General Public License as
  11. ;; published by the Free Software Foundation; either version 2, or
  12. ;; (at your option) any later version.
  13. ;;
  14. ;; This program is distributed in the hope that it will be useful,
  15. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  17. ;; General Public License for more details.
  18. ;;
  19. ;; You should have received a copy of the GNU General Public License
  20. ;; along with this software; see the file COPYING. If not, write to
  21. ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  22. ;; Boston, MA 02110-1301 USA
  23. ;;; Author: Thien-Thi Nguyen <ttn@gnu.org>
  24. ;;; Commentary:
  25. ;; Usage: frisk [options] file ...
  26. ;;
  27. ;; Analyze FILE... module interfaces in aggregate (as a "body"),
  28. ;; and display a summary. Modules that are `define-module'd are
  29. ;; considered "internal" (and those not, "external"). When module X
  30. ;; uses module Y, X is said to be "(a) downstream of" Y, and Y is
  31. ;; "(an) upstream of" X.
  32. ;;
  33. ;; Normally, the summary displays external modules and their internal
  34. ;; downstreams, as this is the usual question asked by a body. There
  35. ;; are several options that modify this output.
  36. ;;
  37. ;; -u, --upstream show upstream edges
  38. ;; -d, --downstream show downstream edges (default)
  39. ;; -i, --internal show internal modules
  40. ;; -x, --external show external modules (default)
  41. ;;
  42. ;; If given both `upstream' and `downstream' options ("frisk -ud"), the
  43. ;; output is formatted: "C MODULE --- UP-LS --- DOWN-LS", where C is
  44. ;; either `i' or `x', and each element of UP-LS and DOWN-LS is (TYPE
  45. ;; MODULE-NAME ...).
  46. ;;
  47. ;; In all other cases, the "C MODULE" occupies its own line, and
  48. ;; subsequent lines list the up- or downstream edges, respectively,
  49. ;; indented by some non-zero amount of whitespace.
  50. ;;
  51. ;; Top-level `use-modules' (or `load' or 'primitive-load') forms in a
  52. ;; file that do not follow a `define-module' result an edge where the
  53. ;; downstream is the "default module", normally `(guile-user)'. This
  54. ;; can be set to another value by using:
  55. ;;
  56. ;; -m, --default-module MOD set MOD as the default module
  57. ;; Usage from a Scheme Program: (use-modules (scripts frisk))
  58. ;;
  59. ;; Module export list:
  60. ;; (frisk . args)
  61. ;; (make-frisker . options) => (lambda (files) ...) [see below]
  62. ;; (mod-up-ls module) => upstream edges
  63. ;; (mod-down-ls module) => downstream edges
  64. ;; (mod-int? module) => is the module internal?
  65. ;; (edge-type edge) => symbol: {regular,autoload,computed}
  66. ;; (edge-up edge) => upstream module
  67. ;; (edge-down edge) => downstream module
  68. ;;
  69. ;; OPTIONS is an alist. Recognized keys are:
  70. ;; default-module
  71. ;;
  72. ;; `make-frisker' returns a procedure that takes a list of files, the
  73. ;; FRISKER. FRISKER returns a closure, REPORT, that takes one of the
  74. ;; keys:
  75. ;; modules -- entire list of modules
  76. ;; internal -- list of internal modules
  77. ;; external -- list of external modules
  78. ;; i-up -- list of modules upstream of internal modules
  79. ;; x-up -- list of modules upstream of external modules
  80. ;; i-down -- list of modules downstream of internal modules
  81. ;; x-down -- list of modules downstream of external modules
  82. ;; edges -- list of edges
  83. ;; Note that `x-up' should always be null, since by (lack of!)
  84. ;; definition, we only know external modules by reference.
  85. ;;
  86. ;; The module and edge objects managed by REPORT can be examined in
  87. ;; detail by using the other (self-explanatory) procedures. Be careful
  88. ;; not to confuse a freshly consed list of symbols, like `(a b c)' with
  89. ;; the module `(a b c)'. If you want to find the module by that name,
  90. ;; try: (cond ((member '(a b c) (REPORT 'modules)) => car)).
  91. ;; TODO: Make "frisk -ud" output less ugly.
  92. ;; Consider default module as internal; add option to invert.
  93. ;; Support `edge-misc' data.
  94. ;;; Code:
  95. (define-module (scripts frisk)
  96. :autoload (ice-9 getopt-long) (getopt-long)
  97. :use-module ((srfi srfi-1) :select (filter remove))
  98. :export (frisk
  99. make-frisker
  100. mod-up-ls mod-down-ls mod-int?
  101. edge-type edge-up edge-down))
  102. (define *default-module* '(guile-user))
  103. (define (grok-proc default-module note-use!)
  104. (lambda (filename)
  105. (let* ((p (open-file filename "r"))
  106. (next (lambda () (read p)))
  107. (ferret (lambda (use) ;;; handle "((foo bar) :select ...)"
  108. (let ((maybe (car use)))
  109. (if (list? maybe)
  110. maybe
  111. use))))
  112. (curmod #f))
  113. (let loop ((form (next)))
  114. (cond ((eof-object? form))
  115. ((not (list? form)) (loop (next)))
  116. (else (case (car form)
  117. ((define-module)
  118. (let ((module (cadr form)))
  119. (set! curmod module)
  120. (note-use! 'def module #f)
  121. (let loop ((ls form))
  122. (or (null? ls)
  123. (case (car ls)
  124. ((:use-module)
  125. (note-use! 'regular module (ferret (cadr ls)))
  126. (loop (cddr ls)))
  127. ((:autoload)
  128. (note-use! 'autoload module (cadr ls))
  129. (loop (cdddr ls)))
  130. (else (loop (cdr ls))))))))
  131. ((use-modules)
  132. (for-each (lambda (use)
  133. (note-use! 'regular
  134. (or curmod default-module)
  135. (ferret use)))
  136. (cdr form)))
  137. ((load primitive-load)
  138. (note-use! 'computed
  139. (or curmod default-module)
  140. (let ((file (cadr form)))
  141. (if (string? file)
  142. file
  143. (format #f "[computed in ~A]"
  144. filename))))))
  145. (loop (next))))))))
  146. (define up-ls (make-object-property)) ; list
  147. (define dn-ls (make-object-property)) ; list
  148. (define int? (make-object-property)) ; defined via `define-module'
  149. (define mod-up-ls up-ls)
  150. (define mod-down-ls dn-ls)
  151. (define mod-int? int?)
  152. (define (i-or-x module)
  153. (if (int? module) 'i 'x))
  154. (define edge-type (make-object-property)) ; symbol
  155. (define (make-edge type up down)
  156. (let ((new (cons up down)))
  157. (set! (edge-type new) type)
  158. new))
  159. (define edge-up car)
  160. (define edge-down cdr)
  161. (define (up-ls+! m new) (set! (up-ls m) (cons new (up-ls m))))
  162. (define (dn-ls+! m new) (set! (dn-ls m) (cons new (dn-ls m))))
  163. (define (make-body alist)
  164. (lambda (key)
  165. (assq-ref alist key)))
  166. (define (scan default-module files)
  167. (let* ((modules (list))
  168. (edges (list))
  169. (intern (lambda (module)
  170. (cond ((member module modules) => car)
  171. (else (set! (up-ls module) (list))
  172. (set! (dn-ls module) (list))
  173. (set! modules (cons module modules))
  174. module))))
  175. (grok (grok-proc default-module
  176. (lambda (type d u)
  177. (let ((d (intern d)))
  178. (if (eq? type 'def)
  179. (set! (int? d) #t)
  180. (let* ((u (intern u))
  181. (edge (make-edge type u d)))
  182. (set! edges (cons edge edges))
  183. (up-ls+! d edge)
  184. (dn-ls+! u edge))))))))
  185. (for-each grok files)
  186. (make-body
  187. `((modules . ,modules)
  188. (internal . ,(filter int? modules))
  189. (external . ,(remove int? modules))
  190. (i-up . ,(filter int? (map edge-down edges)))
  191. (x-up . ,(remove int? (map edge-down edges)))
  192. (i-down . ,(filter int? (map edge-up edges)))
  193. (x-down . ,(remove int? (map edge-up edges)))
  194. (edges . ,edges)))))
  195. (define (make-frisker . options)
  196. (let ((default-module (or (assq-ref options 'default-module)
  197. *default-module*)))
  198. (lambda (files)
  199. (scan default-module files))))
  200. (define (dump-updown modules)
  201. (for-each (lambda (m)
  202. (format #t "~A ~A --- ~A --- ~A\n"
  203. (i-or-x m) m
  204. (map (lambda (edge)
  205. (cons (edge-type edge)
  206. (edge-up edge)))
  207. (up-ls m))
  208. (map (lambda (edge)
  209. (cons (edge-type edge)
  210. (edge-down edge)))
  211. (dn-ls m))))
  212. modules))
  213. (define (dump-up modules)
  214. (for-each (lambda (m)
  215. (format #t "~A ~A\n" (i-or-x m) m)
  216. (for-each (lambda (edge)
  217. (format #t "\t\t\t ~A\t~A\n"
  218. (edge-type edge) (edge-up edge)))
  219. (up-ls m)))
  220. modules))
  221. (define (dump-down modules)
  222. (for-each (lambda (m)
  223. (format #t "~A ~A\n" (i-or-x m) m)
  224. (for-each (lambda (edge)
  225. (format #t "\t\t\t ~A\t~A\n"
  226. (edge-type edge) (edge-down edge)))
  227. (dn-ls m)))
  228. modules))
  229. (define (frisk . args)
  230. (let* ((parsed-opts (getopt-long
  231. (cons "frisk" args) ;;; kludge
  232. '((upstream (single-char #\u))
  233. (downstream (single-char #\d))
  234. (internal (single-char #\i))
  235. (external (single-char #\x))
  236. (default-module
  237. (single-char #\m)
  238. (value #t)))))
  239. (=u (option-ref parsed-opts 'upstream #f))
  240. (=d (option-ref parsed-opts 'downstream #f))
  241. (=i (option-ref parsed-opts 'internal #f))
  242. (=x (option-ref parsed-opts 'external #f))
  243. (files (option-ref parsed-opts '() (list)))
  244. (report ((make-frisker
  245. `(default-module
  246. . ,(option-ref parsed-opts 'default-module
  247. *default-module*)))
  248. files))
  249. (modules (report 'modules))
  250. (internal (report 'internal))
  251. (external (report 'external))
  252. (edges (report 'edges)))
  253. (format #t "~A ~A, ~A ~A (~A ~A, ~A ~A), ~A ~A\n\n"
  254. (length files) "files"
  255. (length modules) "modules"
  256. (length internal) "internal"
  257. (length external) "external"
  258. (length edges) "edges")
  259. ((cond ((and =u =d) dump-updown)
  260. (=u dump-up)
  261. (else dump-down))
  262. (cond ((and =i =x) modules)
  263. (=i internal)
  264. (else external)))))
  265. (define main frisk)
  266. ;;; frisk ends here