doc/clang.tex: Squish the `\desclabel' mess for simple C types.
[sod] / doc / list-exports
CommitLineData
fae90f24 1#! /bin/sh
d610d8be 2":"; ### -*-lisp-*-
df28942d
MW
3":"; CL_SOURCE_REGISTRY=$(pwd)/build/src/:
4":"; ASDF_OUTPUT_TRANSLATIONS=$(pwd)/src:$(pwd)/build/src
5":"; export CL_SOURCE_REGISTRY ASDF_OUTPUT_TRANSLATIONS
fae90f24
MW
6":"; exec cl-launch -X -l "sbcl cmucl" -s asdf -i "(sod-exports::main)" -- "$0" "$@" || exit 1
7
cf268da2 8(cl:defpackage #:sod-exports
91d9ba3c
MW
9 (:use #:common-lisp
10 #+cmu #:mop
11 #+sbcl #:sb-mop))
cf268da2 12
6163fb10 13;; Load the target system so that we can poke about in it.
cf268da2 14(cl:in-package #:sod-exports)
91d9ba3c
MW
15(eval-when (:compile-toplevel :load-toplevel :execute)
16 (mapc #'asdf:load-system '(:sod :sod-frontend)))
cf268da2 17
6163fb10
MW
18;;;--------------------------------------------------------------------------
19;;; Miscelleneous utilities.
20
097d5a3e 21(defun symbolicate (&rest things)
6163fb10 22 "Concatenate the THINGS and turn the result into a symbol."
097d5a3e
MW
23 (intern (apply #'concatenate 'string (mapcar #'string things))))
24
6163fb10
MW
25;;;--------------------------------------------------------------------------
26;;; Determining the symbols exported by particular files.
27
097d5a3e 28(defun incomprehensible-form (head tail)
6163fb10 29 "Report an incomprehensible form (HEAD . TAIL)."
097d5a3e
MW
30 (format *error-output* ";; incomprehensible: ~S~%" (cons head tail)))
31
32(defgeneric form-list-exports (head tail)
6163fb10
MW
33 (:documentation
34 "Return a list of symbols exported by the form (HEAD . TAIL).
35
36 This is called from `form-exports' below.")
097d5a3e 37 (:method (head tail)
6163fb10 38 "By default, a form exports nothing."
097d5a3e
MW
39 (declare (ignore head tail))
40 nil))
41
ea28678b 42(defmethod form-list-exports ((head (eql 'cl:export)) tail)
6163fb10
MW
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
097d5a3e
MW
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
ea28678b 54(defmethod form-list-exports ((head (eql 'sod:definst)) tail)
6163fb10
MW
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
097d5a3e 75 (destructuring-bind (code (streamvar &key export) args &body body) tail
34c51b1c 76 (declare (ignore streamvar body))
6163fb10 77
097d5a3e 78 (and export
34c51b1c
MW
79 (list* (symbolicate code '-inst)
80 (symbolicate 'make- code '-inst)
6163fb10 81
8db2259b 82 (labels ((dig (tree path)
6163fb10
MW
83 ;; Dig down into a TREE, following the PATH. Stop
84 ;; when we find an atom, or reach the end of the
85 ;; path.
8db2259b
MW
86 (if (or (atom tree) (null path)) tree
87 (dig (nth (car path) tree) (cdr path))))
88 (cook (arg)
6163fb10 89 ;; Convert an ARG name which might start with `%'.
8db2259b
MW
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)
6163fb10 96 ;; Convert ARG name into the `inst-ARG' accessor.
8db2259b 97 (symbolicate 'inst- (cook arg))))
6163fb10
MW
98
99 ;; Work through the lambda-list, keeping track of where we
100 ;; expect the argument symbols to be.
8db2259b
MW
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)))))))
097d5a3e 114
ea28678b 115(defmethod form-list-exports ((head (eql 'sod::define-tagged-type)) tail)
6163fb10
MW
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
097d5a3e
MW
124 (destructuring-bind (kind what) tail
125 (declare (ignore what))
126 (list kind
127 (symbolicate 'c- kind '-type)
128 (symbolicate 'make- kind '-type))))
129
e43d3532 130(defmethod form-list-exports ((head (eql 'sod:defctype)) tail)
6163fb10
MW
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
e43d3532
MW
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)
6163fb10
MW
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
e43d3532
MW
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
ea28678b 162(defmethod form-list-exports ((head (eql 'cl:macrolet)) tail)
6163fb10
MW
163 "Return the symbols expored by a toplevel `macrolet' form.
164
165 Which are simply the symbols exported by its body."
097d5a3e
MW
166 (mapcan #'form-exports (cdr tail)))
167
ea28678b 168(defmethod form-list-exports ((head (eql 'cl:eval-when)) tail)
6163fb10
MW
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.
fdc3e506
MW
174 (mapcan #'form-exports (cdr tail)))
175
ea28678b 176(defmethod form-list-exports ((head (eql 'cl:progn)) tail)
6163fb10
MW
177 "Return the symbols expored by a toplevel `progn' form.
178
179 Which are simply the symbols exported by its body."
097d5a3e
MW
180 (mapcan #'form-exports tail))
181
182(defgeneric form-exports (form)
6163fb10
MW
183 (:documentation
184 "Return a list of symbols exported by a toplevel FORM.")
097d5a3e
MW
185 (:method (form) nil)
186 (:method ((form cons)) (form-list-exports (car form) (cdr form))))
187
6163fb10
MW
188(defgeneric list-exports (thing)
189 (:documentation
190 "Return a list of symbols exported by THING."))
097d5a3e
MW
191
192(defmethod list-exports ((stream stream))
6163fb10
MW
193 "Return a list of symbols exported by a STREAM.
194
195 By reading it and analysing the forms."
196
097d5a3e
MW
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))
6163fb10
MW
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
097d5a3e
MW
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))
6163fb10
MW
213 "Return a list of symbols exported by a PATH string.
214
215 By converting it into a pathname."
216
097d5a3e
MW
217 (list-exports (pathname path)))
218
219(defun list-exported-symbols (package)
6163fb10 220 "Return a sorted list of symbols exported by PACKAGE."
097d5a3e
MW
221 (sort (loop for s being the external-symbols of package collect s)
222 #'string< :key #'symbol-name))
223
c2937ad0 224(defun list-all-symbols (package)
6163fb10 225 "Return a sorted list of all symbols exported by or private to PACKAGE."
c2937ad0
MW
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
097d5a3e 235(defun find-symbol-homes (paths package)
6163fb10
MW
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.
097d5a3e 246 (let* ((symbols (list-exported-symbols package))
ea28678b
MW
247 (exports-alist (let ((*package* package))
248 (mapcan #'list-exports paths)))
097d5a3e 249 (homes (make-hash-table :test #'equal)))
6163fb10
MW
250
251 ;; Work through the alist recording where we found each symbol. Check
252 ;; that they're actually exported by poking at the package.
097d5a3e
MW
253 (dolist (assoc exports-alist)
254 (let ((home (car assoc)))
255 (dolist (symbol (cdr assoc))
256 (let ((name (symbol-name symbol)))
de8f0794 257 (unless (nth-value 1 (find-symbol name package))
097d5a3e
MW
258 (format *error-output* ";; unexported: ~S~%" symbol))
259 (setf (gethash name homes) home)))))
6163fb10
MW
260
261 ;; Check that all of the symbols exported by the package are accounted
262 ;; for in our alist.
097d5a3e
MW
263 (dolist (symbol symbols)
264 (unless (gethash (symbol-name symbol) homes)
265 (format *error-output* ";; mysterious: ~S~%" symbol)))
6163fb10
MW
266
267 ;; We're done.
097d5a3e
MW
268 exports-alist))
269
6163fb10
MW
270;;;--------------------------------------------------------------------------
271;;; Determining the kinds of definitions attached to symbols.
272
097d5a3e 273(defun boring-setf-expansion-p (symbol)
6163fb10
MW
274 "Return non-nil if SYMBOL has a trivial `setf' expansion.
275
276 i.e., (setf (SYMBOL ...) ...) works by (funcall #'(setf SYMBOL) ...)."
277
097d5a3e
MW
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)
6163fb10
MW
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
097d5a3e 296 (some (lambda (method)
91d9ba3c
MW
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)))
097d5a3e
MW
301
302(defun categorize (symbol)
6163fb10
MW
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
097d5a3e
MW
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)
e9f884f9
MW
333 (etypecase (ignore-errors (fdefinition (list 'setf symbol)))
334 (generic-function (push :setf-generic things))
335 (function (push :setf-function things))
336 (null)))
097d5a3e
MW
337 (when (find-class symbol nil)
338 (push :class things))
6213be58
MW
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))
61982981
MW
347 (when (get symbol 'optparse::opthandler)
348 (push :opthandler things))
349 (when (get symbol 'optparse::optmacro)
350 (push :optmacro things))
097d5a3e
MW
351 (nreverse things)))
352
353(defun categorize-symbols (paths package)
6163fb10
MW
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')."
097d5a3e
MW
359 (mapcar (lambda (assoc)
360 (let ((home (car assoc))
8922d110
MW
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))))
097d5a3e
MW
371 (cons home (mapcar (lambda (symbol)
372 (cons symbol (categorize symbol)))
373 symbols))))
649798ab 374 (find-symbol-homes paths package)))
097d5a3e 375
6163fb10
MW
376;;;--------------------------------------------------------------------------
377;;; Reporting.
378
097d5a3e 379(defun best-package-name (package)
6163fb10 380 "Return a convenient name for PACKAGE."
d185dba5
MW
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)))
097d5a3e
MW
392
393(defvar charbuf-size 0)
394
b9d603a0 395(defun exported-symbol-p (symbol &optional (package (symbol-package symbol)))
6163fb10
MW
396 "Return whether SYMBOL is exported by PACKAGE.
397
398 PACKAGE default's to the SYMBOL's home package, but may be different."
b9d603a0
MW
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
765231c0 405(defun downcase-or-escape (name)
6163fb10
MW
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
765231c0
MW
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
097d5a3e 428(defun pretty-symbol-name (symbol package)
6163fb10
MW
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
b9d603a0
MW
436 (let ((pkg (symbol-package symbol))
437 (exportp (exported-symbol-p symbol)))
765231c0 438 (format nil "~:[~A:~:[:~;~]~;~2*~]~A"
ed006915 439 (and exportp (eq pkg package))
b9d603a0
MW
440 (cond ((keywordp symbol) "")
441 ((eq pkg nil) "#")
765231c0
MW
442 (t (downcase-or-escape (best-package-name pkg))))
443 (or exportp (null pkg))
444 (downcase-or-escape (symbol-name symbol)))))
097d5a3e 445
b8eeeb37 446(deftype interesting-class ()
6163fb10 447 "The type of `interesting' classes, which might be user-defined."
b8eeeb37
MW
448 '(or standard-class
449 structure-class
450 #.(class-name (class-of (find-class 'condition)))))
451
097d5a3e 452(defun analyse-classes (package)
6163fb10
MW
453 "Print a report on the classes defined by PACKAGE."
454
455 ;; Canonify PACKAGE into a package object.
097d5a3e 456 (setf package (find-package package))
6163fb10 457
097d5a3e
MW
458 (let ((classes (mapcan (lambda (symbol)
459 (let ((class (find-class symbol nil)))
460 (and class
b8eeeb37 461 (typep class 'interesting-class)
097d5a3e
MW
462 (list class))))
463 (list-exported-symbols package)))
464 (subs (make-hash-table)))
6163fb10
MW
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.
097d5a3e
MW
470 (let ((done (make-hash-table)))
471 (labels ((walk-up (class)
472 (unless (gethash class done)
91d9ba3c 473 (dolist (super (class-direct-superclasses class))
097d5a3e
MW
474 (push class (gethash super subs))
475 (walk-up super))
476 (setf (gethash class done) t))))
477 (dolist (class classes)
478 (walk-up class))))
6163fb10 479
097d5a3e 480 (labels ((walk-down (this super depth)
6163fb10
MW
481 ;; Recursively traverse the class graph from THIS, recalling
482 ;; that our parent is SUPER, and that we are DEPTH levels
483 ;; down.
484
097d5a3e
MW
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
91d9ba3c 492 (class-direct-superclasses this))))
7a35400d
MW
493 (dolist (sub (sort (copy-list (gethash this subs))
494 #'string< :key #'class-name))
097d5a3e 495 (walk-down sub this (1+ depth)))))
6163fb10
MW
496
497 ;; Print the relevant fragment of the class graph.
097d5a3e
MW
498 (walk-down (find-class t) nil 0))))
499
b9d603a0 500(defmacro deep-compare ((left right) &body body)
6163fb10
MW
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
b9d603a0
MW
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)
6163fb10
MW
568 "Return whether specializers LA should be sorted before LB."
569
b9d603a0 570 (deep-compare (la lb)
6163fb10
MW
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)))))
b9d603a0 622
a535feed 623(defun analyse-generic-functions (package)
6163fb10
MW
624 "Print a report of the generic functions and methods defined by PACKAGE."
625
626 ;; Canonify package into a package object.
a535feed 627 (setf package (find-package package))
6163fb10 628
a535feed 629 (flet ((function-name-core (name)
6163fb10
MW
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
e36ab294
MW
635 (typecase name
636 (symbol (values name t))
637 ((cons (eql setf) t) (values (cadr name) t))
638 (t (values nil nil)))))
6163fb10 639
a535feed
MW
640 (let ((methods (make-hash-table))
641 (functions (make-hash-table))
642 (externs (make-hash-table)))
6163fb10
MW
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.
a535feed
MW
648 (dolist (symbol (list-exported-symbols package))
649 (setf (gethash symbol externs) t))
6163fb10
MW
650
651 ;; Collect the FUNCTIONS and METHODS.
a535feed 652 (dolist (symbol (list-exported-symbols package))
6163fb10
MW
653
654 ;; Mark the generic functions and `setf'-functions named by exported
655 ;; symbols as interesting, along with all of their methods.
a535feed
MW
656 (flet ((dofunc (func)
657 (when (typep func 'generic-function)
658 (setf (gethash func functions) t)
91d9ba3c 659 (dolist (method (generic-function-methods func))
a535feed
MW
660 (setf (gethash method methods) t)))))
661 (dofunc (and (fboundp symbol) (fdefinition symbol)))
662 (dofunc (ignore-errors (fdefinition (list 'setf symbol)))))
6163fb10
MW
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.
a535feed
MW
667 (when (eq (symbol-package symbol) package)
668 (let ((class (find-class symbol nil)))
669 (when class
6163fb10 670 (dolist (func (specializer-direct-generic-functions class))
e36ab294
MW
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)))
a535feed 676 (setf (gethash func functions) t)
91d9ba3c 677 (dolist (method (specializer-direct-methods class))
a535feed 678 (setf (gethash method methods) t)))))))))
6163fb10
MW
679
680 ;; Print the report.
a535feed 681 (let ((funclist nil))
6163fb10
MW
682
683 ;; Gather the functions we've decided are interesting, and sort them.
a535feed
MW
684 (maphash (lambda (func value)
685 (declare (ignore value))
686 (push func funclist))
687 functions)
688 (setf funclist (sort funclist
689 (lambda (a b)
6163fb10
MW
690 ;; Sort by the core symbols, and order the
691 ;; `setf' variant after the base version.
a535feed
MW
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))))
91d9ba3c 697 :key #'generic-function-name))
6163fb10 698
a535feed 699 (dolist (function funclist)
6163fb10
MW
700 ;; Print out each function in turn.
701
702 ;; Print the header line.
91d9ba3c 703 (let ((name (generic-function-name function)))
a535feed
MW
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)))))
6163fb10
MW
710
711 ;; Report on the function's (interesting) methods.
b9d603a0 712 (dolist (method (sort (copy-list
91d9ba3c 713 (generic-function-methods function))
b9d603a0 714 #'order-specializers
91d9ba3c 715 :key #'method-specializers))
6163fb10 716
a535feed 717 (when (gethash method methods)
4b0283c7 718 (format t "~2T~{~A~^ ~}~@[ [~{~(~S~)~^ ~}]~]~%"
a535feed
MW
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))))
91d9ba3c
MW
726 (eql-specializer
727 (let ((obj (eql-specializer-object spec)))
a535feed
MW
728 (format nil "(eql ~A)"
729 (if (symbolp obj)
730 (pretty-symbol-name obj package)
731 obj))))))
4b0283c7
MW
732 (method-specializers method))
733 (method-qualifiers method)))))))))
a535feed 734
4b8e5c03 735(defun check-slot-names (package)
6163fb10
MW
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.
4b8e5c03 748 (setf package (find-package package))
6163fb10 749
c2937ad0 750 (let* ((symbols (list-all-symbols package))
6163fb10
MW
751
752 ;; Determine all of the named classes.
4b8e5c03
MW
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))
6163fb10
MW
758
759 ;; Build the main alist of offending classes and slots.
4b8e5c03
MW
760 (offenders (mapcan
761 (lambda (class)
762 (let* ((slot-names
91d9ba3c
MW
763 (mapcar #'slot-definition-name
764 (class-direct-slots class)))
b9d603a0 765 (exported (remove-if
4b8e5c03 766 (lambda (sym)
211bfc14
MW
767 (or (not (symbol-package sym))
768 (and (not (exported-symbol-p
769 sym))
770 (eq (symbol-package sym)
771 package))))
4b8e5c03
MW
772 slot-names)))
773 (and exported
774 (list (cons (class-name class)
775 exported)))))
776 classes))
6163fb10
MW
777
778 ;; Distill the bad slot names into a separate list.
4b8e5c03
MW
779 (bad-words (remove-duplicates (mapcan (lambda (list)
780 (copy-list (cdr list)))
781 offenders))))
6163fb10
MW
782
783 ;; Done.
4b8e5c03
MW
784 (values offenders bad-words)))
785
097d5a3e 786(defun report-symbols (paths package)
6163fb10
MW
787 "Report on all of the symbols defined in PACKAGE by the files in PATHS."
788
789 ;; Canonify PACKAGE to a package object.
097d5a3e 790 (setf package (find-package package))
6163fb10
MW
791
792 ;; Print the breakdown of symbols by source file, with their purposes.
097d5a3e
MW
793 (format t "~A~%Package `~(~A~)'~2%"
794 (make-string 77 :initial-element #\-)
795 (package-name package))
b9d603a0
MW
796 (dolist (assoc (sort (categorize-symbols paths package) #'string<
797 :key (lambda (assoc)
798 (file-namestring (car assoc)))))
097d5a3e
MW
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)))
6163fb10
MW
807
808 ;; Report on leaked slot names, if any are exported or foreign.
4b8e5c03
MW
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)))
6163fb10
MW
820
821 ;; Report on classes and generic functions.
388caffa 822 (format t "Classes:~%")
097d5a3e 823 (analyse-classes package)
a535feed
MW
824 (terpri)
825 (format t "Methods:~%")
826 (analyse-generic-functions package)
097d5a3e
MW
827 (terpri))
828
cf268da2 829(export 'report-project-symbols)
097d5a3e 830(defun report-project-symbols ()
6163fb10
MW
831 "Write to `*standard-output*' a report on all of the symbols in Sod."
832
097d5a3e 833 (labels ((components (comp)
6163fb10
MW
834 ;; Return the subcomponents of an ASDF component.
835
e390f747 836 (asdf:component-children comp))
6163fb10 837
097d5a3e 838 (files (comp)
6163fb10
MW
839 ;; Return a list of files needed by an ASDF component.
840
7a35400d 841 (sort (remove-if-not (lambda (comp)
b9d603a0 842 (typep comp 'asdf:cl-source-file))
7a35400d
MW
843 (components comp))
844 #'string< :key #'asdf:component-name))
6163fb10 845
097d5a3e 846 (by-name (comp name)
6163fb10
MW
847 ;; Find the subcomponent called NAME of an ASDF component.
848
e390f747 849 (gethash name (asdf:component-children-by-name comp)))
6163fb10 850
097d5a3e 851 (file-name (file)
6163fb10
MW
852 ;; Return the pathname of an ASDF file component.
853
e390f747 854 (slot-value file 'asdf/component:absolute-pathname)))
6163fb10 855
097d5a3e
MW
856 (let* ((sod (asdf:find-system "sod"))
857 (parser-files (files (by-name sod "parser")))
858 (utilities (by-name sod "utilities"))
61982981 859 (sod-frontend (asdf:find-system "sod-frontend"))
4d757a73 860 (optparse (by-name sod "optparse"))
6ac5b807 861 (frontend (by-name sod-frontend "frontend"))
4d757a73 862 (sod-files (set-difference (files sod) (list optparse utilities))))
6163fb10
MW
863
864 ;; Report on the various major pieces of the project.
097d5a3e 865 (report-symbols (mapcar #'file-name sod-files) "SOD")
6ac5b807 866 (report-symbols (mapcar #'file-name (list frontend)) "SOD-FRONTEND")
097d5a3e 867 (report-symbols (mapcar #'file-name parser-files) "SOD-PARSER")
61982981 868 (report-symbols (mapcar #'file-name (list optparse)) "OPTPARSE")
097d5a3e 869 (report-symbols (mapcar #'file-name (list utilities)) "SOD-UTILITIES"))))
1c1a9bf1 870
6163fb10
MW
871;;;--------------------------------------------------------------------------
872;;; Command-line use.
873
fae90f24 874(defun main ()
6163fb10 875 "Write a report to `doc/SYMBOLS'."
fae90f24
MW
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)
6163fb10
MW
883
884;;;----- That's all, folks --------------------------------------------------