3 ;;; Equipment for building classes and friends
5 ;;; (c) 2009 Straylight/Edgeware
8 ;;;----- Licensing notice ---------------------------------------------------
10 ;;; This file is part of the Simple Object Definition system.
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.
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.
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.
28 ;;;--------------------------------------------------------------------------
29 ;;; Finding things by name
31 (defun find-superclass-by-nick (class nick)
32 "Returns the superclass of CLASS with nickname NICK, or signals an error."
34 ;; Slightly tricky. The class almost certainly hasn't been finalized, so
35 ;; trundle through its superclasses and hope for the best.
36 (if (string= nick (sod-class-nickname class))
38 (or (some (lambda (super)
39 (find nick (sod-class-precedence-list super)
40 :key #'sod-class-nickname
42 (sod-class-direct-superclasses class))
43 (error "No superclass of `~A' with nickname `~A'" class nick))))
45 (flet ((find-item-by-name (what class list name key)
46 (or (find name list :key key :test #'string=)
47 (error "No ~A in class `~A' with name `~A'" what class name))))
49 (defun find-instance-slot-by-name (class super-nick slot-name)
50 (let ((super (find-superclass-by-nick class super-nick)))
51 (find-item-by-name "slot" super (sod-class-slots super)
52 slot-name #'sod-slot-name)))
54 (defun find-class-slot-by-name (class super-nick slot-name)
55 (let* ((meta (sod-class-metaclass class))
56 (super (find-superclass-by-nick meta super-nick)))
57 (find-item-by-name "slot" super (sod-class-slots super)
58 slot-name #'sod-slot-name)))
60 (defun find-message-by-name (class super-nick message-name)
61 (let ((super (find-superclass-by-nick class super-nick)))
62 (find-item-by-name "message" super (sod-class-messages super)
63 message-name #'sod-message-name))))
65 ;;;--------------------------------------------------------------------------
66 ;;; Class construction.
68 (defun make-sod-class (name superclasses pset &optional location)
69 "Construct and return a new SOD class with the given NAME and SUPERCLASSES.
71 This is the main constructor function for classes. The protocol works as
72 follows. The :LISP-CLASS property in PSET is checked: if it exists, it
73 must be a symbol naming a (CLOS) class, which is used in place of
74 SOD-CLASS. All of the arguments are then passed to MAKE-INSTANCE; further
75 behaviour is left to the standard CLOS instance construction protocol; for
76 example, SOD-CLASS defines an :AFTER-method on SHARED-INITIALIZE.
78 Minimal sanity checking is done during class construction; most of it is
79 left for FINALIZE-SOD-CLASS to do (via CHECK-SOD-CLASS).
81 Unused properties in PSET are diagnosed as errors."
83 (with-default-error-location (location)
84 (let ((class (make-instance (get-property pset :lisp-class :symbol
87 :superclasses superclasses
88 :location (file-location location)
90 (check-unused-properties pset)
93 (defgeneric guess-metaclass (class)
95 "Determine a suitable metaclass for the CLASS.
97 The default behaviour is to choose the most specific metaclass of any of
98 the direct superclasses of CLASS, or to signal an error if that failed."))
100 (defmethod guess-metaclass ((class sod-class))
101 "Default metaclass-guessing function for classes.
103 Return the most specific metaclass of any of the CLASS's direct
105 (do ((supers (sod-class-direct-superclasses class) (cdr supers))
106 (meta nil (let ((candidate (sod-class-metaclass (car supers))))
107 (cond ((null meta) candidate)
108 ((sod-subclass-p meta candidate) meta)
109 ((sod-subclass-p candidate meta) candidate)
110 (t (error "Unable to choose metaclass for `~A'"
112 ((endp supers) meta)))
114 (defmethod shared-initialize :after ((class sod-class) slot-names &key pset)
115 "Specific behaviour for SOD class initialization.
117 Properties inspected are as follows:
119 * :METACLASS names the metaclass to use. If unspecified, NIL is stored,
120 and (unless you intervene later) GUESS-METACLASS will be called by
121 FINALIZE-SOD-CLASS to find a suitable default.
123 * :NICK provides a nickname for the class. If unspecified, a default
124 (the class's name, forced to lowercase) will be chosen in
127 * :LINK names the chained superclass. If unspecified, this class will
128 be left at the head of its chain."
130 ;; If no nickname, copy the class name. It won't be pretty, though.
131 (default-slot (class 'nickname)
132 (get-property pset :nick :id (string-downcase (slot-value class 'name))))
134 ;; If no metaclass, guess one in a (Lisp) class-specific way.
135 (default-slot (class 'metaclass)
136 (multiple-value-bind (name floc) (get-property pset :metaclass :id)
138 (find-sod-class name floc)
139 (guess-metaclass class))))
141 ;; If no chain-link, then start a new chain here.
142 (default-slot (class 'chain-link)
143 (multiple-value-bind (name floc) (get-property pset :link :id)
145 (find-sod-class name floc)
148 ;;;--------------------------------------------------------------------------
149 ;;; Slot construction.
151 (defgeneric make-sod-slot (class name type pset &optional location)
153 "Construct, add, and attach a new slot with given NAME and TYPE, to CLASS.
155 This is the main constructor function for slots. This is a generic
156 function primarily so that the CLASS can intervene in the construction
157 process. The default method uses the :LISP-CLASS property (defaulting to
158 SOD-SLOT) to choose a (CLOS) class to instantiate. The slot is then
159 constructed by MAKE-INSTANCE passing the arguments as initargs; further
160 behaviour is left to the standard CLOS instance construction protocol; for
161 example, SOD-SLOT defines an :AFTER-method on SHARED-INITIALIZE.
163 Unused properties on PSET are diagnosed as errors."))
165 (defmethod make-sod-slot
166 ((class sod-class) name type pset &optional location)
167 (with-default-error-location (location)
168 (let ((slot (make-instance (get-property pset :lisp-class :symbol
173 :location (file-location location)
175 (with-slots (slots) class
176 (setf slots (append slots (list slot))))
177 (check-unused-properties pset))))
179 (defmethod shared-initialize :after ((slot sod-slot) slot-names &key pset)
180 "This method exists so that it isn't an error to provide a :PSET initarg
181 to (make-instance 'sod-slot ...). It does nothing."
182 (declare (ignore slot-names pset))
185 ;;;--------------------------------------------------------------------------
186 ;;; Slot initializer construction.
188 (defgeneric make-sod-instance-initializer
189 (class nick name value-kind value-form pset &optional location)
191 "Construct and attach an instance slot initializer, to CLASS.
193 This is the main constructor function for instance initializers. This is
194 a generic function primarily so that the CLASS can intervene in the
195 construction process. The default method looks up the slot using
196 FIND-INSTANCE-SLOT-BY-NAME, calls MAKE-SOD-INITIALIZER-USING-SLOT to
197 actually make the initializer object, and adds it to the appropriate list
200 Unused properties on PSET are diagnosed as errors."))
202 (defgeneric make-sod-class-initializer
203 (class nick name value-kind value-form pset &optional location)
205 "Construct and attach a class slot initializer, to CLASS.
207 This is the main constructor function for class initializers. This is a
208 generic function primarily so that the CLASS can intervene in the
209 construction process. The default method looks up the slot using
210 FIND-CLASS-SLOT-BY-NAME, calls MAKE-SOD-INITIALIZER-USING-SLOT to actually
211 make the initializer object, and adds it to the appropriate list in CLASS.
213 Unused properties on PSET are diagnosed as errors."))
215 (defgeneric make-sod-initializer-using-slot
216 (class slot init-class value-kind value-form pset location)
218 "Common construction protocol for slot initializers.
220 This generic function does the common work for constructing instance and
221 class initializers. It can usefully be specialized according to both the
222 class and slot types. The default method uses the :LISP-CLASS property
223 (defaulting to INIT-CLASS) to choose a (CLOS) class to instantiate. The
224 slot is then constructed by MAKE-INSTANCE passing the arguments as
225 initargs; further behaviour is left to the standard CLOS instance
226 construction protocol; for example, SOD-INITIALIZER defines
227 an :AFTER-method on SHARED-INITIALIZE.
229 Diagnosing unused properties is left for the caller (usually
230 MAKE-SOD-INSTANCE-INITIALIZER or MAKE-SOD-CLASS-INITIALIZER) to do. The
231 caller is also expected to have set WITH-DEFAULT-ERROR-LOCATION if
234 You are not expected to call this generic function directly; it's more
235 useful as a place to hang methods for custom initializer classes."))
237 (defmethod make-sod-instance-initializer
238 ((class sod-class) nick name value-kind value-form pset
240 (with-default-error-location (location)
241 (let* ((slot (find-instance-slot-by-name class nick name))
242 (initializer (make-sod-initializer-using-slot
243 class slot 'sod-instance-initializer
244 value-kind value-form pset
245 (file-location location))))
246 (with-slots (instance-initializers) class
247 (setf instance-initializers (append instance-initializers
248 (list initializer))))
249 (check-unused-properties pset))))
251 (defmethod make-sod-class-initializer
252 ((class sod-class) nick name value-kind value-form pset
254 (with-default-error-location (location)
255 (let* ((slot (find-class-slot-by-name class nick name))
256 (initializer (make-sod-initializer-using-slot
257 class slot 'sod-class-initializer
258 value-kind value-form pset
259 (file-location location))))
260 (with-slots (class-initializers) class
261 (setf class-initializers (append class-initializers
262 (list initializer))))
263 (check-unused-properties pset))))
265 (defmethod make-sod-initializer-using-slot
266 ((class sod-class) (slot sod-slot)
267 init-class value-kind value-form pset location)
268 (make-instance (get-property pset :lisp-class :symbol init-class)
271 :value-kind value-kind
272 :value-form value-form
276 (defmethod shared-initialize :after
277 ((init sod-initializer) slot-names &key pset)
278 "This method exists so that it isn't an error to provide a :PSET initarg
279 to (make-instance 'sod-initializer ...). It does nothing."
280 (declare (ignore slot-names pset))
283 ;;;--------------------------------------------------------------------------
284 ;;; Message construction.
286 (defgeneric make-sod-message (class name type pset &optional location)
288 "Construct and attach a new message with given NAME and TYPE, to CLASS.
290 This is the main constructor function for messages. This is a generic
291 function primarily so that the CLASS can intervene in the construction
292 process. The default method uses the :LISP-CLASS property (defaulting to
293 SOD-MESSAGE) to choose a (CLOS) class to instantiate. The message is then
294 constructed by MAKE-INSTANCE passing the arguments as initargs; further
295 behaviour is left to the standard CLOS instance construction protocol; for
296 example, SOD-MESSAGE defines an :AFTER-method on SHARED-INITIALIZE.
298 Unused properties on PSET are diagnosed as errors."))
300 (defgeneric check-message-type (message type)
302 "Check that TYPE is a suitable type for MESSAGE. Signal errors if not.
304 This is separated out of SHARED-INITIALIZE, where it's called, so that it
305 can be overridden conveniently by subclasses."))
307 (defmethod make-sod-message
308 ((class sod-class) name type pset &optional location)
309 (with-default-error-location (location)
310 (let ((message (make-instance (get-property pset :lisp-class :symbol
315 :location (file-location location)
317 (with-slots (messages) class
318 (setf messages (append messages (list message))))
319 (check-unused-properties pset))))
321 (defmethod check-message-type ((message sod-message) (type c-function-type))
323 (defmethod check-message-type ((message sod-message) (type c-type))
324 (error "Messages must have function type, not ~A" type))
326 (defmethod shared-initialize :after
327 ((message sod-message) slot-names &key pset)
328 (declare (ignore slot-names pset))
329 (with-slots (type) message
330 (check-message-type message type)))
332 ;;;--------------------------------------------------------------------------
333 ;;; Method construction.
335 (defgeneric make-sod-method
336 (class nick name type body pset &optional location)
338 "Construct and attach a new method to CLASS.
340 This is the main constructor function for methods. This is a generic
341 function primarily so that the CLASS can intervene in the message lookup
342 process, though this is actually a fairly unlikely occurrence.
344 The default method looks up the message using FIND-MESSAGE-BY-NAME,
345 invokes MAKE-SOD-METHOD-USING-MESSAGE to make the method object, and then
346 adds the method to the class's list of methods. This split allows the
347 message class to intervene in the class selection process, for example.
349 Unused properties on PSET are diagnosed as errors."))
351 (defgeneric make-sod-method-using-message
352 (message class type body pset location)
354 "Main construction subroutine for method construction.
356 This is a generic function so that it can be specialized according to both
357 a class and -- more particularly -- a message. The default method uses
358 the :LISP-CLASS property (defaulting to calling SOD-MESSAGE-METHOD-CLASS)
359 to choose a (CLOS) class to instantiate. The method is then constructed
360 by MAKE-INSTANCE passing the arguments as initargs; further behaviour is
361 left to the standard CLOS instance construction protocol; for example,
362 SOD-METHOD defines an :AFTER-method on SHARED-INITIALIZE.
364 Diagnosing unused properties is left for the caller (usually
365 MAKE-SOD-METHOD) to do. The caller is also expected to have set
366 WITH-DEFAULT-ERROR-LOCATION if appropriate.
368 You are not expected to call this generic function directly; it's more
369 useful as a place to hang methods for custom initializer classes."))
371 (defgeneric sod-message-method-class (message class pset)
373 "Return the preferred class for methods on MESSAGE.
375 The message can inspect the PSET to decide on a particular message. A
376 :LISP-CLASS property will usually override this decision: it's then the
377 programmer's responsibility to ensure that the selected method class is
380 (defgeneric check-method-type (method message type)
382 "Check that TYPE is a suitable type for METHOD. Signal errors if not.
384 This is separated out of SHARED-INITIALIZE, where it's called, so that it
385 can be overridden conveniently by subclasses."))
387 (defmethod make-sod-method
388 ((class sod-class) nick name type body pset &optional location)
389 (with-default-error-location (location)
390 (let* ((message (find-message-by-name class nick name))
391 (method (make-sod-method-using-message message class
393 (file-location location))))
394 (with-slots (methods) class
395 (setf methods (append methods (list method)))))
396 (check-unused-properties pset)))
398 (defmethod make-sod-method-using-message
399 ((message sod-message) (class sod-class) type body pset location)
400 (make-instance (or (get-property pset :lisp-class :symbol)
401 (sod-message-method-class message class pset))
409 (defmethod sod-message-method-class
410 ((message sod-message) (class sod-class) pset)
411 (declare (ignore pset))
414 (defmethod check-method-type
415 ((method sod-method) (message sod-message) (type c-type))
416 (error "Methods must have function type, not ~A" type))
418 (defun argument-lists-compatible-p (message-args method-args)
419 "Compare argument lists for compatibility.
421 Return true if METHOD-ARGS is a suitable method argument list
422 corresponding to the message argument list MESSAGE-ARGS. This is the case
423 if the lists are the same length, each message argument has a
424 corresponding method argument with the same type, and if the message
425 arguments end in an ellpisis, the method arguments must end with a
426 `va_list' argument. (We can't pass actual variable argument lists around,
427 except as `va_list' objects, which are devilish inconvenient things and
428 require much hacking. See the method combination machinery for details.)"
430 (and (= (length message-args) (length method-args))
431 (every (lambda (message-arg method-arg)
432 (if (eq message-arg :ellipsis)
433 (eq method-arg (c-type va-list))
434 (c-type-equal-p (argument-type message-arg)
435 (argument-type method-arg))))
436 message-args method-args)))
438 (defmethod check-method-type
439 ((method sod-method) (message sod-message) (type c-function-type))
440 (with-slots ((msgtype type)) message
441 (unless (c-type-equal-p (c-type-subtype msgtype)
442 (c-type-subtype type))
443 (error "Method return type ~A doesn't match message ~A"
444 (c-type-subtype msgtype) (c-type-subtype type)))
445 (unless (argument-lists-compatible-p (c-function-arguments msgtype)
446 (c-function-arguments type))
447 (error "Method arguments ~A don't match message ~A" type msgtype))))
449 (defmethod shared-initialize :after
450 ((method sod-method) slot-names &key pset)
451 (declare (ignore slot-names pset))
453 ;; Check that the arguments are named if we have a method body.
454 (with-slots (body type) method
455 (unless (or (not body)
456 (every #'argument-name (c-function-arguments type)))
457 (error "Abstract declarators not permitted in method definitions")))
459 ;; Check the method type.
460 (with-slots (message type) method
461 (check-method-type method message type)))
463 ;;;--------------------------------------------------------------------------
466 (defmacro define-sod-class (name (&rest superclasses) &body body)
468 (classvar (gensym "CLASS")))
470 (when (or (null body)
471 (not (keywordp (car body))))
473 (push (pop body) plist)
474 (push (pop body) plist))
475 `(let ((,classvar (make-sod-class ,name
476 (mapcar #'find-sod-class
477 (list ,@superclasses))
479 ,@(nreverse plist)))))
480 (macrolet ((message (name type &rest plist)
481 `(make-sod-message ,',classvar ,name (c-type ,type)
482 (make-property-set ,@plist)))
483 (method (nick name type body &rest plist)
484 `(make-sod-method ,',classvar ,nick ,name (c-type ,type)
485 ,body (make-property-set ,@plist)))
486 (slot (name type &rest plist)
487 `(make-sod-slot ,',classvar ,name (c-type ,type)
488 (make-property-set ,@plist)))
489 (instance-initializer
490 (nick name value-kind value-form &rest plist)
491 `(make-sod-instance-initializer ,',classvar ,nick ,name
492 ,value-kind ,value-form
496 (nick name value-kind value-form &rest plist)
497 `(make-sod-class-initializer ,',classvar ,nick ,name
498 ,value-kind ,value-form
502 (finalize-sod-class ,classvar)
503 (add-to-module *module* ,classvar)))))
505 ;;;----- That's all, folks --------------------------------------------------