chiark / gitweb /
src/class-finalize-impl.lisp: Fix bungled format string.
[sod] / doc / list-exports
1 #! /bin/sh
2 ":"; ### -*-lisp-*-
3 ":"; CL_SOURCE_REGISTRY=$(pwd)/build/src/:
4 ":"; ASDF_OUTPUT_TRANSLATIONS=$(pwd)/src:$(pwd)/build/src
5 ":"; export CL_SOURCE_REGISTRY ASDF_OUTPUT_TRANSLATIONS
6 ":"; exec cl-launch -X -l "sbcl cmucl" -s asdf -i "(sod-exports::main)" -- "$0" "$@" || exit 1
7
8 (cl:defpackage #:sod-exports
9   (:use #:common-lisp
10         #+cmu #:mop
11         #+sbcl #:sb-mop))
12
13 ;; Load the target system so that we can poke about in it.
14 (cl:in-package #:sod-exports)
15 (eval-when (:compile-toplevel :load-toplevel :execute)
16   (mapc #'asdf:load-system '(:sod :sod-frontend)))
17
18 ;;;--------------------------------------------------------------------------
19 ;;; Miscelleneous utilities.
20
21 (defun symbolicate (&rest things)
22   "Concatenate the THINGS and turn the result into a symbol."
23   (intern (apply #'concatenate 'string (mapcar #'string things))))
24
25 ;;;--------------------------------------------------------------------------
26 ;;; Determining the symbols exported by particular files.
27
28 (defun incomprehensible-form (head tail)
29   "Report an incomprehensible form (HEAD . TAIL)."
30   (format *error-output* ";; incomprehensible: ~S~%" (cons head tail)))
31
32 (defgeneric form-list-exports (head tail)
33   (:documentation
34    "Return a list of symbols exported by the form (HEAD . TAIL).
35
36    This is called from `form-exports' below.")
37   (:method (head tail)
38     "By default, a form exports nothing."
39     (declare (ignore head tail))
40     nil))
41
42 (defmethod form-list-exports ((head (eql 'cl:export)) tail)
43   "Return the symbols exported by a toplevel `export' form.
44
45    We can cope with (export 'SYMBOLS), where SYMBOLS is a symbol or a list."
46
47   (let ((symbols (car tail)))
48     (if (and (consp symbols)
49              (eq (car symbols) 'quote))
50         (let ((thing (cadr symbols)))
51           (if (atom thing) (list thing) thing))
52         (incomprehensible-form head tail))))
53
54 (defmethod form-list-exports ((head (eql 'sod:definst)) tail)
55   "Return the symbols exported by a `form-list-exports' form.
56
57    The syntax is:
58
59         (definst CODE (STREAMVAR [[:export FLAG]]) ARGS
60           FORM*)
61
62    If FLAG is non-nil, then we export `CODE-inst', `make-CODE-inst', and
63    `inst-ARG' for each argument ARG in the lambda-list ARGS.  There are some
64    quirks in this lambda-list:
65
66      * If we find a list (PUBLIC PRIVATE) where we expected an argument-name
67        symbol (but not a list), then the argument is PUBLIC.  (PRIVATE is
68        used to name a slot in the class created by the macro, presumably
69        because PUBLIC on its own is a public symbol in some package.)
70
71      * If we find a symbol %NAME, this means the same as the list (NAME
72        %NAME), only we recognize it even where the lambda-list syntax expects
73        a list."
74
75   (destructuring-bind (code (streamvar &key export) args &body body) tail
76     (declare (ignore streamvar body))
77
78     (and export
79          (list* (symbolicate code '-inst)
80                 (symbolicate 'make- code '-inst)
81
82                 (labels ((dig (tree path)
83                            ;; Dig down into a TREE, following the PATH.  Stop
84                            ;; when we find an atom, or reach the end of the
85                            ;; path.
86                            (if (or (atom tree) (null path)) tree
87                                (dig (nth (car path) tree) (cdr path))))
88                          (cook (arg)
89                            ;; Convert an ARG name which might start with `%'.
90                            (if (consp arg) (car arg)
91                                (let ((name (symbol-name arg)))
92                                  (if (char= (char name 0) #\%)
93                                      (intern (subseq name 1))
94                                      arg))))
95                          (instify (arg)
96                            ;; Convert ARG name into the `inst-ARG' accessor.
97                            (symbolicate 'inst- (cook arg))))
98
99                   ;; Work through the lambda-list, keeping track of where we
100                   ;; expect the argument symbols to be.
101                   (loop with state = :mandatory
102                         for arg in args
103                         if (and (symbolp arg)
104                                 (char= (char (symbol-name arg) 0) #\&))
105                           do (setf state arg)
106                         else if (member state '(:mandatory &rest))
107                           collect (instify arg)
108                         else if (member state '(&optional &aux))
109                           collect (instify (dig arg '(0)))
110                         else if (eq state '&key)
111                           collect (instify (dig arg '(0 1)))
112                         else
113                           do (error "Confused by ~S." arg)))))))
114
115 (defmethod form-list-exports ((head (eql 'sod::define-tagged-type)) tail)
116   "Return the symbols exported by a `define-tagged-type' form.
117
118    This is a scummy internal macro in `c-types-impl.lisp'.  The syntax is
119
120         (define-tagged-type KIND DESCRIPTION)
121
122    It exports `KIND' and `make-KIND'."
123
124   (destructuring-bind (kind what) tail
125     (declare (ignore what))
126     (list kind
127           (symbolicate 'c- kind '-type)
128           (symbolicate 'make- kind '-type))))
129
130 (defmethod form-list-exports ((head (eql 'sod:defctype)) tail)
131   "Return the symbols exported by a `defctype' form.
132
133    The syntax is:
134
135         (defctype {NAME | (NAME SYNONYM*)} VALUE [[:export FLAG]])
136
137    If FLAG is non-nil, this form exports `c-type-NAME', `NAME', and all of
138    the `SYNONYM's."
139
140   (destructuring-bind (names value &key export) tail
141     (declare (ignore value))
142     (let ((names (if (listp names) names (list names))))
143       (and export
144            (list* (symbolicate 'c-type- (car names)) names)))))
145
146 (defmethod form-list-exports ((head (eql 'sod:define-simple-c-type)) tail)
147   "Return the symbols exported by a `define-simple-c-type' form.
148
149    The syntax is:
150
151         (define-simple-c-type {NAME | (NAME SYNONYM*)} TYPE [[:export FLAG]])
152
153    If FLAG is non-nil, this form exports `c-type-NAME', `NAME', and all of
154    the `SYNONYM's."
155
156   (destructuring-bind (names type &key export) tail
157     (declare (ignore type))
158     (let ((names (if (listp names) names (list names))))
159       (and export
160            (list* (symbolicate 'c-type- (car names)) names)))))
161
162 (defmethod form-list-exports ((head (eql 'cl:macrolet)) tail)
163   "Return the symbols expored by a toplevel `macrolet' form.
164
165    Which are simply the symbols exported by its body."
166   (mapcan #'form-exports (cdr tail)))
167
168 (defmethod form-list-exports ((head (eql 'cl:eval-when)) tail)
169   "Return the symbols expored by a toplevel `eval-when' form.
170
171    Which are simply the symbols exported by its body."
172
173   ;; We don't bother checking when it'd actually be evaluated.
174   (mapcan #'form-exports (cdr tail)))
175
176 (defmethod form-list-exports ((head (eql 'cl:progn)) tail)
177   "Return the symbols expored by a toplevel `progn' form.
178
179    Which are simply the symbols exported by its body."
180   (mapcan #'form-exports tail))
181
182 (defgeneric form-exports (form)
183   (:documentation
184    "Return a list of symbols exported by a toplevel FORM.")
185   (:method (form) nil)
186   (:method ((form cons)) (form-list-exports (car form) (cdr form))))
187
188 (defgeneric list-exports (thing)
189   (:documentation
190    "Return a list of symbols exported by THING."))
191
192 (defmethod list-exports ((stream stream))
193   "Return a list of symbols exported by a STREAM.
194
195    By reading it and analysing the forms."
196
197   (loop with eof = '#:eof
198         for form = (read stream nil eof)
199         until (eq form eof)
200         when (consp form) nconc (form-exports form)))
201
202 (defmethod list-exports ((path pathname))
203   "Return a list of symbols exported by a directory PATHNAME.
204
205    Return an alist of pairs (PATH . SYMBOL) listing each SYMBOL exported by a
206    PATH of the form PATHNAME/*.lisp."
207
208   (mapcar (lambda (each)
209             (cons each (with-open-file (stream each) (list-exports stream))))
210           (directory (merge-pathnames path #p"*.lisp"))))
211
212 (defmethod list-exports ((path string))
213   "Return a list of symbols exported by a PATH string.
214
215    By converting it into a pathname."
216
217   (list-exports (pathname path)))
218
219 (defun list-exported-symbols (package)
220   "Return a sorted list of symbols exported by PACKAGE."
221   (sort (loop for s being the external-symbols of package collect s)
222         #'string< :key #'symbol-name))
223
224 (defun list-all-symbols (package)
225   "Return a sorted list of all symbols exported by or private to PACKAGE."
226   (let ((externs (make-hash-table)))
227     (dolist (sym (list-exported-symbols package))
228       (setf (gethash sym externs) t))
229     (sort (loop for s being the symbols of package
230                 when (or (not (exported-symbol-p s))
231                          (gethash s externs))
232                   collect s)
233           #'string< :key #'symbol-name)))
234
235 (defun find-symbol-homes (paths package)
236   "Determine the `home' file for the symbols exported by PACKAGE.
237
238    Return an alist of pairs (PATH . SYMBOL) listing each SYMBOL exported by a
239    PATH of the form PATHNAME/*.lisp where PATHNAME is a member of PATHS.  Do
240    this by finding all the files and parsing them (somewhat superficially),
241    and cross-checking the result against the actual symbols exported by the
242    PACKAGE."
243
244   ;; Building the alist is exactly what `list-exports' is for.  The rest of
245   ;; this function is the cross-checking.
246   (let* ((symbols (list-exported-symbols package))
247          (exports-alist (let ((*package* package))
248                           (mapcan #'list-exports paths)))
249          (homes (make-hash-table :test #'equal)))
250
251     ;; Work through the alist recording where we found each symbol.  Check
252     ;; that they're actually exported by poking at the package.
253     (dolist (assoc exports-alist)
254       (let ((home (car assoc)))
255         (dolist (symbol (cdr assoc))
256           (let ((name (symbol-name symbol)))
257             (unless (nth-value 1 (find-symbol name package))
258               (format *error-output* ";; unexported: ~S~%" symbol))
259             (setf (gethash name homes) home)))))
260
261     ;; Check that all of the symbols exported by the package are accounted
262     ;; for in our alist.
263     (dolist (symbol symbols)
264       (unless (gethash (symbol-name symbol) homes)
265         (format *error-output* ";; mysterious: ~S~%" symbol)))
266
267     ;; We're done.
268     exports-alist))
269
270 ;;;--------------------------------------------------------------------------
271 ;;; Determining the kinds of definitions attached to symbols.
272
273 (defun boring-setf-expansion-p (symbol)
274   "Return non-nil if SYMBOL has a trivial `setf' expansion.
275
276    i.e., (setf (SYMBOL ...) ...) works by (funcall #'(setf SYMBOL) ...)."
277
278   (multiple-value-bind (temps args stores store fetch)
279       (ignore-errors (get-setf-expansion (list symbol)))
280     (declare (ignore temps args stores fetch))
281     (and (consp store)
282          (eq (car store) 'funcall)
283          (consp (cdr store)) (consp (cadr store))
284          (eq (caadr store) 'function)
285          (let ((func (cadadr store)))
286            (and (consp func) (consp (cdr func))
287                 (eq (car func) 'setf))))))
288
289 (defun specialized-on-p (func arg what)
290   "Check whether FUNC has a method specialized for the symbol WHAT.
291
292    We assume FUNC is a (well-known) generic function.  ARG is a small integer
293    identifying one of FUNC's mandatory arguments.  Return non-nil if FUNC has
294    a method for which this ARG is `eql'-specialized on WHAT."
295
296   (some (lambda (method)
297           (let ((spec (nth arg (method-specializers method))))
298             (and (typep spec 'eql-specializer)
299                  (eql (eql-specializer-object spec) what))))
300         (generic-function-methods func)))
301
302 (defun categorize (symbol)
303   "Determine what things SYMBOL is defined to do.
304
305    Return a list of keywords:
306
307      * :constant -- SYMBOL's value cell is `boundp' and `constantp'
308      * :variable -- SYMBOL's value cell is `boundp' but not `constantp'
309      * :macro -- SYMBOL's function cell is `macro-function'
310      * :generic -- SYMBOL's function cell is a `generic-function'
311      * :function -- SYMBOL's function cell is a non-generic `function'
312      * :setf-generic -- (setf SYMBOL) is a `generic-function'
313      * :setf-function -- (setf SYMBOL) is a non-generic `function'
314      * :class -- SYMBOL is `find-class'
315      * :c-type -- `expand-c-type-spec' or `expand-c-type-form' has a method
316        specialized on SYMBOL
317      * :parser -- `expand-parser-spec' or `expand-parser-form' has a method
318        specialized on SYMBOL
319      * :opthandler -- SYMBOL has an `opthandler' property
320      * :optmacro -- SYMBOL has an `optmacro' property
321
322    categorizing the kinds of definitions that SYMBOL has."
323
324   (let ((things nil))
325     (when (boundp symbol)
326       (push (if (constantp symbol) :constant :variable) things))
327     (when (fboundp symbol)
328       (push (cond ((macro-function symbol) :macro)
329                   ((typep (fdefinition symbol) 'generic-function)
330                    :generic)
331                   (t :function))
332             things)
333       (etypecase (ignore-errors (fdefinition (list 'setf symbol)))
334         (generic-function (push :setf-generic things))
335         (function (push :setf-function things))
336         (null)))
337     (when (find-class symbol nil)
338       (push :class things))
339     (when (specialized-on-p #'sod:expand-c-type-spec 0 symbol)
340       (push :c-type-spec things))
341     (when (specialized-on-p #'sod:expand-c-type-form 0 symbol)
342       (push :c-type-form things))
343     (when (specialized-on-p #'sod-parser:expand-parser-spec 1 symbol)
344       (push :parser-spec things))
345     (when (specialized-on-p #'sod-parser:expand-parser-form 1 symbol)
346       (push :parser-form things))
347     (when (get symbol 'optparse::opthandler)
348       (push :opthandler things))
349     (when (get symbol 'optparse::optmacro)
350       (push :optmacro things))
351     (nreverse things)))
352
353 (defun categorize-symbols (paths package)
354   "Return a categorized list of the symbols exported by PACKAGE.
355
356    Return an alist of PAIRS (PATH . SYMBOLS), for each PATH in PATHS, where
357    SYMBOLS is itself an alist (SYMBOL . KEYWORDS) listing the kinds of
358    definitions that SYMBOL has (see `categorize')."
359   (mapcar (lambda (assoc)
360             (let ((home (car assoc))
361                   (symbols (delete-duplicates
362                             (sort (mapcan (lambda (sym)
363                                             (multiple-value-bind
364                                                 (symbol foundp)
365                                                 (find-symbol
366                                                  (symbol-name sym)
367                                                  package)
368                                               (and foundp (list symbol))))
369                                           (cdr assoc))
370                                   #'string< :key #'symbol-name))))
371               (cons home (mapcar (lambda (symbol)
372                                    (cons symbol (categorize symbol)))
373                                  symbols))))
374           (find-symbol-homes paths package)))
375
376 ;;;--------------------------------------------------------------------------
377 ;;; Reporting.
378
379 (defun best-package-name (package)
380   "Return a convenient name for PACKAGE."
381
382   ;; We pick the shortest one.  Strangely, there's no `find minimal thing
383   ;; according to this valuation' function in Common Lisp.
384   (loop with best = (package-name package)
385         with best-length = (length best)
386         for name in (package-nicknames package)
387         for name-length = (length name)
388         when (< name-length best-length)
389           do (setf best name
390                    best-length name-length)
391         finally (return best)))
392
393 (defvar charbuf-size 0)
394
395 (defun exported-symbol-p (symbol &optional (package (symbol-package symbol)))
396   "Return whether SYMBOL is exported by PACKAGE.
397
398    PACKAGE default's to the SYMBOL's home package, but may be different."
399   (and package
400        (multiple-value-bind (sym how)
401            (find-symbol (symbol-name symbol) package)
402          (and (eq sym symbol)
403               (eq how :external)))))
404
405 (defun downcase-or-escape (name)
406   "Return a presentable form for a symbol or package name.
407
408    If NAME consists only of uppercase letters and ordinary punctuation, then
409    return NAME in lowercase; otherwise wrap it in `|...|' and escape as
410    necessary."
411
412   (if (every (lambda (char)
413                (or (upper-case-p char)
414                    (digit-char-p char)
415                    (member char '(#\% #\+ #\- #\* #\/ #\= #\[ #\] #\?))))
416              name)
417       (string-downcase name)
418       (with-output-to-string (out)
419         (write-char #\| out)
420         (map nil (lambda (char)
421                    (when (or (char= char #\|)
422                              (char= char #\\))
423                      (write-char #\\ out))
424                    (write-char char out))
425              name)
426         (write-char #\| out))))
427
428 (defun pretty-symbol-name (symbol package)
429   "Return a presentable form for SYMBOL, relative to PACKAGE.
430
431    If SYMBOL is exported by PACKAGE then just write the SYMBOL's name
432    otherwise prefix the name with the SYMBOL's home package name, separated
433    joined with one or two colons.  Uninterned symbols and keywords are also
434    printed specially."
435
436   (let ((pkg (symbol-package symbol))
437         (exportp (exported-symbol-p symbol)))
438     (format nil "~:[~A:~:[:~;~]~;~2*~]~A"
439             (and exportp (eq pkg package))
440             (cond ((keywordp symbol) "")
441                   ((eq pkg nil) "#")
442                   (t (downcase-or-escape (best-package-name pkg))))
443             (or exportp (null pkg))
444             (downcase-or-escape (symbol-name symbol)))))
445
446 (deftype interesting-class ()
447   "The type of `interesting' classes, which might be user-defined."
448   '(or standard-class
449        structure-class
450        #.(class-name (class-of (find-class 'condition)))))
451
452 (defun analyse-classes (package)
453   "Print a report on the classes defined by PACKAGE."
454
455   ;; Canonify PACKAGE into a package object.
456   (setf package (find-package package))
457
458   (let ((classes (mapcan (lambda (symbol)
459                            (let ((class (find-class symbol nil)))
460                              (and class
461                                   (typep class 'interesting-class)
462                                   (list class))))
463                          (list-exported-symbols package)))
464         (subs (make-hash-table)))
465     ;; CLASSES is a list of the `interesting' classes defined by (i.e., whose
466     ;; names are exported by) PACKAGE.  SUBS maps a class to those of its
467     ;; direct subclasses which are relevant to our report.
468
469     ;; Populate the SUBS table.
470     (let ((done (make-hash-table)))
471       (labels ((walk-up (class)
472                  (unless (gethash class done)
473                    (dolist (super (class-direct-superclasses class))
474                      (push class (gethash super subs))
475                      (walk-up super))
476                    (setf (gethash class done) t))))
477         (dolist (class classes)
478           (walk-up class))))
479
480     (labels ((walk-down (this super depth)
481                ;; Recursively traverse the class graph from THIS, recalling
482                ;; that our parent is SUPER, and that we are DEPTH levels
483                ;; down.
484
485                (format t "~v,0T~A~@[ [~{~A~^ ~}]~]~%"
486                        (* 2 depth)
487                        (pretty-symbol-name (class-name this) package)
488                        (mapcar (lambda (class)
489                                  (pretty-symbol-name (class-name class)
490                                                      package))
491                                (remove super
492                                        (class-direct-superclasses this))))
493                (dolist (sub (sort (copy-list (gethash this subs))
494                                   #'string< :key #'class-name))
495                  (walk-down sub this (1+ depth)))))
496
497       ;; Print the relevant fragment of the class graph.
498       (walk-down (find-class t) nil 0))))
499
500 (defmacro deep-compare ((left right) &body body)
501   "Helper macro for traversing two similar objects in parallel.
502
503    Specifically it's good at defining complex structural ordering relations,
504    answering the question: is the LEFT value strictly less than the RIGHT
505    value.
506
507    Evaluate the BODY forms, maintaining a pair of `cursors', initially at the
508    LEFT and RIGHT values.
509
510    The following local macros are defined to do useful things.
511
512      * (focus EXPR . BODY) -- EXPR is an expression in terms of `it': advance
513        each of the cursors to the result of evaluating this expression, with
514        `it' bound to the current cursor value, and evaluate the BODY in the
515        resulting environment.
516
517      * (update EXPR) -- as `focus', but mutate the cursors rather than
518        binding them.
519
520      * (compare EXPR) -- EXPR is an expression in terms of the literal
521        symbols `left' and `right', which returns non-nil if it thinks `left'
522        is (strictly) less than `right' in some sense: evaluate this both ways
523        round, and return if LEFT is determined to be less than or greater
524        than RIGHT.
525
526      * (typesw (TYPE . BODY)*) -- process each clause in turn: if the left
527        cursor value has TYPE, but the right does not, then LEFT is less than
528        RIGHT; if the right cursor value has TYPE but the left does not, then
529        LEFT is greater than RIGHT; otherwise, evaluate BODY."
530
531   (let ((block (gensym "BLOCK-")) (func (gensym "FUNC-"))
532         (l (gensym "LEFT-")) (r (gensym "RIGHT-")))
533     `(macrolet ((focus (expr &body body)
534                   `(flet ((,',func (it) ,expr))
535                      (let ((,',l (,',func ,',l))
536                            (,',r (,',func ,',r)))
537                        ,@body)))
538                 (update (expr)
539                   `(flet ((,',func (it) ,expr))
540                      (psetf ,',l (,',func ,',l)
541                             ,',r (,',func ,',r))))
542                 (compare (expr)
543                   `(cond ((let ((left ,',l) (right ,',r)) ,expr)
544                           (return-from ,',block t))
545                          ((let ((right ,',l) (left ,',r)) ,expr)
546                           (return-from ,',block nil))))
547                 (typesw (&rest clauses)
548                   (labels ((iter (clauses)
549                              (if (null clauses)
550                                  'nil
551                                  (destructuring-bind (type &rest body)
552                                      (car clauses)
553                                    (if (eq type t)
554                                        `(progn ,@body)
555                                        `(if (typep ,',l ',type)
556                                             (if (typep ,',r ',type)
557                                                 (progn ,@body)
558                                                 (return-from ,',block t))
559                                             (if (typep ,',r ',type)
560                                                 (return-from ,',block nil)
561                                                 ,(iter (cdr clauses)))))))))
562                     (iter clauses))))
563        (let ((,l ,left) (,r ,right))
564          (block ,block
565            ,@body)))))
566
567 (defun order-specializers (la lb)
568   "Return whether specializers LA should be sorted before LB."
569
570   (deep-compare (la lb)
571     ;; Iterate over the two lists.  The cursors advance down the spine, and
572     ;; we focus on each car in turn.
573
574     (loop
575       (typesw (null (return nil)))
576       ;; If one list reaches the end, then it's lesser; if both, they're
577       ;; equal.
578
579       (focus (car it)
580         ;; Examine the two specializers at this position.
581
582         (typesw (eql-specializer
583                  (focus (eql-specializer-object it)
584                    ;; We found an `eql' specializer.  Compare the objects.
585
586                    (typesw (keyword
587                             ;; Keywords compare by name.
588
589                             (compare (string< left right)))
590
591                            (symbol
592                             ;; Symbols compare by package and name.
593
594                             (focus (package-name (symbol-package it))
595                               (compare (string< left right)))
596                             (compare (string< left right)))
597
598                            (t
599                             ;; Compare two other objects by comparing their
600                             ;; string representations.
601
602                             (focus (with-output-to-string (out)
603                                      (prin1 it out)
604                                      (write-char #\nul))
605                               (compare (string< left right)))))))
606
607                 (class
608                  ;; We found a class,   Compare the class names.
609                  (focus (class-name it)
610                    (focus (package-name (symbol-package it))
611                      (compare (string< left right)))
612                    (compare (string< left right))))
613
614                 (t
615                  ;; We found some other kind of specializer that we don't
616                  ;; understand.
617
618                  (error "unexpected things"))))
619
620       ;; No joy with that pair of specializers: try the next.
621       (update (cdr it)))))
622
623 (defun analyse-generic-functions (package)
624   "Print a report of the generic functions and methods defined by PACKAGE."
625
626   ;; Canonify package into a package object.
627   (setf package (find-package package))
628
629   (flet ((function-name-core (name)
630            ;; Return the underlying name for a function NAME.  Specifically,
631            ;; if NAME is (setf THING) then the core is THING; if NAME is a
632            ;; symbol then the core is simply NAME; otherwise we're confused.
633            ;; Return a second value to say whether we got the job done.
634
635            (typecase name
636              (symbol (values name t))
637              ((cons (eql setf) t) (values (cadr name) t))
638              (t (values nil nil)))))
639
640     (let ((methods (make-hash-table))
641           (functions (make-hash-table))
642           (externs (make-hash-table)))
643       ;; EXTERNS is a set of the symbols exported by PACKAGE.  FUNCTIONS and
644       ;; METHODS are sets of generic function names (not cores), and method
645       ;; objects, which we've decided are worth reporting.
646
647       ;; Collect the EXTERNS symbols.
648       (dolist (symbol (list-exported-symbols package))
649         (setf (gethash symbol externs) t))
650
651       ;; Collect the FUNCTIONS and METHODS.
652       (dolist (symbol (list-exported-symbols package))
653
654         ;; Mark the generic functions and `setf'-functions named by exported
655         ;; symbols as interesting, along with all of their methods.
656         (flet ((dofunc (func)
657                  (when (typep func 'generic-function)
658                    (setf (gethash func functions) t)
659                    (dolist (method (generic-function-methods func))
660                      (setf (gethash method methods) t)))))
661           (dofunc (and (fboundp symbol) (fdefinition symbol)))
662           (dofunc (ignore-errors (fdefinition (list 'setf symbol)))))
663
664         ;; For symbols whose home package is PACKAGE, and which name a class,
665         ;; also collect functions with methods specialized on that class, and
666         ;; (only) the specialized methods.
667         (when (eq (symbol-package symbol) package)
668           (let ((class (find-class symbol nil)))
669             (when class
670               (dolist (func (specializer-direct-generic-functions class))
671                 (multiple-value-bind (name knownp)
672                     (function-name-core (generic-function-name func))
673                   (when (and knownp
674                              (or (not (eq (symbol-package name) package))
675                                  (gethash name externs)))
676                     (setf (gethash func functions) t)
677                     (dolist (method (specializer-direct-methods class))
678                       (setf (gethash method methods) t)))))))))
679
680       ;; Print the report.
681       (let ((funclist nil))
682
683         ;; Gather the functions we've decided are interesting, and sort them.
684         (maphash (lambda (func value)
685                    (declare (ignore value))
686                    (push func funclist))
687                  functions)
688         (setf funclist (sort funclist
689                              (lambda (a b)
690                                ;; Sort by the core symbols, and order the
691                                ;; `setf' variant after the base version.
692                                (let ((core-a (function-name-core a))
693                                      (core-b (function-name-core b)))
694                                  (if (eq core-a core-b)
695                                      (and (atom a) (consp b))
696                                      (string< core-a core-b))))
697                              :key #'generic-function-name))
698
699         (dolist (function funclist)
700           ;; Print out each function in turn.
701
702           ;; Print the header line.
703           (let ((name (generic-function-name function)))
704             (etypecase name
705               (symbol
706                (format t "~A~%" (pretty-symbol-name name package)))
707               ((cons (eql setf) t)
708                (format t "(setf ~A)~%"
709                        (pretty-symbol-name (cadr name) package)))))
710
711           ;; Report on the function's (interesting) methods.
712           (dolist (method (sort (copy-list
713                                  (generic-function-methods function))
714                                 #'order-specializers
715                                 :key #'method-specializers))
716
717             (when (gethash method methods)
718               (format t "~2T~{~A~^ ~}~@[ [~{~(~S~)~^ ~}]~]~%"
719                       (mapcar
720                        (lambda (spec)
721                          (etypecase spec
722                            (class
723                             (let ((name (class-name spec)))
724                               (if (eq name t) "t"
725                                   (pretty-symbol-name name package))))
726                            (eql-specializer
727                             (let ((obj (eql-specializer-object spec)))
728                               (format nil "(eql ~A)"
729                                       (if (symbolp obj)
730                                           (pretty-symbol-name obj package)
731                                           obj))))))
732                        (method-specializers method))
733                       (method-qualifiers method)))))))))
734
735 (defun check-slot-names (package)
736   "Check that PACKAGE defines no slots whose names are exported symbols.
737
738    This acts to discourage the use of `slot-value' by external callers.
739    Return two values:
740
741      * an alist of entries (CLASS . SLOT-NAMES), listing for each offending
742        class, whose of its slot names which are either (a) external or (b)
743        from a foreign package; and
744
745      * the distilled list of bad SLOT-NAMES."
746
747   ;; Canonify PACKAGE into a package objects.
748   (setf package (find-package package))
749
750   (let* ((symbols (list-all-symbols package))
751
752          ;; Determine all of the named classes.
753          (classes (mapcan (lambda (symbol)
754                             (when (eq (symbol-package symbol) package)
755                               (let ((class (find-class symbol nil)))
756                                 (and class (list class)))))
757                           symbols))
758
759          ;; Build the main alist of offending classes and slots.
760          (offenders (mapcan
761                      (lambda (class)
762                        (let* ((slot-names
763                                (mapcar #'slot-definition-name
764                                        (class-direct-slots class)))
765                               (exported (remove-if
766                                          (lambda (sym)
767                                            (or (not (symbol-package sym))
768                                                (and (not (exported-symbol-p
769                                                           sym))
770                                                     (eq (symbol-package sym)
771                                                         package))))
772                                          slot-names)))
773                          (and exported
774                               (list (cons (class-name class)
775                                           exported)))))
776                             classes))
777
778          ;; Distill the bad slot names into a separate list.
779          (bad-words (remove-duplicates (mapcan (lambda (list)
780                                                  (copy-list (cdr list)))
781                                                offenders))))
782
783     ;; Done.
784     (values offenders bad-words)))
785
786 (defun report-symbols (paths package)
787   "Report on all of the symbols defined in PACKAGE by the files in PATHS."
788
789   ;; Canonify PACKAGE to a package object.
790   (setf package (find-package package))
791
792   ;; Print the breakdown of symbols by source file, with their purposes.
793   (format t "~A~%Package `~(~A~)'~2%"
794           (make-string 77 :initial-element #\-)
795           (package-name package))
796   (dolist (assoc (sort (categorize-symbols paths package) #'string<
797                        :key (lambda (assoc)
798                               (file-namestring (car assoc)))))
799     (when (cdr assoc)
800       (format t "~A~%" (file-namestring (car assoc)))
801       (dolist (def (cdr assoc))
802         (let ((sym (car def)))
803           (format t "  ~A~@[~48T~{~(~A~)~^ ~}~]~%"
804                   (pretty-symbol-name sym package)
805                   (cdr def))))
806       (terpri)))
807
808   ;; Report on leaked slot names, if any are exported or foreign.
809   (multiple-value-bind (alist names) (check-slot-names package)
810     (when names
811       (format t "Leaked slot names: ~{~A~^, ~}~%"
812               (mapcar (lambda (name) (pretty-symbol-name name package))
813                       names))
814       (dolist (assoc alist)
815         (format t "~2T~A: ~{~A~^, ~}~%"
816                 (pretty-symbol-name (car assoc) package)
817                 (mapcar (lambda (name) (pretty-symbol-name name package))
818                         (cdr assoc))))
819       (terpri)))
820
821   ;; Report on classes and generic functions.
822   (format t "Classes:~%")
823   (analyse-classes package)
824   (terpri)
825   (format t "Methods:~%")
826   (analyse-generic-functions package)
827   (terpri))
828
829 (export 'report-project-symbols)
830 (defun report-project-symbols ()
831   "Write to `*standard-output*' a report on all of the symbols in Sod."
832
833   (labels ((components (comp)
834              ;; Return the subcomponents of an ASDF component.
835
836              (asdf:component-children comp))
837
838            (files (comp)
839              ;; Return a list of files needed by an ASDF component.
840
841              (sort (remove-if-not (lambda (comp)
842                                     (typep comp 'asdf:cl-source-file))
843                                   (components comp))
844                    #'string< :key #'asdf:component-name))
845
846            (by-name (comp name)
847              ;; Find the subcomponent called NAME of an ASDF component.
848
849              (gethash name (asdf:component-children-by-name comp)))
850
851            (file-name (file)
852              ;; Return the pathname of an ASDF file component.
853
854              (slot-value file 'asdf/component:absolute-pathname)))
855
856   (let* ((sod (asdf:find-system "sod"))
857          (parser-files (files (by-name sod "parser")))
858          (utilities (by-name sod "utilities"))
859          (sod-frontend (asdf:find-system "sod-frontend"))
860          (optparse (by-name sod "optparse"))
861          (frontend (by-name sod-frontend "frontend"))
862          (sod-files (set-difference (files sod) (list optparse utilities))))
863
864     ;; Report on the various major pieces of the project.
865     (report-symbols (mapcar #'file-name sod-files) "SOD")
866     (report-symbols (mapcar #'file-name (list frontend)) "SOD-FRONTEND")
867     (report-symbols (mapcar #'file-name parser-files) "SOD-PARSER")
868     (report-symbols (mapcar #'file-name (list optparse)) "OPTPARSE")
869     (report-symbols (mapcar #'file-name (list utilities)) "SOD-UTILITIES"))))
870
871 ;;;--------------------------------------------------------------------------
872 ;;; Command-line use.
873
874 (defun main ()
875   "Write a report to `doc/SYMBOLS'."
876   (with-open-file (*standard-output* #p"doc/SYMBOLS"
877                    :direction :output
878                    :if-exists :supersede
879                    :if-does-not-exist :create)
880     (report-project-symbols)))
881
882 #+interactive (main)
883
884 ;;;----- That's all, folks --------------------------------------------------