Static instance support.
[sod] / src / class-make-impl.lisp
index dba6965..b3347bd 100644 (file)
 ;;;--------------------------------------------------------------------------
 ;;; 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)
 ;;; 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)
 ;;; 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))
 
   (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)
 
 (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)
 ;;; 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
     ((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 --------------------------------------------------