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