Up | Next | Prev | PrevTail | Tail |
Once a function has been defined, one may want it to do something a little different, or just a little bit more. Breaking and tracing functions for debugging purposes can be thought of in this way. Having a function keep track of the time spent in its execution can also be thought of in this way. The Wrappers module provides a convenient facility to support this.
When a function is wrapped, its name becomes a reference for two different function definitions. In PSL it is possible to create distinct identifiers which have the same name. The original name of the function is associated with a new function called the wrapper. In general a wrapper does additional work before and/or after applying the original definition. The original definition is associated with an identifier whose name is identical with the original name but which is not interned. Application of getd to the original name will return the original definition. Since a wrapper is identified by an indicator on the property list of its name getd knows when to look elsewhere for the original function definition, and putd knows to alter the original definition, not the wrapper.
Typically a wrapper may be added to a function and later removed. A function potentially may be wrapped up inside more than one wrapper at the same time. It is possible to redefine a wrapped function but if the order or number of formal parameters is changed then it will be necessary to unwrap all wrappers first.
This section describes guidelines for writing wrappers.
If a wrapper is put on a function that is used either directly or indirectly by the wrapper body an infinite recursion may result. Functions used by the PSL interpreter are particularly susceptible to this problem. We require a means to avoid infinite recursion. In general it is not easy to restrict the set of functions which can be called indirectly by a piece of code. Many PSL system functions use other system functions. Furthermore, a modification to the system may change these relationships. Some system functions call functions through functional variables or functional values stored on property lists. This means that some of the relationships between system functions change over time.
Instead of avoiding recursion introduced by wrappers, we can detect and recover from it. Wrapper bypasses do this. A wrapper bypass is implemented through a fluid variable, called the controlling variable for that wrapper. On entry to a wrapper, the value of the controlling variable is checked. A non-nil value implies that a previous invocation of this wrapper has not been completed. To avoid a recursive call on the wrapper, a call on the wrapped function replaces evaluation of the wrapper.
If the value of the controlling variable is nil the wrapper is evaluated. During evaluation of the wrapper the controlling variable is set to t except during the application of the wrapped function, at which point it should be set to nil.
Syntactically, wrapper bodies of the following form are supported:
The controlling variable of the wrapper is < var >. It must be a fluid variable whose top level value is nil.
The notation is BNF with the addition of the regular expression ”*” operator. This operator is used to indicate zero or more repetitions of an item.
Note that setf is not supported in place of setq. You should not use apply in place of funcall or let
in place of prog. You can eliminate redundant instances of (setq < var > t) and (setq < var >
nil). For example, if a <wrapped-fn-call> appears at the beginning of a prog then the form
(setq <var> nil) can be omitted.
When BODY is a lambda expression (you are encouraged to use a lambda expression for this
argument), then the new name of the function which is being wrapped is substituted for each
occurance of wrapper-function. The result of this substitution becomes the definition of the
wrapper.
When BODY is not a lambda expression a different method is used to create the wrapper. BODY
is embedded within a lambda expression which has the same number of arguments as the
function which is being wrapped. Each occurance of the form (wrapped-invocation), is replaced
by an application of the wrapped function.
If COMPILE? is t then the wrapper will be compiled.
The wrappers package permits a function to have any number of wrappers of any permitted type.
For a given type of wrapper, a new wrapper always encloses preexisting wrappers of that type.
However, you are advised against depending upon this ordering.
It is also an error if FN is an identifier which does not have a functional value.
⋆⋆⋆⋆⋆ FN is not defined as a function.
Assume that foo is a commonly called function whose argument is a large data structure. You
have found that foo is sometimes called with an argument of nil and would like to cause a
break when this situation arises so that you can discover where foo is being called
from.
Since foo is a commonly used function a break on each entry to foo is unsatisfactory. A
trace of each entry and exit is not sufficient – it doesn’t tell you who called foo in
the critical case. The solution is to wrap foo is an advice wrapper. Such a wrapper
will print a backtrace and enter a continuable break loop when an argument of nil is
discovered.
First, load the break-trace module. This will result in the wrappers module being
loaded and the breaktrace function being defined (see Chapter 17 for more information
on breaktrace). The wrapper-type advice is added as the innermost wrapper on the
ordered list referenced by wrapper-standard-order (assuming it is not already on the
list).
The above will work fine as long as foo is an expr which is not used by the interpreter or by the
wrappers module itself. In the more general case, the function debug-foo must have three
changes:
The revised definition of debug-foo follows.
The function break-on-condition will cause entry into a continuable break loop just before the
function fn is applied if the result of evaluating the form bound to bool-expr is non-nil. A sample
call might be (break-on-condition ’foo ’(eq x 2)).
8.2.2 Exported Functions
8.2.3 Examples
breakpoint function
(if (not member 'advice wrapper-standard-order) then
(setf wrapper-standard-order
(append wrapper-standard-order '(advice))))
(de debug-foo ()
(wrap
'foo % function to wrap
'advice % wrapper type
‘(lambda (x) % wrapper body generator
(cond ((null x)
(backtrace)
(breakpoint "Foo (x=nil)")))
(wrapped-function x))
nil)) % don't compile
% the wrapper body
(fluid '(in-foo-wrapper?)) % necessary initializations of
(setq in-foo-wrapper? nil) % the controlling variable
(change 2)
(wrap
'foo
'advice
‘(lambda (x)
(cond (in-foo-wrapper? % change 2
(funcall 'wrapped-function x)) % change 1
(t
(prog (in-foo-wrapper?)
(setq in-foo-wrapper? t)
(cond ((null x)
(backtrace)
(breakpoint "Foo (x=nil)")))
(setq in-foo-wrapper? nil)
(return
(funcall 'wrapped-function x) % change 1
))))
(not (interpretive-wrapper-ok? 'foo)))) % change 3
(fluid '(in-wrapper?))
(de break-on-condition (fn bool-expr)
% First, get the parameter-list of the function.
(let ((parameter-list (function-lambda-list fn)))
(wrap
fn
'advice
‘(lambda ,parameter-list
(cond (in-wrapper?
(funcall 'wrapped-function ,@parameter-list))
(t
(prog (in-wrapper?)
(setq in-wrapper? t)
(cond (,bool-expr
(breakpoint "In %p, condition %p=%p"
',fn ',bool-expr ,bool-expr)))
(setq in-wrapper? nil)
(return
(funcall 'wrapped-function
,@parameter-list))
))))
(not (interpretive-wrapper-ok? fn))
)
))
Up Next Prev PrevTail Front