diff lisp/efs/efs-defun.el @ 98:0d2f883870bc r20-1b1

Import from CVS: tag r20-1b1
author cvs
date Mon, 13 Aug 2007 09:13:56 +0200
parents 8fc7fe29b841
children 8b8b7f3559a2 8619ce7e4c50
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/efs/efs-defun.el	Mon Aug 13 09:13:56 2007 +0200
@@ -0,0 +1,393 @@
+;; -*-Emacs-Lisp-*-
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;
+;; File:         efs-defun.el
+;; Release:      $efs release: 1.15 $
+;; 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 warrranty (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.15 $" 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