summaryrefslogtreecommitdiff
path: root/module/scripts/frisk.scm
blob: 0cf50d6a8191db5a62da0e8a69632e4db33ced91 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
;;; frisk --- Grok the module interfaces of a body of files

;; 	Copyright (C) 2002, 2006 Free Software Foundation, Inc.
;;
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU Lesser General Public License
;; as published by the Free Software Foundation; either version 3, or
;; (at your option) any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; Lesser General Public License for more details.
;;
;; You should have received a copy of the GNU Lesser General Public
;; License along with this software; see the file COPYING.LESSER.  If
;; not, write to the Free Software Foundation, Inc., 51 Franklin
;; Street, Fifth Floor, Boston, MA 02110-1301 USA

;;; Author: Thien-Thi Nguyen <ttn@gnu.org>

;;; Commentary:

;; Usage: frisk [options] file ...
;;
;; Analyze FILE... module interfaces in aggregate (as a "body"),
;; and display a summary.  Modules that are `define-module'd are
;; considered "internal" (and those not, "external").  When module X
;; uses module Y, X is said to be "(a) downstream of" Y, and Y is
;; "(an) upstream of" X.
;;
;; Normally, the summary displays external modules and their internal
;; downstreams, as this is the usual question asked by a body.  There
;; are several options that modify this output.
;;
;;  -u, --upstream      show upstream edges
;;  -d, --downstream    show downstream edges (default)
;;  -i, --internal      show internal modules
;;  -x, --external      show external modules (default)
;;
;; If given both `upstream' and `downstream' options ("frisk -ud"), the
;; output is formatted: "C MODULE --- UP-LS --- DOWN-LS", where C is
;; either `i' or `x', and each element of UP-LS and DOWN-LS is (TYPE
;; MODULE-NAME ...).
;;
;; In all other cases, the "C MODULE" occupies its own line, and
;; subsequent lines list the up- or downstream edges, respectively,
;; indented by some non-zero amount of whitespace.
;;
;; Top-level `use-modules' (or `load' or 'primitive-load') forms in a
;; file that do not follow a `define-module' result an edge where the
;; downstream is the "default module", normally `(guile-user)'.  This
;; can be set to another value by using:
;;
;;  -m, --default-module MOD    set MOD as the default module

;; Usage from a Scheme Program: (use-modules (scripts frisk))
;;
;; Module export list:
;;  (frisk . args)
;;  (make-frisker . options)    => (lambda (files) ...) [see below]
;;  (mod-up-ls module)          => upstream edges
;;  (mod-down-ls module)        => downstream edges
;;  (mod-int? module)           => is the module internal?
;;  (edge-type edge)            => symbol: {regular,autoload,computed}
;;  (edge-up edge)              => upstream module
;;  (edge-down edge)            => downstream module
;;
;; OPTIONS is an alist.  Recognized keys are:
;;  default-module
;;
;; `make-frisker' returns a procedure that takes a list of files, the
;; FRISKER.  FRISKER returns a closure, REPORT, that takes one of the
;; keys:
;;  modules  -- entire list of modules
;;  internal -- list of internal modules
;;  external -- list of external modules
;;  i-up     -- list of modules upstream of internal modules
;;  x-up     -- list of modules upstream of external modules
;;  i-down   -- list of modules downstream of internal modules
;;  x-down   -- list of modules downstream of external modules
;;  edges    -- list of edges
;; Note that `x-up' should always be null, since by (lack of!)
;; definition, we only know external modules by reference.
;;
;; The module and edge objects managed by REPORT can be examined in
;; detail by using the other (self-explanatory) procedures.  Be careful
;; not to confuse a freshly consed list of symbols, like `(a b c)' with
;; the module `(a b c)'.  If you want to find the module by that name,
;; try: (cond ((member '(a b c) (REPORT 'modules)) => car)).

;; TODO: Make "frisk -ud" output less ugly.
;;       Consider default module as internal; add option to invert.
;;       Support `edge-misc' data.

;;; Code:

(define-module (scripts frisk)
  :autoload (ice-9 getopt-long) (getopt-long)
  :use-module ((srfi srfi-1) :select (filter remove))
  :export (frisk
           make-frisker
           mod-up-ls mod-down-ls mod-int?
           edge-type edge-up edge-down))

(define *default-module* '(guile-user))

(define (grok-proc default-module note-use!)
  (lambda (filename)
    (let* ((p (open-file filename "r"))
           (next (lambda () (read p)))
           (ferret (lambda (use)   ;;; handle "((foo bar) :select ...)"
                     (let ((maybe (car use)))
                       (if (list? maybe)
                           maybe
                           use))))
           (curmod #f))
      (let loop ((form (next)))
        (cond ((eof-object? form))
              ((not (list? form)) (loop (next)))
              (else (case (car form)
                      ((define-module)
                       (let ((module (cadr form)))
                         (set! curmod module)
                         (note-use! 'def module #f)
                         (let loop ((ls form))
                           (or (null? ls)
                               (case (car ls)
                                 ((:use-module)
                                  (note-use! 'regular module (ferret (cadr ls)))
                                  (loop (cddr ls)))
                                 ((:autoload)
                                  (note-use! 'autoload module (cadr ls))
                                  (loop (cdddr ls)))
                                 (else (loop (cdr ls))))))))
                      ((use-modules)
                       (for-each (lambda (use)
                                   (note-use! 'regular
                                              (or curmod default-module)
                                              (ferret use)))
                                 (cdr form)))
                      ((load primitive-load)
                       (note-use! 'computed
                                  (or curmod default-module)
                                  (let ((file (cadr form)))
                                    (if (string? file)
                                        file
                                        (format #f "[computed in ~A]"
                                                filename))))))
                    (loop (next))))))))

(define up-ls (make-object-property))   ; list
(define dn-ls (make-object-property))   ; list
(define int?  (make-object-property))   ; defined via `define-module'

(define mod-up-ls up-ls)
(define mod-down-ls dn-ls)
(define mod-int? int?)

(define (i-or-x module)
  (if (int? module) 'i 'x))

(define edge-type (make-object-property)) ; symbol

(define (make-edge type up down)
  (let ((new (cons up down)))
    (set! (edge-type new) type)
    new))

(define edge-up car)
(define edge-down cdr)

(define (up-ls+! m new) (set! (up-ls m) (cons new (up-ls m))))
(define (dn-ls+! m new) (set! (dn-ls m) (cons new (dn-ls m))))

(define (make-body alist)
  (lambda (key)
    (assq-ref alist key)))

(define (scan default-module files)
  (let* ((modules (list))
         (edges (list))
         (intern (lambda (module)
                   (cond ((member module modules) => car)
                         (else (set! (up-ls module) (list))
                               (set! (dn-ls module) (list))
                               (set! modules (cons module modules))
                               module))))
         (grok (grok-proc default-module
                          (lambda (type d u)
                            (let ((d (intern d)))
                              (if (eq? type 'def)
                                  (set! (int? d) #t)
                                  (let* ((u (intern u))
                                         (edge (make-edge type u d)))
                                    (set! edges (cons edge edges))
                                    (up-ls+! d edge)
                                    (dn-ls+! u edge))))))))
    (for-each grok files)
    (make-body
     `((modules  . ,modules)
       (internal . ,(filter int? modules))
       (external . ,(remove int? modules))
       (i-up     . ,(filter int? (map edge-down edges)))
       (x-up     . ,(remove int? (map edge-down edges)))
       (i-down   . ,(filter int? (map edge-up   edges)))
       (x-down   . ,(remove int? (map edge-up   edges)))
       (edges    . ,edges)))))

(define (make-frisker . options)
  (let ((default-module (or (assq-ref options 'default-module)
                            *default-module*)))
    (lambda (files)
      (scan default-module files))))

(define (dump-updown modules)
  (for-each (lambda (m)
              (format #t "~A ~A --- ~A --- ~A\n"
                      (i-or-x m) m
                      (map (lambda (edge)
                             (cons (edge-type edge)
                                   (edge-up edge)))
                           (up-ls m))
                      (map (lambda (edge)
                             (cons (edge-type edge)
                                   (edge-down edge)))
                           (dn-ls m))))
            modules))

(define (dump-up modules)
  (for-each (lambda (m)
              (format #t "~A ~A\n" (i-or-x m) m)
              (for-each (lambda (edge)
                          (format #t "\t\t\t ~A\t~A\n"
                                  (edge-type edge) (edge-up edge)))
                        (up-ls m)))
            modules))

(define (dump-down modules)
  (for-each (lambda (m)
              (format #t "~A ~A\n" (i-or-x m) m)
              (for-each (lambda (edge)
                          (format #t "\t\t\t ~A\t~A\n"
                                  (edge-type edge) (edge-down edge)))
                        (dn-ls m)))
            modules))

(define (frisk . args)
  (let* ((parsed-opts (getopt-long
                       (cons "frisk" args)    ;;; kludge
                       '((upstream (single-char #\u))
                         (downstream (single-char #\d))
                         (internal (single-char #\i))
                         (external (single-char #\x))
                         (default-module
                           (single-char #\m)
                           (value #t)))))
         (=u (option-ref parsed-opts 'upstream #f))
         (=d (option-ref parsed-opts 'downstream #f))
         (=i (option-ref parsed-opts 'internal #f))
         (=x (option-ref parsed-opts 'external #f))
         (files    (option-ref parsed-opts '() (list)))
         (report   ((make-frisker
                     `(default-module
                        . ,(option-ref parsed-opts 'default-module
                                       *default-module*)))
                    files))
         (modules  (report 'modules))
         (internal (report 'internal))
         (external (report 'external))
         (edges    (report 'edges)))
    (format #t "~A ~A, ~A ~A (~A ~A, ~A ~A), ~A ~A\n\n"
            (length files)    "files"
            (length modules)  "modules"
            (length internal) "internal"
            (length external) "external"
            (length edges)    "edges")
    ((cond ((and =u =d) dump-updown)
           (=u dump-up)
           (else dump-down))
     (cond ((and =i =x) modules)
           (=i internal)
           (else external)))))

(define main frisk)

;;; frisk ends here