A simple SAS macro library is usually quick and easy to create, maintain and support. Once the library grows, other projects get in the way, or you split that simple library into multiple libraries, the effort increases significantly, the code is more complex, the number of individual macros snowballs, help is available from other developers, nesting or simply time. The effort of debugging or just simple to unravel where in this chain of macros an issue occurs can be very time consuming, unless SAS can tell you where you are and how you got there, which it can.
The SYSMACRONAME automatic macro variable is a popular approach get the name of the current macro, but it will not include from where the current macro is being called.
SAS has two of macro features that can make it easy to identify the nested path of to an executing macro code.
- %SYSMEXECDEPTH will return the level of nesting
- %SYSMEXECNAME will return the name of the macro at specific depth
%sysmexecname( %sysmexecdepth ) is the same as SYSMACRONAME and will return the name of the current macro. If we use a simple DO-loop and
%SYSMEXECDEPTH, we can easily get the nesting sequence.
%let breadcrumbs = ; /* <-- initialize as empty */ %do i = 0 %to %sysmexecdepth ; %let breadcrumbs = &breadcrumbs %str( -> ) %sysmexecname( &i ) ; %end;
If we have three macros where
%three() contains our DO-loop, we get the nesting sequence. The part
%str( -> ) is just to get some spacing.
%macro three() ; %let breadcrumbs = ; /* <-- initialize as empty */ %do i = 0 %to %sysmexecdepth ; %let breadcrumbs = &breadcrumbs %str( -> ) %sysmexecname( &i ) ; %end; %put &breadcrumbs ; %mend; %macro two(); %three() ; %mend; %macro one(); %two(); %mend;
The SAS log displays the nested sequence.
109 %one(); -> OPEN CODE -> ONE -> TWO -> THREE
I included depth 0 in the nested loop just to point out that the code outside of the top level macro is referred to as
OPEN CODE. The SYSMEXECNAME macro does not return the program name.
A simple %trace() macro
Adding the code loop in macro
%three() to every macro does not really make sense, but we can use the code to turn
%three() into a simple general
%macro trace() ; /* futility ... if we call the trace macro outside of a macro, just exit */ %if ( %sysmexecdepth <= 1 ) %then %goto macro_exit ; %let breadcrumbs = %sysmexecname( 1 ); /* <-- initialize to first level */ %do i = 2 %to %eval( %sysmexecdepth - 1 ); %let breadcrumbs = &breadcrumbs %str(-> ) %sysmexecname( &i ) ; %end; %put &breadcrumbs ; %macro_exit: /* macro exit point */ %mend;
There are a few items to note in the above
%trace() macro. If the depth is equal to or less than 1, the
%trace() macro is called directly from within the program. As there is no parent macro or any level of nesting, we exit in futility.
The listed macro sequence follows the highest to the deepest nested level. We could just as easily reversed that order. The expression
%eval( %sysmexecdepth - 1 ) is used so that the
%trace() macro itself is not listed in the nesting sequence. If you recall from above, the expression
%sysmexecname( %sysmexecdepth ) will return the value of the current macro.
Finally, if we replace the DO-loop in macro
%three() with a call to
%trace(), we get the macro nesting sequence.
150 151 %one(); ONE -> TWO -> THREE
%trace() macro is quite useful. I use it, or extended variations, frequently in libraries where there is a documentation requirement or simply as an aide when trying to track down where to start looking for issues.
An extended version of
%trace() with a set of additional features will be part of the cx Library when the first version is released in the near future.