Contents: | Purpose / Requirements / Usage / Details / See Also |
There are macros to facilitate debugging and testing, and there are improved versions of %SUBSTR, %SCAN, the MERGE statement, and the LAGn function. There's also %INDEXC with no X prefix.
Outline of use
You must call %XINIT before using the other x macros and you must call %XTERM before terminating the main macro. It is strongly recommended that you follow the outline below as closely as possible to get all the features to work as intended.
* NO defaults in macro statement. put defaults in %xchk... instead; %macro whatever( data=, var=, freq=, weight=, by=, singular=, options=, ...); ************** initialize xmacros, turn off notes ************; %xinit( whatever) ************** check EVERY argument ONCE ************; * %xchk macros also set defaults, do range checks, echo arguments; %xchkdata( data, _LAST_) %xchklist( var, _NUMERIC_) %xchklist( freq, , , 1, 1) %xchklist( weight, , , 1, 1) %xchklist( by) %xchknum( singular, 1e-7, 0<SINGULAR and SINGULAR<1) ... ************** process options if you got any **************; %let print=0; * for example; %let nomiss=0; * for example; %let n=1; %let token=%qscan(&options,&n,%str( )); %do %while(&token^=); %let token=%qupcase(&token); %if %xsubstr(&token,1,5)=PRINT %then %let print=1; %else %if %xsubstr(&token,1,6)=NOMISS %then %let nomiss=1; %else %xerrset(Unrecognized option &token); %let n=%eval(&n+1); %let token=%qscan(&options,&n,%str( )); %end; %xbug(Options:, print nomiss) ************** process BY variables ************; %xbylist; %xvlist( data=&data, _list=by, _name=by, valid=0123) ************** dummy data step to check for errors ***************; %xchkvar( &data, &by, &var &freq &weight) %if &_xrc_^=OK %then %goto chkend; ************** process FREQ= variable ***************; %xvfreq( &data) ************** process WEIGHT= variable ***************; %xvweight( &data) ************** process VAR= list ***************; %let remove=; %if %qupcase(&var)=_NUMERIC_ %then %do; %if %bquote(&by)^= %then %let remove=&remove by; %if %bquote(&freq)^= %then %let remove=&remove freq; %if %bquote(&weight)^= %then %let remove=&remove weight; %end; %xvlist( data=&data, _list=var, _name=var, _type=type, _count=nvar, remove=&remove, replace=1) ************** set more defaults if there are any ***********; ... ************** echo arguments with defaults set and check for no observations in the data set **********; %chkend: %xchkend(&data) %if &_xrc_^=OK %then %goto exit; ************** turn notes back on before creating the real output data sets ****************; %xnotes(1) ************** do something useful here ***************; ... ... ... ************** check for errors after each DATA or PROC step *******************; %if &syserr>4 %then %do; * Assign descriptive message to &_xrc_ (which %xterm checks) and print ERROR: message; %xerrset(Something went wrong while the WHATEVER macro was trying to do something useful); %end; %exit: ************** issue termination message, reset notes and _LAST_ **; %xterm; %mend whatever;
The %xmain macro enables use of the &_test_ global variable for labeling test output. %xmain generates a %macro statement and a call to %xinit. You can use the following statement before including the main macro to automatically generate TITLEn statements to label test output:
%let _test_=&line; %* Defines a title line containing the macro invocation, where 1<=&line<=10;
For the example above, the %xmain macro would be used like this:
%unquote(%xmain( whatever( data=, var=, freq=, weight=, by=, singular=, options=, ...) )) ************** check EVERY argument ONCE ************; etc.
A test job would start like this:
title "Test WHATEVER macro"; %let _test_=2; %* echoes macro invocation in title2; %include 'whatever.sas';
This works by specifying the PARMBUFF option in the main macro statement. You should not hard-code the PARMBUFF option because of a bug that prevents the macro processor from checking the arguments to the main macro when it is invoked--in other words, if the user makes a typo in one of the argument names, there will be no error message.
Checking for inclusion of xmacro
Unless you copy the xmacro macros that you need into your macro file, it's a good idea to check whether xmacro has been included. This is especially important if you use xmain, otherwise you can get vast quantities of error messages. You can put a macro like this at the beginning of your macro file:
If xmacro has not been included, the above macro invocation begins a slash-star comment. You will also need to include a macro comment containing a star-slash at the end of your macro file like this:
%* close comment possibly generated by xmacinc *_/;
except, of course, that the _ between the * and / should be deleted. The _ is there to keep from terminating the comment that contains this text.
Never use slash-star comments in macros.
Debugging
The following statements in open code may be useful for debugging:
%let _notes_=1; %* Prints SAS notes for all steps; %let _echo_=1; %* Prints the arguments to the main macro; %let _echo_=2; %* Prints the arguments to the main macro again after defaults have been set; %let _debug_=1; %* Prints debugging information from non-x macros; %let _xdebug_=1; %* Prints debugging information from xmacros; options mprint; %* Prints SAS code generated by the macro language; options mlogic macrogen symbolgen; %* Prints lots of macro debugging info;
To turn on all diagnostic information, use %XNOISY. To turn it back off, use %XQUIET. These can be called in open code.
In your macro, Use the &_DEBUG_ variable to determine whether to print debugging information. The %XBUG macro is handy for printing the values of many macro variables conditional on &_DEBUG_. The %XBUGDO macro generates SAS code conditional on &_DEBUG_.
To speed things up, use:
%let _check_=1; %* Supresses checks for excessively large integers and for non-existent data sets and libraries; %let _check_=0; %* Supresses most argument checking;
If your macro can do extra, time-consuming error checking, make the extra error checks conditional on &_CHECK_ being greater than 1.
Argument checking
The %XCHK... macros are for checking and echoing the arguments to the main macro. You should generally call an %XCHK... macro for every argument before you do anything else with the argument, so that dangerous values will be detected. Users often forget the = sign for a keyword argument, so most of the %XCHK... macros check for extraneous ='s and extra tokens. Assiduous use of the %XCHK... macros greatly reduces the number of inscrutable error messages that users get.
Each %XCHK.. macro echoes the argument if &_ECHO_>=1. After checking all the arguments to the main macro and setting defaults, use %XCHKEND to echo the arguments again if &_ECHO_>=2.
%XCHKDEF Sets default value. Use this if no other checking is possible. %XCHKEQ Issues error if argument contains an equals (=) sign. %XCHKONE Issues error if argument has more than one token or contains an equals (=) sign. %XCHKUINT Checks an unsigned integer argument. %XCHKINT Checks an (optionally signed) integer argument. %XCHKNUM Checks a numeric (integer or floating point) argument. %XCHKMISS Checks a numeric or missing argument. %XCHKNAME Checks a SAS name. %XCHKDSN Checks a data set name. %XCHKDATA Checks an input data set name. %XCHKKEY Checks a value from a list of key words or values. %XCHKLIST Checks a list of integers, names, quoted strings, or special characters. %XCHKEND Checks if a data set is empty, echoes all previously checked arguments if &_ECHO_>=2.
You should not call any of the above macros more than once for a single argument, since that would cause %XCHKEND to echo the argument more than once. However, in addition to checking arguments individually, you can call %XCHKVAR to check an input data set to see if variables from one or more lists are in the data set:
%XCHKVAR Runs a dummy DATA step to check if variables exist. Does not echo anything.
All of the %XCHK... macros except %XCHKEND and %XCHKVAR return the argument that is checked in quoted form.
Macro quoting
Macro quoting is an arcane art that requires much study and dedication to master. The basic rules (which, of course, have exceptions) are:
A corollary of (1) is:
1a. Always use the Q... or X... versions of %SUBSTR, %UPCASE, %TRIM, %LEFT, etc.
The main purpose of quoting is to prevent an implicit %EVAL from going berserk. Implicit %EVALs occur in the condition of an %IF statement, various parts of a %DO statement, and some arguments to %(Q)SCAN and %(Q)SUBSTR. The trouble is that %EVAL evaluates _everything_ in an expression. So, for example,
%IF &A=&B %THEN ...
doesn't simply do a character comparison of &A and &B, but tries to interpret each of those values as an expression. If either &A or &B contains an operator such as AND or -, %EVAL will try do an operation. If &A is something like X1-X99, %EVAL will try to subtract two character strings and get very upset. For example:
%LET A=AND; %LET B=%EVAL(&A=OR); ERROR: A character operand was found in the %EVAL function or %IF condition where a numeric operand is required. The condition was: and=or
The circumvention is:
%LET B=%EVAL(%NRBQUOTE(&A)=%STR(OR));
%NRBQUOTE quotes the value &A at execution time. %STR quotes the literal OR at compile time. If the first operand were &&&A, then you would have to use %BQUOTE instead of %NRBQUOTE, since the latter would not rescan (hence the NR in the name) the argument.
Quoting is also needed when a positional argument to a macro contains an =, since the macro processor may misinterpret it as a keyword argument.
Quoting turns operators and other special characters into different, nonprintable characters. Quoting can also do funny things to tokenization. When you generate code from a macro variable, it's supposed to be automatically unquoted, but that doesn't necessarily work. If you get weird characters in your generated code, you forgot to unquote something. If ordinary ‘’ or "" quoted strings in your generated code cause weird errors, you forgot to unquote something.
If you use the %&ABC construct, &ABC must be unquoted. For example:
%macro hello(n); %do i=1 %to &n; %put Hello; %end; %mend; %let x=hello(2); %&x; Hello Hello %let x=%bquote(&x); %&x; ERROR: %EVAL function has no expression to evaluate, or %IF statement has no condition.
You can do the %UNQUOTE in a separate statement:
%let x=%unquote(&x); %&x; Hello Hello
or, if you prefer really bizarre code, you can use:
%unquote(%str(%%)%unquote(&x)) Hello Hello
Macro usage notes
Macros ordinarily return a single value. The macro language does not support call-by-address. So to return more than one value, you have to use call-by-name. That is, you pass the name of an existing macro variable that you want the value returned in. In the x macros, arguments for returning a value (or sometimes many values) by call-by-name have leading underscores in their names.
For example, suppose you want to write a macro called %REP to repeat an argument n times with blanks in between. You could write it to return a value like this:
%macro rep(arg,n); %* call by value; %local r i; %* must declare local variables; %let r=&arg; %do i=2 %to &n; %let r=&r &arg; %end; &r %* returned value; %mend rep; %let string=abc; %let repstr=%rep(&string,3); %* call by value requires ampersand; %put repstr=&repstr; repstr=abc abc abc
You could also write a macro to update a macro variable like this:
%macro uprep(_arg,n); %* underscore indicates call by name; %local __r __i; %* must declare local variables and use double underscores for names; %let __r=&&&_arg; %* reference to value of argument passed by name requires three ampersands; %do __i=2 %to &n; %let __r=&__r &&&_arg; %end; %let &_arg=&__r; %* to assign a value to an argument passed by name requires one ampersand on the left side; %mend uprep; %let string=abc; %uprep(string,3); %* call by name requires no ampersand; %put string=&string; string=abc abc abc
Be careful to avoid conflicts between the name of an argument passed by call-by-name and local macro variables or argumemt names. Macros that take call-by-name should use names for local variables beginning with 2 underscores.
The macro language does not support arrays. Instead of arrays, names are used that are composed of a constant prefix and a varying numerical suffix. If an array needs to be returned by a macro, the caller provides the prefix, and the macro constructs the names and declares them global.
To reduce the chances of name conflicts, local variables should always be declared in a %LOCAL statement. The names of global variables should usually begin with an underscore, but this hasn't been done everywhere yet. Global variables that users should know about should begin and end with an underscore. Other prefix naming conventions may be needed.
The macro language cannot do floating point operations. It does not issue an error message if you try to do a floating point comparison, but the comparison may be incorrect. You can use %XFLOAT to run a DATA step to evaluate a floating point expression.
Every line of SAS code that the macro language generates is saved by the SAS supervisor in a utility file (the "spool" file) in the WORK library with an overhead of about 100 bytes per line. Therefore, you should try to minimize the number of lines of SAS code generated in order to postpone running out of disk space. In batch mode, this utility file is automatically reinitialized after each step, so it's usually not a problem except with IML (in IML, the EXECUTE, PUSH, and QUEUE statements add stuff to this utility file). In line mode, OPTIONS NOSPOOL may prevent the utility file from growing until it uses up all your disk space, so this option is set by %XINIT. In DMS mode, OPTIONS NOSPOOL has no effect, and the user must issue a CLEAR RECALL command to reinitialize this utility file. If you actually run out of disk space, you should get a multiple-choice quiz that says something like this (this is from MVS):
Out of disk space for library WORK while trying to reserve a page for member @T000000.UTILITY. Select: ... 3. Clear source spooling/DMS recall buffers. ...
Number 3 is the correct answer.
SAS usage notes
&SYSERR should be checked after all DATA and PROC steps that can fail in a way that damages subsequent steps.
Variables created in DATA steps or IML that might conflict with names of the user's variables should have names beginning with underscores.
In order to support OPTIONS FIRSTOBS= and OBS= and data set options on the DATA= data set, the following restrictions must be imposed on code generated by macros:
Weird things you should know
%if %bquote(_xyz_)= %then %include 'xyz.sas';the semicolon terminates the %THEN statement, but the %INCLUDE statement is generated as regular SAS code WITHOUT a semicolon.
These sample files and code examples are provided by SAS Institute Inc. "as is" without warranty of any kind, either express or implied, including but not limited to the implied warranties of merchantability and fitness for a particular purpose. Recipients acknowledge and agree that SAS Institute shall not be liable for any damages whatsoever arising out of their use of this material. In addition, SAS Institute will provide no support for the materials contained herein.
These sample files and code examples are provided by SAS Institute Inc. "as is" without warranty of any kind, either express or implied, including but not limited to the implied warranties of merchantability and fitness for a particular purpose. Recipients acknowledge and agree that SAS Institute shall not be liable for any damages whatsoever arising out of their use of this material. In addition, SAS Institute will provide no support for the materials contained herein.
Right-click on the link below and select Save to save
the %XMACRO macro definitions
to a file. It is recommended that you name the file
xmacro.sas
.
Type: | Sample |
Date Modified: | 2017-03-23 14:06:25 |
Date Created: | 2005-01-18 07:16:39 |
Product Family | Product | Host | SAS Release | |
Starting | Ending | |||
SAS System | SAS/STAT | All | n/a | n/a |