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