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