| 1 | ;; -*- mode: Lisp; Syntax: Common-Lisp; -*- |
|---|
| 2 | ;;; |
|---|
| 3 | ;;; Copyright (c) 2006 by the authors. |
|---|
| 4 | ;;; |
|---|
| 5 | ;;; See LICENCE for details. |
|---|
| 6 | |
|---|
| 7 | (in-package :defclass-star) |
|---|
| 8 | |
|---|
| 9 | (enable-sharp-boolean-syntax) |
|---|
| 10 | |
|---|
| 11 | ;; set up swank:*readtable-alist* |
|---|
| 12 | #+#.(cl:when (cl:find-package "SWANK") '(:and)) |
|---|
| 13 | (unless (assoc "DEFCLASS-STAR" swank:*readtable-alist* :test #'string=) |
|---|
| 14 | (let ((*readtable* (copy-readtable))) |
|---|
| 15 | (enable-sharp-boolean-syntax) |
|---|
| 16 | (push (cons "DEFCLASS-STAR" *readtable*) swank:*readtable-alist*) |
|---|
| 17 | (push (cons "DEFCLASS-STAR.TEST" *readtable*) swank:*readtable-alist*))) |
|---|
| 18 | |
|---|
| 19 | (defmacro make-name-transformer (&rest elements) |
|---|
| 20 | `(lambda (name definition) |
|---|
| 21 | (declare (ignorable definition)) |
|---|
| 22 | (concatenate-symbol ,@(mapcar (lambda (el) |
|---|
| 23 | (if (and (symbolp el) |
|---|
| 24 | (string= (symbol-name el) "NAME")) |
|---|
| 25 | 'name |
|---|
| 26 | el)) |
|---|
| 27 | elements)))) |
|---|
| 28 | |
|---|
| 29 | ;; more or less public vars (it's discouraged to set them globally) |
|---|
| 30 | (defvar *accessor-name-package* nil |
|---|
| 31 | "A package, or :slot-name means the home-package of the slot-name symbol and nil means *package*") |
|---|
| 32 | (defvar *accessor-name-transformer* 'default-accessor-name-transformer) |
|---|
| 33 | (defvar *automatic-accessors-p* #t) |
|---|
| 34 | |
|---|
| 35 | ;; these control whether the respective names should be exported from *package* (which is samples at macroexpan time) |
|---|
| 36 | (defvar *export-class-name-p* nil) |
|---|
| 37 | (defvar *export-accessor-names-p* nil) |
|---|
| 38 | (defvar *export-slot-names-p* nil) |
|---|
| 39 | |
|---|
| 40 | (defvar *initarg-name-transformer* 'default-initarg-name-transformer) |
|---|
| 41 | (defvar *automatic-initargs-p* #t) |
|---|
| 42 | |
|---|
| 43 | (defvar *slot-definition-transformer* 'default-slot-definition-transformer) |
|---|
| 44 | |
|---|
| 45 | (defun default-slot-definition-transformer (slot-def) |
|---|
| 46 | "Converts illegal (list foo) :type declarations into simple list declarations." |
|---|
| 47 | (let ((name (pop slot-def)) |
|---|
| 48 | (type (getf slot-def :type))) |
|---|
| 49 | (when (and type (listp type) (eq (first type) 'list)) |
|---|
| 50 | (setf (getf slot-def :type) 'list)) |
|---|
| 51 | (push name slot-def) |
|---|
| 52 | slot-def)) |
|---|
| 53 | |
|---|
| 54 | (defvar *allowed-slot-definition-properties* '(:documentation :type :reader :writer :allocation) |
|---|
| 55 | "Holds a list of keywords that are allowed in slot definitions (:accessor and :initarg are implicitly included).") |
|---|
| 56 | |
|---|
| 57 | ;; expand-time temporary dynamic vars |
|---|
| 58 | (defvar *accessor-names*) |
|---|
| 59 | (defvar *slot-names*) |
|---|
| 60 | |
|---|
| 61 | (define-condition defclass-star-style-warning (simple-condition style-warning) |
|---|
| 62 | ()) |
|---|
| 63 | |
|---|
| 64 | (defun style-warn (datum &rest args) |
|---|
| 65 | (warn 'defclass-star-style-warning :format-control datum :format-arguments args)) |
|---|
| 66 | |
|---|
| 67 | (defun default-accessor-name-transformer (name definition) |
|---|
| 68 | (let ((type (getf definition :type)) |
|---|
| 69 | (package (if (packagep *accessor-name-package*) |
|---|
| 70 | *accessor-name-package* |
|---|
| 71 | (case *accessor-name-package* |
|---|
| 72 | (:slot-name (symbol-package name)) |
|---|
| 73 | (:default *package*) |
|---|
| 74 | (t *package*))))) |
|---|
| 75 | (if (eq type 'boolean) |
|---|
| 76 | (let* ((name-string (string name)) |
|---|
| 77 | (last-char (aref name-string (1- (length name-string))))) |
|---|
| 78 | (cond ((char-equal last-char #\p) |
|---|
| 79 | name) |
|---|
| 80 | ;; i like unconditional -p postfix. ymmv. |
|---|
| 81 | #+nil((not (find #\- name-string)) |
|---|
| 82 | (concatenate-symbol name "P" package)) |
|---|
| 83 | (t (concatenate-symbol name "-P" package)))) |
|---|
| 84 | (concatenate-symbol name "-OF" package)))) |
|---|
| 85 | |
|---|
| 86 | (defun default-initarg-name-transformer (name definition) |
|---|
| 87 | (declare (ignorable definition)) |
|---|
| 88 | (concatenate-symbol name #.(symbol-package :asdf))) |
|---|
| 89 | |
|---|
| 90 | (defun process-slot-definition (definition) |
|---|
| 91 | (unless (consp definition) |
|---|
| 92 | (setf definition (list definition))) |
|---|
| 93 | (let ((name (pop definition)) |
|---|
| 94 | (initform 'missing) |
|---|
| 95 | (entire-definition definition)) |
|---|
| 96 | (push name *slot-names*) |
|---|
| 97 | (when (oddp (length definition)) |
|---|
| 98 | (setf initform (pop definition)) |
|---|
| 99 | (setf entire-definition definition) |
|---|
| 100 | (when (eq initform :unbound) |
|---|
| 101 | (setf initform 'missing))) |
|---|
| 102 | (assert (eq (getf definition :initform 'missing) 'missing) () |
|---|
| 103 | ":initform is not allowed by the defclass-star syntax, the initform is taken from the first element of odd length slot definitions.") |
|---|
| 104 | (assert (every #'keywordp (loop for el :in definition :by #'cddr |
|---|
| 105 | collect el)) |
|---|
| 106 | () "Found non-keywords in ~S" definition) |
|---|
| 107 | (destructuring-bind (&key (accessor 'missing) (initarg 'missing) |
|---|
| 108 | (reader 'missing) (writer 'missing) |
|---|
| 109 | &allow-other-keys) |
|---|
| 110 | definition |
|---|
| 111 | (remf-keywords definition :accessor :reader :writer :initform :initarg) |
|---|
| 112 | (let ((unknown-keywords (loop for el :in definition :by #'cddr |
|---|
| 113 | unless (or (member t *allowed-slot-definition-properties*) |
|---|
| 114 | (member el *allowed-slot-definition-properties*)) |
|---|
| 115 | collect el))) |
|---|
| 116 | (when unknown-keywords |
|---|
| 117 | (style-warn "Unexpected properties in slot definition ~S.~%~ |
|---|
| 118 | The unexpected properties are ~S.~%~ |
|---|
| 119 | To avoid this warning (pushnew (or T :your-custom-keyword) defclass-star:*allowed-slot-definition-properties*)" |
|---|
| 120 | entire-definition unknown-keywords)) |
|---|
| 121 | (flet ((provided-p (value) |
|---|
| 122 | (and value |
|---|
| 123 | (not (eq value 'missing))))) |
|---|
| 124 | (prog1 |
|---|
| 125 | (funcall *slot-definition-transformer* |
|---|
| 126 | (append (list name) |
|---|
| 127 | (unless (eq initform 'missing) |
|---|
| 128 | (list :initform initform)) |
|---|
| 129 | (if (and (eq accessor 'missing) |
|---|
| 130 | (eq reader 'missing) |
|---|
| 131 | (eq writer 'missing)) |
|---|
| 132 | (when *automatic-accessors-p* |
|---|
| 133 | (setf accessor (funcall *accessor-name-transformer* name entire-definition)) |
|---|
| 134 | (list :accessor accessor)) |
|---|
| 135 | (append (when (provided-p accessor) |
|---|
| 136 | (list :accessor accessor)) |
|---|
| 137 | (when (provided-p reader) |
|---|
| 138 | (list :reader reader)) |
|---|
| 139 | (when (provided-p writer) |
|---|
| 140 | (list :writer writer)))) |
|---|
| 141 | (if (eq initarg 'missing) |
|---|
| 142 | (when *automatic-initargs-p* |
|---|
| 143 | (list :initarg (funcall *initarg-name-transformer* name entire-definition))) |
|---|
| 144 | (when initarg |
|---|
| 145 | (list :initarg initarg))) |
|---|
| 146 | definition)) |
|---|
| 147 | (when (provided-p accessor) |
|---|
| 148 | (pushnew accessor *accessor-names*)) |
|---|
| 149 | (when (provided-p reader) |
|---|
| 150 | (pushnew reader *accessor-names*)) |
|---|
| 151 | (when (provided-p writer) |
|---|
| 152 | (pushnew (second writer) *accessor-names*)))))))) |
|---|
| 153 | |
|---|
| 154 | (defun extract-options-into-bindings (options) |
|---|
| 155 | (let ((binding-names) |
|---|
| 156 | (binding-values) |
|---|
| 157 | (clean-options)) |
|---|
| 158 | (macrolet ((rebinding-table (&rest args) |
|---|
| 159 | `(case (car option) |
|---|
| 160 | ,@(loop for (arg-name var-name) :on args :by #'cddr |
|---|
| 161 | collect `(,arg-name |
|---|
| 162 | (assert (= (length option) 2)) |
|---|
| 163 | (push ',var-name binding-names) |
|---|
| 164 | (push (second option) binding-values))) |
|---|
| 165 | (t (push option clean-options))))) |
|---|
| 166 | (dolist (option options) |
|---|
| 167 | (rebinding-table |
|---|
| 168 | :accessor-name-package *accessor-name-package* |
|---|
| 169 | :accessor-name-transformer *accessor-name-transformer* |
|---|
| 170 | :automatic-accessors-p *automatic-accessors-p* |
|---|
| 171 | :initarg-name-transformer *initarg-name-transformer* |
|---|
| 172 | :automatic-initargs-p *automatic-initargs-p* |
|---|
| 173 | :export-class-name-p *export-class-name-p* |
|---|
| 174 | :export-accessor-names-p *export-accessor-names-p* |
|---|
| 175 | :export-slot-names-p *export-slot-names-p* |
|---|
| 176 | :slot-definition-transformer *slot-definition-transformer*))) |
|---|
| 177 | (values binding-names binding-values (nreverse clean-options)))) |
|---|
| 178 | |
|---|
| 179 | (defmacro def-star-macro (macro-name expand-to-name) |
|---|
| 180 | `(defmacro ,macro-name (name direct-superclasses direct-slots &rest options) |
|---|
| 181 | (unless (eq (symbol-package name) *package*) |
|---|
| 182 | (style-warn "defclass* for ~A while its home package is not *package* (~A)" |
|---|
| 183 | (let ((*package* (find-package "KEYWORD"))) |
|---|
| 184 | (format nil "~S" name)) *package*)) |
|---|
| 185 | (let ((*accessor-names* nil) |
|---|
| 186 | (*slot-names* nil)) |
|---|
| 187 | (multiple-value-bind (binding-names binding-values clean-options) |
|---|
| 188 | (extract-options-into-bindings options) |
|---|
| 189 | (progv binding-names (mapcar #'eval binding-values) |
|---|
| 190 | (let ((result `(,',expand-to-name ,name |
|---|
| 191 | ,direct-superclasses |
|---|
| 192 | ,(mapcar 'process-slot-definition direct-slots) |
|---|
| 193 | ,@clean-options))) |
|---|
| 194 | (if (or *export-class-name-p* |
|---|
| 195 | *export-accessor-names-p* |
|---|
| 196 | *export-slot-names-p*) |
|---|
| 197 | `(progn |
|---|
| 198 | ,result |
|---|
| 199 | (eval-when (:compile-toplevel :load-toplevel :execute) |
|---|
| 200 | (export (list ,@(mapcar (lambda (el) |
|---|
| 201 | (list 'quote el)) |
|---|
| 202 | (append (when *export-class-name-p* |
|---|
| 203 | (list name)) |
|---|
| 204 | (when *export-accessor-names-p* |
|---|
| 205 | (nreverse *accessor-names*)) |
|---|
| 206 | (when *export-slot-names-p* |
|---|
| 207 | (nreverse *slot-names*))))) |
|---|
| 208 | ,*package*)) |
|---|
| 209 | (find-class ',name nil)) |
|---|
| 210 | result))))))) |
|---|
| 211 | |
|---|
| 212 | (def-star-macro defclass* defclass) |
|---|
| 213 | (def-star-macro defcondition* define-condition) |
|---|