3 ":"; CL_SOURCE_REGISTRY=$(pwd)/build/src/:; export CL_SOURCE_REGISTRY
4 ":"; exec cl-launch -X -l "sbcl cmucl" -s asdf -i "(sod-exports::main)" -- "$0" "$@" || exit 1
6 (cl:defpackage #:sod-exports
11 (cl:in-package #:sod-exports)
12 (eval-when (:compile-toplevel :load-toplevel :execute)
13 (mapc #'asdf:load-system '(:sod :sod-frontend)))
15 (defun symbolicate (&rest things)
16 (intern (apply #'concatenate 'string (mapcar #'string things))))
18 (defun incomprehensible-form (head tail)
19 (format *error-output* ";; incomprehensible: ~S~%" (cons head tail)))
21 (defgeneric form-list-exports (head tail)
23 (declare (ignore head tail))
26 (defmethod form-list-exports ((head (eql 'cl:export)) tail)
27 (let ((symbols (car tail)))
28 (if (and (consp symbols)
29 (eq (car symbols) 'quote))
30 (let ((thing (cadr symbols)))
31 (if (atom thing) (list thing) thing))
32 (incomprehensible-form head tail))))
34 (defmethod form-list-exports ((head (eql 'sod:definst)) tail)
35 (destructuring-bind (code (streamvar &key export) args &body body) tail
36 (declare (ignore streamvar body))
38 (list* (symbolicate code '-inst)
39 (symbolicate 'make- code '-inst)
40 (labels ((dig (tree path)
41 (if (or (atom tree) (null path)) tree
42 (dig (nth (car path) tree) (cdr path))))
44 (if (consp arg) (car arg)
45 (let ((name (symbol-name arg)))
46 (if (char= (char name 0) #\%)
47 (intern (subseq name 1))
50 (symbolicate 'inst- (cook arg))))
51 (loop with state = :mandatory
54 (char= (char (symbol-name arg) 0) #\&))
56 else if (member state '(:mandatory &rest))
58 else if (member state '(&optional &aux))
59 collect (instify (dig arg '(0)))
60 else if (eq state '&key)
61 collect (instify (dig arg '(0 1)))
63 do (error "Confused by ~S." arg)))))))
65 (defmethod form-list-exports ((head (eql 'sod::define-tagged-type)) tail)
66 (destructuring-bind (kind what) tail
67 (declare (ignore what))
69 (symbolicate 'c- kind '-type)
70 (symbolicate 'make- kind '-type))))
72 (defmethod form-list-exports ((head (eql 'sod:defctype)) tail)
73 (destructuring-bind (names value &key export) tail
74 (declare (ignore value))
75 (let ((names (if (listp names) names (list names))))
77 (list* (symbolicate 'c-type- (car names)) names)))))
79 (defmethod form-list-exports ((head (eql 'sod:define-simple-c-type)) tail)
80 (destructuring-bind (names type &key export) tail
81 (declare (ignore type))
82 (let ((names (if (listp names) names (list names))))
84 (list* (symbolicate 'c-type- (car names)) names)))))
86 (defmethod form-list-exports ((head (eql 'cl:macrolet)) tail)
87 (mapcan #'form-exports (cdr tail)))
89 (defmethod form-list-exports ((head (eql 'cl:eval-when)) tail)
90 (mapcan #'form-exports (cdr tail)))
92 (defmethod form-list-exports ((head (eql 'cl:progn)) tail)
93 (mapcan #'form-exports tail))
95 (defgeneric form-exports (form)
97 (:method ((form cons)) (form-list-exports (car form) (cdr form))))
99 (defgeneric list-exports (thing))
101 (defmethod list-exports ((stream stream))
102 (loop with eof = '#:eof
103 for form = (read stream nil eof)
105 when (consp form) nconc (form-exports form)))
107 (defmethod list-exports ((path pathname))
108 (mapcar (lambda (each)
109 (cons each (with-open-file (stream each) (list-exports stream))))
110 (directory (merge-pathnames path #p"*.lisp"))))
112 (defmethod list-exports ((path string))
113 (list-exports (pathname path)))
115 (defun list-exported-symbols (package)
116 (sort (loop for s being the external-symbols of package collect s)
117 #'string< :key #'symbol-name))
119 (defun list-all-symbols (package)
120 (let ((externs (make-hash-table)))
121 (dolist (sym (list-exported-symbols package))
122 (setf (gethash sym externs) t))
123 (sort (loop for s being the symbols of package
124 when (or (not (exported-symbol-p s))
127 #'string< :key #'symbol-name)))
129 (defun find-symbol-homes (paths package)
130 (let* ((symbols (list-exported-symbols package))
131 (exports-alist (let ((*package* package))
132 (mapcan #'list-exports paths)))
133 (homes (make-hash-table :test #'equal)))
134 (dolist (assoc exports-alist)
135 (let ((home (car assoc)))
136 (dolist (symbol (cdr assoc))
137 (let ((name (symbol-name symbol)))
138 (unless (nth-value 1 (find-symbol name package))
139 (format *error-output* ";; unexported: ~S~%" symbol))
140 (setf (gethash name homes) home)))))
141 (dolist (symbol symbols)
142 (unless (gethash (symbol-name symbol) homes)
143 (format *error-output* ";; mysterious: ~S~%" symbol)))
146 (defun boring-setf-expansion-p (symbol)
147 (multiple-value-bind (temps args stores store fetch)
148 (ignore-errors (get-setf-expansion (list symbol)))
149 (declare (ignore temps args stores fetch))
151 (eq (car store) 'funcall)
152 (consp (cdr store)) (consp (cadr store))
153 (eq (caadr store) 'function)
154 (let ((func (cadadr store)))
155 (and (consp func) (consp (cdr func))
156 (eq (car func) 'setf))))))
158 (defun specialized-on-p (func arg what)
159 (some (lambda (method)
160 (let ((spec (nth arg (method-specializers method))))
161 (and (typep spec 'eql-specializer)
162 (eql (eql-specializer-object spec) what))))
163 (generic-function-methods func)))
165 (defun categorize (symbol)
167 (when (boundp symbol)
168 (push (if (constantp symbol) :constant :variable) things))
169 (when (fboundp symbol)
170 (push (cond ((macro-function symbol) :macro)
171 ((typep (fdefinition symbol) 'generic-function)
175 (etypecase (ignore-errors (fdefinition (list 'setf symbol)))
176 (generic-function (push :setf-generic things))
177 (function (push :setf-function things))
179 (when (find-class symbol nil)
180 (push :class things))
181 (when (or (specialized-on-p #'sod:expand-c-type-spec 0 symbol)
182 (specialized-on-p #'sod:expand-c-type-form 0 symbol))
183 (push :c-type things))
184 (when (or (specialized-on-p #'sod-parser:expand-parser-spec 1 symbol)
185 (specialized-on-p #'sod-parser:expand-parser-form 1 symbol))
186 (push :parser things))
187 (when (get symbol 'optparse::opthandler)
188 (push :opthandler things))
189 (when (get symbol 'optparse::optmacro)
190 (push :optmacro things))
193 (defun categorize-symbols (paths package)
194 (mapcar (lambda (assoc)
195 (let ((home (car assoc))
196 (symbols (delete-duplicates
197 (sort (mapcan (lambda (sym)
203 (and foundp (list symbol))))
205 #'string< :key #'symbol-name))))
206 (cons home (mapcar (lambda (symbol)
207 (cons symbol (categorize symbol)))
210 (find-symbol-homes paths package)))
212 (defun best-package-name (package)
214 ;; We pick the shortest one. Strangely, there's no `find minimal thing
215 ;; according to this valuation' function in Common Lisp.
216 (loop with best = (package-name package)
217 with best-length = (length best)
218 for name in (package-nicknames package)
219 for name-length = (length name)
220 when (< name-length best-length)
222 best-length name-length)
223 finally (return best)))
225 (defvar charbuf-size 0)
227 (defun exported-symbol-p (symbol &optional (package (symbol-package symbol)))
229 (multiple-value-bind (sym how)
230 (find-symbol (symbol-name symbol) package)
232 (eq how :external)))))
234 (defun downcase-or-escape (name)
235 (if (every (lambda (char)
236 (or (upper-case-p char)
238 (member char '(#\% #\+ #\- #\* #\/ #\= #\[ #\] #\?))))
240 (string-downcase name)
241 (with-output-to-string (out)
243 (map nil (lambda (char)
244 (when (or (char= char #\|)
246 (write-char #\\ out))
247 (write-char char out))
249 (write-char #\| out))))
251 (defun pretty-symbol-name (symbol package)
252 (let ((pkg (symbol-package symbol))
253 (exportp (exported-symbol-p symbol)))
254 (format nil "~:[~A:~:[:~;~]~;~2*~]~A"
255 (and exportp (eq pkg package))
256 (cond ((keywordp symbol) "")
258 (t (downcase-or-escape (best-package-name pkg))))
259 (or exportp (null pkg))
260 (downcase-or-escape (symbol-name symbol)))))
262 (deftype interesting-class ()
265 #.(class-name (class-of (find-class 'condition)))))
267 (defun analyse-classes (package)
268 (setf package (find-package package))
269 (let ((classes (mapcan (lambda (symbol)
270 (let ((class (find-class symbol nil)))
272 (typep class 'interesting-class)
274 (list-exported-symbols package)))
275 (subs (make-hash-table)))
276 (let ((done (make-hash-table)))
277 (labels ((walk-up (class)
278 (unless (gethash class done)
279 (dolist (super (class-direct-superclasses class))
280 (push class (gethash super subs))
282 (setf (gethash class done) t))))
283 (dolist (class classes)
285 (labels ((walk-down (this super depth)
286 (format t "~v,0T~A~@[ [~{~A~^ ~}]~]~%"
288 (pretty-symbol-name (class-name this) package)
289 (mapcar (lambda (class)
290 (pretty-symbol-name (class-name class)
293 (class-direct-superclasses this))))
294 (dolist (sub (sort (copy-list (gethash this subs))
295 #'string< :key #'class-name))
296 (walk-down sub this (1+ depth)))))
297 (walk-down (find-class t) nil 0))))
299 (defmacro deep-compare ((left right) &body body)
300 (let ((block (gensym "BLOCK-")) (func (gensym "FUNC-"))
301 (l (gensym "LEFT-")) (r (gensym "RIGHT-")))
302 `(macrolet ((focus (expr &body body)
303 `(flet ((,',func (it) ,expr))
304 (let ((,',l (,',func ,',l))
305 (,',r (,',func ,',r)))
308 `(flet ((,',func (it) ,expr))
309 (psetf ,',l (,',func ,',l)
310 ,',r (,',func ,',r))))
312 `(cond ((let ((left ,',l) (right ,',r)) ,expr)
313 (return-from ,',block t))
314 ((let ((right ,',l) (left ,',r)) ,expr)
315 (return-from ,',block nil))))
316 (typesw (&rest clauses)
317 (labels ((iter (clauses)
320 (destructuring-bind (type &rest body)
324 `(if (typep ,',l ',type)
325 (if (typep ,',r ',type)
327 (return-from ,',block t))
328 (if (typep ,',r ',type)
329 (return-from ,',block nil)
330 ,(iter (cdr clauses)))))))))
332 (let ((,l ,left) (,r ,right))
336 (defun order-specializers (la lb)
337 (deep-compare (la lb)
338 (loop (typesw (null (return nil)))
340 (typesw (eql-specializer
341 (focus (eql-specializer-object it)
343 (compare (string< left right)))
345 (focus (package-name (symbol-package it))
346 (compare (string< left right)))
347 (compare (string< left right)))
349 (focus (with-output-to-string (out)
352 (compare (string< left right)))))))
354 (focus (class-name it)
355 (focus (package-name (symbol-package it))
356 (compare (string< left right)))
357 (compare (string< left right))))
359 (error "unexpected things"))))
362 (defun analyse-generic-functions (package)
363 (setf package (find-package package))
364 (flet ((function-name-core (name)
366 (symbol (values name t))
367 ((cons (eql setf) t) (values (cadr name) t))
368 (t (values nil nil)))))
369 (let ((methods (make-hash-table))
370 (functions (make-hash-table))
371 (externs (make-hash-table)))
372 (dolist (symbol (list-exported-symbols package))
373 (setf (gethash symbol externs) t))
374 (dolist (symbol (list-exported-symbols package))
375 (flet ((dofunc (func)
376 (when (typep func 'generic-function)
377 (setf (gethash func functions) t)
378 (dolist (method (generic-function-methods func))
379 (setf (gethash method methods) t)))))
380 (dofunc (and (fboundp symbol) (fdefinition symbol)))
381 (dofunc (ignore-errors (fdefinition (list 'setf symbol)))))
382 (when (eq (symbol-package symbol) package)
383 (let ((class (find-class symbol nil)))
386 (func (specializer-direct-generic-functions class))
387 (multiple-value-bind (name knownp)
388 (function-name-core (generic-function-name func))
390 (or (not (eq (symbol-package name) package))
391 (gethash name externs)))
392 (setf (gethash func functions) t)
393 (dolist (method (specializer-direct-methods class))
394 (setf (gethash method methods) t)))))))))
395 (let ((funclist nil))
396 (maphash (lambda (func value)
397 (declare (ignore value))
398 (push func funclist))
400 (setf funclist (sort funclist
402 (let ((core-a (function-name-core a))
403 (core-b (function-name-core b)))
404 (if (eq core-a core-b)
405 (and (atom a) (consp b))
406 (string< core-a core-b))))
407 :key #'generic-function-name))
408 (dolist (function funclist)
409 (let ((name (generic-function-name function)))
412 (format t "~A~%" (pretty-symbol-name name package)))
414 (format t "(setf ~A)~%"
415 (pretty-symbol-name (cadr name) package)))))
416 (dolist (method (sort (copy-list
417 (generic-function-methods function))
419 :key #'method-specializers))
420 (when (gethash method methods)
421 (format t "~2T~{~A~^ ~}~@[ [~{~(~S~)~^ ~}]~]~%"
426 (let ((name (class-name spec)))
428 (pretty-symbol-name name package))))
430 (let ((obj (eql-specializer-object spec)))
431 (format nil "(eql ~A)"
433 (pretty-symbol-name obj package)
435 (method-specializers method))
436 (method-qualifiers method)))))))))
438 (defun check-slot-names (package)
439 (setf package (find-package package))
440 (let* ((symbols (list-all-symbols package))
441 (classes (mapcan (lambda (symbol)
442 (when (eq (symbol-package symbol) package)
443 (let ((class (find-class symbol nil)))
444 (and class (list class)))))
449 (mapcar #'slot-definition-name
450 (class-direct-slots class)))
453 (or (not (symbol-package sym))
454 (and (not (exported-symbol-p
456 (eq (symbol-package sym)
460 (list (cons (class-name class)
463 (bad-words (remove-duplicates (mapcan (lambda (list)
464 (copy-list (cdr list)))
466 (values offenders bad-words)))
468 (defun report-symbols (paths package)
469 (setf package (find-package package))
470 (format t "~A~%Package `~(~A~)'~2%"
471 (make-string 77 :initial-element #\-)
472 (package-name package))
473 (dolist (assoc (sort (categorize-symbols paths package) #'string<
475 (file-namestring (car assoc)))))
477 (format t "~A~%" (file-namestring (car assoc)))
478 (dolist (def (cdr assoc))
479 (let ((sym (car def)))
480 (format t " ~A~@[~48T~{~(~A~)~^ ~}~]~%"
481 (pretty-symbol-name sym package)
484 (multiple-value-bind (alist names) (check-slot-names package)
486 (format t "Leaked slot names: ~{~A~^, ~}~%"
487 (mapcar (lambda (name) (pretty-symbol-name name package))
489 (dolist (assoc alist)
490 (format t "~2T~A: ~{~A~^, ~}~%"
491 (pretty-symbol-name (car assoc) package)
492 (mapcar (lambda (name) (pretty-symbol-name name package))
495 (format t "Classes:~%")
496 (analyse-classes package)
498 (format t "Methods:~%")
499 (analyse-generic-functions package)
502 (export 'report-project-symbols)
503 (defun report-project-symbols ()
504 (labels ((components (comp)
505 (asdf:component-children comp))
507 (sort (remove-if-not (lambda (comp)
508 (typep comp 'asdf:cl-source-file))
510 #'string< :key #'asdf:component-name))
512 (gethash name (asdf:component-children-by-name comp)))
514 (slot-value file 'asdf/component:absolute-pathname)))
515 (let* ((sod (asdf:find-system "sod"))
516 (parser-files (files (by-name sod "parser")))
517 (utilities (by-name sod "utilities"))
518 (sod-frontend (asdf:find-system "sod-frontend"))
519 (optparse (by-name sod "optparse"))
520 (frontend (by-name sod-frontend "frontend"))
521 (sod-files (set-difference (files sod) (list optparse utilities))))
522 (report-symbols (mapcar #'file-name sod-files) "SOD")
523 (report-symbols (mapcar #'file-name (list frontend)) "SOD-FRONTEND")
524 (report-symbols (mapcar #'file-name parser-files) "SOD-PARSER")
525 (report-symbols (mapcar #'file-name (list optparse)) "OPTPARSE")
526 (report-symbols (mapcar #'file-name (list utilities)) "SOD-UTILITIES"))))
529 (with-open-file (*standard-output* #p"doc/SYMBOLS"
531 :if-exists :supersede
532 :if-does-not-exist :create)
533 (report-project-symbols)))