X-Git-Url: https://git.distorted.org.uk/~mdw/sod/blobdiff_plain/284f1fa2ace3e276052ff1bd7d66442500e693da..00d59354c311fb28730b7c9b117b0d91aac092cc:/src/class-make-impl.lisp diff --git a/src/class-make-impl.lisp b/src/class-make-impl.lisp index dba6965..b3347bd 100644 --- a/src/class-make-impl.lisp +++ b/src/class-make-impl.lisp @@ -28,32 +28,52 @@ ;;;-------------------------------------------------------------------------- ;;; Classes. +(defmethod guess-metaclass ((class sod-class)) + "Default metaclass-guessing function for classes. + + Return the most specific metaclass of any of the CLASS's direct + superclasses." + + (select-minimal-class-property (sod-class-direct-superclasses class) + #'sod-class-metaclass + #'sod-subclass-p class "metaclass")) + (defmethod shared-initialize :after ((class sod-class) slot-names &key pset) "Specific behaviour for SOD class initialization. Properties inspected are as follows: - * `:metaclass' names the metaclass to use. If unspecified, nil is - stored, and (unless you intervene later) `guess-metaclass' will be - called by `finalize-sod-class' to find a suitable default. + * `:metaclass' names the metaclass to use. If unspecified, this will be + left unbound, and (unless you intervene later) `guess-metaclass' will + be called by `finalize-sod-class' to find a suitable default. * `:nick' provides a nickname for the class. If unspecified, a default (the class's name, forced to lowercase) will be chosen in `finalize-sod-class'. * `:link' names the chained superclass. If unspecified, this class will - be left at the head of its chain." + be left at the head of its chain. + + Usually, the class's metaclass is determined here, either direcly from the + `:metaclass' property or by calling `guess-metaclass'. Guessing is + inhibited if the `:%bootstrapping' property is non-nil." ;; If no nickname, copy the class name. It won't be pretty, though. (default-slot-from-property (class 'nickname slot-names) (pset :nick :id) (string-downcase (slot-value class 'name))) - ;; Set the metaclass if the appropriate property has been provided; - ;; otherwise leave it unbound for now, and we'll sort out the mess during - ;; finalization. - (default-slot-from-property (class 'metaclass slot-names) - (pset :metaclass :id meta (find-sod-class meta))) + ;; Set the metaclass if the appropriate property has been provided or we're + ;; not bootstreapping; otherwise leave it unbound for now, and trust the + ;; caller to sort out the mess. + (multiple-value-bind (meta floc) (get-property pset :metaclass :id) + (cond (floc + (setf (slot-value class 'metaclass) + (with-default-error-location (floc) + (find-sod-class meta)))) + ((not (get-property pset :%bootstrapping :boolean)) + (default-slot (class 'metaclass slot-names) + (guess-metaclass class))))) ;; If no chain-link, then start a new chain here. (default-slot-from-property (class 'chain-link slot-names) @@ -64,17 +84,23 @@ ;;; Slots. (defmethod make-sod-slot - ((class sod-class) name type pset &optional location) + ((class sod-class) name type pset &key location) (with-default-error-location (location) + (when (typep type 'c-function-type) + (error "Slot declarations cannot have function type")) (let ((slot (make-instance (get-property pset :slot-class :symbol 'sod-slot) :class class :name name :type type :location (file-location location) - :pset pset))) + :pset pset)) + (initarg-name (get-property pset :initarg :id))) (with-slots (slots) class (setf slots (append slots (list slot)))) + (when initarg-name + (make-sod-slot-initarg-using-slot class initarg-name slot pset + :location location)) slot))) (defmethod shared-initialize :after ((slot sod-slot) slot-names &key pset) @@ -89,41 +115,45 @@ ;;; Slot initializers. (defmethod make-sod-instance-initializer - ((class sod-class) nick name value-kind value-form pset - &optional location) + ((class sod-class) nick name value pset + &key location inhibit-initargs (add-to-class t)) (with-default-error-location (location) (let* ((slot (find-instance-slot-by-name class nick name)) - (initializer (make-sod-initializer-using-slot - class slot 'sod-instance-initializer - value-kind value-form pset - (file-location location)))) + (initarg-name (get-property pset :initarg :id)) + (initializer (and value + (make-sod-initializer-using-slot + class slot 'sod-instance-initializer + value pset (file-location location))))) (with-slots (instance-initializers) class - (setf instance-initializers - (append instance-initializers (list initializer)))) + (unless (or initarg-name initializer) + (error "Slot initializer declaration with no effect")) + (when (and initarg-name (not inhibit-initargs)) + (make-sod-slot-initarg-using-slot class initarg-name slot pset + :location location)) + (when (and initializer add-to-class) + (setf instance-initializers + (append instance-initializers (list initializer))))) initializer))) (defmethod make-sod-class-initializer - ((class sod-class) nick name value-kind value-form pset - &optional location) + ((class sod-class) nick name value pset &key location (add-to-class t)) (with-default-error-location (location) (let* ((slot (find-class-slot-by-name class nick name)) (initializer (make-sod-initializer-using-slot class slot 'sod-class-initializer - value-kind value-form pset - (file-location location)))) - (with-slots (class-initializers) class - (setf class-initializers - (append class-initializers (list initializer)))) + value pset (file-location location)))) + (when add-to-class + (with-slots (class-initializers) class + (setf class-initializers + (append class-initializers (list initializer))))) initializer))) (defmethod make-sod-initializer-using-slot - ((class sod-class) (slot sod-slot) - init-class value-kind value-form pset location) + ((class sod-class) (slot sod-slot) init-class value pset location) (make-instance (get-property pset :initializer-class :symbol init-class) :class class :slot slot - :value-kind value-kind - :value-form value-form + :value value :location (file-location location) :pset pset)) @@ -136,11 +166,62 @@ (declare (ignore slot-names pset)) nil) +(defmethod make-sod-user-initarg + ((class sod-class) name type pset &key default location) + (with-slots (initargs) class + (push (make-instance (get-property pset :initarg-class :symbol + 'sod-user-initarg) + :location (file-location location) + :class class :name name :type type :default default) + initargs))) + +(defmethod make-sod-slot-initarg + ((class sod-class) name nick slot-name pset &key location) + (let ((slot (find-instance-slot-by-name class nick slot-name))) + (make-sod-slot-initarg-using-slot class name slot pset + :location location))) + +(defmethod make-sod-slot-initarg-using-slot + ((class sod-class) name (slot sod-slot) pset &key location) + (with-slots (initargs) class + (with-slots ((type %type)) slot + (setf initargs + (append initargs + (cons (make-instance (get-property pset :initarg-class + :symbol + 'sod-slot-initarg) + :location (file-location location) + :class class :name name + :type type :slot slot) + nil)))))) + +(defmethod sod-initarg-default ((initarg sod-initarg)) nil) + +(defmethod sod-initarg-argument ((initarg sod-initarg)) + (make-argument (sod-initarg-name initarg) + (sod-initarg-type initarg) + (sod-initarg-default initarg))) + +;;;-------------------------------------------------------------------------- +;;; Initialization and teardown fragments. + +(defmethod make-sod-class-initfrag + ((class sod-class) frag pset &key location) + (declare (ignore pset location)) + (with-slots (initfrags) class + (setf initfrags (append initfrags (list frag))))) + +(defmethod make-sod-class-tearfrag + ((class sod-class) frag pset &key location) + (declare (ignore pset location)) + (with-slots (tearfrags) class + (setf tearfrags (append tearfrags (list frag))))) + ;;;-------------------------------------------------------------------------- ;;; Messages. (defmethod make-sod-message - ((class sod-class) name type pset &optional location) + ((class sod-class) name type pset &key location) (with-default-error-location (location) (let* ((msg-class (or (get-property pset :message-class :symbol) (and (get-property pset :combination :keyword) @@ -158,9 +239,11 @@ (defmethod shared-initialize :after ((message sod-message) slot-names &key pset) - (declare (ignore slot-names pset)) (with-slots ((type %type)) message - (check-message-type message type))) + (check-message-type message type)) + (default-slot-from-property (message 'readonlyp slot-names) + (pset :readonly :boolean) + nil)) (defmethod check-message-type ((message sod-message) (type c-function-type)) nil) @@ -172,7 +255,7 @@ ;;; Methods. (defmethod make-sod-method - ((class sod-class) nick name type body pset &optional location) + ((class sod-class) nick name type body pset &key location) (with-default-error-location (location) (let* ((message (find-message-by-name class nick name)) (method (make-sod-method-using-message message class @@ -221,15 +304,108 @@ ((method sod-method) (message sod-message) (type c-type)) (error "Methods must have function type, not ~A" type)) +(export 'check-method-return-type) +(defun check-method-return-type (method-type wanted-type) + "Signal an error unless METHOD-TYPE does not return the WANTED-TYPE." + (let ((method-returns (c-type-subtype method-type))) + (unless (c-type-equal-p method-returns wanted-type) + (error "Method return type ~A should be ~A" + method-returns wanted-type)))) + +(export 'check-method-return-type-against-message) +(defun check-method-return-type-against-message (method-type message-type) + "Signal an error unless METHOD-TYPE and MESSAGE-TYPE return the same type." + (let ((message-returns (c-type-subtype message-type)) + (method-returns (c-type-subtype method-type))) + (unless (c-type-equal-p message-returns method-returns) + (error "Method return type ~A doesn't match message ~A" + method-returns message-returns)))) + +(export 'check-method-argument-lists) +(defun check-method-argument-lists (method-type message-type) + "Signal an error unless METHOD-TYPE and MESSAGE-TYPE have matching argument + lists. + + This checks (a) that the two types have matching lists of mandatory + arguments, and (b) that either both or neither types accept keyword + arguments." + (let ((message-keywords-p (typep message-type 'c-keyword-function-type)) + (method-keywords-p (typep method-type 'c-keyword-function-type))) + (cond (message-keywords-p + (unless method-keywords-p + (error "Method must declare a keyword argument list"))) + (method-keywords-p + (error "Method must not declare a keyword argument list")))) + (unless (argument-lists-compatible-p (c-function-arguments message-type) + (c-function-arguments method-type)) + (error "Method arguments ~A don't match message ~A" + method-type message-type))) + (defmethod check-method-type ((method sod-method) (message sod-message) (type c-function-type)) (with-slots ((msgtype %type)) message - (unless (c-type-equal-p (c-type-subtype msgtype) - (c-type-subtype type)) - (error "Method return type ~A doesn't match message ~A" - (c-type-subtype msgtype) (c-type-subtype type))) - (unless (argument-lists-compatible-p (c-function-arguments msgtype) - (c-function-arguments type)) - (error "Method arguments ~A don't match message ~A" type msgtype)))) + (check-method-return-type-against-message type msgtype) + (check-method-argument-lists type msgtype))) + +;;;-------------------------------------------------------------------------- +;;; Static instances. + +(defmethod shared-initialize :after + ((instance static-instance) slot-names &key pset) + "Initialize a static instance." + (default-slot-from-property (instance 'externp slot-names) + (pset :extern :boolean) + nil) + (default-slot-from-property (instance 'constp slot-names) + (pset :const :boolean) + t)) + +(defmethod make-static-instance ((class sod-class) name initializers + pset location &key) + + ;; Check that the initializers are all for distinct slots. + (find-duplicates (lambda (initializer previous) + (let ((slot (sod-initializer-slot initializer))) + (cerror*-with-location initializer + "Duplicate initializer for ~ + instance slot `~A' in ~ + static instance `~A'" + slot name) + (info-with-location previous + "Previous definition was here"))) + initializers + :key #'sod-initializer-slot) + + ;; Ensure that every slot will have an initializer, either defined directly + ;; on the instance or as part of some class definition. + (let ((have (make-hash-table))) + + ;; First, populate the hash table with all of the slots for which we have + ;; initializers. + (flet ((seen-slot-initializer (init) + (setf (gethash (sod-initializer-slot init) have) t))) + (mapc #'seen-slot-initializer + initializers) + (dolist (super (sod-class-precedence-list class)) + (mapc #'seen-slot-initializer + (sod-class-instance-initializers super)))) + + ;; Now go through all of the slots and check that they have initializers. + (dolist (super (sod-class-precedence-list class)) + (dolist (slot (sod-class-slots super)) + (unless (gethash slot have) + (cerror*-with-location location + "No initializer for instance slot `~A', ~ + required by static instance `~A'" + slot name) + (info-with-location slot "Slot `~A' defined here" slot))))) + + ;; Make the instance. + (make-instance 'static-instance + :class class + :name name + :initializers initializers + :location (file-location location) + :pset pset)) ;;;----- That's all, folks --------------------------------------------------