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