;;;-*-Mode:LISP; Package:(PCL LISP 1000); Base:10; Syntax:Common-lisp -*-
;;;
;;; *************************************************************************
;;; Copyright (c) 1985, 1986, 1987 Xerox Corporation.  All rights reserved.
;;;
;;; Use and copying of this software and preparation of derivative works
;;; based upon this software are permitted.  Any distribution of this
;;; software or derivative works must comply with all applicable United
;;; States export control laws.
;;; 
;;; This software is made available AS IS, and Xerox Corporation makes no
;;; warranty about the software, its performance or its conformity to any
;;; specification.
;;; 
;;; Any person obtaining a copy of this software is requested to send their
;;; name and post office or electronic mail address to:
;;;   CommonLoops Coordinator
;;;   Xerox Artifical Intelligence Systems
;;;   2400 Hanover St.
;;;   Palo Alto, CA 94303
;;; (or send Arpanet mail to CommonLoops-Coordinator.pa@Xerox.arpa)
;;;
;;; Suggestions, comments and requests for improvements are also welcome.
;;; *************************************************************************
;;;

(in-package 'pcl)

(defvar *early-defclass-forms*
  '(

    (defclass t () ())

    (defclass object (t) ())

    (defclass essential-class (object)
	 ((name
	    :initform nil
	    :accessor class-name)
	  (class-precedence-list
	    :initform ()
	    :accessor class-precedence-list
	    :accessor class-class-precedence-list)
	  (local-supers
	    :initform ()
	    :accessor class-local-supers)
	  (local-slots
	    :initform ()
	    :accessor class-local-slots)
	  (direct-subclasses
	    :initform ()
	    :accessor class-direct-subclasses)
	  (direct-methods
	    :initform ())
	  (forward-referenced-supers
	    :initform ()
	    :accessor class-forward-referenced-supers)))

    (defclass basic-class (essential-class)
	 ((no-of-instance-slots
	    :initform 0
	    :accessor class-no-of-instance-slots)
	  (instance-slots
	    :initform ())
	  (non-instance-slots
	    :initform ()
	    :accessor class-non-instance-slots)
	  (wrapper
	    :initform nil
	    :accessor class-wrapper)
	  (direct-generic-functions
	    :initform ()
	    :accessor class-direct-generic-functions)
	  (generic-functions-which-combine-methods
	    :initform ()
	    :accessor class-generic-functions-which-combine-methods)
	  (prototype
	    :initform nil)
	  (options
	    :initform ()
	    :accessor class-options)
	  (constructors
	    :initform ()
	    :accessor class-constructors)))

    (defclass class (basic-class) ())

    (defclass standard-slotd (object)
	 ((name
	    :initform nil)
	  (keyword
	    :initform nil
	    :accessor slotd-keyword)
	  (initform
	    :initform nil
	    :accessor slotd-initform)
	  (accessors
	    :initform nil
	    :accessor slotd-accessors)
	  (readers
	    :initform nil
	    :accessor slotd-readers)
	  (allocation
	    :initform nil
	    :accessor slotd-allocation)
	  (type
	    :initform nil
	    :accessor slotd-type)))
      

    ))

(defvar *fsc-defclass-forms*
  '((defclass funcallable-standard-class (class)
      ())))

(defvar *methods-defclass-forms*
  '(
    
    (defclass essential-method ()
        ()
      (:accessor-prefix method-))
        
    
    (defclass basic-method (essential-method)
	 ((function nil)
	  (generic-function nil)
	  (type-specifiers ())
	  (arglist ())
	  (options :initform ())
	  (documentation nil))
      (:accessor-prefix method-))
    
    (defclass method (basic-method)
      ())

    (defclass standard-reader/writer-method (method)
	 ((slot-name nil))
      ;; There is a hand coded reader method for this which appears
      ;; in the beginning of methods.  See the comment there.
      ;(:reader-prefix reader/writer-method)
      )

    (defclass standard-reader-method (standard-reader/writer-method) ())
    (defclass standard-writer-method (standard-reader/writer-method) ())
    
    (defclass generic-function ()
      ((name nil)
       (methods ())
       (discriminating-function ())
       (classical-method-table :initform nil)
       (cache ())
       (pretty-arglist ())
       (method-class (class-named 'method)))
      (:metaclass funcallable-standard-class)
      (:accessor-prefix generic-function-))
    
    ;(defclass method-combination-mixin ()
    ;     ((method-combination-type :daemon)
    ;      (method-combination-parameters ())
    ;      (methods-combine-p ()))
    ;  (:metaclass funcallable-standard-class)
    ;  (:accessor-prefix ||))
    
    (defclass standard-generic-function (generic-function)
      ((dispatch-order
	 :initform :default
	 :accessor generic-function-dispatch-order)
       (method-combination-type
	 :initform :daemon
	 :accessor method-combination-type)
       (method-combination-parameters
	 :initform ()
	 :accessor method-combination-parameters)
       (methods-combine-p
	 :initform ()
	 :accessor methods-combine-p))
      (:metaclass funcallable-standard-class))))

;;;
;;; make-setf-method-lambda-list is used by any part of PCL that has to
;;; construct the lambda-list of a setf-method from an access lambda list
;;; and a new value lambda list.  This function is not (yet) a documented
;;; part of CLOS, but it is a documented part of PCL.
;;; 
(defun make-setf-method-lambda-list (access-lambda-list new-value-lambda-list)
  (when (or (cdr new-value-lambda-list)
	    (memq (car new-value-lambda-list) lambda-list-keywords))
    (error "The new-value lambda-list is only allowed to contain one~%~
            argument, and it must be a required argument.~%~
            The new-value lambda-list ~S is illegal."
	   new-value-lambda-list))
  (let* ((setf-ll (list nil))
	 (setf-tail setf-ll)
	 (access-tail access-lambda-list))
    (loop
      (cond ((null access-tail)
	     (setf (cdr setf-tail) (list (car new-value-lambda-list)))
	     (return (cdr setf-ll)))
	    ((memq (car access-tail) lambda-list-keywords)
	     (setf (cdr setf-tail) (cons (car new-value-lambda-list)
					 (copy-list access-tail)))
	     (return (cdr setf-ll)))
	    (t
	     (setf (cdr setf-tail) (list (pop access-tail))
		   setf-tail (cdr setf-tail)))))))





;;;
;;; This is like fdefinition on the Lispm.  If Common Lisp had something like
;;; function specs I wouldn't need this.  On the other hand, I don't like the
;;; way this really works so maybe function specs aren't really right either?
;;; 
;;; I also don't understand the real implications of a Lisp-1 on this sort of
;;; thing.  Certainly some of the lossage in all of this is because these
;;; SPECs name global definitions.
;;;
;;; Note that this implementation is set up so that an implementation which
;;; has a 'real' function spec mechanism can use that instead and in that way
;;; get rid of setf generic function names.
;;;
(defmacro parse-gspec (spec
		       (non-setf-var . non-setf-case)
		       (setf-var . setf-case))
  (declare (indentation 1 1))
  (once-only (spec)
    `(cond ((symbolp ,spec)
	    (let ((,non-setf-var ,spec)) ,@non-setf-case))
	   ((and (listp ,spec)
		 (eq (car ,spec) 'setf)
		 (symbolp (cadr ,spec)))
	    (let ((,setf-var (cadr ,spec))) ,@setf-case))
	   (t
	    (error "Can't understand ~S as a generic function specifier.~%~
                    It must be either a symbol which can name a function or~%~
                    a like ~S, where the car is the symbol ~S and the cadr~%~
                    is a symbol which can name a generic function."
		   ,spec '(setf <foo>) 'setf)))))

;;;
;;; Note that this is just one possible implementation of these functions.  In
;;; particular, an implementation that has some 'real' function spec mechanism
;;; is free to redefine these functions not to use these crufty setf generic
;;; function names.
;;;
(defvar *setf-generic-function-names* (make-hash-table :size 100))

(defun get-setf-generic-function-name (name)
  (or (gethash name *setf-generic-function-names*)
      (setf (gethash name *setf-generic-function-names*)
	    (intern (string-append "setf  " name)
		    (symbol-package name)))))

(defun gboundp (spec)
  (parse-gspec spec
    (name (fboundp name))
    (name (fboundp (get-setf-generic-function-name name)))))

(defun gdefinition (spec)
  (parse-gspec spec
    (name (or (macro-function name)		;??
	      (symbol-function name)))
    (name (symbol-function (get-setf-generic-function-name name)))))

(defun set-gdefinition (spec new-value)
  (parse-gspec spec
    (name (setf (symbol-function name)
		new-value))
    (name (setf (symbol-function (get-setf-generic-function-name name))
		new-value))))

(defsetf gdefinition set-gdefinition)


;;;
;;; This is the documented function (in the CLOS spec) for getting at the setf
;;; generic function for a symbol.  Of course propagating all these different
;;; namespaces this way is folly, but since we are on a (losing) roll...
;;; 
(defun GET-SETF-GENERIC-FUNCTION (name)
  (gdefinition `(setf ,name)))

(defsetf get-setf-generic-function (name) (new-value)
  `(setf (gdefinition ,name) ,new-value))


;;;
;;; do-defmethod-setf-defsetf is called whenever any part of PCL (typically
;;; load-defmethod-setf) wants to do a 'defsetf' to establish the setf
;;; generic function for a symbol.
;;; 
(defun do-defmethod-setf-defsetf (generic-function-name
				  arglist
				  &optional (new-value-arglist '(new-value)))
  (when (member '&aux arglist)
    (setq arglist (reverse arglist))
    (loop (when (eq (pop arglist) '&aux)
	    (return (setq arglist (nreverse arglist))))))
  (let* ((setf-name (get-setf-generic-function-name generic-function-name))
	 (setf-ll (make-setf-method-lambda-list arglist new-value-arglist)))
    (do-defsetf generic-function-name
		arglist		
		new-value-arglist
		``(,',setf-name
		   ,,@(remove-if #'(lambda (x)
				     (member x lambda-list-keywords))
				 setf-ll)))))

(defun do-defsetf (access store-or-args &optional store-vars &rest body)
  (let #+Symbolics ((si:inhibit-fdefine-warnings t))
       #-Symbolics ()
    #+Lispm (setq body (copy-list body))
    (if body
	(eval `(defsetf ,access ,store-or-args ,store-vars ,@body))
	(eval `(defsetf ,access ,store-or-args)))))



(defun do-deftype (name lambda-list &rest body)
  (let #+Symbolics ((si:inhibit-fdefine-warnings t))
       #-Symbolics ()
    #+Lispm (setq body (copy-list body))
    (eval `(deftype ,name ,lambda-list ,@body))))


(defun make-type-predicate-name (class-name)
  (intern (string-append class-name " predicate")
	  (symbol-package class-name)))


;;;
;;; Do the defsetfs for accessors defined by defclass's in the bootstrap.
;;; These have to be here because we want to be able to compile setfs of
;;; calls to those accessors before we have actually been able to evaluate
;;; those defclass forms.
;;;
(defun define-early-setfs-and-type-predicates ()
  (dolist (forms-var '(*early-defclass-forms*
		       *fsc-defclass-forms*
		       *methods-defclass-forms*))
    (dolist (defclass (eval forms-var))
      (destructuring-bind (ignore name supers slots . options)
			  defclass      
	(unless (eq name 't)
	  (do-deftype name
		      ()
		      `'(satisfies ,(make-type-predicate-name name))))
	
	(dolist (slot slots)
	  (let ((slot-options (cdr slot)))
	    (loop (when (null slot-options) (return t))
		  (when (eq (car slot-options) ':accessor)
		    (do-defmethod-setf-defsetf (cadr slot-options)
					       (list name)))
		  (setq slot-options (cddr slot-options)))))

	(dolist (option options)
	  (when (and (listp option)
		     (eq (car option) :accessor-prefix))
	    (setq option (cadr option))
	    (dolist (slot slots)
	      (if (null option)
		  (do-defmethod-setf-defsetf (car slot) (list name))
		  (do-defmethod-setf-defsetf
		    (intern (string-append (symbol-name option)
					   (symbol-name (car slot))))
		    (list name))))))))))


(eval-when (load eval)
  (define-early-setfs-and-type-predicates))

;;;
;;; Extra little defsetfs which we need now.
;;; 
(defsetf class-instance-slots set-class-instance-slots)
(defsetf slotd-name set-slotd-name)
(do-defmethod-setf-defsetf 'class-options '(standard-class))

(defsetf slot-value set-slot-value)

(defsetf slot-value-always (object slot-name &optional default) (new-value)
  `(put-slot-always ,object ,slot-name ,new-value))