Article View: gnu.emacs.sources
Article #270advice.el - code
From: Hans Chalupsky
Date: Tue, 15 Dec 1992 05:12
Date: Tue, 15 Dec 1992 05:12
1649 lines
70654 bytes
70654 bytes
Here's the code promised in the previous announcement message. Look for
the ;; eof at the end of the message to check whether you got the complete
file (about 70k).
Enjoy,
Hans
hans@cs.buffalo.edu Thm 4.2.7: The number of polar bears
at an italian funeral is
always even.
;; ------------------------ cut here -----------------------------------------
;; -*-Emacs-Lisp-*-
;;
;; Copyright (C) 1992 Hans Chalupsky
;;
;; File: advice.el
;; Revision: $Revision: 1.7 $
;; Author: Hans Chalupsky (hans@cs.buffalo.edu)
;; Created: Date: 92/12/08 17:37:33
;; Modified: $Date: 92/12/14 22:41:49 $
;;
;;
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 1, or (at your option)
;; any later version.
;;
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; A copy of the GNU General Public License can be obtained from this
;; program's author (send electronic mail to hans@cs.buffalo.edu) or from
;; the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA
;; 02139, USA.
;;
;;
;; Description:
;; ============
;; This package implements a full-fledged Lisp-style advice mechanism
;; for Emacs Lisp. Advice is a clean and efficient way to modify the
;; behavior of Emacs Lisp functions without having to keep personal
;; modified copies of such functions around. A great number of such
;; modifications can be achieved by treating the original function as a
;; black box and specifying a different execution environment for it
;; with a piece of advice. Think of a piece of advice as a kind of fancy
;; hook that you can attach to any function/macro/subr.
;;
;; Highlights:
;; ===========
;; - Clean definition of multiple, named before/around/after advices
;; for functions, macros, subrs and special forms
;; - Full control over the arguments an advised function will receive,
;; the binding environment in which it will be executed, as well as the
;; value it will return.
;; - Allows redefinition of interactive behavior of functions and subrs,
;; as well as making previously non-interactive functions and subrs
;; interactive.
;; - Every piece of advice can have a documentation string which will be
;; combined with the original documentation of the advised function
;; - Documentation indirection ensures that command-key substitution of
;; documentation strings occurs when the documentation function is called,
;; and not when the advised function is constructed.
;; - Forward advice is possible, that is functions which have autoload
;; definitions, or functions which might be defined later during a
;; load/autoload can be advised without having to preload the file in
;; which they are defined.
;; - Forward redefinition is possible because around advice can be used to
;; completely redefine a function.
;; - The execution of every piece of advice can be protected against error
;; and non-local exits in preceding code or advices.
;; - Advised functions can be byte-compiled.
;; - Separation of advice definition and activation/deactivation
;; - Allows definition of load-hooks for arbitrary Emacs Lisp files without
;; modification of these files
;;
;; How to get the latest advice.el:
;; ================================
;; You can get the latest version of this file either via anonymous ftp from
;; ftp.cs.buffalo.edu (128.205.32.9) with pathname /pub/Emacs/advice.el,
;; or send email to hans@cs.buffalo.edu and I'll mail it to you.
;;
;; Overview:
;; =========
;; This file was subject to serious documentation overkill, and hence had to
;; be organized into the following parts (search for the items in double
;; quotes to skip to that part):
;;
;; 1) Initial information (short but insufficient)
;; 2) "General Documentation" (boring and/or confusing)
;; 3) "Foo games", a tutorial (hopefully less boring; for people who
;; like to learn by example)
;; 4) "Advice Implementation" (boring code)
;; 5) "Advising DEFUN" (exiting application of advice to
;; bootstrap parts of the system)
;;
;; Restrictions:
;; =============
;; - When an advised subr is called directly from other subrs or C-code
;; it will not exhibit the advised behavior. The same holds for advised
;; macros which were expanded during byte-compilation before their advice
;; was activated.
;; - This package was developed under GNU-Emacs version 18.57. For different
;; versions your mileage may vary. In particular, running this under
;; version 19 will probably need some work.
;;
;; Credits:
;; ========
;; This package is an extension and generalization of packages such as
;; insert-hooks.el written by Noah S. Friedman (friedman@prep.ai.mit.edu),
;; and advise.el written by Raul J. Acevedo (acevedo@MIT.EDU). Some ideas
;; used in here come from these packages, others come from the various Lisp
;; advice mechanisms I've come across so far, and a few are simply mine.
;;
;; Installation:
;; =============
;; Put this file somewhere into your Emacs load-path, byte-compile it for
;; efficiency, and put the following autoload declaration into your .emacs
;;
;; (autoload 'defadvice "advice" "Define a piece of advice" nil t)
;;
;; or explicitly load it with (require 'advice) or (load "advice").
;;
;; Comments, suggestions, bug reports
;; ==================================
;; are strongly appreciated, please email them to hans@cs.buffalo.edu.
;; Also feel free to mail me any questions you might have about advice.el.
;;
;; Motivation:
;; ===========
;; Before I go on explaining how advice works, here are three examples how
;; this package can be used. The first two are very useful, the third one
;; is just a joke:
;;
;;(defadvice switch-to-buffer (before existing-buffer activate)
;; " Switch to existing buffers only when called interactively, unless
;;when called with a prefix argument."
;; (interactive
;; (list (call-interactively
;; (if (call-interactively '(lambda (arg) (interactive "P") arg))
;; '(lambda (buffer) (interactive "BSwitch to buffer: ") buffer)
;; '(lambda (buffer) (interactive "bSwitch to buffer: ") buffer))))))
;;
;;(defadvice find-file (before existing-file activate)
;; " Find existing files only"
;; (interactive "fFind file: "))
;;
;;(defadvice car (around interactive activate)
;; " Make car an interactive function."
;; (interactive "xCar of list: ")
;; ad-do-it
;; (if (interactive-p)
;; (message "%s" ad-return-value)))
;;
;; Planned Extensions:
;; ===================
;; - A caching mechanism that reuses previously constructed advised definitions
;; on activation if the advice info of a function hasn't changed
;; - Associate an enable flag with every single piece of advice such that only
;; enabled pieces of advice will be used on activation
;; - Generalize the advice mechanism to a transformation mechanism that uses
;; a source definition according to some specification, changes it according
;; to the pieces of advice defined on it, and transforms it into a target
;; definition according to some specification. With this not just named
;; functions but also hook-functions (values of some hook-variables) and
;; definitions of any kind could make use of the advice facility.
;;
;; General Documentation:
;; ======================
;; The main means of defining a piece of advice for some function is the macro
;; defadvice (type C-h d defadvice to learn more about defadvice). There is no
;; interactive way of specifying a piece of advice. However, the following
;; interactive functions can be used to manipulate the state of functions
;; advised with defadvice:
;;
;; - ad-activate combines all currently defined pieces of advice of a certain
;; function with its original definition and redefines it with
;; this advised definition
;; - ad-deactivate defines a function back to its unadvised original definition
;; but keeps all advice information around so it can be
;; activated again
;; - ad-unadvise deactivates a function and removes all of its advice
;; information, hence it cannot be activated again
;; - ad-recover tries to redefine a function to its original definition and
;; discards all advice information (a low-level ad-unadvise).
;; Use this function in emergencies only.
;;
;; - ad-remove-advice removes a particular piece of advice of a function.
;; You still have to do ad-activate to activate the new state
;; of advice.
;;
;; - ad-activate-all activates all advised functions
;; - ad-deactivate-all deactivates all advised functions
;; - ad-unadvise-all unadvises all advised functions
;; - ad-recover-all recovers all advised functions
;;
;; - ad-compile byte-compiles a function/macro if it is compilable.
;;
;; The non-interactive function ad-add-advice can be used to add a piece of
;; advice to some function without using defadvice. This is useful if advice
;; has to be added somewhere by a function (also look at ad-make-advice).
;;
;; Separation of advice definition and activation, forward advice:
;; ===============================================================
;; Advising happens in two stages:
;;
;; 1) definition of various pieces of advice
;; 2) activation of all advice currently defined
;;
;; The advantage of this is that various pieces of advice can be defined before
;; they get combined into an advised definition which avoids unnecessary
;; constructions of intermediate advised definitions. The more important
;; advantage is that it allows a simple implementation of forward advice.
;; Advice information for a certain function accumulates as the value of the
;; advice-info property of the function symbol. This accumulation is completely
;; independent of the fact that that function might not yet be defined. The
;; special forms defun and defmacro have been advised to check whether the
;; function/macro they defined had advice information associated with it. If
;; so, the original definition will be saved, and then the advice will be
;; activated. When a file is loaded the functions/macros it defines are defined
;; with calls to defun/defmacro. Hence, we can forward advise functions/macros
;; which will be defined later during a load/autoload of some file.
;;
;; As an extension of the current activation method one could associate an
;; enable/disable flag with every piece of advice such that during activation
;; only enabled pieces of advice would be considered.
;;
;; Defining a piece of advice with defadvice:
;; ==========================================
;; A call to defadvice has the following syntax which is very similar
;; to the syntax of defun/defmacro with the difference that defadvice
;; does not specify any argument variables:
;;
;; (defadvice <function> (<class> <name> [<position>] {<flags>}*)
;; [ [<documentation-string>] [(interactive ...)] ]
;; {<body-form>}* )
;;
;; <function> is the name of the function to be advised.
;;
;; <class> is the class of the advice which has to be one of `before',
;; `around', `after', `activation' or `deactivation' (the last two allow
;; definition of special act/deactivation hooks).
;;
;; <name> is the name of the advice which has to be a symbol (nil can be used
;; for unnamed advices). Names uniquely identify a piece of advice in a
;; certain advice class, hence named advices can be redefined by defining
;; an advice with the same class and name. They can also be removed with
;; ad-remove-advice. Unnamed advices can not be redefined or removed, the
;; only way to get rid of them is to completely unadvise the function.
;;
;; An optional <position> specifies where in the current list of advices of
;; the specified <class> this new advice will be placed. <position> has to be
;; either `first', `last' or a number that specifies a zero-based position
;; (`first' is equivalent to 0). If no position is specified `first' will be
;; used as a default. If this call to defadvice redefines an already existing
;; advice (see above) then the position argument will be ignored and the
;; position of the already existing advice will be used.
;;
;; <flags> is a list of flags that specify further information about the
;; advice. An `activate' flag specifies that the advice information of the
;; advised function should be activated right after this advice has been
;; defined. In forward advices `activate' will be ignored. `protect'
;; specifies that this advice should be protected against non-local exits
;; and errors in preceding code/advices. `compile' specifies that the
;; advised function should be byte-compiled. This flag will be ignored
;; unless `activate' is also specified.
;;
;; An optional <documentation-string> can be supplied to document the advice.
;; On activation of the advice it will be combined with the documentation of
;; the original function.
;;
;; An optional (interactive ...) form can be supplied to change/add interactive
;; behavior of the original function. If more than one advice has an
;; (interactive ...) specification then the one from the advice with the
;; smallest position will be used. `before' advices go before `around'
;; advices and only interactive specifications in these two advice classes
;; will be considered.
;;
;; A possibly empty list of <body-forms> specifies the body of the advice in
;; an implicit progn. The body of an advice can access/change arguments,
;; the return value, the binding environment, and can have all sorts of
;; other side effects.
;;
;; Assembling advised definitions:
;; ===============================
;; Suppose a function/macro/subr/special-form has N pieces of before advice,
;; M pieces of around advice and K pieces of after advice. Assuming none of
;; the advices is protected, its advised definition will look like this
;; (body-form indices correspond to the position of the respective advice in
;; that advice class):
;;
;; ([macro] lambda <arglist>
;; [ [<combined-docstring>] [(interactive ...)] ]
;; (let (ad-return-value)
;; {<before-0-body-form>}*
;; ....
;; {<before-N-1-body-form>}*
;; {<around-0-body-form>}*
;; {<around-1-body-form>}*
;; ....
;; {<around-M-1-body-form>}*
;; (setq ad-return-value
;; <apply original-body to arglist>)
;; {<other-around-M-1-body-form>}*
;; ....
;; {<other-around-1-body-form>}*
;; {<other-around-0-body-form>}*
;; {<after-0-body-form>}*
;; ....
;; {<after-K-1-body-form>}*
;; ad-return-value))
;;
;; Macros and special forms will be redefined as macros, hence the optional
;; [macro] in the beginning of the definition.
;;
;; <arglist> is the argument list of the original function, hence values of
;; <arglist> variables can be accessed/changed in the body of an advice by
;; simply referring to them by their original name. For subrs/special forms
;; <arglist> will be (&rest ad-subr-args) because argument lists of subrs
;; are not accessible. Changing the value of ad-subr-args will change the
;; arguments supplied to the original subr.
;;
;; <combined-docstring> is the optional documentation string constructed from
;; the original documentation and the advice documentation strings.
;;
;; (interactive ...) is an optional interactive form either taken from the
;; original function or from a before/around advice. For advised interactive
;; subrs that do not have an interactive form specified in any advice we
;; have to use (interactive) and then call the subr interactively if the
;; advised function was called interactively, because the interactive
;; specification of subrs is not accessible. This is the only case where
;; changing the values of arguments will not have an affect because they
;; will be reset by the interactive specification of the subr. If this is a
;; problem one can always specify an interactive form in a before/around
;; advice to gain control over argument values that were supplied
;; interactively.
;;
;; Then the body forms of the various advices in the various classes of advice
;; are assembled in order. The forms of around advice L are normally part of
;; one of the forms of around advice L-1. An around advice can specify where
;; the forms of the wrapped or surrounded forms should go with the special
;; keyword ad-do-it, which will be substituted with a progn containing the
;; forms of the surrounded code.
;;
;; The innermost part of the around advice onion is
;; <apply original-body to arglist>
;; whose form depends on the type of the original function. The variable
;; ad-return-value will be set to its result. This variable is visible to
;; all pieces of advice which can access and modify it before it gets returned.
;;
;; The semantic structure of advised functions that contain protected pieces
;; of advice is the same. The only difference is that unwind-protect forms
;; make sure that the protected advice gets executed even if some previous
;; piece of advice had an error or a non-local exit. If any around advice is
;; protected then the whole around advice onion will be protected.
;;
;; Accessing argument bindings of arbitrary functions:
;; ===================================================
;; Some functions (such as trace) need a general method of accessing the
;; names and bindings of the arguments of an advised function. To do that
;; within an advice one can use the special keyword ad-arg-bindings which
;; is a text macro that will be substituted with a form that will evaluate
;; to a list of binding specifications, one for every argument variable.
;; These binding specifications can then be examined in the body of the advice.
;; For example, somewhere in an advice we could do this:
;;
;; (let* ((bindings ad-arg-bindings)
;; (firstarg (car bindings))
;; (secondarg (car (cdr bindings))))
;; ;; Print info about first argument
;; (print (format "%s=%s (%s)"
;; (ad-arg-binding-field firstarg 'name)
;; (ad-arg-binding-field firstarg 'value)
;; (ad-arg-binding-field firstarg 'type)))
;; ;; Set value of second argument to nil unless it was a number:
;; (if (not (numberp (ad-arg-binding-field secondarg 'value)))
;; (set (ad-arg-binding-field secondarg 'name) nil))
;; ....)
;;
;; The `type' of an argument is either `required', `optional' or `rest'.
;; Wherever ad-arg-bindings appears a form will be inserted that evaluates
;; to the list of bindings, hence, in order to avoid multiple unnecessary
;; evaluations one should always bind it to some variable.
;;
;; Summary of symbols with special meanings when used within an advice:
;; ====================================================================
;; ad-return-value name of the return value variable (get/settable)
;; ad-subr-args name of &rest argument variable used for advised
;; subrs (get/settable)
;; ad-arg-bindings text macro that returns the actual names, values
;; and types of the arguments as a list of bindings. The
;; order of the bindings corresponds to the order of the
;; arguments. The individual fields of every binding (name,
;; value and type) can be accessed with the function
;; ad-arg-binding-field (see example above).
;; ad-do-it text macro that identifies the place where the original
;; or wrapped definition should go in an around advice
;;
;; Activation/deactivation advices, file load hooks:
;; =================================================
;; There are two special classes of advice called `activation' and
;; `deactivation'. The body forms of these advices are not included into the
;; advised definition of a function, they rather are assembled into a hook
;; form which will be evaluated whenever the advice info of the advised
;; function gets activated or deactivated. One application of this mechanism
;; is to define file load hooks for files that do not provide such hooks.
;; For example, suppose you want to print a message whenever file-x gets
;; loaded, and suppose the last function defined in file-x is file-x-last-fn.
;; Then we can define the following advice:
;;
;; (defadvice file-x-last-fn (activation file-x-load-hook)
;; "Executed whenever file-x is loaded"
;; (if load-in-progress (message "Loaded file-x")))
;;
;; This will constitute a forward advice for function file-x-last-fn which
;; will get activated when file-x is loaded. Because there are no "real"
;; pieces of advice available for it, its definition will not be changed,
;; but the activation advice will be run during its activation which is
;; equivalent to having a file load hook for file-x.
;;
;;
;; Foo games: An advice tutorial
;; =============================
;; ;; We start with a very innocent looking function foo that just
;; ;; adds 1 to its argument x:
;;
;; (defun foo (x)
;; "Add 1 to x."
;; (1+ x))
;; foo
;;
;; (foo 3)
;; 4
;;
;; ;; Let's define the first piece of advice for function foo. To do that we
;; ;; use the macro defadvice which takes a function name, a list of advice
;; ;; specifiers and a list of body forms as arguments. The first element of
;; ;; the advice specifiers is the class of the advice, the second is its
;; ;; name, the third its position and the rest are some flags. The class of
;; ;; our first advice is `before', its name is `add2', its position among
;; ;; the currently defined before advices (none so far) is `first', and the
;; ;; advice will be `activate'ed immediately. In the body of an advice we
;; ;; can refer to the argument variables of the original function by name.
;; ;; Here we add 1 to x so the effect of calling foo will be to actually add
;; ;; 2. All of the advice definitions below only have one body form for
;; ;; simplicity, but there is no restriction to that extent. Every piece of
;; ;; advice can have a documentation string which will be combined with the
;; ;; documentation of the original function.
;;
;; (defadvice foo (before add2 first activate)
;; " Add 2 to x"
;; (setq x (1+ x)))
;; foo
;;
;; (foo 3)
;; 5
;;
;; ;; Now we define the second `before' advice which will cancel the effect
;; ;; of the previous advice. This time we specify the position as 0 which is
;; ;; equivalent to `first'. A number can be used to specify the zero-based
;; ;; position of an advice among the list of advices in the same class. This
;; ;; time we already have one before advice hence the position specification
;; ;; actually has an effect. So, after the following definition the position
;; ;; of the previous advice will be 1 even though we specified it with
;; ;; `first' above, the reason for this is that the position argument is
;; ;; relative to the currently defined pieces of advice which by now has
;; ;; changed.
;;
;; (defadvice foo (before cancel-add2 0 activate)
;; " Again only add 1 to x"
;; (setq x (1- x)))
;; foo
;;
;; (foo 3)
;; 4
;;
;; ;; Now we define an advice with the same class and same name but with a
;; ;; different position. Defining an advice in a class in which an advice
;; ;; with that name already exists is interpreted as a redefinition of that
;; ;; particular advice, in which case the position argument will be ignored
;; ;; and the previous position of the redefined piece of advice is used:
;;
;; (defadvice foo (before cancel-add2 last activate)
;; " Again only add 1 to x"
;; (setq x (1- x)))
;; foo
;;
;; ;; The documentation strings of the various pieces of advice are assembled
;; ;; in order which shows that advice `cancel-add2' is still the first
;; ;; `before' advice even though we specified position `last' above:
;;
;; (documentation 'foo)
;; "Add 1 to x.
;;
;; Function foo is advised:
;;
;; cancel-add2 (before):
;; Again only add 1 to x
;; add2 (before):
;; Add 2 to x"
;;
;; ;; We can make a function interactive (or changing its interactive
;; ;; behavior) by specifying an interactive form in one of the `before' or
;; ;; `around' advices (there could be more body forms in this advice). The
;; ;; particular definition always assigns 5 as an argument to x which gives
;; ;; us 6 as a result when we call foo interactively:
;;
;; (defadvice foo (before inter last activate)
;; " Use 5 as argument when called interactively"
;; (interactive (list 5)))
;; foo
;;
;; (call-interactively 'foo)
;; 6
;;
;; ;; If more than one advices have an interactive declaration, then the one
;; ;; of the advice with the smallest position will be used (`before' advices
;; ;; go before `around' advices), hence the declaration below does not have
;; ;; any effect:
;;
;; (defadvice foo (before inter2 last activate)
;; (interactive (list 6)))
;; foo
;;
;; (call-interactively 'foo)
;; 6
;;
;; ;; Let's have a look at what the definition of foo looks like now
;; ;; (indentation added by hand for legibility):
;;
;; (symbol-function 'foo)
;; (lambda (x)
;; "\\^origdoc
;;
;; Function foo is advised:
;;
;; cancel-add2 (before):
;; Again only add 1 to x
;; add2 (before):
;; Add 2 to x
;; inter (before):
;; Use 5 as argument when called interactively"
;; (interactive (list 5))
;; (let (ad-return-value)
;; (setq x (1- x))
;; (setq x (1+ x))
;; (setq ad-return-value (1+ x))
;; ad-return-value))
;;
;; ;; Now we'll try some `around' advices. An `around' advice is a wrapper
;; ;; around the original definition. It can shadow or establish bindings for
;; ;; the original definition, and it can look at and manipulate the value
;; ;; returned by the original function. The position of the special keyword
;; ;; `ad-do-it' specifies where the code of the original function will be
;; ;; executed. The keyword can appear multiple times which will result in
;; ;; multiple copies of the original function in the resulting advised code.
;; ;; Note, that if we don't specify a position argument (i.e., `first',
;; ;; `last' or a number), then `first' (or 0) is the default):
;;
;; (defadvice foo (around times-2 activate)
;; " First double x"
;; (let ((x (* x 2)))
;; ad-do-it))
;; foo
;;
;; (foo 3)
;; 7
;;
;; ;; Around advices are assembled like onion skins where the around advice
;; ;; with position 0 is the outermost skin and the advice at the last
;; ;; position is the innermost skin which is directly wrapped around the
;; ;; original definition of the function. Hence after the next defadvice we
;; ;; will first multiply x by 2 then add 1 and then call the original
;; ;; definition (i.e., add 1 again):
;;
;; (defadvice foo (around add-1 last activate)
;; " Add 1 to x"
;; (let ((x (1+ x)))
;; ad-do-it))
;; foo
;;
;; (foo 3)
;; 8
;;
;; ;; Again, let's see what the definition of foo looks like so far:
;; (symbol-function 'foo)
;; (lambda (x)
;; "\\^origdoc
;;
;; Function foo is advised:
;;
;; cancel-add2 (before):
;; Again only add 1 to x
;; add2 (before):
;; Add 2 to x
;; inter (before):
;; Use 5 as argument when called interactively
;; times-2 (around):
;; First double x
;; add-1 (around):
;; Add 1 to x"
;; (interactive (list 5))
;; (let (ad-return-value)
;; (setq x (1- x))
;; (setq x (1+ x))
;; (let ((x (* x 2)))
;; (let ((x (1+ x)))
;; (setq ad-return-value (1+ x))))
;; ad-return-value))
;;
;; ;; In every defadvice so far we have used the flag `activate' to activate
;; ;; the advice immediately after its definition, and that's what we want in
;; ;; most cases. However, if we define multiple pieces of advice for a
;; ;; single function then activating every advice immediately is
;; ;; inefficient. A better way to do this is to only activate the last
;; ;; defined advice. For example:
;;
;; (defadvice foo (after times-x)
;; " Multiply the result with x"
;; (setq ad-return-value (* ad-return-value x)))
;; foo
;;
;; ;; This still yields the same result as before:
;; (foo 3)
;; 8
;;
;; ;; Now we define another advice and activate it which will also activate
;; ;; the previous advice `times-x'. Note the use of the special variable
;; ;; ad-return-value in the body of the advice which is set to the result of
;; ;; the original function. If we change its value then the value returned
;; ;; by the advised function will be changed accordingly:
;;
;; (defadvice foo (after times-x-again activate)
;; " Again multiply the result with x"
;; (setq ad-return-value (* ad-return-value x)))
;; foo
;;
;; ;; Now the advices have an effect:
;; (foo 3)
;; 72
;;
;; ;; Once in a while we define an advice to perform some cleanup action,
;; ;; for example:
;;
;; (defadvice foo (after cleanup last activate)
;; " Do some cleanup"
;; (print "Let's clean up now!"))
;; foo
;;
;; ;; However, in case of an error the cleanup won't be performed:
;; (condition-case error
;; (foo t)
;; (error 'error-in-foo))
;; error-in-foo
;;
;; ;; To make sure a certain piece of advice gets executed even if some error
;; ;; or non-local exit occurred in any preceding code, we can protect it by
;; ;; using the `protect' keyword. (if any of the around advices is protected
;; ;; then the whole around advice onion will be protected):
;;
;; (defadvice foo (after cleanup protect activate)
;; " Do some protected cleanup"
;; (print "Let's clean up now!"))
;; foo
;;
;; ;; Now the cleanup form will be executed even in case of an error:
;; (condition-case error
;; (foo t)
;; (error 'error-in-foo))
;; "Let's clean up now!"
;; error-in-foo
;;
;; ;; Again, let's see what foo looks like:
;; (symbol-function 'foo)
;; (lambda (x)
;; "\\^origdoc
;;
;; Function foo is advised:
;;
;; cancel-add2 (before):
;; Again only add 1 to x
;; add2 (before):
;; Add 2 to x
;; inter (before):
;; Use 5 as argument when called interactively
;; times-2 (around):
;; First double x
;; add-1 (around):
;; Add 1 to x
;; times-x-again (after):
;; Again multiply the result with x
;; times-x (after):
;; Multiply the result with x
;; cleanup (after):
;; Do some protected cleanup"
;; (interactive (list 5))
;; (let (ad-return-value)
;; (unwind-protect
;; (progn (setq x (1- x))
;; (setq x (1+ x))
;; (let ((x (* x 2)))
;; (let ((x (1+ x)))
;; (setq ad-return-value (1+ x))))
;; (setq ad-return-value (* ad-return-value x))
;; (setq ad-return-value (* ad-return-value x)))
;; (print "Let's clean up now!"))
;; ad-return-value))
;;
;; ;; Finally, we can specify the `compile' keyword in a defadvice to say
;; ;; that we want the resulting advised function to be byte-compiled
;; ;; (`compile' will be ignored unless we also specified `activate'):
;;
;; (defadvice foo (after cleanup protect activate compile)
;; " Do some protected cleanup"
;; (print "Let's clean up now!"))
;; foo
;;
;; ;; Now foo is byte-compiled:
;; (symbol-function 'foo)
;; (lambda (x)
;; "\\^origdoc
;;
;; Function foo is advised:
;;
;; cancel-add2 (before):
;; Again only add 1 to x
;; add2 (before):
;; Add 2 to x
;; inter (before):
;; Use 5 as argument when called interactively
;; times-2 (around):
;; First double x
;; add-1 (around):
;; Add 1 to x
;; times-x-again (after):
;; Again multiply the result with x
;; times-x (after):
;; Multiply the result with x
;; cleanup (after):
;; Do some protected cleanup"
;; (interactive (byte-code "\300C\207" [5] 1))
;; (byte-code "\302\210\302^X303\216 S\211^Q\210 T\211^Q\210\304 \305\"^Y T^Y T\211^P))\210\304^H \"\211^P\210\304^H \"\211^P)\210^H)\207" [ad-return-value x nil ((byte-code "\300\301!\207" [print "Let's clean up now!"] 2)) * 2] 5))
;;
;; (foo 3)
;; "Let's clean up now!"
;; 72
;;
;;
;; ;; Forward Advice:
;; ;; ===============
;;
;; ;; Define a piece of advice for an undefined function:
;;
;; (defadvice bar (before sub-1-more activate)
;; " Subtract one more from x"
;; (setq x (1- x)))
;; bar
;;
;; ;; bar it is not yet defined:
;; (fboundp 'bar)
;; nil
;;
;; ;; Now we define it and the forward advice will get activated:
;;
;; (defun bar (x)
;; "Subtract 1 from x"
;; (1- x))
;; bar
;;
;; (bar 4)
;; 2
;;
;; ;; Redefinition will also activate any available advice:
;;
;; (defun bar (x)
;; "Subtract 2 from x"
;; (- x 2))
;; bar
;;
;; (bar 4)
;; 1
;;
;; Advice Implementation:
;; ======================
(require 'backquote)
;; I need a function to be defined during byte-compilation for
;; proper macro expansion, hence I preload the file if necessary
;; (bummer, no eval-when available in Emacs-Lisp):
(provide 'advice)
(require 'advice)
(defconst ad-version (substring "$Revision: 1.7 $" 11 -2))
(defvar ad-advised-functions nil
"List of currently advised though not necessarily activated functions")
;; First some utilities:
;; =====================
(defun ad-map-tree (subtree-test function tree)
"Maps over TREE and returns a tree with identical structure but every
subtree for which SUBTREE-TEST is T replaced by the result of applying
FUNCTION to it. TREE might be an atom or a list."
(cond ((funcall subtree-test tree)
(funcall function tree))
((listp tree)
(mapcar (function
(lambda (subtree)
(ad-map-tree subtree-test function subtree)))
tree))
(t tree)))
(defun ad-substitute (old new tree)
"Substitutes all occurrences of OLD in TREE with NEW and returns the
modified tree."
(ad-map-tree (function (lambda (subtree) (equal subtree old)))
(function (lambda (subtree) new))
tree))
(defmacro ad-dolist (varform &rest body)
"A Common-Lisp-style dolist iterator with the following syntax:
(ad-dolist (<var> <init-form> [<result-form>])
{body-form}*)
which will iterate over the list yielded by <init-form> binding <var> to the
current head at every iteration. If <result-form> is supplied its value will
be returned at the end of the iteration, NIL otherwise. The iteration can be
exited prematurely with (ad-do-return [<value>])."
(let* ((var (car varform))
(init (car (cdr varform)))
(result (car (cdr (cdr varform))))
;; Use uninterned symbols that won't conflict with anything:
(listvar (make-symbol "ad-do-var"))
(tagsym (make-symbol "ad-do-exit"))
(code (` (let (((, listvar) (, init))
(, var))
(while (, listvar)
(setq (, var) (car (, listvar)))
(,@ body)
(setq (, listvar) (cdr (, listvar))))
(, result))))
contains-return)
;; Look for ad-do-return forms and substitute them with throw's:
(setq code (ad-map-tree
(function (lambda (subtree)
(and (consp subtree)
(eq (car subtree) 'ad-do-return)
(setq contains-return t))))
(function (lambda (subtree)
(` (throw '(, tagsym) (, (car (cdr subtree)))))))
code))
;; If we had an ad-do-return we need a catch:
(cond (contains-return (` (catch '(, tagsym) (, code))))
(t code))))
;; Advice info access fns:
;; =======================
;; Advice information for a particular function is stored on the
;; advice-info property of the function symbol. It is stored as an
;; alist of the following format:
;;
;; ((active . t/nil)
;; (before adv1 adv2 ...)
;; (around adv1 adv2 ...)
;; (after adv1 adv2 ...)
;; (activation adv1 adv2 ...)
;; (deactivation adv1 adv2 ...)
;; (origdef . <definition>))
(defun ad-get-advice-info (function)
"Retrieves all the advice info for FUNCTION"
(get function 'ad-advice-info))
(defun ad-set-advice-info (function advice-info)
"Sets the advice info for FUNCTION"
(put function 'ad-advice-info advice-info))
(defun ad-is-advised (function)
"Returns non-NIL if FUNCTION has any advice info associated with it. This
does not mean that the advice is also active."
(ad-get-advice-info function))
(defun ad-initialize-advice-info (function)
"Initializes the advice info for FUNCTION. Assumes that FUNCTION
has not yet been advised."
(if (not (member function ad-advised-functions))
(setq ad-advised-functions (cons function ad-advised-functions)))
(ad-set-advice-info function (list (cons 'active nil))))
(defun ad-get-advice-info-field (function field)
"Retrieves the value of the advice info FIELD of FUNCTION."
(and (ad-is-advised function)
(cdr (assoc field (ad-get-advice-info function)))))
(defun ad-set-advice-info-field (function field value)
"Destructively modifies the VALUE of the advice info FIELD of FUNCTION."
(and (ad-is-advised function)
(cond ((assoc field (ad-get-advice-info function))
;; A field with that name is already present:
(rplacd (assoc field (ad-get-advice-info function)) value))
(t;; otherwise, create a new field with that name:
(nconc (ad-get-advice-info function)
(list (cons field value)))))))
(defvar ad-advice-classes '(before around after activation deactivation)
"List of defined advice classes")
(defun ad-has-at-least-one-redefining-advice (function)
"non-NIL if the advice info of FUNCTION defines at least one advice that
will lead to its redefinition on activation of the advice."
(and (ad-is-advised function)
(or (ad-get-advice-info-field function 'before)
(ad-get-advice-info-field function 'around)
(ad-get-advice-info-field function 'after))))
(defun ad-has-at-least-one-advice (function)
"non-NIL if the advice info of FUNCTION defines at least one advice."
(and (ad-is-advised function)
(ad-dolist (class ad-advice-classes nil)
(if (ad-get-advice-info-field function class)
(ad-do-return t)))))
(defun ad-is-active (function)
"non-NIL if FUNCTION is advised and activated"
(and (ad-is-advised function)
(ad-get-advice-info-field function 'active)))
;; Access fns for single pieces of advice and related predicates:
;; ==============================================================
(defun ad-make-advice (name protect forms)
"Constructs a single piece of advice to be stored in some advice field of
the advice info of some function."
(list name protect forms))
(defun ad-advice-name (advice)
(nth 0 advice))
(defun ad-advice-protected (advice)
(nth 1 advice))
(defun ad-advice-forms (advice)
(nth 2 advice))
(defun ad-class-p (thing)
(member thing ad-advice-classes))
(defun ad-name-p (thing)
(symbolp thing))
(defun ad-position-p (thing)
(or (natnump thing)
(member thing '(first last))))
;; Adding and removing pieces of advice:
;; =====================================
(defun ad-remove-advice (function class name)
"If FUNCTION has a piece of advice of CLASS with a non-NIL NAME, then this
advice will be removed from the list of advices in this class. If such an
advice could be found its zero-based position will be returned. NIL otherwise."
(interactive
"SRemove advice of function: \nSClass (before, around, after): \nSName: ")
(if (ad-is-advised function)
(let ((index 0) new-advices found-at-index)
(ad-dolist (advice (ad-get-advice-info-field function class))
(cond ((and name (eq name (ad-advice-name advice)))
(setq found-at-index index))
(t (setq new-advices (cons advice new-advices))))
(setq index (1+ index)))
(ad-set-advice-info-field function class (reverse new-advices))
(if (and (interactive-p)
(not found-at-index))
(error "ad-remove-advice: No such advice found for %s: %s %s"
function class name))
found-at-index)
(if (interactive-p)
(error "ad-remove-advice: Function %s is not advised" function))
nil))
(defun ad-add-advice (function advice class position)
"Adds a piece of ADVICE of CLASS to the advice info of FUNCTION. If FUNCTION
has already one or more pieces of advice of the specified CLASS then POSITION
determines where the new piece will go. The value of POSITION can either be
first, last or a number where 0 corresponds to first. Numbers outside the range
will be mapped to the closest extreme position. If there was already a piece
of ADVICE with the same name, then the position argument will be ignored and
the old advice will be overwritten with the new one.
If the FUNCTION was not advised already, then its advice info will be
initialized and its original definition will be saved (this is not the case
for forward advice of functions that might get autoloaded later)"
(cond ((not (ad-is-advised function))
(ad-initialize-advice-info function)
(if (ad-has-proper-definition function)
(ad-set-advice-info-field
function 'origdef (symbol-function function)))))
(let* ((previous-position
;; Check whether we already had an advice with the same name:
(ad-remove-advice function class (ad-advice-name advice)))
(advices (ad-get-advice-info-field function class))
;; Determine a numerical position of the new advice:
(position (cond (previous-position)
((eq position 'first) 0)
((eq position 'last) (length advices))
((numberp position)
(max 0 (min position (length advices))))
(t (length advices))))
(index 0)
pre-advices)
(while (< index position)
(setq pre-advices (append pre-advices (list (car advices))))
(setq advices (cdr advices))
(setq index (1+ index)))
(ad-set-advice-info-field
function class (append pre-advices (cons advice advices)))))
;; Accessing and manipulating function definitions:
;; ================================================
;; Some of these functions most likely have to be changed in order to work
;; under Emacs 19.
(defun ad-parse-definition (definition part)
"Takes a function/macro/advice DEFINITION (a list of forms) and returns
the form(s) identified by PART. If PART is `arglist' then the argument list
of the function will be returned, if it is `docstring' then the documentation
string will be returned, if it is `interactive' then the interactive form will
be returned, and if it is `body' then the body forms without any arglists,
docstrings or interactive forms will be returned."
(let* ((args-index (cond ((eq (car definition) 'macro) 2)
((eq (car definition) 'lambda) 1)
;; for advice definitions:
(t -1)))
(doc-skip (cond ((or (stringp (nth (1+ args-index) definition))
(natnump (nth (1+ args-index) definition)))
1)
(t 0)))
(inter-form (nth (+ args-index doc-skip 1) definition))
(inter-skip (cond ((eq (car-safe inter-form) 'interactive) 1)
(t 0))))
(cond ((and (eq part 'arglist)
(> args-index 0))
(nth args-index definition))
((and (eq part 'docstring)
(> doc-skip 0))
(nth (1+ args-index) definition))
((and (eq part 'interactive)
(> inter-skip 0))
inter-form)
((eq part 'body)
(nthcdr (+ args-index doc-skip inter-skip 1) definition)))))
(defvar ad-special-forms '(and catch cond condition-case defconst defmacro
defun defvar function if interactive let let*
or prog1 prog2 progn quote save-excursion
save-restriction save-window-excursion setq
setq-default unwind-protect while
with-output-to-temp-buffer)
"There is no way to determine whether some subr is a special form or not,
hence we need this list.")
(defun ad-special-form-p (subr-name)
"non-NIL if SUBR-NAME is the name of a special form."
(member subr-name ad-special-forms))
(defun ad-interactive-p (definition)
"non-NIL if DEFINITION can be called interactively"
(commandp definition))
(defun ad-subr-p (definition)
"non-NIL if DEFINITION is a subr"
(subrp definition))
(defun ad-macro-p (definition)
"non-NIL if DEFINITION is a macro"
(eq (car-safe definition) 'macro))
(defun ad-lambda-p (definition)
"non-NIL if DEFINITION is a lambda expression"
(eq (car-safe definition) 'lambda))
(defun ad-compiled-p (definition)
"non-NIL if DEFINITION is byte compiled"
(and (or (ad-lambda-p definition)
(ad-macro-p definition))
(eq (car-safe (car-safe (ad-parse-definition definition 'body)))
'byte-code)))
(defun ad-has-proper-definition (function)
"Returns T if FUNCTION is a symbol with a proper definition, i.e., it is
fbound and its definition is not an autoload declaration."
(and (symbolp function)
(fboundp function)
(not (eq (car-safe (symbol-function function)) 'autoload))))
(defun ad-real-definition (function)
"Chases function cell indirection of FUNCTION until if finds an actual
definition which it will return"
(if (ad-has-proper-definition function)
(let ((definition (symbol-function function)))
(if (symbolp definition)
(ad-real-definition definition)
definition))))
(defun ad-real-orig-definition (function)
"Chases function cell indirection of the original definition of FUNCTION
until if finds an actual definition which it will return"
(if (and (ad-is-advised function)
(ad-get-advice-info-field function 'origdef))
(let ((origdef (ad-get-advice-info-field function 'origdef)))
(if (symbolp origdef)
(ad-real-definition origdef)
origdef))))
(defun ad-interactive-form (definition)
"Returns the interactive form of DEFINITION if it is available"
(if (consp (commandp definition))
(commandp definition)))
(defun ad-lambda-expression (definition)
"Returns the lambda expression of DEFINITION if it is a macro or a lambda"
(cond ((ad-macro-p definition)
(cdr definition))
((ad-lambda-p definition) definition)))
(defun ad-is-compilable (function)
"Check whether FUNCTION has an interpreted definition that can be compiled."
(and (ad-has-proper-definition function)
(or (ad-lambda-p (symbol-function function))
(ad-macro-p (symbol-function function)))
(not (ad-compiled-p (symbol-function function)))))
(defun ad-compile-function (function)
"Byte-compiles FUNCTION if it is not yet compiled."
(interactive "aByte-compile function: ")
(require 'byte-compile "bytecomp")
(if (ad-is-compilable function)
(let* ((definition (symbol-function function))
(is-macro (ad-macro-p definition))
(lambda (ad-lambda-expression definition))
(compiled-lambda
(byte-compile-lambda lambda)))
(fset function
(cond (is-macro (cons 'macro compiled-lambda))
(t compiled-lambda))))))
(defun ad-optimize-definition (definition)
"Takes a function DEFINITION and replaces all one-element progn forms
in it with the according body form."
(ad-map-tree
(function
(lambda (form)
(and (eq (car-safe form) 'progn)
(= (length (cdr form)) 1))))
(function
(lambda (progn-form)
(car (ad-optimize-definition (cdr progn-form)))))
definition))
(defun ad-retrieve-args-form (arglist)
"Generates a form from ARGLIST which when evaluated within a function with
that argument list will result in a list with one entry for each argument,
where the first element of each entry is the name of the argument, the second
element is its actual current value, and the third element is either
`required', `optional' or `rest' depending on the type of the argument."
(let* (required optional rest)
(setq rest (cdr (member '&rest arglist)))
(if rest (setq arglist (reverse (cdr (member '&rest (reverse arglist))))))
(setq optional (cdr (member '&optional arglist)))
(if optional
(setq required (reverse (cdr (member '&optional (reverse arglist)))))
(setq required arglist))
(` (list
(,@ (mapcar (function
(lambda (req)
(` (list '(, req) (, req) 'required))))
required))
(,@ (mapcar (function
(lambda (opt)
(` (list '(, opt) (, opt) 'optional))))
optional))
(,@ (if rest (list (` (list '(, (car rest)) (, (car rest)) 'rest)))))
))))
(defun ad-arg-binding-field (binding field)
(cond ((eq field 'name) (car binding))
((eq field 'value) (car (cdr binding)))
((eq field 'type) (car (cdr (cdr binding))))))
;; Body access fns for pieces of advice:
;; =====================================
(defun ad-advice-docstring (advice)
(ad-parse-definition (ad-advice-forms advice) 'docstring))
(defun ad-advice-interactive (advice)
(ad-parse-definition (ad-advice-forms advice) 'interactive))
(defun ad-advice-body-forms (advice)
(ad-parse-definition (ad-advice-forms advice) 'body))
;; Constructing advised definitions:
;; =================================
(defun ad-assemble-advised-definition
(type args docstring interactive orig &optional befores arounds afters)
"Constructs a function or macro definition according to TYPE which has to
be either `macro', `function' or `special-form'. ARGS is the argument list
that has to be used, DOCSTRING if non-NIL defines the documentation of the
definition, INTERACTIVE if non-NIL is the interactive form that has to be used,
ORIG is a form that evaluates the body of the original unadvised function,
and BEFORES, AROUNDS and AFTERS are the lists of advices with which ORIG
should be modified. The assembled function will be returned."
(let (before-forms around-form around-form-protected after-forms definition)
(ad-dolist (advice befores)
(cond ((and (ad-advice-protected advice)
before-forms)
(setq before-forms
(` ((unwind-protect
(progn (,@ before-forms))
(,@ (ad-advice-body-forms advice)))))))
(t (setq before-forms
(append before-forms
(ad-advice-body-forms advice))))))
(setq around-form (` (setq ad-return-value (, orig))))
(setq arounds (reverse arounds))
(ad-dolist (advice arounds)
;; If any of the around advices is protected then we
;; protect the complete around advice onion:
(if (ad-advice-protected advice)
(setq around-form-protected t))
(setq around-form
(ad-substitute
'ad-do-it around-form
(` (progn (,@ (ad-advice-body-forms advice)))))))
(setq after-forms
(if (and around-form-protected before-forms)
(` ((unwind-protect
(progn (,@ before-forms))
(, around-form))))
(append before-forms (list around-form))))
(ad-dolist (advice afters)
(cond ((and (ad-advice-protected advice)
after-forms)
(setq after-forms
(` ((unwind-protect
(progn (,@ after-forms))
(,@ (ad-advice-body-forms advice)))))))
(t (setq after-forms
(append after-forms
(ad-advice-body-forms advice))))))
(setq definition
(` ((,@ (if (member type '(macro special-form)) '(macro)))
lambda
(, args)
(,@ (if docstring (list docstring)))
(,@ (if interactive (list interactive)))
(let (ad-return-value)
;; Construct part of this "by hand" because of some
;; strange backquote behavior (or bug):
(,@ (append after-forms
(if (eq type 'special-form)
;; Do this because special forms become macros:
(list '(list 'quote ad-return-value))
(list 'ad-return-value))))
))))
;; Insert argument bindings access form if we need to:
(setq definition
(ad-substitute
'ad-arg-bindings (ad-retrieve-args-form args) definition))
;; Optimize one-element progns away (I guess a special constructor
;; function that checked for this special case would have worked too):
(ad-optimize-definition definition)))
(defvar ad-doc-indirection-pointer "\\^origdoc"
"String used in a docstring to indicate the position where the docstring of
the original function should get included.")
(defvar ad-doc-indirection-pointer-regexp
(regexp-quote ad-doc-indirection-pointer)
"Regexp that matches an indirection pointer in a docstring")
(defun ad-make-single-advice-docstring (advice class)
(let ((advice-docstring (ad-advice-docstring advice)))
(if advice-docstring
(format "%s (%s):\n%s"
(if (ad-advice-name advice)
(ad-advice-name advice)
"unnamed")
class
advice-docstring))))
(defun ad-make-advised-docstring (function)
"Constructs a documentation string for the advised FUNCTION by
concatenating the original documentation with the advice documentation
strings. Every advice documentation string will start on a new line."
(let* ((origdef (ad-real-orig-definition function))
(origdoc (documentation origdef))
(advice-docstring
;; Combine advice docstrings with original docstring, for now
;; just concatenate them with newlines inbetween:
(mapconcat
(function
(lambda (class)
(mapconcat (function
(lambda (advice)
(let ((doc (ad-make-single-advice-docstring
advice class)))
(and doc (format "\n%s" doc)))))
(ad-get-advice-info-field function class)
"")))
ad-advice-classes "")))
(if (and origdoc (ad-is-active 'documentation))
(setq origdoc ad-doc-indirection-pointer))
(if (not (equal advice-docstring ""))
(setq advice-docstring
(format "%s %s is advised:\n%s"
(cond ((ad-special-form-p function) "Special form")
((ad-subr-p origdef) "Subr")
((ad-macro-p origdef) "Macro")
(t "Function"))
function
advice-docstring)))
(cond ((equal advice-docstring "") origdoc)
(origdoc (format "%s\n\n%s" origdoc advice-docstring))
(t advice-docstring))))
(defun ad-advised-interactive-form (function)
"Maps over before and around advices of FUNCTION and returns the first
interactive form it finds, NIL otherwise."
(ad-dolist (advice (append (ad-get-advice-info-field function 'before)
(ad-get-advice-info-field function 'around)))
(if (ad-advice-interactive advice)
;; We found the first one, use it:
(ad-do-return (ad-advice-interactive advice)))))
;; There are various decisions to be made about what the advised definition
;; of a function/macro/subr/special form should look like:
;; - Should it have the same argument list as the original (when possible),
;; or should it use a universal (&rest args) form? I chose the former
;; because it allows pieces of advice to refer to arguments by name, instead
;; of having to fiddle them out of a list and changing the whole list
;; (for subrs we still need the &rest approach though)
;; - What should the call to the original code look like? With a &rest argument
;; form one could use (apply orig args) for functions and subrs, but not for
;; macros and special forms. With function indirection one could construct
;; a form (orig-fn-symbol arg1 ... argn) as long as there are no &rest
;; variables. I chose to copy the code of the original function/macro into
;; the advised function, which is a bit of a waste but avoids all the
;; argument list problems and works for macros as well. Subrs still have
;; to be applied though. If somebody has a better idea how to do this
;; let me know.
(defun ad-make-advised-definition (function)
"Generates an advised definition of FUNCTION from its advice info."
(if (and (ad-is-advised function)
(ad-has-at-least-one-redefining-advice function))
(let* ((origdef (ad-real-orig-definition function))
(orig-interactive-p (ad-interactive-p origdef))
(orig-subr-p (ad-subr-p origdef))
(orig-special-form-p (ad-special-form-p function))
(orig-macro-p (ad-macro-p origdef))
;; Construct the individual pieces that we need for assembly:
(arglist (cond ((or orig-subr-p orig-special-form-p)
;; The argument lists of subrs/special forms are
;; not accessible, hence we use &rest ad-subr-args
'(&rest ad-subr-args))
;; otherwise, use same args as the original fn
(t (ad-parse-definition origdef 'arglist))))
(advised-interactive-form (ad-advised-interactive-form function))
(interactive-form
(cond (orig-macro-p nil)
(advised-interactive-form)
((ad-interactive-form origdef))
;; Otherwise, just make it interactive if we have to:
(orig-interactive-p '(interactive))))
(orig-form
(cond (orig-special-form-p
;; Special forms are tricky because they are not apply-
;; able, hence we temporarily bind ad-original-subr to
;; their original definition and construct an EVAL form
;; that provides the proper arguments. This is save even
;; if the evaluation of the arguments causes another
;; advised special form to rebind ad-original-subr,
;; because EVAL saves the definition of the symbol in
;; functional position right away and later applies it
;; to the evaluated arguments (actually, special forms
;; should not lead to immediate argument evaluation
;; anyway):
(` (progn (fset 'ad-original-subr '(, origdef))
(eval (cons 'ad-original-subr ad-subr-args)))))
(orig-subr-p
(cond ((or (not orig-interactive-p)
advised-interactive-form)
;; If the advised subr is not interactive, or if
;; somebody advised interactive handling then we
;; can simply apply the subr without interaction...
(` (apply (, origdef) ad-subr-args)))
(t;; ...otherwise we have to check whether we
;; were called interactively in order to do
;; proper prompting:
(` (if (interactive-p)
(call-interactively (, origdef))
(apply (, origdef) ad-subr-args))))))
;; And now for normal functions (winner, this will work
;; for macros as well):
(t (` (progn (,@ (ad-parse-definition origdef 'body))))))))
;; Finally, build the sucker:
(ad-assemble-advised-definition
(cond (orig-macro-p 'macro)
(orig-special-form-p 'special-form)
(t 'lambda))
arglist
(ad-make-advised-docstring function)
interactive-form
orig-form
(ad-get-advice-info-field function 'before)
(ad-get-advice-info-field function 'around)
(ad-get-advice-info-field function 'after)))))
(defun ad-make-hook-form (function hook-name)
"Uses the body forms of all advices of FUNCTION that are of class HOOK-NAME
to generate a single form that can be evaluated in a hook-style fashion."
(let ((hook-forms
(mapcar 'ad-advice-body-forms
(ad-get-advice-info-field function hook-name))))
(if hook-forms
(cons 'progn (apply 'append hook-forms)))))
;; The top-level advice interface:
;; ===============================
(defun ad-activate (function &optional compile)
"If FUNCTION is an advised function with a proper original definition, then
an advised definition will be generated from FUNCTION's advice info and the
definition of FUNCTION will be replaced with the advised definition. With an
argument (compile is non-NIL) the resulting function will also be compiled.
Activation of an advised function that has an advice info but no actual pieces
of advice is equivalent to a call to ad-unadvise."
(interactive "aActivate advice of: \nP")
(if (not (ad-is-advised function))
(error "ad-activate: No advice information available for %s" function)
(if (not (ad-get-advice-info-field function 'origdef))
(error "ad-activate: Function %s has not yet been defined" function)
(if (not (ad-has-at-least-one-advice function))
(ad-unadvise function)
;; Otherwise activate the advice:
(cond ((ad-has-at-least-one-redefining-advice function)
(fset function (ad-make-advised-definition function))
(if compile (ad-compile-function function))))
(ad-set-advice-info-field function 'active t)
(eval (ad-make-hook-form function 'activation))
function))))
(defun ad-deactivate (function)
"If FUNCTION is an advised function with a proper original definition,
then the current definition of FUNCTION will be replaced with its original
definition. All the advice information will still be available. It can be
activated again with a call to ad-activate."
(interactive "aDeactivate advice of: ")
(if (not (ad-is-advised function))
(error "ad-deactivate: %s does not have any advice information" function)
(if (null (ad-get-advice-info-field function 'origdef))
(error "ad-deactivate: No original definition available for %s"
function)
(fset function (ad-get-advice-info-field function 'origdef))
(ad-set-advice-info-field function 'active nil)
(eval (ad-make-hook-form function 'deactivation))
function)))
(defun ad-unadvise (function)
"Deactivates the advice of FUNCTION if its advice is currently active, and
then removes all its advice information. If FUNCTION was not advised this
will be a noop."
(interactive "aDeactivate advice and remove all advice info of: ")
(cond ((ad-is-advised function)
(if (ad-is-active function)
(ad-deactivate function))
(ad-set-advice-info function nil)
(setq ad-advised-functions (delq function ad-advised-functions)))))
(defun ad-recover (function)
"Recovers the original definition of FUNCTION if available, and then
removes all its advice information without doing any deactivation.
Use in emergencies."
(interactive "aRecover and remove all advice info of: ")
(cond ((ad-is-advised function)
(if (ad-get-advice-info-field function 'origdef)
(fset function (ad-get-advice-info-field function 'origdef)))
(ad-set-advice-info function nil)
(setq ad-advised-functions (delq function ad-advised-functions)))))
(defun ad-activate-all ()
"Activates all currently advised functions"
(interactive)
(ad-dolist (function ad-advised-functions)
(ad-activate function)))
(defun ad-deactivate-all ()
"Deactivates all currently advised functions"
(interactive)
(ad-dolist (function ad-advised-functions)
(ad-deactivate function)))
(defun ad-unadvise-all ()
"Unadvises all currently advised functions"
(interactive)
(ad-dolist (function (copy-alist ad-advised-functions))
(ad-unadvise function)))
(defun ad-recover-all ()
"Recovers all currently advised functions. Use in emergencies"
(interactive)
(ad-dolist (function (copy-alist ad-advised-functions))
(ad-recover function)))
(defvar ad-defadvice-flags '(protect activate compile)
"Set of legal defadvice flags")
(defmacro defadvice (function args &rest body)
"Defines a piece of advice for FUNCTION (a symbol). ARGS is a list of
symbols which specify the class, name and position of the advice and
some flags. The first element of ARGS specifies the class and must be
one of `before', `around', `after', `activation' or `deactivation'.
The second element must be a symbol that specifies the name of the
advice. Unnamed advices are specified with NIL as their name. If the
third element is either `first', `last' or an integer it will be used
as the position for the advice. Otherwise the position defaults to
`first'. The rest of ARGS is a set of flags. If `protect' is member
of these flags then the piece of advice will be protected against
non-local exits in any code that precedes it. If any around advice of
a function is protected then automatically all around advices will be
protected (the complete onion). If `activate' is member of the flags
then all advice of FUNCTION will be activated immediately (only if
FUNCTION has been properly defined prior to the defadvice). If
`compile' is a member of the flags together with `activate' then the
resulting advised function will be compiled.
Finally, BODY is the list of forms that define the advice. The
format is similar to that used within defun. The BODY may have a
documentation string which will be combined with the documentation
strings of other pieces of advice as well as with the documentation of
the original definition. BODY may also have an (interactive ...)
declaration which will be used instead of the one of the original
function if this is the first advice that has such a declaration. For
example, if a function has two pieces of before advice both of which
have an interactive declaration, then the declaration of the before
advice at position 0 will be used. Interactive declarations in after
advices will be ignored. An advice that only contains an interactive
declaration can be used to change the interactive behavior of a
function without executing any additional advice code. Interactive
declarations in advices can also be used to make previously
non-interactive functions interactive."
(if (not (and function (symbolp function)))
(error "defadvice: Can only advise named functions: %s" function))
(let* ((class (nth 0 args))
(name (nth 1 args))
(position (if (ad-position-p (nth 2 args))
(nth 2 args)))
(flags (cond (position (nthcdr 3 args))
(t (nthcdr 2 args)))))
(if (not (ad-class-p class))
(error "defadvice: Illegal advice class: %s" class))
(if (not (ad-name-p name))
(error "defadvice: Illegal advice name: %s" name))
(ad-dolist (flag flags)
(if (not (member flag ad-defadvice-flags))
(error "defadvice: Illegal flag: %s" flag)))
(if (null position) (setq position 'first))
(ad-add-advice
function (ad-make-advice name (member 'protect flags) body)
class position)
(if (and (member 'activate flags)
(ad-has-proper-definition function))
(ad-activate function (member 'compile flags)))
(list 'quote function)))
;; Advising DEFUN, DEFMACRO and DOCUMENTATION
;; ==========================================
;; Use the advise mechanism to advise defun/defmacro so we can forward advise
;; functions that might be defined later during load/autoload:
(defun ad-activate-redefined-function (function)
"Assumes the current definition of FUNCTION to be its original definition,
and if FUNCTION has any advice info saves that as its new origdef and activates
all of its advice. If the original definition was byte-compiled the new advised
function will get compiled too."
(cond ((ad-is-advised function)
;; save the original definition
(ad-set-advice-info-field
function 'origdef (symbol-function function))
;; activate the advice and compile it if the original was compiled:
(ad-activate
function
(ad-compiled-p (symbol-function function))))))
(defadvice defun (after check-advice first activate compile)
"Whenever an advised function gets redefined with defun all its
advices will be activated immediately after redefinition. If the
original function was compiled, e.g., when a byte-compiled file was
loaded, then the advised function will be compiled too."
(ad-activate-redefined-function (car ad-subr-args)))
(defadvice defmacro (after check-advice first activate compile)
"Whenever an advised macro gets redefined with defmacro all its
advices will be activated immediately after redefinition. If the
original macro was compiled, e.g., when a byte-compiled file was
loaded, then the advised macro will be compiled too.\n"
(ad-activate-redefined-function (car ad-subr-args)))
(defadvice documentation (after expand-indirection activate compile)
"Expands documentation indirection pointers to insert original docstrings
in order to properly substitute key bindings at the time this function
is called.\n"
(if (and (symbolp (car ad-subr-args))
(stringp ad-return-value)
(ad-is-active (car ad-subr-args))
(string-match ad-doc-indirection-pointer-regexp ad-return-value))
(setq ad-return-value
(format "%s%s%s"
(substring ad-return-value 0 (match-beginning 0))
;; Make sure that in the very odd case that the original
;; docstring of the advised functions contains the
;; indirection pointer we don't go into infinite recursion
;; by binding the regexp to something that won't ever match
(let ((ad-doc-indirection-pointer-regexp " \\`"))
(documentation
(ad-get-advice-info-field (car ad-subr-args) 'origdef)))
(substring ad-return-value (match-end 0))))))
;; eof
Technical Details
Message-ID:
<BzABs5.9EL@acsu.buffalo.edu>Path:
arc1-ca.pugleaf.net!sparky!uunet!zaphod.mps.ohio-state.edu!darwin.sura.net!rsg1.er.usgs.gov!ukma!cs.widener.edu!dsinc!ub!acsu.buffalo.edu!hansShow Full Headers
Complete Article Headers
Path: arc1-ca.pugleaf.net!sparky!uunet!zaphod.mps.ohio-state.edu!darwin.sura.net!rsg1.er.usgs.gov!ukma!cs.widener.edu!dsinc!ub!acsu.buffalo.edu!hans From: hans@acsu.buffalo.edu (Hans Chalupsky) Newsgroups: gnu.emacs.sources Subject: advice.el - code Message-ID: <BzABs5.9EL@acsu.buffalo.edu> Date: 15 Dec 92 05:12:04 GMT Sender: nntp@acsu.buffalo.edu Distribution: gnu Organization: State University of New York at Buffalo/Comp Sci Lines: 1649 Nntp-Posting-Host: hadar.cs.buffalo.edu