doc/list-exports.lisp: Also scan `optparse.lisp'.
[sod] / doc / list-exports.lisp
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
12 (defmethod form-list-exports ((head (eql 'export)) tail)
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
20 (defmethod form-list-exports ((head (eql 'definst)) tail)
21 (destructuring-bind (code (streamvar &key export) args &body body) tail
22 (declare (ignore streamvar body))
23 (and export
24 (list* (symbolicate code '-inst)
25 (symbolicate 'make- code '-inst)
26 (mapcar (lambda (arg)
27 (symbolicate 'inst- arg))
28 args)))))
29
30 (defmethod form-list-exports ((head (eql 'define-tagged-type)) tail)
31 (destructuring-bind (kind what) tail
32 (declare (ignore what))
33 (list kind
34 (symbolicate 'c- kind '-type)
35 (symbolicate 'make- kind '-type))))
36
37 (defmethod form-list-exports ((head (eql 'macrolet)) tail)
38 (mapcan #'form-exports (cdr tail)))
39
40 (defmethod form-list-exports ((head (eql 'eval-when)) tail)
41 (mapcan #'form-exports (cdr tail)))
42
43 (defmethod form-list-exports ((head (eql 'progn)) tail)
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))
72 (exports-alist (mapcan #'list-exports paths))
73 (homes (make-hash-table :test #'equal)))
74 (dolist (assoc exports-alist)
75 (let ((home (car assoc)))
76 (dolist (symbol (cdr assoc))
77 (let ((name (symbol-name symbol)))
78 (unless (nth-value 1 (find-symbol name package))
79 (format *error-output* ";; unexported: ~S~%" symbol))
80 (setf (gethash name homes) home)))))
81 (dolist (symbol symbols)
82 (unless (gethash (symbol-name symbol) homes)
83 (format *error-output* ";; mysterious: ~S~%" symbol)))
84 exports-alist))
85
86 (defun boring-setf-expansion-p (symbol)
87 (multiple-value-bind (temps args stores store fetch)
88 (ignore-errors (get-setf-expansion (list symbol)))
89 (declare (ignore temps args stores fetch))
90 (and (consp store)
91 (eq (car store) 'funcall)
92 (consp (cdr store)) (consp (cadr store))
93 (eq (caadr store) 'function)
94 (let ((func (cadadr store)))
95 (and (consp func) (consp (cdr func))
96 (eq (car func) 'setf))))))
97
98 (defun specialized-on-p (func arg what)
99 (some (lambda (method)
100 (let ((spec (nth arg (sb-mop:method-specializers method))))
101 (and (typep spec 'sb-mop:eql-specializer)
102 (eql (sb-mop:eql-specializer-object spec) what))))
103 (sb-mop:generic-function-methods func)))
104
105 (defun categorize (symbol)
106 (let ((things nil))
107 (when (boundp symbol)
108 (push (if (constantp symbol) :constant :variable) things))
109 (when (fboundp symbol)
110 (push (cond ((macro-function symbol) :macro)
111 ((typep (fdefinition symbol) 'generic-function)
112 :generic)
113 (t :function))
114 things)
115 (when (or ;;(not (boring-setf-expansion-p symbol))
116 (ignore-errors (fdefinition (list 'setf symbol))))
117 (push :setf things)))
118 (when (find-class symbol nil)
119 (push :class things))
120 (when (or (specialized-on-p #'sod:expand-c-type-spec 0 symbol)
121 (specialized-on-p #'sod:expand-c-type-form 0 symbol))
122 (push :c-type things))
123 (when (or (specialized-on-p #'sod-parser:expand-parser-spec 1 symbol)
124 (specialized-on-p #'sod-parser:expand-parser-form 1 symbol))
125 (push :parser things))
126 (when (get symbol 'optparse::opthandler)
127 (push :opthandler things))
128 (when (get symbol 'optparse::optmacro)
129 (push :optmacro things))
130 (nreverse things)))
131
132 (defun categorize-symbols (paths package)
133 (mapcar (lambda (assoc)
134 (let ((home (car assoc))
135 (symbols (delete-duplicates
136 (sort (mapcan (lambda (sym)
137 (multiple-value-bind
138 (symbol foundp)
139 (find-symbol
140 (symbol-name sym)
141 package)
142 (and foundp (list symbol))))
143 (cdr assoc))
144 #'string< :key #'symbol-name))))
145 (cons home (mapcar (lambda (symbol)
146 (cons symbol (categorize symbol)))
147 symbols))))
148
149 (find-symbol-homes paths package)))
150
151 (defun best-package-name (package)
152 (car (sort (cons (package-name package)
153 (copy-list (package-nicknames package)))
154 #'< :key #'length)))
155
156 (defvar charbuf-size 0)
157
158 (defun pretty-symbol-name (symbol package)
159 (let* ((pkg (symbol-package symbol))
160 (exportp (member symbol (list-exported-symbols pkg))))
161 (format nil "~(~:[~A:~:[:~;~]~;~2*~]~A~)"
162 (and exportp (eq pkg package))
163 (if (keywordp symbol) "" (best-package-name pkg))
164 exportp (symbol-name symbol))))
165
166 (defun analyse-classes (package)
167 (setf package (find-package package))
168 (let ((classes (mapcan (lambda (symbol)
169 (let ((class (find-class symbol nil)))
170 (and class
171 (typep class '(or standard-class
172 structure-class))
173 (list class))))
174 (list-exported-symbols package)))
175 (subs (make-hash-table)))
176 (let ((done (make-hash-table)))
177 (labels ((walk-up (class)
178 (unless (gethash class done)
179 (dolist (super (sb-mop:class-direct-superclasses class))
180 (push class (gethash super subs))
181 (walk-up super))
182 (setf (gethash class done) t))))
183 (dolist (class classes)
184 (walk-up class))))
185 (labels ((walk-down (this super depth)
186 (format t "~v,0T~A~@[ [~{~A~^ ~}]~]~%"
187 (* 2 depth)
188 (pretty-symbol-name (class-name this) package)
189 (mapcar (lambda (class)
190 (pretty-symbol-name (class-name class)
191 package))
192 (remove super
193 (sb-mop:class-direct-superclasses this))))
194 (dolist (sub (sort (copy-list (gethash this subs))
195 #'string< :key #'class-name))
196 (walk-down sub this (1+ depth)))))
197 (walk-down (find-class t) nil 0))))
198
199 (defun analyse-generic-functions (package)
200 (setf package (find-package package))
201 (flet ((function-name-core (name)
202 (etypecase name
203 (symbol name)
204 ((cons (eql setf) t) (cadr name)))))
205 (let ((methods (make-hash-table))
206 (functions (make-hash-table))
207 (externs (make-hash-table)))
208 (dolist (symbol (list-exported-symbols package))
209 (setf (gethash symbol externs) t))
210 (dolist (symbol (list-exported-symbols package))
211 (flet ((dofunc (func)
212 (when (typep func 'generic-function)
213 (setf (gethash func functions) t)
214 (dolist (method (sb-mop:generic-function-methods func))
215 (setf (gethash method methods) t)))))
216 (dofunc (and (fboundp symbol) (fdefinition symbol)))
217 (dofunc (ignore-errors (fdefinition (list 'setf symbol)))))
218 (when (eq (symbol-package symbol) package)
219 (let ((class (find-class symbol nil)))
220 (when class
221 (dolist
222 (func (sb-mop:specializer-direct-generic-functions class))
223 (let ((name (function-name-core
224 (sb-mop:generic-function-name func))))
225 (when (or (not (eq (symbol-package name) package))
226 (gethash name externs))
227 (setf (gethash func functions) t)
228 (dolist (method (sb-mop:specializer-direct-methods class))
229 (setf (gethash method methods) t)))))))))
230 (let ((funclist nil))
231 (maphash (lambda (func value)
232 (declare (ignore value))
233 (push func funclist))
234 functions)
235 (setf funclist (sort funclist
236 (lambda (a b)
237 (let ((core-a (function-name-core a))
238 (core-b (function-name-core b)))
239 (if (eq core-a core-b)
240 (and (atom a) (consp b))
241 (string< core-a core-b))))
242 :key #'sb-mop:generic-function-name))
243 (dolist (function funclist)
244 (let ((name (sb-mop:generic-function-name function)))
245 (etypecase name
246 (symbol
247 (format t "~A~%" (pretty-symbol-name name package)))
248 ((cons (eql setf) t)
249 (format t "(setf ~A)~%"
250 (pretty-symbol-name (cadr name) package)))))
251 (dolist (method (sb-mop:generic-function-methods function))
252 (when (gethash method methods)
253 (format t "~2T~{~A~^ ~}~%"
254 (mapcar
255 (lambda (spec)
256 (etypecase spec
257 (class
258 (let ((name (class-name spec)))
259 (if (eq name t) "t"
260 (pretty-symbol-name name package))))
261 (sb-mop:eql-specializer
262 (let ((obj (sb-mop:eql-specializer-object spec)))
263 (format nil "(eql ~A)"
264 (if (symbolp obj)
265 (pretty-symbol-name obj package)
266 obj))))))
267 (sb-mop:method-specializers method))))))))))
268
269 (defun check-slot-names (package)
270 (setf package (find-package package))
271 (let* ((symbols (list-exported-symbols package))
272 (classes (mapcan (lambda (symbol)
273 (when (eq (symbol-package symbol) package)
274 (let ((class (find-class symbol nil)))
275 (and class (list class)))))
276 symbols))
277 (offenders (mapcan
278 (lambda (class)
279 (let* ((slot-names
280 (mapcar #'sb-mop:slot-definition-name
281 (sb-mop:class-direct-slots class)))
282 (exported (remove-if-not
283 (lambda (sym)
284 (or (and (symbol-package sym)
285 (not (eq (symbol-package
286 sym)
287 package)))
288 (member sym symbols)))
289 slot-names)))
290 (and exported
291 (list (cons (class-name class)
292 exported)))))
293 classes))
294 (bad-words (remove-duplicates (mapcan (lambda (list)
295 (copy-list (cdr list)))
296 offenders))))
297 (values offenders bad-words)))
298
299 (defun report-symbols (paths package)
300 (setf package (find-package package))
301 (format t "~A~%Package `~(~A~)'~2%"
302 (make-string 77 :initial-element #\-)
303 (package-name package))
304 (dolist (assoc (categorize-symbols paths package))
305 (when (cdr assoc)
306 (format t "~A~%" (file-namestring (car assoc)))
307 (dolist (def (cdr assoc))
308 (let ((sym (car def)))
309 (format t " ~A~@[~48T~{~(~A~)~^ ~}~]~%"
310 (pretty-symbol-name sym package)
311 (cdr def))))
312 (terpri)))
313 (multiple-value-bind (alist names) (check-slot-names package)
314 (when names
315 (format t "Leaked slot names: ~{~A~^, ~}~%"
316 (mapcar (lambda (name) (pretty-symbol-name name package))
317 names))
318 (dolist (assoc alist)
319 (format t "~2T~A: ~{~A~^, ~}~%"
320 (pretty-symbol-name (car assoc) package)
321 (mapcar (lambda (name) (pretty-symbol-name name package))
322 (cdr assoc))))
323 (terpri)))
324 (format t "Classes:~%")
325 (analyse-classes package)
326 (terpri)
327 (format t "Methods:~%")
328 (analyse-generic-functions package)
329 (terpri))
330
331 (defun report-project-symbols ()
332 (labels ((components (comp)
333 (slot-value comp 'asdf::components))
334 (files (comp)
335 (sort (remove-if-not (lambda (comp)
336 (typep comp 'asdf:cl-source-file))
337 (components comp))
338 #'string< :key #'asdf:component-name))
339 (by-name (comp name)
340 (find name (components comp)
341 :test #'string= :key #'asdf:component-name))
342 (file-name (file)
343 (slot-value file 'asdf::absolute-pathname)))
344 (let* ((sod (asdf:find-system "sod"))
345 (parser-files (files (by-name sod "parser")))
346 (utilities (by-name sod "utilities"))
347 (sod-frontend (asdf:find-system "sod-frontend"))
348 (optparse (by-name sod-frontend "optparse"))
349 (sod-files (set-difference (files sod) (list utilities))))
350 (report-symbols (mapcar #'file-name sod-files) "SOD")
351 (report-symbols (mapcar #'file-name parser-files) "SOD-PARSER")
352 (report-symbols (mapcar #'file-name (list optparse)) "OPTPARSE")
353 (report-symbols (mapcar #'file-name (list utilities)) "SOD-UTILITIES"))))