Commit | Line | Data |
---|---|---|
1f1d88f5 MW |
1 | ;;; -*-lisp-*- |
2 | ;;; | |
3 | ;;; Class finalization | |
4 | ;;; | |
5 | ;;; (c) 2009 Straylight/Edgeware | |
6 | ;;; | |
7 | ||
8 | ;;;----- Licensing notice --------------------------------------------------- | |
9 | ;;; | |
10 | ;;; This file is part of the Simple Object Definition system. | |
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 | ;;; Class finalization. | |
30 | ||
31 | ;; Protocol. | |
32 | ||
33 | (defgeneric compute-chains (class) | |
34 | (:documentation | |
35 | "Compute the layout chains for CLASS. | |
36 | ||
37 | Returns the following three values. | |
38 | ||
39 | * the head of the class's primary chain; | |
40 | ||
41 | * the class's primary chain as a list, most- to least-specific; and | |
42 | ||
43 | * the complete collection of chains, as a list of lists, each most- to | |
44 | least-specific, with the primary chain first. | |
45 | ||
46 | These values will be stored in the CHAIN-HEAD, CHAIN and CHAINS slots. | |
47 | ||
48 | If the chains are ill-formed (i.e., not distinct) then an error is | |
49 | signalled.")) | |
50 | ||
51 | (defgeneric check-sod-class (class) | |
52 | (:documentation | |
53 | "Check the CLASS for validity. | |
54 | ||
55 | This is done as part of class finalization. The checks performed are as | |
56 | follows. | |
57 | ||
58 | * The class name and nickname, and the names of messages, obey the | |
59 | rules (see VALID-NAME-P). | |
60 | ||
61 | * The messages and slots have distinct names. | |
62 | ||
63 | * The classes in the class-precedence-list have distinct nicknames. | |
64 | ||
65 | * The chain-link is actually a proper (though not necessarily direct) | |
66 | superclass. | |
67 | ||
68 | * The chosen metaclass is actually a subclass of all of the | |
69 | superclasses' metaclasses. | |
70 | ||
71 | Returns true if all is well; false (and signals errors) if anything was | |
72 | wrong.")) | |
73 | ||
74 | (defgeneric finalize-sod-class (class) | |
75 | (:documentation | |
76 | "Computes all of the gory details about a class. | |
77 | ||
78 | Once one has stopped inserting methods and slots and so on into a class, | |
79 | one needs to finalize it to determine the layout structure and the class | |
80 | precedence list and so on. More precisely that gets done is this: | |
81 | ||
82 | * Related classes (i.e., direct superclasses and the metaclass) are | |
83 | finalized if they haven't been already. | |
84 | ||
85 | * If you've been naughty and failed to store a list of slots or | |
86 | whatever, then an empty list is inserted. | |
87 | ||
88 | * The class precedence list is computed and stored. | |
89 | ||
90 | * The class is checked for compiance with the well-formedness rules. | |
91 | ||
92 | * The layout chains are computed. | |
93 | ||
94 | Other stuff will need to happen later, but it's not been done yet. In | |
95 | particular: | |
96 | ||
97 | * Actually computing the layout of the instance and the virtual tables. | |
98 | ||
99 | * Combining the applicable methods into effective methods. | |
100 | ||
101 | FIXME this needs doing.")) | |
102 | ||
103 | ;; Implementation. | |
104 | ||
105 | (defun sod-subclass-p (class-a class-b) | |
106 | "Return whether CLASS-A is a descendent of CLASS-B." | |
107 | (member class-b (sod-class-precedence-list class-a))) | |
108 | ||
109 | (defun valid-name-p (name) | |
110 | "Checks whether NAME is a valid name. | |
111 | ||
112 | The rules are: | |
113 | ||
114 | * the name must be a string | |
115 | * which is nonempty | |
116 | * whose first character is alphabetic | |
117 | * all of whose characters are alphanumeric or underscores | |
118 | * and which doesn't contain two consecutive underscores." | |
119 | ||
120 | (and (stringp name) | |
121 | (plusp (length name)) | |
122 | (alpha-char-p (char name 0)) | |
123 | (every (lambda (ch) (or (alphanumericp ch) (char= ch #\_))) name) | |
124 | (not (search "__" name)))) | |
125 | ||
126 | (defmethod compute-chains ((class sod-class)) | |
127 | (with-default-error-location (class) | |
128 | (with-slots (chain-link class-precedence-list) class | |
129 | (let* ((head (if chain-link | |
130 | (sod-class-chain-head chain-link) | |
131 | class)) | |
132 | (chain (cons class (and chain-link | |
133 | (sod-class-chain chain-link)))) | |
134 | (table (make-hash-table))) | |
135 | ||
136 | ;; Check the chains. We work through each superclass, maintaining a | |
137 | ;; hash table keyed by class. If we encounter a class C which links | |
138 | ;; to L, then we store C as L's value; if L already has a value then | |
139 | ;; we've found an error. By the end of all of this, the classes | |
140 | ;; which don't have an entry are the chain tails. | |
141 | (dolist (super class-precedence-list) | |
142 | (let ((link (sod-class-chain-link super))) | |
143 | (when link | |
144 | (when (gethash link table) | |
145 | (error "Conflicting chains in class ~A: ~ | |
146 | (~A and ~A both link to ~A)" | |
147 | class super (gethash link table) link)) | |
148 | (setf (gethash link table) super)))) | |
149 | ||
150 | ;; Done. | |
151 | (values head chain | |
152 | (cons chain | |
153 | (mapcar #'sod-class-chain | |
154 | (remove-if (lambda (super) | |
155 | (gethash super table)) | |
156 | (cdr class-precedence-list))))))))) | |
157 | ||
158 | (defmethod check-sod-class ((class sod-class)) | |
159 | (with-default-error-location (class) | |
160 | ||
161 | ;; Check the names of things are valid. | |
162 | (with-slots (name nickname messages) class | |
163 | (unless (valid-name-p name) | |
164 | (error "Invalid class name `~A'" class)) | |
165 | (unless (valid-name-p nickname) | |
166 | (error "Invalid class nickname `~A' on class `~A'" nickname class)) | |
167 | (dolist (message messages) | |
168 | (unless (valid-name-p (sod-message-name message)) | |
169 | (error "Invalid message name `~A' on class `~A'" | |
170 | (sod-message-name message) class)))) | |
171 | ||
ddee4bb1 | 172 | ;; Check that the slots and messages have distinct names. |
1f1d88f5 MW |
173 | (with-slots (slots messages class-precedence-list) class |
174 | (flet ((check-list (list what namefunc) | |
175 | (let ((table (make-hash-table :test #'equal))) | |
176 | (dolist (item list) | |
177 | (let ((name (funcall namefunc item))) | |
178 | (if (gethash name table) | |
179 | (error "Duplicate ~A name `~A' on class `~A'" | |
180 | what name class) | |
181 | (setf (gethash name table) item))))))) | |
182 | (check-list slots "slot" #'sod-slot-name) | |
183 | (check-list messages "message" #'sod-message-name) | |
184 | (check-list class-precedence-list "nickname" #'sod-class-name))) | |
185 | ||
186 | ;; Check that the CHAIN-TO class is actually a proper superclass. (This | |
187 | ;; eliminates hairy things like a class being its own link.) | |
188 | (with-slots (class-precedence-list chain-link) class | |
189 | (unless (or (not chain-link) | |
190 | (member chain-link (cdr class-precedence-list))) | |
191 | (error "In `~A~, chain-to class `~A' is not a proper superclass" | |
192 | class chain-link))) | |
193 | ||
ddee4bb1 MW |
194 | ;; Check for circularity in the superclass graph. Since the superclasses |
195 | ;; should already be acyclic, it suffices to check that our class is not | |
196 | ;; a superclass of any of its own direct superclasses. | |
197 | (let ((circle (find-if (lambda (super) | |
198 | (sod-subclass-p super class)) | |
199 | (sod-class-direct-superclasses class)))) | |
200 | (when circle | |
201 | (error "Circularity: ~A is already a superclass of ~A" | |
202 | class circle))) | |
203 | ||
204 | ;; Check that the class has a unique root superclass. | |
205 | (find-root-superclass class) | |
206 | ||
1f1d88f5 MW |
207 | ;; Check that the metaclass is a subclass of each direct superclass's |
208 | ;; metaclass. | |
209 | (with-slots (metaclass direct-superclasses) class | |
210 | (dolist (super direct-superclasses) | |
211 | (unless (sod-subclass-p metaclass (sod-class-metaclass super)) | |
212 | (error "Incompatible metaclass for `~A': ~ | |
213 | `~A' isn't a subclass of `~A' (of `~A')" | |
214 | class metaclass (sod-class-metaclass super) super)))))) | |
215 | ||
216 | (defmethod finalize-sod-class ((class sod-class)) | |
217 | ||
218 | ;; CLONE-AND-HACK WARNING: Note that BOOTSTRAP-CLASSES has a (very brief) | |
219 | ;; clone of the CPL and chain establishment code. If the interface changes | |
220 | ;; then BOOTSTRAP-CLASSES will need to be changed too. | |
221 | ||
222 | (with-default-error-location (class) | |
223 | (ecase (sod-class-state class) | |
224 | ((nil) | |
225 | ||
226 | ;; If this fails, mark the class as a loss. | |
227 | (setf (sod-class-state class) :broken) | |
228 | ||
229 | ;; Finalize all of the superclasses. There's some special pleading | |
230 | ;; here to make bootstrapping work: we don't try to finalize the | |
231 | ;; metaclass if we're a root class (no direct superclasses -- because | |
232 | ;; in that case the metaclass will have to be a subclass of us!), or | |
233 | ;; if it's equal to us. This is enough to tie the knot at the top of | |
234 | ;; the class graph. | |
235 | (with-slots (name direct-superclasses metaclass) class | |
236 | (dolist (super direct-superclasses) | |
237 | (finalize-sod-class super)) | |
238 | (unless (or (null direct-superclasses) | |
239 | (eq class metaclass)) | |
240 | (finalize-sod-class metaclass))) | |
241 | ||
ddee4bb1 MW |
242 | ;; Stash the class's type. |
243 | (setf (sod-class-type class) | |
244 | (make-class-type (sod-class-name class))) | |
245 | ||
1f1d88f5 MW |
246 | ;; Clobber the lists of items if they've not been set. |
247 | (dolist (slot '(slots instance-initializers class-initializers | |
248 | messages methods)) | |
249 | (unless (slot-boundp class slot) | |
250 | (setf (slot-value class slot) nil))) | |
251 | ||
252 | ;; If the CPL hasn't been done yet, compute it. | |
253 | (with-slots (class-precedence-list) class | |
254 | (unless (slot-boundp class 'class-precedence-list) | |
255 | (setf class-precedence-list (compute-cpl class)))) | |
256 | ||
257 | ;; If no metaclass has been established, then choose one. | |
258 | (with-slots (metaclass) class | |
259 | (unless (and (slot-boundp class 'metaclass) metaclass) | |
260 | (setf metaclass (guess-metaclass class)))) | |
261 | ||
262 | ;; If no nickname has been set, choose a default. This might cause | |
263 | ;; conflicts, but, well, the user should have chosen an explicit | |
264 | ;; nickname. | |
265 | (with-slots (name nickname) class | |
266 | (unless (and (slot-boundp class 'nickname) nickname) | |
267 | (setf nickname (string-downcase name)))) | |
268 | ||
269 | ;; Check that the class is fairly sane. | |
270 | (check-sod-class class) | |
271 | ||
272 | ;; Determine the class's layout. | |
273 | (with-slots (chain-head chain chains) class | |
274 | (setf (values chain-head chain chains) (compute-chains class))) | |
275 | ||
276 | (with-slots (ilayout effective-methods vtables) class | |
277 | (setf ilayout (compute-ilayout class)) | |
278 | (setf effective-methods (compute-effective-methods class)) | |
279 | (setf vtables (compute-vtables class))) | |
280 | ||
281 | ;; Done. | |
282 | (setf (sod-class-state class) :finalized) | |
283 | t) | |
284 | ||
285 | (:broken | |
286 | nil) | |
287 | ||
288 | (:finalized | |
289 | t)))) | |
290 | ||
291 | ;;;----- That's all, folks -------------------------------------------------- |