doc/list-exports.lisp: Ignore generic functions with strange names.
[sod] / doc / list-exports.lisp
CommitLineData
cf268da2 1(cl:defpackage #:sod-exports
91d9ba3c
MW
2 (:use #:common-lisp
3 #+cmu #:mop
4 #+sbcl #:sb-mop))
cf268da2
MW
5
6(cl:in-package #:sod-exports)
91d9ba3c
MW
7(eval-when (:compile-toplevel :load-toplevel :execute)
8 (mapc #'asdf:load-system '(:sod :sod-frontend)))
cf268da2 9
097d5a3e
MW
10(defun symbolicate (&rest things)
11 (intern (apply #'concatenate 'string (mapcar #'string things))))
12
13(defun incomprehensible-form (head tail)
14 (format *error-output* ";; incomprehensible: ~S~%" (cons head tail)))
15
16(defgeneric form-list-exports (head tail)
17 (:method (head tail)
18 (declare (ignore head tail))
19 nil))
20
ea28678b 21(defmethod form-list-exports ((head (eql 'cl:export)) tail)
097d5a3e
MW
22 (let ((symbols (car tail)))
23 (if (and (consp symbols)
24 (eq (car symbols) 'quote))
25 (let ((thing (cadr symbols)))
26 (if (atom thing) (list thing) thing))
27 (incomprehensible-form head tail))))
28
ea28678b 29(defmethod form-list-exports ((head (eql 'sod:definst)) tail)
097d5a3e 30 (destructuring-bind (code (streamvar &key export) args &body body) tail
34c51b1c 31 (declare (ignore streamvar body))
097d5a3e 32 (and export
34c51b1c
MW
33 (list* (symbolicate code '-inst)
34 (symbolicate 'make- code '-inst)
35 (mapcar (lambda (arg)
36 (symbolicate 'inst- arg))
37 args)))))
097d5a3e 38
ea28678b 39(defmethod form-list-exports ((head (eql 'sod::define-tagged-type)) tail)
097d5a3e
MW
40 (destructuring-bind (kind what) tail
41 (declare (ignore what))
42 (list kind
43 (symbolicate 'c- kind '-type)
44 (symbolicate 'make- kind '-type))))
45
ea28678b 46(defmethod form-list-exports ((head (eql 'cl:macrolet)) tail)
097d5a3e
MW
47 (mapcan #'form-exports (cdr tail)))
48
ea28678b 49(defmethod form-list-exports ((head (eql 'cl:eval-when)) tail)
fdc3e506
MW
50 (mapcan #'form-exports (cdr tail)))
51
ea28678b 52(defmethod form-list-exports ((head (eql 'cl:progn)) tail)
097d5a3e
MW
53 (mapcan #'form-exports tail))
54
55(defgeneric form-exports (form)
56 (:method (form) nil)
57 (:method ((form cons)) (form-list-exports (car form) (cdr form))))
58
59(defgeneric list-exports (thing))
60
61(defmethod list-exports ((stream stream))
62 (loop with eof = '#:eof
63 for form = (read stream nil eof)
64 until (eq form eof)
65 when (consp form) nconc (form-exports form)))
66
67(defmethod list-exports ((path pathname))
68 (mapcar (lambda (each)
69 (cons each (with-open-file (stream each) (list-exports stream))))
70 (directory (merge-pathnames path #p"*.lisp"))))
71
72(defmethod list-exports ((path string))
73 (list-exports (pathname path)))
74
75(defun list-exported-symbols (package)
76 (sort (loop for s being the external-symbols of package collect s)
77 #'string< :key #'symbol-name))
78
79(defun find-symbol-homes (paths package)
80 (let* ((symbols (list-exported-symbols package))
ea28678b
MW
81 (exports-alist (let ((*package* package))
82 (mapcan #'list-exports paths)))
097d5a3e
MW
83 (homes (make-hash-table :test #'equal)))
84 (dolist (assoc exports-alist)
85 (let ((home (car assoc)))
86 (dolist (symbol (cdr assoc))
87 (let ((name (symbol-name symbol)))
de8f0794 88 (unless (nth-value 1 (find-symbol name package))
097d5a3e
MW
89 (format *error-output* ";; unexported: ~S~%" symbol))
90 (setf (gethash name homes) home)))))
91 (dolist (symbol symbols)
92 (unless (gethash (symbol-name symbol) homes)
93 (format *error-output* ";; mysterious: ~S~%" symbol)))
94 exports-alist))
95
96(defun boring-setf-expansion-p (symbol)
97 (multiple-value-bind (temps args stores store fetch)
98 (ignore-errors (get-setf-expansion (list symbol)))
99 (declare (ignore temps args stores fetch))
100 (and (consp store)
101 (eq (car store) 'funcall)
102 (consp (cdr store)) (consp (cadr store))
103 (eq (caadr store) 'function)
104 (let ((func (cadadr store)))
105 (and (consp func) (consp (cdr func))
106 (eq (car func) 'setf))))))
107
108(defun specialized-on-p (func arg what)
109 (some (lambda (method)
91d9ba3c
MW
110 (let ((spec (nth arg (method-specializers method))))
111 (and (typep spec 'eql-specializer)
112 (eql (eql-specializer-object spec) what))))
113 (generic-function-methods func)))
097d5a3e
MW
114
115(defun categorize (symbol)
116 (let ((things nil))
117 (when (boundp symbol)
118 (push (if (constantp symbol) :constant :variable) things))
119 (when (fboundp symbol)
120 (push (cond ((macro-function symbol) :macro)
121 ((typep (fdefinition symbol) 'generic-function)
122 :generic)
123 (t :function))
124 things)
125 (when (or ;;(not (boring-setf-expansion-p symbol))
126 (ignore-errors (fdefinition (list 'setf symbol))))
127 (push :setf things)))
128 (when (find-class symbol nil)
129 (push :class things))
130 (when (or (specialized-on-p #'sod:expand-c-type-spec 0 symbol)
131 (specialized-on-p #'sod:expand-c-type-form 0 symbol))
132 (push :c-type things))
133 (when (or (specialized-on-p #'sod-parser:expand-parser-spec 1 symbol)
134 (specialized-on-p #'sod-parser:expand-parser-form 1 symbol))
135 (push :parser things))
61982981
MW
136 (when (get symbol 'optparse::opthandler)
137 (push :opthandler things))
138 (when (get symbol 'optparse::optmacro)
139 (push :optmacro things))
097d5a3e
MW
140 (nreverse things)))
141
142(defun categorize-symbols (paths package)
143 (mapcar (lambda (assoc)
144 (let ((home (car assoc))
8922d110
MW
145 (symbols (delete-duplicates
146 (sort (mapcan (lambda (sym)
147 (multiple-value-bind
148 (symbol foundp)
149 (find-symbol
150 (symbol-name sym)
151 package)
152 (and foundp (list symbol))))
153 (cdr assoc))
154 #'string< :key #'symbol-name))))
097d5a3e
MW
155 (cons home (mapcar (lambda (symbol)
156 (cons symbol (categorize symbol)))
157 symbols))))
158
649798ab 159 (find-symbol-homes paths package)))
097d5a3e
MW
160
161(defun best-package-name (package)
162 (car (sort (cons (package-name package)
163 (copy-list (package-nicknames package)))
164 #'< :key #'length)))
165
166(defvar charbuf-size 0)
167
b9d603a0
MW
168(defun exported-symbol-p (symbol &optional (package (symbol-package symbol)))
169 (and package
170 (multiple-value-bind (sym how)
171 (find-symbol (symbol-name symbol) package)
172 (and (eq sym symbol)
173 (eq how :external)))))
174
097d5a3e 175(defun pretty-symbol-name (symbol package)
b9d603a0
MW
176 (let ((pkg (symbol-package symbol))
177 (exportp (exported-symbol-p symbol)))
097d5a3e 178 (format nil "~(~:[~A:~:[:~;~]~;~2*~]~A~)"
ed006915 179 (and exportp (eq pkg package))
b9d603a0
MW
180 (cond ((keywordp symbol) "")
181 ((eq pkg nil) "#")
182 (t (best-package-name pkg)))
183 (or exportp (null pkg)) (symbol-name symbol))))
097d5a3e
MW
184
185(defun analyse-classes (package)
186 (setf package (find-package package))
187 (let ((classes (mapcan (lambda (symbol)
188 (let ((class (find-class symbol nil)))
189 (and class
190 (typep class '(or standard-class
191 structure-class))
192 (list class))))
193 (list-exported-symbols package)))
194 (subs (make-hash-table)))
195 (let ((done (make-hash-table)))
196 (labels ((walk-up (class)
197 (unless (gethash class done)
91d9ba3c 198 (dolist (super (class-direct-superclasses class))
097d5a3e
MW
199 (push class (gethash super subs))
200 (walk-up super))
201 (setf (gethash class done) t))))
202 (dolist (class classes)
203 (walk-up class))))
204 (labels ((walk-down (this super depth)
205 (format t "~v,0T~A~@[ [~{~A~^ ~}]~]~%"
206 (* 2 depth)
207 (pretty-symbol-name (class-name this) package)
208 (mapcar (lambda (class)
209 (pretty-symbol-name (class-name class)
210 package))
211 (remove super
91d9ba3c 212 (class-direct-superclasses this))))
7a35400d
MW
213 (dolist (sub (sort (copy-list (gethash this subs))
214 #'string< :key #'class-name))
097d5a3e
MW
215 (walk-down sub this (1+ depth)))))
216 (walk-down (find-class t) nil 0))))
217
b9d603a0
MW
218(defmacro deep-compare ((left right) &body body)
219 (let ((block (gensym "BLOCK-")) (func (gensym "FUNC-"))
220 (l (gensym "LEFT-")) (r (gensym "RIGHT-")))
221 `(macrolet ((focus (expr &body body)
222 `(flet ((,',func (it) ,expr))
223 (let ((,',l (,',func ,',l))
224 (,',r (,',func ,',r)))
225 ,@body)))
226 (update (expr)
227 `(flet ((,',func (it) ,expr))
228 (psetf ,',l (,',func ,',l)
229 ,',r (,',func ,',r))))
230 (compare (expr)
231 `(cond ((let ((left ,',l) (right ,',r)) ,expr)
232 (return-from ,',block t))
233 ((let ((right ,',l) (left ,',r)) ,expr)
234 (return-from ,',block nil))))
235 (typesw (&rest clauses)
236 (labels ((iter (clauses)
237 (if (null clauses)
238 'nil
239 (destructuring-bind (type &rest body)
240 (car clauses)
241 (if (eq type t)
242 `(progn ,@body)
243 `(if (typep ,',l ',type)
244 (if (typep ,',r ',type)
245 (progn ,@body)
246 (return-from ,',block t))
247 (if (typep ,',r ',type)
248 (return-from ,',block nil)
249 ,(iter (cdr clauses)))))))))
250 (iter clauses))))
251 (let ((,l ,left) (,r ,right))
252 (block ,block
253 ,@body)))))
254
255(defun order-specializers (la lb)
256 (deep-compare (la lb)
257 (loop (typesw (null (return nil)))
258 (focus (car it)
91d9ba3c
MW
259 (typesw (eql-specializer
260 (focus (eql-specializer-object it)
b9d603a0
MW
261 (typesw (keyword
262 (compare (string< left right)))
263 (symbol
264 (focus (package-name (symbol-package it))
265 (compare (string< left right)))
266 (compare (string< left right)))
267 (t
268 (focus (with-output-to-string (out)
269 (prin1 it out)
270 (write-char #\nul))
271 (compare (string< left right)))))))
272 (class
273 (focus (class-name it)
274 (focus (package-name (symbol-package it))
275 (compare (string< left right)))
276 (compare (string< left right))))
277 (t
278 (error "unexpected things"))))
279 (update (cdr it)))))
280
a535feed
MW
281(defun analyse-generic-functions (package)
282 (setf package (find-package package))
283 (flet ((function-name-core (name)
e36ab294
MW
284 (typecase name
285 (symbol (values name t))
286 ((cons (eql setf) t) (values (cadr name) t))
287 (t (values nil nil)))))
a535feed
MW
288 (let ((methods (make-hash-table))
289 (functions (make-hash-table))
290 (externs (make-hash-table)))
291 (dolist (symbol (list-exported-symbols package))
292 (setf (gethash symbol externs) t))
293 (dolist (symbol (list-exported-symbols package))
294 (flet ((dofunc (func)
295 (when (typep func 'generic-function)
296 (setf (gethash func functions) t)
91d9ba3c 297 (dolist (method (generic-function-methods func))
a535feed
MW
298 (setf (gethash method methods) t)))))
299 (dofunc (and (fboundp symbol) (fdefinition symbol)))
300 (dofunc (ignore-errors (fdefinition (list 'setf symbol)))))
301 (when (eq (symbol-package symbol) package)
302 (let ((class (find-class symbol nil)))
303 (when class
304 (dolist
91d9ba3c 305 (func (specializer-direct-generic-functions class))
e36ab294
MW
306 (multiple-value-bind (name knownp)
307 (function-name-core (generic-function-name func))
308 (when (and knownp
309 (or (not (eq (symbol-package name) package))
310 (gethash name externs)))
a535feed 311 (setf (gethash func functions) t)
91d9ba3c 312 (dolist (method (specializer-direct-methods class))
a535feed
MW
313 (setf (gethash method methods) t)))))))))
314 (let ((funclist nil))
315 (maphash (lambda (func value)
316 (declare (ignore value))
317 (push func funclist))
318 functions)
319 (setf funclist (sort funclist
320 (lambda (a b)
321 (let ((core-a (function-name-core a))
322 (core-b (function-name-core b)))
323 (if (eq core-a core-b)
324 (and (atom a) (consp b))
325 (string< core-a core-b))))
91d9ba3c 326 :key #'generic-function-name))
a535feed 327 (dolist (function funclist)
91d9ba3c 328 (let ((name (generic-function-name function)))
a535feed
MW
329 (etypecase name
330 (symbol
331 (format t "~A~%" (pretty-symbol-name name package)))
332 ((cons (eql setf) t)
333 (format t "(setf ~A)~%"
334 (pretty-symbol-name (cadr name) package)))))
b9d603a0 335 (dolist (method (sort (copy-list
91d9ba3c 336 (generic-function-methods function))
b9d603a0 337 #'order-specializers
91d9ba3c 338 :key #'method-specializers))
a535feed
MW
339 (when (gethash method methods)
340 (format t "~2T~{~A~^ ~}~%"
341 (mapcar
342 (lambda (spec)
343 (etypecase spec
344 (class
345 (let ((name (class-name spec)))
346 (if (eq name t) "t"
347 (pretty-symbol-name name package))))
91d9ba3c
MW
348 (eql-specializer
349 (let ((obj (eql-specializer-object spec)))
a535feed
MW
350 (format nil "(eql ~A)"
351 (if (symbolp obj)
352 (pretty-symbol-name obj package)
353 obj))))))
91d9ba3c 354 (method-specializers method))))))))))
a535feed 355
4b8e5c03
MW
356(defun check-slot-names (package)
357 (setf package (find-package package))
358 (let* ((symbols (list-exported-symbols package))
359 (classes (mapcan (lambda (symbol)
360 (when (eq (symbol-package symbol) package)
361 (let ((class (find-class symbol nil)))
362 (and class (list class)))))
363 symbols))
364 (offenders (mapcan
365 (lambda (class)
366 (let* ((slot-names
91d9ba3c
MW
367 (mapcar #'slot-definition-name
368 (class-direct-slots class)))
b9d603a0 369 (exported (remove-if
4b8e5c03 370 (lambda (sym)
b9d603a0
MW
371 (and (not (exported-symbol-p sym))
372 (eq (symbol-package sym)
373 package)))
4b8e5c03
MW
374 slot-names)))
375 (and exported
376 (list (cons (class-name class)
377 exported)))))
378 classes))
379 (bad-words (remove-duplicates (mapcan (lambda (list)
380 (copy-list (cdr list)))
381 offenders))))
382 (values offenders bad-words)))
383
097d5a3e
MW
384(defun report-symbols (paths package)
385 (setf package (find-package package))
386 (format t "~A~%Package `~(~A~)'~2%"
387 (make-string 77 :initial-element #\-)
388 (package-name package))
b9d603a0
MW
389 (dolist (assoc (sort (categorize-symbols paths package) #'string<
390 :key (lambda (assoc)
391 (file-namestring (car assoc)))))
097d5a3e
MW
392 (when (cdr assoc)
393 (format t "~A~%" (file-namestring (car assoc)))
394 (dolist (def (cdr assoc))
395 (let ((sym (car def)))
396 (format t " ~A~@[~48T~{~(~A~)~^ ~}~]~%"
397 (pretty-symbol-name sym package)
398 (cdr def))))
399 (terpri)))
4b8e5c03
MW
400 (multiple-value-bind (alist names) (check-slot-names package)
401 (when names
402 (format t "Leaked slot names: ~{~A~^, ~}~%"
403 (mapcar (lambda (name) (pretty-symbol-name name package))
404 names))
405 (dolist (assoc alist)
406 (format t "~2T~A: ~{~A~^, ~}~%"
407 (pretty-symbol-name (car assoc) package)
408 (mapcar (lambda (name) (pretty-symbol-name name package))
409 (cdr assoc))))
410 (terpri)))
388caffa 411 (format t "Classes:~%")
097d5a3e 412 (analyse-classes package)
a535feed
MW
413 (terpri)
414 (format t "Methods:~%")
415 (analyse-generic-functions package)
097d5a3e
MW
416 (terpri))
417
cf268da2 418(export 'report-project-symbols)
097d5a3e
MW
419(defun report-project-symbols ()
420 (labels ((components (comp)
421 (slot-value comp 'asdf::components))
422 (files (comp)
7a35400d 423 (sort (remove-if-not (lambda (comp)
b9d603a0 424 (typep comp 'asdf:cl-source-file))
7a35400d
MW
425 (components comp))
426 #'string< :key #'asdf:component-name))
097d5a3e
MW
427 (by-name (comp name)
428 (find name (components comp)
429 :test #'string= :key #'asdf:component-name))
430 (file-name (file)
431 (slot-value file 'asdf::absolute-pathname)))
432 (let* ((sod (asdf:find-system "sod"))
433 (parser-files (files (by-name sod "parser")))
434 (utilities (by-name sod "utilities"))
61982981
MW
435 (sod-frontend (asdf:find-system "sod-frontend"))
436 (optparse (by-name sod-frontend "optparse"))
6ac5b807 437 (frontend (by-name sod-frontend "frontend"))
61982981 438 (sod-files (set-difference (files sod) (list utilities))))
097d5a3e 439 (report-symbols (mapcar #'file-name sod-files) "SOD")
6ac5b807 440 (report-symbols (mapcar #'file-name (list frontend)) "SOD-FRONTEND")
097d5a3e 441 (report-symbols (mapcar #'file-name parser-files) "SOD-PARSER")
61982981 442 (report-symbols (mapcar #'file-name (list optparse)) "OPTPARSE")
097d5a3e 443 (report-symbols (mapcar #'file-name (list utilities)) "SOD-UTILITIES"))))
1c1a9bf1
MW
444
445#+interactive
446(with-open-file (*standard-output* #p"doc/SYMBOLS" :direction :output
447 :if-exists :supersede :if-does-not-exist :create)
448 (report-project-symbols))