src/{class-,}utilities.lisp: Add machinery for showing inheritance paths.
[sod] / src / class-utilities.lisp
CommitLineData
dea4d055
MW
1;;; -*-lisp-*-
2;;;
3;;; A collection of utility functions for SOD classes
4;;;
5;;; (c) 2009 Straylight/Edgeware
6;;;
7
8;;;----- Licensing notice ---------------------------------------------------
9;;;
e0808c47 10;;; This file is part of the Sensible Object Design, an object system for C.
dea4d055
MW
11;;;
12;;; SOD is free software; you can redistribute it and/or modify
13;;; it under the terms of the GNU General Public License as published by
14;;; the Free Software Foundation; either version 2 of the License, or
15;;; (at your option) any later version.
16;;;
17;;; SOD is distributed in the hope that it will be useful,
18;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;;; GNU General Public License for more details.
21;;;
22;;; You should have received a copy of the GNU General Public License
23;;; along with SOD; if not, write to the Free Software Foundation,
24;;; Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
26(cl:in-package #:sod)
27
28;;;--------------------------------------------------------------------------
29;;; Finding things by name
30
31(export 'find-superclass-by-nick)
32(defun find-superclass-by-nick (class nick)
33 "Returns the superclass of CLASS with nickname NICK, or signals an error."
34
35 ;; Slightly tricky. The class almost certainly hasn't been finalized, so
36 ;; trundle through its superclasses and hope for the best.
37 (if (string= nick (sod-class-nickname class))
38 class
39 (or (some (lambda (super)
40 (find nick (sod-class-precedence-list super)
41 :key #'sod-class-nickname
42 :test #'string=))
43 (sod-class-direct-superclasses class))
44 (error "No superclass of `~A' with nickname `~A'" class nick))))
45
46(export '(find-instance-slot-by-name find-class-slot-by-name
47 find-message-by-name))
48(flet ((find-thing-by-name (what class list name key)
49 (or (find name list :key key :test #'string=)
50 (error "No ~A in class `~A' with name `~A'" what class name))))
51
52 (defun find-instance-slot-by-name (class super-nick slot-name)
53 (let ((super (find-superclass-by-nick class super-nick)))
bf090e02 54 (find-thing-by-name "instance slot" super (sod-class-slots super)
dea4d055
MW
55 slot-name #'sod-slot-name)))
56
57 (defun find-class-slot-by-name (class super-nick slot-name)
58 (let* ((meta (sod-class-metaclass class))
59 (super (find-superclass-by-nick meta super-nick)))
bf090e02 60 (find-thing-by-name "class slot" super (sod-class-slots super)
dea4d055
MW
61 slot-name #'sod-slot-name)))
62
63 (defun find-message-by-name (class super-nick message-name)
64 (let ((super (find-superclass-by-nick class super-nick)))
65 (find-thing-by-name "message" super (sod-class-messages super)
66 message-name #'sod-message-name))))
67
68;;;--------------------------------------------------------------------------
17c7c784
MW
69;;; Describing class inheritance paths in diagnostics.
70
71(export 'inheritance-path-reporter-state)
72(defclass inheritance-path-reporter-state ()
73 ((%class :type sod-class :initarg :class)
74 (paths :type list :initarg :paths)
75 (seen :type hash-table :initform (make-hash-table))))
76
77(export 'make-inheritance-path-reporter-state)
78(defun make-inheritance-path-reporter-state (class)
79 (make-instance 'inheritance-path-reporter-state :class class))
80
81(export 'report-inheritance-path)
82(defun report-inheritance-path (state super)
83 "Issue informational messages showing how CLASS inherits from SUPER."
84 (with-slots (paths (class %class) include-boundary seen) state
85 (unless (slot-boundp state 'paths)
86 (setf paths (distinguished-point-shortest-paths
87 class
88 (lambda (c)
89 (mapcar (lambda (super) (cons super 1))
90 (sod-class-direct-superclasses c))))))
91 (dolist (hop (mapcon (lambda (subpath)
92 (let ((super (car subpath))
93 (sub (and (cdr subpath)
94 (cadr subpath))))
95 (if (or (not sub) (gethash super seen))
96 nil
97 (progn
98 (setf (gethash super seen) t)
99 (list (cons super sub))))))
100 (cdr (find super paths :key #'cadr))))
101 (let ((super (car hop))
102 (sub (cdr hop)))
103 (info-with-location sub
104 "Class `~A' is a direct superclass ~
105 of `~A', defined here"
106 super sub)))))
107
108;;;--------------------------------------------------------------------------
dea4d055
MW
109;;; Miscellaneous useful functions.
110
111(export 'sod-subclass-p)
112(defun sod-subclass-p (class-a class-b)
113 "Return whether CLASS-A is a descendent of CLASS-B.
114
115 Careful! Assumes that the class precedence list of CLASS-A has been
116 computed!"
117 (member class-b (sod-class-precedence-list class-a)))
118
119(export 'valid-name-p)
120(defun valid-name-p (name)
121 "Checks whether NAME is a valid name.
122
123 The rules are:
124
125 * the name must be a string
126 * which is nonempty
127 * whose first character is alphabetic
128 * all of whose characters are alphanumeric or underscores
129 * and which doesn't contain two consecutive underscores."
130
131 (and (stringp name)
132 (plusp (length name))
133 (alpha-char-p (char name 0))
134 (every (lambda (ch) (or (alphanumericp ch) (char= ch #\_))) name)
135 (not (search "__" name))))
136
137(export 'find-root-superclass)
138(defun find-root-superclass (class)
139 "Returns the `root' superclass of CLASS.
140
141 The root superclass is the superclass which itself has no direct
142 superclasses. In universes not based on the provided builtin module, the
3109662a 143 root class may not be our beloved `SodObject'; however, there must be one
dea4d055
MW
144 (otherwise the class graph is cyclic, which should be forbidden), and we
145 insist that it be unique."
146
147 ;; The root superclass must be a chain head since the chains partition the
148 ;; superclasses; the root has no superclasses so it can't have a link and
149 ;; must therefore be a head. This narrows the field down quite a lot.
150 ;;
151 ;; Note! This function gets called from `check-sod-class' before the
152 ;; class's chains have been computed. Therefore we iterate over the direct
bf090e02 153 ;; superclasses' chains rather than the class's own. This misses a chain
dea4d055
MW
154 ;; only in the case where the class is its own chain head. There are two
155 ;; subcases: if there are no direct superclasses at all, then the class is
156 ;; its own root; otherwise, it clearly can't be the root and the omission
157 ;; is harmless.
158
159 (let* ((supers (sod-class-direct-superclasses class))
160 (roots (if supers
161 (remove-duplicates
162 (remove-if #'sod-class-direct-superclasses
163 (mappend (lambda (super)
164 (mapcar (lambda (chain)
165 (sod-class-chain-head
166 (car chain)))
167 (sod-class-chains super)))
168 supers)))
169 (list class))))
170 (cond ((null roots) (error "Class ~A has no root class!" class))
171 ((cdr roots) (error "Class ~A has multiple root classes ~
172 ~{~A~#[~; and ~;, ~]~}"
173 class roots))
174 (t (car roots)))))
175
176(export 'find-root-metaclass)
177(defun find-root-metaclass (class)
178 "Returns the `root' metaclass of CLASS.
179
180 The root metaclass is the metaclass of the root superclass -- see
181 `find-root-superclass'."
182 (sod-class-metaclass (find-root-superclass class)))
183
184;;;--------------------------------------------------------------------------
185;;; Type hacking.
186
187(export 'argument-lists-compatible-p)
188(defun argument-lists-compatible-p (message-args method-args)
189 "Compare argument lists for compatibility.
190
191 Return true if METHOD-ARGS is a suitable method argument list
192 corresponding to the message argument list MESSAGE-ARGS. This is the case
193 if the lists are the same length, each message argument has a
194 corresponding method argument with the same type, and if the message
195 arguments end in an ellpisis, the method arguments must end with a
196 `va_list' argument. (We can't pass actual variable argument lists around,
197 except as `va_list' objects, which are devilish inconvenient things and
198 require much hacking. See the method combination machinery for details.)"
199
200 (and (= (length message-args) (length method-args))
201 (every (lambda (message-arg method-arg)
202 (if (eq message-arg :ellipsis)
8dba302b 203 (c-type-equal-p (argument-type method-arg)
e85df3ff 204 c-type-va-list)
dea4d055
MW
205 (c-type-equal-p (argument-type message-arg)
206 (argument-type method-arg))))
207 message-args method-args)))
208
209;;;--------------------------------------------------------------------------
210;;; Names of things.
211
212(export 'islots-struct-tag)
213(defun islots-struct-tag (class)
214 (format nil "~A__islots" class))
215
216(export 'ichain-struct-tag)
217(defun ichain-struct-tag (class chain-head)
218 (format nil "~A__ichain_~A" class (sod-class-nickname chain-head)))
219
220(export 'ichain-union-tag)
221(defun ichain-union-tag (class chain-head)
222 (format nil "~A__ichainu_~A" class (sod-class-nickname chain-head)))
223
224(export 'ilayout-struct-tag)
225(defun ilayout-struct-tag (class)
226 (format nil "~A__ilayout" class))
227
228(export 'vtmsgs-struct-tag)
229(defun vtmsgs-struct-tag (class super)
230 (format nil "~A__vtmsgs_~A" class (sod-class-nickname super)))
231
c2438e62
MW
232(export 'vtable-union-tag)
233(defun vtable-union-tag (class chain-head)
234 (format nil "~A__vtu_~A" class (sod-class-nickname chain-head)))
235
dea4d055
MW
236(export 'vtable-struct-tag)
237(defun vtable-struct-tag (class chain-head)
238 (format nil "~A__vt_~A" class (sod-class-nickname chain-head)))
239
240(export 'vtable-name)
241(defun vtable-name (class chain-head)
242 (format nil "~A__vtable_~A" class (sod-class-nickname chain-head)))
243
6bc944c3 244(export 'message-macro-name)
b426ab51
MW
245(defun message-macro-name (class entry)
246 (format nil "~A_~A" class (method-entry-slot-name entry)))
6bc944c3 247
dea4d055 248;;;----- That's all, folks --------------------------------------------------