;;; -*- Mode:Lisp; Readtable:ZL; Package:USER; Base:8; Patch-File:T -*-
;;; Patch file for System version 121.35
;;; Reason:
;;;  Mostly Brand S compatible DEFGENERIC.
;;; Written 17-Feb-87 15:03:07 by RpK (Robert P. Krajewski) at site LMI Cambridge
;;; while running on Cthulhu from band 3
;;; with Experimental System 121.34, Experimental Lambda-Diag 15.0, Experimental ZMail 70.2, Experimental KERMIT 32.0, Experimental Unix-Interface 10.0, Experimental Local-File 72.0, Experimental FILE-Server 21.0, Experimental Tape 13.0, Experimental Site Data Editor 5.0, Experimental GRUNCH 4.5, microcode 1742, SDU Boot Tape 3.12, SDU ROM 102, the old ones.

(eval-when (load) ; sigh....

(intern "DEFGENERIC" "GLOBAL")
(export "DEFGENERIC" "GLOBAL")

)

; From modified file DJ: L.SYS2; FLAVOR.LISP#315 at 17-Feb-87 15:35:54
#8R SYSTEM-INTERNALS#: 
(COMPILER-LET ((*PACKAGE* (PKG-FIND-PACKAGE "SYSTEM-INTERNALS")))
  (COMPILER::PATCH-SOURCE-FILE "SYS: SYS2; FLAVOR  "

(eval-when (load compile eval)
(defun arglist-has-lambda-list-keywords-p (arglist)
  (lisp:some #'(lambda (x) (member x lambda-list-keywords)) (the list arglist)))
)

;;; Tentative support for generic functions.
;;; The main motivation for this is to make it easy to define functional interfaces
;;; to system facilities that used to only have message interfaces.
(defvar *function-support* '())

(defstruct (function-support (:type list) (:conc-name fs-))
  function
  message
  optimizer)

(defun find-function-support (function)
  (lisp:assoc function *function-support*))

(defun generic-function-message (function)
  (fs-message (find-function-support function)))

;;; Let's get rid of obsolete messages while we're are it.
(defvar *message-synonyms* '())

(defsubst get-message-synonym (message)
  (cdr (lisp:assoc message *message-synonyms*)))

(defun find-message-support-1 (message)
  (car (lisp:member message *function-support* :key #'fs-message)))

(defun find-message-support (message)
  (find-message-support-1 (or (get-message-synonym message) message)))

(defmacro defmessage-synonym (message for)
  `(set-message-synonym ',message ',for))

(defun set-message-synonym (message for)
  (setq *function-support* (lisp:delete (find-message-support message) *function-support*))
  (let ((entry (lisp:assoc message *message-synonyms*)))
    (if entry
	(setf (cdr entry) for)
      (consing-in-area (permanent-storage-area)
	(push (cons message for) *message-synonyms*))))
  message)
    
(defun message-generic-function (message)
  (fs-function (find-message-support message)))

(defun msg-function (message)
  (or (message-generic-function message)
      (error "No generic function for message ~S" message)))

(defun function-msg (function)
  (or (generic-function-message function)
      (error "No message for generic function ~S" function)))
  
;;; Note that this preserves optimizers
(defun set-generic-operation (function flavor-message)
  (let ((fs (find-function-support function)))
    (if fs
	(setf (fs-message fs) flavor-message)
      (consing-in-area (permanent-storage-area)
	(push (make-function-support :function function :message flavor-message)
	      *function-support*))))
  function)

(defun set-optimizer-support (function optimizer)
  (let ((fs (find-function-support function)))
    (unless fs
      (error "No support previously defined for ~S" function))
    (setf (fs-optimizer fs) optimizer)))

(defun optimize-generic-call (form)
  (let ((optimizer (fs-optimizer (find-function-support (car form)))))
    (if optimizer
	(funcall optimizer form)
      form)))

(defun generic-inline-send (form)
  (let ((message (generic-function-message (car form))))
    (if message
	`(zl:send ,(cadr form) ',message ,@(cddr form))
      form)))

(defvar *use-generic-function-as-message* nil)

(defun check-compatible-message (generic-function-name message)
  (cond (message)
	(*use-generic-function-as-message*
	 (setq message generic-function-name)
	 (when (eq *use-generic-function-as-message* :warn)
	   (warn "No explicit message message for generic function ~S." generic-function-name)))
	(t
	 (multiple-cerror '(no-compatible-message) ()
	     ("No compatible message for generic function ~S." generic-function-name)
	   ("Use the generic function name as the message."
	    (setq message generic-function-name))
	   ("Supply a message name for the generic function."
	    (loop
	      (setq message (prompt-and-read (list :eval-read :default generic-function-name)
					      "Message name for ~S: " generic-function-name))
	      (if (typep message '(and symbol (not null)))
		  (return nil)
		(format *query-io* "~&~S is not a valid message name.~%" message)))))))
  message)

(defun expand-defgeneric-old-flavors (function arglist options)
  (let ((declarations '()) (documentation nil) (message nil) (pending-methods '())
	;; There seems to be a bug in the spec -- Brand S says that :METHOD-ARGLIST
	;; is used only by :FUNCTION (which we can't implement right now).  The :METHOD
	;; option, which allows DEFMETHODS to happen, says it uses main arglist.  But it
	;; would seem more proper to use the :METHOD-ARGLIST if :METHODS and :FUNCTION were
	;; supplied as well.
	(method-arglist (cdr arglist)))
    (dolist (option options)
      (typecase option
	(string (setq documentation option))
	((member :inline-methods)) ; ignore
	(cons
	 (let ((keyword (car option)))
	   (case keyword
	     (:compatible-message
	      (setq message (second option))
	      (check-type message (and symbol (not null))))
	     (declare
	      (setq declarations (nconc declarations (copy-list (cdr option)))))
	     (:documentation
	      (setq documentation (cadr option))
	      (check-type documentation string))
	     ((:dispatch :function)
	      (cerror "Ignore the option."
		      "The old flavors version of ~S cannot implement the ~S option."
		      'defgeneric keyword))
	     ((:inline-methods :optimize)) ; safe to ignore
	     (:method-arglist (setq method-arglist (cdr option)))
	     (:method (setq pending-methods (nconc pending-methods (ncons (cdr option)))))
	     (:method-combination
	      (warn "The old flavors version of ~S cannot implement ~S; use ~S instead."
		    'defgeneric keyword 'defflavor))
	     (otherwise
	      (error "Unknown option keyword to ~S: ~S" 'defgeneric keyword)))))
	(t (error "Strange object in ~S options: ~S" 'defgeneric option))))
    (setq message (check-compatible-message function message))
    (let ((defmethods (mapcar #'(lambda (spec)
				  `(defmethod (,(caar spec) ,@(cdar spec) ,message)
					      ,method-arglist ; See note above
				     ,@(cdr spec)))
			      pending-methods))
	  (hairy-p (arglist-has-lambda-list-keywords-p arglist)))
      (values
	(list* 'progn
	       (if hairy-p
		   `(defun ,function (,(car arglist) &rest .args.)
		      ,@(and documentation (list documentation))
		      (declare (zl:arglist ,@arglist) ,@declarations)
		      (zl:lexpr-send ,(car arglist) ',message .args.))
		 `(zl:defsubst ,function ,arglist
		    ,@(and documentation (list documentation))
		    (declare ,@declarations)
		    (zl:send ,(car arglist) ',message ,@(cdr arglist))))
	       defmethods)
	message
	(and hairy-p 'generic-inline-send)))))

(defmacro defgeneric (function arglist &rest options)
  "Define a generic FUNCTION with ARGLIST.
ARGLIST should have at least one parameter.

Understood options are:
 a string : the documentation string.  
/(:DOCUMENTATION string) works as well.
/(DECLARE declarations), which can be repeated any number of times.
 The declarations should refer to the function as whole.
/(:COMPATIBLE-MESSAGE message), specifying which message implements this
 generic function.  In old Flavors, this is required.
/(:METHOD (flavor . options) body), which defines a method for the flavor
 with body.  This can appear more than once, with different flavors.
/(:METHOD-ARGLIST . arglist), specifying the argument list of the methods
 which implements this generic function.

:INLINE-METHODS and :OPTIMIZE have no effect.  :METHOD-COMBINATION is not
 accepted and must be specified under DEFFLAVOR.
:DISPATCH and :FUNCTION are currently not allowed."
  (check-type function (and symbol (not keyword)))
  (check-type arglist cons)
  (assert (and (symbolp (car arglist))
	       (not (lisp:member (car arglist) lambda-list-keywords)))
	  ()
	  "The first element of the argument list must be an argument")
  (multiple-value-bind (functional-support message optimizer)
      (expand-defgeneric-old-flavors function arglist options)
    `(progn
       (eval-when (compile load eval)
	 (set-generic-operation ',function ',message)
	 ,@(when optimizer
	     `((compiler:defoptimizer optimize-generic-call ,function)))
	 (set-optimizer-support ',function ,(and optimizer (list 'quote optimizer))))
       ,functional-support)))


))
