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