;; -*-Emacs-Lisp-*-
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; File:         efs-defun.el
;; Release:      $efs release: 1.22 $
;; Version:      #Revision: 1.1 $
;; RCS:          
;; Description:  efs-defun allows for OS-dependent coding of functions
;; Author:       Sandy Rutherford <sandy@ibm550.sissa.it>
;; Created:      Thu Oct 22 17:58:14 1992
;; Modified:     Sun Nov 27 12:18:35 1994 by sandy on gandalf
;; Language:     Emacs-Lisp
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;; This file is part of efs. See efs.el for copyright
;;; (it's copylefted) and warranty (there isn't one) information.

;;; efs-defun allows object-oriented emacs lisp definitions.
;;; In efs, this feature is used to support multiple host types.
;;; 
;;; The first arg after the function name is a key which determines
;;; which version of the function is being defined. Normally, when the function
;;; is called this key is given as the first argument to the function.
;;;
;;; For example:
;;; 
;;; (efs-defun foobar vms (x y)
;;;   (message "hello vms world")
;;;   (+ x y))
;;;   => foobar
;;;
;;; (foobar 'vms 1 2)
;;;   => 3

;;; The key nil plays a special role: 
;;;
;;; First, it defines a default action. If there is no function
;;; definition associated with a given OS-key, then the function
;;; definition associated with nil is used.  If further there is no
;;; function definition associated with nil, then an error is
;;; signaled.  
;;;
;;; Second, the documentation string for the function is the one given
;;; with the nil definition. You can supply doc-strings with other
;;; definitions of the function, but they are not accessible with
;;; 'describe-function. In fact, when the function is either loaded or
;;; byte-compiled, they are just thrown away.

;;; There is another way to define the default action of an efs-function.
;;; This is with the use flag. If you give as the key (&use foobar),
;;; then when the function is called the variable foobar will be used to
;;; determine which OS version of the function to use. As well as
;;; allowing you to define the doc string, if the use flag is used,
;;; then you can specify an interactive specification with the function.
;;; Although a function is only interactive, if the default definition
;;; has an interactive spec, it is still necessary to give interactive
;;; specs for the other definitions of the function as well. It is possible
;;; for these interactive specs to differ.
;;; 
;;; For example:
;;; 
;;; (efs-defun fizzle (&use foobar)
;;;   "Fizzle's doc string."
;;;   (interactive)
;;;   (message "fizz wizz"))
;;; 
;;; (efs-defun fizzle vms
;;;   (interactive)
;;;   (message "VMS is fizzled."))
;;; 
;;; (setq foobar 'unix)
;;; => unix
;;; 
;;; (fizzle)
;;; => "fizz wizz"
;;; 
;;; (setq foobar 'vms)
;;; => vms
;;; 
;;; (fizzle)
;;; => "VMS is fizzled."
;;; 
;;; M-x f i z z l e <return>
;;; => "VMS is fizzled."
;;;
;;; Actually, when you use the &use spec, whatever follows it is simply
;;; evaluated at call time.

;;; Note that when the function is defined, the key is implicitly
;;; quoted, whereas when the function is called, the key is
;;; evaluated.  If this seems strange, think about how efs-defuns
;;; are used in practice.

;;; There are no restrictions on the order in which the different OS-type
;;; definitions are done.

;;; There are no restrictions on the keys that can be used, nor on the
;;; symbols that can be used as arguments to an efs-defun.  We go
;;; to some lengths to avoid potential conflicts. In particular, when
;;; the OS-keys are looked up in the symbol's property list, we
;;; actually look for a symbol with the same name in the special
;;; obarray, efs-key-obarray. This avoids possible conflicts with
;;; other entries in the property list, that are usually accessed with
;;; symbols in the standard obarray.

;;; The V19 byte-compiler will byte-compile efs-defun's.
;;; The standard emacs V18 compiler will not, however they will still
;;; work, just not at byte-compiled speed.

;;; efs-autoload works much like the standard autoload, except it
;;; defines the efs function cell for a given host type as an autoload.
;;; The from-kbd arg only makes sense if the default action of the autoload
;;; has been defined with a &use.

;;; To do:
;;;
;;; 1. Set an edebug-form-hook for efs-defun

;;; Known Bugs:
;;;
;;; 1. efs-autoload will correctly NOT overload an existing function
;;;    definition with an autoload definition. However, it will also
;;;    not overload a previous autoload with a new one. It should. An
;;;    overload can be forced for the KEY def of function FUN by doing
;;;    (put 'FUN (intern "KEY" efs-key-obarray) nil) first.
;;;

;;; Provisions and requirements

(provide 'efs-defun)
(require 'backquote)

;;; Variables

(defconst efs-defun-version
  (concat (substring "$efs release: 1.22 $" 14 -2)
	  "/"
	  (substring "#Revision: 1.1 $" 11 -2)))

(defconst efs-key-obarray (make-vector 7 0))

;; Unfortunately, we need to track this in bytecomp.el.
;; It's not much to keep track of, although.
(defconst efs-defun-bytecomp-buffer "*Compile-Log*")

(defvar efs-key nil
  "Inside an efs function, this is set to the key that was used to
call the function. You can test this inside the default definition, to
determine which key was actually used.")
(defvar efs-args nil
  "Inside an efs function, this is set to a list of the calling args
of the function.")

;;; Utility Functions

;;; These functions are called when the macros efs-defun and efs-autoload
;;; are expanded. Their purpose is to help in producing the expanded code.

(defun efs-defun-arg-count (list)
  ;; Takes a list of arguments, and returns a list of three
  ;; integers giving the number of normal args, the number
  ;; of &optional args, and the number of &rest args (this should
  ;; only be 0 or 1, but we don't check this).
  (let ((o-leng (length (memq '&optional list)))
	(r-leng (length (memq '&rest list)))
	(leng (length list)))
    (list (- leng (max o-leng r-leng))
	  (max 0 (- o-leng r-leng 1))
	  (max 0 (1- r-leng)))))

;; For each efs-function the property efs-function-arg-structure
;; is either a list of three integers to indicate the number of normal,
;; optional, and rest args, or it can be the symbol 'autoload to indicate
;; that all definitions of the function are autoloads, and we have no
;; idea of its arg structure.

(defun efs-defun-arg-check (fun key list)
  ;; Checks that the LIST of args is consistent for the KEY def
  ;; of function FUN.
  (let ((prop (get fun 'efs-function-arg-structure))
	count)
    (if (eq list 'autoload)
	(or prop (put fun 'efs-function-arg-structure 'autoload))
      (setq count (efs-defun-arg-count list))
      (if (and prop (not (eq prop 'autoload)) (not (equal prop count)))
	  (let ((warning
		 (format
		  "args. for the %s def. of %s don't agree with previous defs."
		  key fun)))
	    (message (concat "Warning: " warning))
	    ;; We are compiling, I suppose...
	    (if (get-buffer efs-defun-bytecomp-buffer)
		(save-excursion
		  (set-buffer efs-defun-bytecomp-buffer)
		  (goto-char (point-max))
		  (insert "efs warning:\n  " warning "\n")))))
      (put fun 'efs-function-arg-structure count))))

(defun efs-def-generic (fun use doc-string interactive-p)
  ;; Generates a generic function def using USE.
  ;; If use is nil, the first arg of the function
  ;; is the key.
  (let ((def-args '(&rest efs-args))
	result)
    (or use
	(setq def-args (cons 'efs-key def-args)))
    (setq result
	  (` (or (get (quote (, fun))
		      (, (if use
			     (list 'intern
				   (list 'symbol-name use)
				   'efs-key-obarray)
			   '(intern
			     (symbol-name efs-key)
			     efs-key-obarray))))
		 (get (quote (, fun))
		      (intern "nil" efs-key-obarray)))))
    ;; Make the gen fun interactive, if nec.
    (setq result
	  (if interactive-p
	      (` ((interactive)
		  (if (interactive-p)
		      (let ((prefix-arg current-prefix-arg))
			(call-interactively
			 (, result)))
		    (, (cons 'apply (list result 'efs-args))))))
	    (list (cons 'apply (list result 'efs-args)))))
    (if doc-string (setq result (cons doc-string result)))
    (cons 'defun (cons fun (cons def-args result)))))

(defun efs-def-autoload (fun key file from-kbd)
  ;; Returns the autoload lambda for FUN and FILE.
  ;; I really should have some notion of efs-autoload
  ;; objects, and not just plain lambda's.
  (let ((result
	 (if from-kbd
	     (`
	      (lambda (&rest args)
		(interactive)
		(let ((qkey (intern (symbol-name (quote (, key)))
				    efs-key-obarray))
		      (tmp1 (intern "tmp1" efs-key-obarray))
		      (tmp2 (intern "tmp2" efs-key-obarray)))
		  ;; Need to store the a-f-function, to see if it has been
		  ;; re-defined by the load. This is avoid to an infinite loop.
		  (set tmp1 (get (quote (, fun)) qkey))
		  ;; Need to store the prefix arg in case it's interactive.
		  ;; These values are stored in variables interned in the
		  ;; efs-key-obarray, because who knows what loading a
		  ;; file might do.
		  (set tmp2 current-prefix-arg)
		  (load (, file))
		  ;; check for re-def
		  (if (equal (symbol-value tmp1)
			     (get (quote (, fun)) qkey))
		      (error "%s definition of %s is not defined by loading %s"
			     qkey (quote (, fun)) (, file)))
		  ;; call function
		  (if (interactive-p)
		      (let ((prefix-arg (symbol-value tmp2)))
			(call-interactively
			 (get (quote (, fun)) qkey)))
		    (apply (get (quote (, fun)) qkey) args)))))
	   (` (lambda (&rest args)
		(let ((qkey (intern (symbol-name (quote (, key)))
				    efs-key-obarray))
		      (tmp1 (intern "tmp1" efs-key-obarray)))
		  ;; Need to store the a-f-function, to see if it has been
		  ;; re-defined by the load. This is avoid to an infinite loop.
		  (set tmp1 (get (quote (, fun)) qkey))
		  (load (, file))
		  ;; check for re-def
		  (if (equal (symbol-value tmp1)
			     (get (quote (, fun)) qkey))
		      (error "%s definition of %s is not defined by loading %s"
			     qkey (quote (, fun)) (, file)))
		  ;; call function
		  (apply (get (quote (, fun)) qkey) args)))))))
    (list 'put (list 'quote fun)
	  (list 'intern
		(list 'symbol-name (list 'quote key))
		'efs-key-obarray)
	  (list 'function result))))
 
;;; User level macros -- efs-defun and efs-autoload.

(defmacro efs-defun (funame key args &rest body)
  (let* ((use (and (eq (car-safe key) '&use)
		   (nth 1 key)))
	 (key (and (null use) key))
	 result doc-string interactive-p)
    ;; check args
    (efs-defun-arg-check funame key args)
    ;; extract doc-string
    (if (stringp (car body))
	(setq doc-string  (car body)
	      body (cdr body)))
    ;; If the default fun is interactive, and it's a use construct,
    ;; then we allow the gen fun to be interactive.
    (if use
	(setq interactive-p (eq (car-safe (car-safe body)) 'interactive)))
    (setq result
	  (` ((put (quote (, funame))
		   (intern (symbol-name (quote (, key)))
			   efs-key-obarray)
		   (function
		    (, (cons 'lambda
			     (cons args body)))))
	      (quote (, funame)))))
    ;; if the key is null, make a generic def
    (if (null key)
	(setq result
	      (cons (efs-def-generic
		     funame use doc-string interactive-p)
		    result)))
    ;; return
    (cons 'progn result)))

;;; For lisp-mode

(put 'efs-defun 'lisp-indent-hook 'defun)

;; efs-autoload
;; Allows efs function cells to be defined as autoloads.
;; If efs-autoload inserted autoload objects in the property list,
;; and the funcall mechanism in efs-defun checked for such
;; auto-load objects, we could reduce the size of the code
;; resulting from expanding efs-autoload. However, the expansion
;; of efs-defun would be larger. What is the best thing to do?

(defmacro efs-autoload (fun key file &optional docstring from-kbd)
  (let* ((use (and (eq (car-safe key) '&use)
		   (nth 1 key)))
	 (key (and (null use) key)))
    (efs-defun-arg-check (eval fun) key 'autoload)
    ;; has the function been previously defined?
    (`
     (if (null (get (, fun)
		    (intern (symbol-name (quote (, key)))
			    efs-key-obarray)))
	 (,
	  (if (null key)
	      (list 'progn
		    ;; need to eval fun, since autoload wants an explicit
		    ;; quote built into the fun arg.
		    (efs-def-generic
		     (eval fun) use docstring from-kbd )
		    (efs-def-autoload (eval fun) key file from-kbd)
		    (list 'quote
			  (list
			   'efs-autoload
			   key file docstring from-kbd)))
	    (list 'progn
		  (efs-def-autoload (eval fun) key file from-kbd)
		  (list 'quote
			(list 
			 'efs-autoload
			 key file docstring from-kbd)))))))))

(defun efs-fset (sym key fun)
  ;; Like fset but sets KEY's definition of SYM.
  (put sym (intern (symbol-name key) efs-key-obarray) fun))

(defun efs-fboundp (key fun)
  ;; Like fboundp, but checks for KEY's def.
  (null (null (get fun (intern (symbol-name key) efs-key-obarray)))))

;; If we are going to use autoload objects, the following two functions
;; will be useful.
;;
;; (defun efs-defun-do-autoload (fun file key interactive-p args)
;;   ;; Loads FILE and runs the KEY def of FUN.
;;   (let (fun file key interactive-p args)
;;     (load file))
;;   (let ((new-def (get fun key)))
;;     (if (eq (car-safe new-def) 'autoload)
;; 	(error "%s definition of %s is not defined by loading %s"
;; 	       key fun file)
;;       (if interactive-p
;; 	  (let ((prefix-arg current-predix-arg))
;; 	    (call-interactively fun))
;; 	(apply new-def args)))))
;; 
;; (defun efs-defun-autoload (fun key file doc-string from-kbd)
;;   ;; Sets the KEY def of FUN to an autoload object.
;;   (let* ((key (intern (symbol-name key) efs-key-obarray))
;; 	 (def (get fun key)))
;;     (if (or (null def)
;; 	    (eq (car-safe def) 'autoload))
;; 	(put fun key (list 'autoload file doc-string from-kbd)))))

;;; end of efs-defun.el
