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