(defpackage :clos* (:use #+genera clos #+MCL ccl common-lisp) (:export "DEFCLASS*" "DEFMETHOD*")) (in-package :clos*) #| ###################################################################### Alternate syntax for CLOS Copyright © 1994-97 Michael Travers Permission is given to use and modify this code as long as the copyright notice is preserved. Send questions, comments, and fixes to mt@media.mit.edu. ------------------------------------------------------------------------- This package provides two defining forms, defclass* and defmethod*, that work like the ordinary CLOS forms but provide some of the convenience features of the Flavors system. Initially developed to run Flavors code under CLOS without conversion, CLOS* is also useful for rapid prototyping since it allows class and method definitions to expressed more succinctly than the default CLOS syntax. defclass* uses a more convienient defflavor-like syntax for expressing slot options. Example: (defclass* pixmap-dialog-item (dialog-item) (pixmap (dispose nil)) ; slot with an initial value of NIL :initable-instance-variables) ; define init keywords for all slots defmethod* makes the slots of the first argument available as symbols (that is, it does an implicit (with-slots <1st-arg> ...)). Purists will disapprove. WITH-SLOTS generates calls to SLOT-VALUE, which is generally slower than using accessors. WARNING: defmethod* requires that the class be fully defined at compile time. Compiling a defclass* form to a file will also define the class in Lisp, but compiling a normal defclass won't. So, using defclass and defmethod* together may break depending on your compilation and loading scheme. History: Based on my old Artificial Flavors package PCL version 31 March 90 CLOS version 13 August 90 6/13/97 7:52pm DEFCLASS* can deal with DEFCLASS style iv-defs ###################################################################### |# (defmacro defclass* (name components ivs &rest options) (let ((reader-slots nil) (writer-slots nil) (init-slots nil) (bare-ivs nil) (class-options nil) (keyword-package (find-package 'keyword))) (dolist (option options) (let ((option-name (if (listp option) (car option) option)) (option-value (if (listp option) (cdr option) nil))) (flet ((spec-or-all-ivs (spec) (or spec (mapcar #'(lambda (iv) (if (listp iv) (car iv) iv)) ivs)))) (case option-name (:writable-instance-variables (setq writer-slots (spec-or-all-ivs option-value)) (setq reader-slots (nunion reader-slots (spec-or-all-ivs option-value)))) (:readable-instance-variables (setq reader-slots (nunion reader-slots (spec-or-all-ivs option-value)))) (:initable-instance-variables (setq init-slots (spec-or-all-ivs option-value))) (t (push option class-options)))))) (setq reader-slots (set-difference reader-slots writer-slots)) (flet ((process-iv (iv-form) (let ((iv (if (listp iv-form) (car iv-form) iv-form))) (push iv bare-ivs) `(,iv ,@(if (listp iv-form) (if (= 2 (length iv-form)) `(:initform ,(cadr iv-form)) (cdr iv-form))) ,@(if (member iv init-slots) `(:initarg ,(intern (symbol-name iv) keyword-package))) ,@(if (member iv reader-slots) `(:reader ,(symbol-conc name "-" iv))) ,@(if (member iv writer-slots) `(:accessor ,(symbol-conc name "-" iv))))))) (let ((defclass-form `(defclass ,name ,components ,(mapcar #'process-iv ivs) ,@class-options))) `(progn (eval-when (:compile-toplevel) ,defclass-form) ,defclass-form)) ))) (defun symbol-conc (&rest components) (intern (apply #'concatenate 'string (mapcar #'string components)))) ;;; If you use defmethod* but don't make use of any of the defined slots, you will get a warning ;;; that a temporary variable (generated by with-slots) is unused. I'm leaving this ;;; in to discourage the use of defmethod* unless necessary. (defmacro defmethod* (&rest args &environment env) (multiple-value-bind (name qualifiers lambda-list body) (parse-defmethod args) (multiple-value-bind (decls main-body) (split-off-declarations body) (let* ((class (cadar lambda-list)) (slots (slots-for-class class))) (unless slots (warn "No slots for ~A, may be some finalization problems" class)) `(defmethod ,name ,@qualifiers ,lambda-list ,@decls (with-slots ,slots ,(caar lambda-list) ;; Eliminate compiler warnings #+:CCL (declare (ignore-if-unused ,@slots)) ,@main-body)))))) (setf (arglist 'defmethod*) '(name |{method-qualifier}*| specialized-lambda-list &body body)) (defun parse-defmethod (form) (let ((name (first form)) (qualifiers nil)) (do ((rest (cdr form) (cdr rest))) ((not (symbolp (car rest))) (values name (nreverse qualifiers) (car rest) (cdr rest))) (push (car rest) qualifiers)))) (defun slots-for-class (class-name) (let ((class (find-class class-name))) (nconc (mapcar #'slot-definition-name (class-instance-slots class)) (mapcar #'slot-definition-name (class-class-slots class))))) (defun split-off-declarations (body) (do ((rest body (cdr rest)) (declarations nil)) ((null rest) (values declarations nil)) (if (or (stringp (car rest)) (and (listp (car rest)) (eq 'declare (car (car rest))))) (push (car rest) declarations) (return-from split-off-declarations (values (nreverse declarations) rest))))) #+MCL (add-definition-type 'class "CLASS*") #+MCL (add-definition-type 'method "METHOD*") (export '(defmethod* defclass*)) (provide :closstar)