src/method-impl.lisp, etc.: Add a `readonly' message property.
[sod] / src / class-make-impl.lisp
index 32b2e61..1da8bac 100644 (file)
@@ -7,7 +7,7 @@
 
 ;;;----- Licensing notice ---------------------------------------------------
 ;;;
 
 ;;;----- Licensing notice ---------------------------------------------------
 ;;;
-;;; This file is part of the Sensble Object Design, an object system for C.
+;;; This file is part of the Sensible Object Design, an object system for C.
 ;;;
 ;;; SOD is free software; you can redistribute it and/or modify
 ;;; it under the terms of the GNU General Public License as published by
 ;;;
 ;;; SOD is free software; you can redistribute it and/or modify
 ;;; it under the terms of the GNU General Public License as published by
 
    Return the most specific metaclass of any of the CLASS's direct
    superclasses."
 
    Return the most specific metaclass of any of the CLASS's direct
    superclasses."
-  (do ((supers (sod-class-direct-superclasses class) (cdr supers))
-       (meta nil (let ((candidate (sod-class-metaclass (car supers))))
-                  (cond ((null meta) candidate)
-                        ((sod-subclass-p meta candidate) meta)
-                        ((sod-subclass-p candidate meta) candidate)
-                        (t (error "Unable to choose metaclass for `~A'"
-                                  class))))))
-      ((endp supers) meta)))
+
+  (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:
 
 
 (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
 
      * `: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)))
 
 
   ;; 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)))
 
-  ;; If no metaclass, guess one in a (Lisp) class-specific way.
-  (default-slot-from-property (class 'metaclass slot-names)
-      (pset :metaclass :id meta (find-sod-class meta))
-    (guess-metaclass class))
+  ;; 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)
 
   ;; If no chain-link, then start a new chain here.
   (default-slot-from-property (class 'chain-link slot-names)
 ;;; Slots.
 
 (defmethod make-sod-slot
 ;;; 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)
   (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)
     (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
       (with-slots (slots) class
-       (setf slots (append slots (list slot)))))))
+       (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)
   "This method does nothing.
 
 (defmethod shared-initialize :after ((slot sod-slot) slot-names &key pset)
   "This method does nothing.
 ;;; Slot initializers.
 
 (defmethod make-sod-instance-initializer
 ;;; 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)
   (with-default-error-location (location)
     (let* ((slot (find-instance-slot-by-name class nick name))
   (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
       (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 initializer
+         (setf instance-initializers
+               (append instance-initializers (list initializer)))))
+      initializer)))
 
 (defmethod make-sod-class-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)
   (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
   (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))))
+                        value pset (file-location location))))
       (with-slots (class-initializers) class
        (setf class-initializers
       (with-slots (class-initializers) class
        (setf class-initializers
-             (append class-initializers (list initializer)))))))
+             (append class-initializers (list initializer))))
+      initializer)))
 
 (defmethod make-sod-initializer-using-slot
 
 (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
   (make-instance (get-property pset :initializer-class :symbol init-class)
                 :class class
                 :slot slot
-                :value-kind value-kind
-                :value-form value-form
-                :location location
+                :value value
+                :location (file-location location)
                 :pset pset))
 
 (defmethod shared-initialize :after
                 :pset pset))
 
 (defmethod shared-initialize :after
   (declare (ignore slot-names pset))
   nil)
 
   (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
 ;;;--------------------------------------------------------------------------
 ;;; 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)
   (with-default-error-location (location)
     (let* ((msg-class (or (get-property pset :message-class :symbol)
                          (and (get-property pset :combination :keyword)
                                   :location (file-location location)
                                   :pset pset)))
       (with-slots (messages) class
                                   :location (file-location location)
                                   :pset pset)))
       (with-slots (messages) class
-       (setf messages (append messages (list message)))))))
+       (setf messages (append messages (list message))))
+      message)))
 
 (defmethod shared-initialize :after
     ((message sod-message) slot-names &key pset)
 
 (defmethod shared-initialize :after
     ((message sod-message) slot-names &key pset)
-  (declare (ignore slot-names pset))
-  (with-slots (type) message
-    (check-message-type message type)))
+  (with-slots ((type %type)) message
+    (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)
 
 (defmethod check-message-type ((message sod-message) (type c-function-type))
   nil)
 ;;; Methods.
 
 (defmethod make-sod-method
 ;;; 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
                                                  type body pset
                                                  (file-location location))))
       (with-slots (methods) class
   (with-default-error-location (location)
     (let* ((message (find-message-by-name class nick name))
           (method (make-sod-method-using-message message class
                                                  type body pset
                                                  (file-location location))))
       (with-slots (methods) class
-       (setf methods (append methods (list method)))))))
+       (setf methods (append methods (list method))))
+      method)))
 
 (defmethod make-sod-method-using-message
     ((message sod-message) (class sod-class) type body pset location)
 
 (defmethod make-sod-method-using-message
     ((message sod-message) (class sod-class) type body pset location)
                 :class class
                 :type type
                 :body body
                 :class class
                 :type type
                 :body body
-                :location location
+                :location (file-location location)
                 :pset pset))
 
 (defmethod sod-message-method-class
                 :pset pset))
 
 (defmethod sod-message-method-class
   (declare (ignore slot-names pset))
 
   ;; Check that the arguments are named if we have a method body.
   (declare (ignore slot-names pset))
 
   ;; Check that the arguments are named if we have a method body.
-  (with-slots (body type) method
+  (with-slots (body (type %type)) method
     (unless (or (not body)
                (every (lambda (arg)
                         (or (eq arg :ellipsis)
                             (argument-name arg)
     (unless (or (not body)
                (every (lambda (arg)
                         (or (eq arg :ellipsis)
                             (argument-name arg)
-                            (eq (argument-type arg) (c-type void))))
+                            (c-type-equal-p (argument-type arg)
+                                            c-type-void)))
                       (c-function-arguments type)))
       (error "Abstract declarators not permitted in method definitions")))
 
   ;; Check the method type.
                       (c-function-arguments type)))
       (error "Abstract declarators not permitted in method definitions")))
 
   ;; Check the method type.
-  (with-slots (message type) method
+  (with-slots (message (type %type)) method
     (check-method-type method message type)))
 
 (defmethod check-method-type
     ((method sod-method) (message sod-message) (type c-type))
   (error "Methods must have function type, not ~A" type))
 
     (check-method-type method message type)))
 
 (defmethod check-method-type
     ((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))
 (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))))
+  (with-slots ((msgtype %type)) message
+    (check-method-return-type-against-message type msgtype)
+    (check-method-argument-lists type msgtype)))
 
 ;;;----- That's all, folks --------------------------------------------------
 
 ;;;----- That's all, folks --------------------------------------------------