;; Modified for use with the byte code compiler. THe major change is to use
;; the new class method :METHOD, which takes a message selector symbol
;; and an optional function argument. If the function argument is supplied,
;; it is installed as the method. So
;;
;;     (send myclass :method :hello #'(lambda (self x) (format t "Hello, ~s")))
;;
;; installs a method that prints a greeting. This method is a low level method
;; not designed to be used directly. It is designes for use by the defmethod
;; macro. By using this method, no other action is needed to insure that
;; compile-file compiles methods defined with defmethod.
;;
;; Luke Tierney

; useful stuff for object programming

(in-package "XLISP")

(export '(defclass defmethod definst classp))

; filter certain keyword arguments for passing argument list to superclass

(defun remove-keys (keys list)
    (cond ((null keys) list)
	  ((null list) 'nil)
	  ((member (car list) keys)
	   (remove-keys (remove (car list) keys) (cddr list)))
	  (t (cons (car list) (remove-keys keys (cdr list))))))


; fix so that classes can be named (requires PNAME ivar in class Class)
;  The source files have been modified for PNAME instance variable,
;  and printing of class PNAME if it exists.

(send class :method :set-pname
      #'(lambda (self name) (setf pname (string name))))


; *setf* property of SEND is set to allow setting instance variables

(setf (get 'send '*setf*) 
      #'(lambda (obj ivar value) 
		(send obj :set-ivar
#-:packages	      (get ivar 'ivarname)
#+:packages	      ivar
		      value)))

; (defclass <classname> [(<instvars>) [(<classvars>) [<superclass>]]])
; defclass sets up access methods for all instance and class variables!
; an instance variable can be of form <ivar>  or (<ivar> <init>)
; :ISNEW is automatically defined to accept keyword arguments to overide
; default initialization.

(defmacro defclass (name &optional ivars cvars super 
			 &aux (sym (gensym)) (sym2 (gensym)))
; CIVAR is instance variable list with init values removed
    (let ((civars (mapcar #'(lambda (x) (if (consp x) (car x) x))
			  ivars)))

      `(progn ; Create class and assign to global variable
              (setf ,name
		    (send class :new
			  ',civars
			  ',cvars
			  ,@(if super (list super) nil)))

	      ; Set the name ivar of the class
	      (send ,name :set-pname ',name)

	      ; Generate the :<ivar> and :<cvar> methods
	      ,@(mapcar #'(lambda (arg)
			    `(send ,name
				   :method
#-:packages     		   ,(intern (strcat ":" (string arg)))
#+:packages     		   ,(intern (string arg) :keyword)
				   #'(lambda (self) ,arg)))
		        (append civars cvars))

	      ; The method needed to set the instance variables
	      (send ,name :method :set-ivar
		    #'(lambda (self ,sym ,sym2)
		      (case ,sym
			    ,@(mapcar #'(lambda (arg)
#-:packages				        `(,arg (setq ,arg ,sym2))
#+:packages					`(,(intern (string arg)
							   :keyword)
						  (setq ,arg ,sym2))
						)
				      (append civars cvars))
			    (t (send-super :set-ivar ,sym ,sym2)))))

	      ; Set the ivarname property of the :<ivar> symbols
#-:packages   ,@(mapcar #'(lambda (arg)
	      		    `(setf (get
#-:packages     		    ',(intern (strcat ":" (string arg)))
				    'ivarname)
				   ',arg))
		        civars)

	      ; Generate the :ISNEW method
	      (send ,name
		    :method :isnew
		    #'(lambda (self &rest ,sym &key ,@ivars &allow-other-keys)

		    ; first :ISNEW setfs 
		    ;  for all its declared instance variables
		      ,@(mapcar #'(lambda (arg)
				    `(setf (send self
#-:packages     			   	 ,(intern (strcat ":" 
							   (string arg)))
#+:packages     				 ,(intern (string arg)
							  :keyword)
						 )
					   ,arg))
			        civars)

		      ; then the remaining initialization arguments are
		      ;  passed to the superclass.
		      (apply #'send-super
			     (cons ':isnew
				   (remove-keys
				      ',(mapcar #'(lambda (arg)
#-:packages     				    (intern (strcat ":"
							       (string arg)))
#+:packages     				    (intern (string arg)
							    :keyword)
							  )
					        civars)
				      ,sym)))
		      self))
	      ,name)))


; (defmethod <class> <message> (<arglist>) <body>)

(defmacro defmethod (cls message arglist &rest body)
    `(send ,cls
	   :method
	   ,message
	   #'(lambda (self ,@arglist) ,@body)))

; (definst <class> <instname> [<args>...])

(defmacro definst (cls name &rest args)
    `(setf ,name
           (send ,cls
	         :new
		 ,@args)))

; (extensions suggested by Jim Ferrans)

(defun classp (name)
       (when (objectp name)
	     (eq (send name :class) class)))

(defmethod class :superclass () superclass)
(defmethod class :messages () messages)

(defmethod object :superclass () nil)

(defmethod object :ismemberof (cls)
	   (eq (send self :class) cls))

(defmethod object :iskindof (cls)
	   (do ((this (send self :class) (send this :superclass)))
	       ((or (null this)(eq this cls))
		(eq this cls))))

(defmethod object :respondsto (selector &aux temp)
	   (do ((this (send self :class) (send this :superclass)))
	       ((or (null this)
		    (setq temp 
			  (not (null (assoc selector 
				       (send this :messages))))))
		temp)
	       (setf temp nil)))


(defmethod class :ivars () ivars)

(defmethod class :pname () pname)

; :Storeon returns a list that can be executed to re-generate the object.
; It relies on the object's class being created using DEFCLASS,   so the
; instance variables can be generated.


(defmethod object :storeon (&aux cls ivlist res)
	   (setq cls
		 (send self :class)
		 ivlist
		 (do ((ivars (send cls :ivars)
			     (append (send super :ivars) ivars))
		      (super (send cls :superclass)
			     (send super :superclass)))
		     ((eq super object) ivars))
		 res
		 (mapcan #'(lambda (x) 
				   (let ((temp
#-:packages     			  (intern (strcat ":" (string x)))
#+:packages     			  (intern (string x) :keyword)
					  ))
					(list temp
					      (let ((y (send self temp)))
						   (if (and y 
							    (or (symbolp y)
								(consp y)))
						       (list 'quote y)
						       y)))))
				   ivlist))
	   (append (list 'send (intern (send cls :pname)) ':new)
		   res))

; For classes we must use a different tact.
; We will return a PROGN that uses SENDs to create the class and any methods.
; It also assumes the global environment. None of the DEFxxx functions
; are needed to do this.

; because of the subrs used in messages, :storeon cannot be  used to
; generate a reconstructable copy of classes Object and Class.

; Class variables are not set, because there are no class methods in XLISP
; to do this (one would have to create an instance, and send messages to
; the instance, and I feel that is going too far).


(defmethod class :storeon (&aux (classname (intern pname)))
   (nconc (list 'progn)
	  (list (list 'setq classname
		      (list 'send 'class :new ivars cvars 
			    (if superclass 
				(intern (send superclass :pname))
				nil))))
	  (list (list 'send classname :set-pname pname))
	  (mapcar #'(lambda (mess &aux 
				  (val (if (typep (cdr mess) 'closure)
					   (get-lambda-expression (cdr mess))
					   (list nil nil mess))))
			    (list 'send classname :answer
				  (first mess)
				  (list 'quote (cdadr val))
				  (list 'quote (cddr val))))
		  messages)))

(setq *features* (cons :classes *features*))


syntax highlighted by Code2HTML, v. 0.9.1