Image483_fmt.png

Chapter 13 Interfacing with the Macro Language

13.1 Avoiding Macro Variable Collisions—Make Your Macro Variables %Local

13.2 Using the SYMPUTX Routine

13.2.1 Compared to CALL SYMPUT

13.2.2 Using SYMPUTX to Save Values of Options

13.2.3 Using SYMPUTX to Build a List of Macro Variables

13.3 Generalized Programs—Variations on a Theme

13.3.1 Steps to the Generalization of a Program

13.3.2 Levels of Generalization and Levels of Macro Language Understanding

13.4 Utilizing Macro Libraries

13.4.1 Establishing an Autocall Library

13.4.2 Tracing Autocall Macro Locations

13.4.3 Using Stored Compiled Macro Libraries

13.4.4 Macro Library Search Order

13.5 Metadata-Driven Programs

13.5.1 Processing across Data Sets

13.5.2 Controlling Data Validations

13.6 Hard Coding—Just Don’t Do It

13.7 Writing Macro Functions

13.8 Macro Information Sources

13.8.1 Using SASHELP and Dictionary tables

13.8.2 Retrieving System Options and Settings

13.8.3 Accessing the Metadata of a SAS Data Set

13.9 Macro Security and Protection

13.9.1 Hiding Macro Code

13.9.2 Executing a Specific Macro Version

13.10 Using the Macro Language IN Operator

13.10.1 What Can Go Wrong

13.10.2 Using the MINOPERATOR Option

13.10.3 Using the MINDELIMITER= Option

13.10.4 Compilation vs. Execution for these Options

13.11 Making Use of the MFILE System Option

13.12 A Bit on Macro Quoting

A great deal has been written on the macro language. The documentation provided by SAS Institute is good and two SAS Press books have been written on the subject, Carpenter’s Complete Guide to the SAS Macro Language, 2nd Edition (Carpenter, 2004), and SAS Macro Programming Made Easy, Second Edition (Burlew, 2006). The treatment of the macro language in this book, therefore, must necessarily be limited to a few topics.

SEE ALSO

Russ Tyndall, a Principal Technical Support Analyst for the DATA Step and Macro Language at SAS Institute, has written TS739 (Tyndall, 2005), which contains a number of advanced tips as well as newer features of the macro language.

13.1 Avoiding Macro Variable Collisions—Make Your Macro Variables %Local

The rule of thumb that a macro variable created within a macro will be local to that macro is a very dangerous rule simply because it is usually correct. In the world of programming the problem that only shows up occasionally is one of the worst to detect and fix. Simply knowing the rules (and there is nothing simple about knowing the rules) for symbol table assignment is not enough. Most symbol table assignments depend on circumstances that are usually unknown, and even more often unknowable, to the macro programmer. The rules for table assignment are described in Carpenter, 2004, Section 13.6, page 395.

Macro variable collisions occur when a macro variable assignment is written to the unintended symbol table and inadvertently overwrites an existing macro variable’s value. Very often this happens when the same macro variable name is inadvertently used in more than one of a series of nested macros.

A subtle example of a macro variable collision, and one that can cause horrid errors while leaving the programmer blissfully unaware, is contained in the program fragments shown here. In this case a secondary macro (%CHKSURVEY) is called from within a %DO loop in %PRIMARY.

The %DO loop in %PRIMARY seems to work without error. But a closer inspection of the inner macro %CHKSURVEY reveals a hidden problem.

%macro primary;

.... code not shown....

  %do i = 1 %to &dsncnt; Callout 2
   %chksurvey(&&dsn&i)
  %end;
  .... code not shown....
%mend primary;
%macro chksurvey(dset); 

.... code not shown....

  %do i = 1 %to 5; Callout 1
   .... code not shown....
%mend chksurvey;

Callout 1 The %DO loop in %CHKSURVEY uses &I as the index variable. This variable would usually be local to %CHKSURVEY; however, since &I already exists in the higher table of the calling macro, it will not be local. When %CHKSURVEY executes, it will modify the value of &I in the higher table of %PRIMARY. Callout 2

In this example, if &DSNCNT Callout 2 is less than or equal to 6, the loop in %PRIMARY will execute only once. If the programmer is lucky, &DSNCNT will be greater than 6 and an infinite loop will be created. This is lucky because then the programmer will at least know to look for the problem!

%macro chksurvey(dset); 
  %local i; Callout 3
   .... code not shown....
  %do i = 1 %to 5;

.... code not shown....

%mend chksurvey;

Unfortunately, we cannot protect the macro variables in a symbol table from being overwritten by a macro that is called by the outer macro. However, we can protect higher symbol tables by forcing all of our macro variables onto the LOCAL symbol table. This is done through the use of the %LOCAL statement. When using nested macro calls, macros that call macros, ALWAYS use %LOCAL to prevent collisions!! In the previous example the collision would have been avoided by simply adding the %LOCAL statement to %CHKSURVEY. Callout 3 As an aside we do not know for sure that the macro variable &I in %PRIMARY will not also cause problems to an even higher table, and a %LOCAL statement should also have been included in that macro.

In the previous paragraph, I suggest that you ALWAYS use the %LOCAL statement. Others have suggested that this is too strident, that the %GLOBAL statement and the global symbol table exist for a reason—so that we can use them. After all, the argument goes, there are situations when you want to pass a value out of a local symbol table and into the global environment. While I concede the desire, and might even admit to having used the global symbol table in this way, it is my admittedly biased opinion that this should not be a first choice, and that it is generally a solution employed when one of the alternative techniques discussed in this chapter are either not possible or more likely are not fully understood. That said, there is nothing wrong with using the global symbol table to pass values as long as the programmer fully understands the risks and has correctly and successfully mitigated them.

The problem with using the global symbol table is that it does not exist in the parent-child hierarchy implied by nested macros. The calling macro is the parent and the called macro is the child macro. We can take advantage of this relationship.

The best way to avoid collisions is to take direct control of symbol table placement. This is what we did by placing the %LOCAL statement in our macro definitions. Another method of doing this, without using the global symbol table, is to define your macro variables, and the symbol

tables that they use, in such a way as to allow their values to flow to a higher symbol table. This technique also allows a macro function to return more than one value without resorting to global macro variables.

While this method is not particularly flexible, it can be useful in some circumstances. The key to its success is to remember that if a macro variable already exists in a higher symbol table, and NOT the most local table, a macro variable assignment will be written to the higher table. This technique is demonstrated in the example that follows.

%macro primary;
  %local dsn; Callout 4
  %getdataname Callout 5
proc print data=&dsn Callout 6
  .... code not shown....
%mend primary; 
%macro getdataname; 
  .... code not shown.... 
  %let dsn = biomass; Callout 5
  .... code not shown....
%mend getdataname;

The name of the analysis data set (&DSN) is determined in the macro %GETDATANAME. In this case, for some reason, %GETDATANAME cannot be written as a macro function (see Section 13.7 for more on writing macro functions), and consequently it passes the data set name out of the macro using the macro variable &DSN. If %GETDATANAME does not explicitly have a %LOCAL statement for &DSN, the macro variable will be written to the next higher table in which it already exists; in this case this is the local table for %PRIMARY Callout 4.

Callout 4 The macro variable &DSN is added to the local symbol table for %PRIMARY (with a null value).

Callout 5 %GETDATANAME assigns a value to the macro variable &DSN. Assuming that &DSN does not already exist on the local symbol table for %GETDATANAME (there is no %LOCAL statement), and since it already exists in a higher table Callout 4, its value is written to the higher table (the local table for %PRIMARY). Callout 6 The data set name generated in %GETDATANAME is available during the execution of the remainder of the macro %PRIMARY, because it resides on the local table for %PRIMARY. The value of &DSN flows from the child macro (%GETDATANAME) to the higher parent macro (%PRIMARY).

MORE INFORMATION

Section 13.6 has another example which purposefully passes a macro variable through a higher table. The macro in Section 13.2.2 passes macro variables out of a macro by using the global symbol table.

SEE ALSO

Carpenter (2005) goes into detail on the subject of macro variable collisions and how they can be avoided. You can also read more about collisions and macro variable referencing scopes in Carpenter (2004, Section 5.4.2).

13.2 Using the SYMPUTX Routine

Starting in SAS®9 the SYMPUTX routine is offered as alternative to the SYMPUT routine for building macro variables from within the DATA step. My preference is to always use the SYMPUTX routine. When I am modifying existing programs that contain a call to SYMPUT, I will whenever possible, convert it to a SYMPUTX.

13.2.1 Compared to CALL SYMPUT

The SYMPUTX routine has two major advantages over SYMPUT, and one minor disadvantage. The only disadvantage of SYMPUTX relative to SYMPUT is that its name has one more letter to type, and that letter is an x! And actually this is not so much of a disadvantage.

Advantages of SYMPUTX over SYMPUT:

  • Automatic conversion of numeric input (without a note in the LOG). Callout 1
  • Uses a field width of up to 32 characters when it converts a numeric second argument to a character value. CALL SYMPUT uses a field width of up to 12 characters.
  • The value’s leading and trailing blanks are removed prior to assignment. Callout 3
  • Ability to force the macro variable onto the local or global symbol table. Callout 4

The advantages of SYMPUTX over SYMPUT are great enough that generally all new coding is being done using SYMPUTX in preference to SYMPUT. The advantages are shown in this example that writes the value of the numeric variable EDU for subject 205 into three macro variables. Callout 1 The use of the numeric variable EDU generates a conversion note in the LOG as well as the storage of a right justified character string.

data _null_;
  set advrpt.demog(where=(subject=205));
  call symput('EDU205a',edu); Callout 1
  call symput('EDU205b',left(put(edu,3.))); Callout 2
  call symputx('EDU205c',edu); Callout 3
  run;
%put |&edu205a| |&edu205b| |&edu205c|;

Callout 2 When using SYMPUT numeric values must be converted using the PUT function to avoid the note in the LOG. Here a TRIM function could have been used to avoid the storage of the trailing blank.

Callout 3 The SYMPUTX routine solves both of the issues shown in Callout 1 and Callout 2.

The LOG shows:

NOTE: Numeric values have been converted to character values at the places given by: Callout 1
   (Line):(Column).
   47:26
NOTE: There were 1 observations read from the data set ADVRPT.DEMOG.
      WHERE subject=205;
NOTE: DATA statement used (Total process time):
      real time      0.06 seconds
      cpu time       0.00 seconds
51  %put |&edu205a| |&edu205b| |&edu205c|;
|     12| |12 |Callout 2 |12| Callout 3

The optional third argument to the SYMPUTX routine can be used to place the macro variable onto the local (‘l’ or ‘L’) or global (‘g’ or ‘G’) symbol table Callout 4. The ability to control the symbol table assignment is especially important when attempting to avoid macro variable collisions (see Section 13.1). When the name of the macro variable is known as it is in this example, it is just as easy to use the %LOCAL (or %GLOBAL) statement. However, when the macro variable’s name is derived during the program’s execution, a %LOCAL statement is not always possible.

call symputx('EDU205c',edu,'l' Callout 4);

				

MORE INFORMATION

The SYMPUTX examples in Sections 13.2.2 and 13.2.3 use the third argument to make the symbol table assignment.

13.2.2 Using SYMPUTX to Save Values of Options

The macro %SCALEPOS is used to rescale the SAS/GRAPH VPOS and HPOS graphics options. The values of these options are stored in macro variables generated by a call to the SYMPUTX routine.

%macro ScalePos(hvscale=2.5);
data _null_;
	set sashelp.vgopt(keep=optname setting);
	where optname in('HPOS','VPOS'),
	call symputx(optname,setting,'G'), Callout 5
	run;
goptions hpos=%sysevalf(&hpos * &hvscale)
	vpos=%sysevalf(&vpos * &hvscale);
%mend scalepos;
goptions reset=all dev=win;
%scalepos(hvscale=1.5)
  . . . . code not shown . . . .
* Reset the HPOS and VPOS graphics options;
goptions hpos=&hpos vpos=&vpos; Callout 6

Callout 5 The name of the macro variable to be created is stored in the variable OPTNAME, and consequently the first argument to the SYMPUTX routine is not a constant. The third argument, which can be either uppercase or lowercase, allows us to place these macro variables onto the global symbol table.

Callout 6 These macro variables can be used later to reset the graphics options back to their original values.

This technique of passing variables to the global symbol table assumes that we know that the macro variables &HPOS and &VPOS either do not already exist or that it is OK for the macro %SCALEPOS to change their values. If this is not the case then we are at risk for having a macro variable collision (see Section 13.1).

SEE ALSO

Other methods of retrieving, storing, and reestablishing options and their values can be found in Carpenter (2004, Section 10.3.1).

13.2.3 Using SYMPUTX to Build a List of Macro Variables

It is very common to work with lists of items within the macro language, and there are several ways to create and process the items in these lists (Fehd and Carpenter, 2007). One common way of creating a list of macro variables is through the use of SYMPUTX.

The following DATA step is part of a macro that creates a list of all variables within a SAS data set that are of one type (numeric or character). The name of the macro variables to be created will take on the values of &VARNAME1, &VARNAME2, etc.

data _null_;
  set sashelp.vcolumn(where=(libname="%upcase(&lib)" & 
               memname="%upcase(&mem)" & 
               type="&type")) end=eof;
  call symputx('varname'||left(put(_n_,9.)),name,'L'),
  if eof then call symputx('varcnt',_n_);
  run;

The CALL SYMPUTX could have been somewhat simplified by using the CATS function to perform the concatenation.

call symputx(cats('varname',_n_),name,'L'),

Using a DATA step is not the only way, nor even necessarily the easiest, to create a list of numbered macro variables. See the sample code associated with this section for a PROC SQL example.

MORE INFORMATION

A list of macro variables is created using a PROC SQL step in Section 13.5.

SEE ALSO

In the macro language there are four primary ways of handling lists. These are discussed in Fehd and Carpenter (2007). Rozhetskin (2010) gives a number of clear and straightforward examples of the use of list processing for a variety of tasks. Crawford (2006) introduces a macro to simplify list processing.

13.3 Generalized Programs—Variations on a Theme

The macro language is first and foremost a code generator. As such, one of its strengths is to create and store reusable code. The next two sections discuss the process of generalization. As you become stronger in the macro language and more comfortable with the process itself, you may find that some of the steps shown in this section will become compressed or even eliminated.

13.3.1 Steps to the Generalization of a Program

Because macro programs can be difficult to debug, it is often easier to start with a working (non-macro) step or program. Then examine your code and modify it using these steps:

  • Identify those things that change from use to use.
  • Convert these items to macro language elements.
  • Use named parameters with reasonable defaults.

Consider the following simple PROC MEANS step. We want to generalize the step to allow processing against any data set and any list of classification and analysis variables. We may also want to allow the user to choose whether or not the procedure will generate printed output. The bolded sections of code are those things that we will need to control using macro language elements. These are the items that will be dependent on run-time conditions.

proc means data=advrpt.demog noprint;
  class sex;
  var ht wt;
  output out=stats
     n=
     mean=
     stderr= / autoname;
  run;
proc means data=&dsn &print;
  class &classlst;
  var &varlst;
  output out=&outdsn
     n=
     mean=
     stderr= / autoname;
  run;

These dependencies are then converted to macro language elements, in this case macro variables. The values of these macro variables can be supplied in a number of different ways. For the simple, most straightforward case, the values can be supplied as parameters in a macro call.

The macro %MYMEANS uses keyword parameters to specify the macro parameters. In addition the macro also performs some logic checks.

%macro mymeans(dsn=advrpt.demog, 
               classlst=sex, 
               varlst=ht wt, 
               outdsn=stats, 
               print=noprint Callout 1);
proc means data=&dsn 
 %if &outdsn = %then print; Callout 2
 %else &print;; Callout 3
%if &classlst ne %then %do;class &classlst;%end; Callout 4
%if &varlst ne %then %do; var &varlst; %end; Callout 4
%if &outdsn ne %then %do; Callout 5
 output out=&outdsn n= mean= stderr= / autoname;
%end; 
run;
%mend mymeans;

Callout 1 By default no printed output will be written; however, if there is no summary data set specified Callout 2 printed output is automatically generated.

Callout 3 The user can request both printed output and a summary data set by setting &PRINT to PRINT. The second semicolon closes the PROC statement.

Callout 4 The CLASS and VAR statements are only written if one or more variables have been specified. The %DO blocks are not really needed here, but they eliminate the need to have a double semicolon, such as was used on the %ELSE Callout 3.

Callout 5 When the name of a summary data set is provided the OUTPUT statement is written.

The following calls of the %MYMEANS macro demonstrate its flexibility.

This call to %MYMEANS will change the data set and analysis variables, use the default classification variable, and will produce no printed output.

%mymeans(dsn=sashelp.class,varlst=height weight)

Here only printed output is generated using all the standard defaults for the macro.

%mymeans(outdsn=)

SEE ALSO

Carpenter (2009) describes these steps to generalization in more detail.

13.3.2 Levels of Generalization and Levels of Macro Language Understanding

Another way of looking at the generalization steps described in Section 13.3.1 is to think of the process that one must go through as they learn the macro language. One could divide the learning process into three primary steps:

  • Code substitution
  • Use of macro statements and macro logic
  • Creation of dynamic applications using the macro language

As your macro language skills increase, and your understanding of the macro language process solidifies, you will find that you will be able to write more complex programs.

SEE ALSO

Stroupe (2003) uses the term Text Substitution in a very nice introduction to the macro language.

Code Substitution

Typically programs and macros written at this level expect that the user will supply all the information needed by the macro. These programs are characterized by a lack of macro logic, and the use of macro variables that contain single items of information.

%let dsn = advrpt.demog;
%let vars= subject ht wt;
proc print data=&dsn;
  var &vars;
  run;

There is only a very short learning curve for these techniques, which can usually be quickly applied even by programmers fairly new to SAS.

Macro Statements and Macro Logic

In this stage the user gives more control to the macro, and the macro can determine some information generalization from its incoming parameters. At this level the programmer starts to take advantage of macro logic and utilizes the macro functions.

%macro printit; 

.... code not shown....

%let dsn = advrpt.demog;
%let vars= subject ht wt;
proc print data=&dsn;
  %if &vars ne %then var &vars;;
  run; 

.... code not shown....

%mend printit;

This level of learning takes longer to master and requires a more thorough understanding of the basic programming aspects of SAS. Many very good macro programmers never venture beyond this level of learning.

Dynamic Programming

Characteristics of applications and programs written using dynamic macro programming techniques include:

  • A minimum of information is passed to the macro.
  • A macro is adept at determining what it needs.
  • Macro logic utilizes information outside of that passed into the macro.
  • Macros that call macros are typical.
  • Utility macros and macro functions are common.

It is common for dynamic macros to build and process lists of values (see Section 13.2.3 for one method for building a list of macro variables). Remember the steps to generalization (see Section 13.3.1) as you begin the process of converting your program from one that is controlled manually to one that builds its code dynamically.

%let dsn = advrpt.demog;
proc print data=&dsn;
  var %varlst(&dsn);

13.4 Utilizing Macro Libraries

If you write more than the occasional macro, or if you share macros with colleagues, or if you ever define the same macro in different programs/places, you should be using macro libraries. Macro libraries provide the ability to remove the macro definition (%MACRO to %MEND statements) from your programs. By placing the macro definitions in a library, other programmers in your group can have access to the same macro definitions. Libraries allow you to effectively share your macro definitions without copying and storing them in multiple locations.

There are three basic types of macro libraries:

  • %INCLUDE

these are not true macro libraries.

  • Autocall

macro definitions (%MACRO to %MEND) are stored as code.

  • Stored Compiled

compiled macros are stored in permanent catalogs.

Each of the three forms has value and each is worth knowing; however, if you only learn one type, learn to use the autocall macro library. This library is most often used, and it has a number of advantages over the other two forms of libraries.

A macro is defined through the use of the %MACRO and %MEND statements. When these statements are executed, the macro facility performs a macro compilation. This is not really a true compilation, and is little more than a check on macro syntax. The compiled macro definition is then written to a SAS catalog with an entry type of MACRO. The default catalog is WORK.SASMACR and the entry name will be the name of the macro itself.

SEE ALSO

A full treatment of the use of macro libraries can be found in Carpenter (2001a and 2004). Extensive use is made of Stored Compiled Macro Libraries in Section 13.9 and in Sun and Carpenter (2011).

13.4.1 Establishing an Autocall Library

By default an autocall macro library is automatically made available. This library contains a fairly extensive collection of macros that are provided with SAS. These include macros such as %LEFT, %VERIFY, and %QTRIM.

Two system options, MAUTOSOURCE and SASAUTOS=, are used to control the use of the autocall library. The ability to access an autocall library is turned on with the MAUTOSOURCE system option (by default this option is on). The physical location of the autocall library is specified using the SASAUTOS= Callout 1 system option.

* Default option settings;
options mautosource
    Callout 1sasautos=sasautosCallout 2;

By default the SASAUTOS= option’s value is an automatic composite fileref also named SASAUTOS Callout 2. This fileref points to various locations (which locations and how many depends to some extent on your release of SAS , and the products that you lease). These locations are used to house the autocall macro definitions that are supplied by SAS.

filename mymacs "<phys path to my macro definitions>";
filename prjmacs "<phys path to the project macro definitions>";
filename COmacs "<phys path to the company wide macro definitions>";
options mautosource
    Callout 1sasautos=(mymacs prjmacs comacs sasautos Callout 2);

You may add your own macro definitions to the autocall library by storing them in one or more locations and then by adding those locations to the SASAUTOS= option. Notice the use of the FILENAME statement, not a LIBNAME Callout 3 statement. Under directory-based systems the fileref will point to the directory level and under zOS to a partitioned data set.

The only further constraint is that the macro name must match the name of the file that contains the definition. If you were to create the definition for the macro %ABC, the %MACRO ABC statement through the %MEND ABC statement, would be stored in a file named ABC.SAS (or under zOS, an ABC member name). On the UNIX OS, the name of the file that stores the macro definition must be in all lowercase characters (abc.sas).

When the %ABC macro is called, SAS will search for the program ABC.SAS in the locations (left to right) specified in the SASAUTOS= option. Once the file is found, the macro definition is included, the %MACRO to %MEND macro definition is compiled, and then the %ABC macro is executed.

While it is possible for the file containing the macro definition to contain code other than just the %MACRO through %MEND statements, it is not a good idea to do so. By segregating the code so that a given file contains only the definition for the macro for which it is named, macro definitions become much easier to find, and control.

There are a couple of caveats to be aware of when using autocall libraries. First, be very careful to include the automatic fileref SASAUTOS Callout 2. Failure to do so results in the loss of the ability to use autocall macros supplied by SAS. Secondly, be sure to specify the library locations using filerefs and not librefs. Use the FILENAME statement even though you are pointing to a location and not to a specific file. Although no error is issued when the SASAUTOS= option is specified using a libref, the use of a libref Callout 3 will cause problems when the library is accessed.

* WRONG WAY TO SPECIFY THE LIBRARY!!!;
libname Callout 3 COmacs "c:	emp";
options mautosource
    sasautos=(comacs Callout 3 sasautos Callout 2);
%silly

SEE ALSO

Heaton and Woodruff (2009) discuss options for establishing a company-wide autocall library. SAS Sample Code 24-841, by Peter Crawford, discusses a macro that inserts filerefs into the SASAUTOS= list of locations http://support.sas.com/kb/24/841.html.

13.4.2 Tracing Autocall Macro Locations

As was discussed in Section 13.4.1, it is not uncommon to have an autocall library point to several locations. When a macro is called, SAS searches each location in turn and executes the first copy of the macro that is encountered. You may need to know which location contains the code for the called macro. The MAUTOLOCDISPLAY system option Callout 1, the default is NOMAUTOLOCDISPLAY, will write the physical location Callout 2 of the macro’s definition, whenever a macro is retrieved from an autocall library and is subsequently used.

In this example the definition for the %OBSCNT macro resides in the directory shown for the fileref MYMACS. Each time the macro is called the LOG shows the path to the program (OBSCNT.SAS) containing the macro definition.

filename mymacs "&pathsascodesasmacros";
options mautosource
    sasautos=(mymacs sasautos)
    mautolocdisplay; Callout 1
%put There are %obscnt(sashelp.shoes) obs in sashelp.shoes;
451 %put There are %obscnt(sashelp.shoes) obs in sashelp.shoes;
MAUTOLOCDISPLAY(OBSCNT): This macro was compiled from the autocall file
             C:AdvTechniquessascodesasmacrosobscnt.sas  
There are 395 obs in sashelp.shoes

13.4.3 Using Stored Compiled Macro Libraries

Stored Compiled Macro Libraries are only available when turned on with the MSTORED system option. The SASMSTORE= option is then used to allocate the stored compiled macro library. Although the SASMSTORE option accepts only one libref, the library associated with that libref can be a concatenated or composite library.

libname complib "&pathsascodestoredmacros";
options mstored
    sasmstore=complib;

If a stored compiled macro library is available, the /STORE option on the %MACRO statement can be used to direct the compiled macro to the permanent COMPLIB.SASMACR catalog.

%macro def / store;
  %put Stored compiled Version of DEF;
%mend def;

13.4.4 Macro Library Search Order

Understanding the macro library search order is crucial to understanding which version of a macro will be executed. When a macro, such as the %ABC macro, is called, SAS must search for the macro’s definition. SAS first looks for the ABC.MACRO entry in the WORK.SASMACR catalog. Then, assuming that it is not found in the WORK catalog, and if stored compiled macro libraries are turned on, a search is made for the ABC.MACRO entry in each SASMACR catalog in the libref designated by the SASMSTORE= system option. Finally if a compiled entry has not yet been found, SAS starts a search in the autocall library locations for a program with the name of ABC.SAS.

In summary the search order is:

  1. WORK.SASMACR
  2. stored compiled macro libraries (COMPLIB.SASMACR in the above example)
  3. autocall macro libraries

13.5 Metadata-Driven Programs

Metadata is data about the data. For the macro language the metadata very often contains the instructions that will be used to drive the macros. Instead of passing macro parameters the macros read data to determine the parameters.

13.5.1 Processing across Data Sets

In this example the researcher wants to print the key variables along with the critical variables for each of several data sets. The %PRINTALL macro has been written to make the listings; however, the macro obtains the information that it needs (data set name, BY variables, critical variables) from a SAS data set. This control file, which has one observation for each data set of interest, contains all the information needed by the %PRINTALL macro.

13.5.1 Using Metadata Across Data Sets
Meta-data Control File
Obs DSN            keyvars            critvars
 1  demog          subject            dob ht wt
 2  Lab_Chemistry  subject visit      labdt
 3  Conmed         subject mednumber  drug

We can use this table when we want to process across all data sets in the study or when we need data set specific information - such as the BY variables. The %PRINTALL macro needs the name of the data set, it’s BY variables, and the list of its critical variables.

%macro printall;
  %local i dsncount;
  * Build lists of macro vars;
  proc sql noprint;
  select dsn,keyvars,critvars Callout 1
   into :dsn1 - :dsn999, Callout 2
      :keyvar1 - :keyvar999, 
      :critvar1 - :critvar999
     from advrpt.dsncontrol; Callout 3
  %let dsncount = &sqlobs; Callout 4
  %do i = 1 %to &dsncount; Callout 5
   title2 "Critical Variables for &&dsn&i";
   proc print data=advrpt.&&dsn&i;
     id &&keyvar&i; Callout 6
     var &&critvar&i;
     run;
  %end;
%mend printall;
%printall

Callout 1 The three variables that contain the metadata values of interest are selected from the control file.

Callout 2 A list of macro variables is created. The SQL step does not support the SYMPUTX routine, instead it uses the INTO : clause to write macro variables to the symbol table.

Callout 3 The control data set is read into the SQL step.

Callout 4 The number of data sets, observations in the control data set, is saved in &DSNCOUNT.

Callout 5 The %DO loop cycles through the &DSNCOUNT data sets with &I as the data set counter.

Callout 6 The macro variable of the form &&VAR&I refers to the Ith element in the list. For &I=2, &&CRITVAR&I resolves to LABDT.

Notice the use of an asterisk style comment in the %PRINTALL macro. SAS recommends the use of the /* */ style of comments. Using asterisk style comments inside a macro to comment out macro language elements can cause problems (Carpenter, 2004, Section 13.3.5). A minimum rule should be to use macro comments to comment out or to annotate macro code. See the examples in Sections 13.5.2 and 13.7.

MORE INFORMATION

In this example and in the example in the following section, the metadata has been manually generated. Metadata can come from a number of sources, and some of these sources are available automatically. Section 13.8 discusses some of those sources of information.

13.5.2 Controlling Data Validations

In Section 2.3.3 there is a discussion of the use of a simple data set, which is used to populate the macro parameters of a data set specific error checking macro. Using similar techniques it is possible to build the checks themselves based on the data that is to be validated.

The metadata shown here contains the data set name and the check information associated with that check. If the metadata is designed to contain sufficient information, it can easily be expanded to accommodate any number of checks on any number of data sets, and multiple checks can be performed on any given variable. The number of checks and the kinds of checks are only limited by the programmer’s imagination.

13.5.2 Metadata Driven Field Checks
Obs  dsn            var     chkrating  chktype   chktext
 1   demog          subject     1      notmiss
 2   demog          RACE        2      list      ('1','2','3','4','5','6')
 3   conmed         medstdt_    4      datefmt   mmddyy10.
 4   lab_chemistry  potassium   2      maximum   6.7

In the examples in Section 2.3.3 a data validation and error reporting macro was developed that utilized metadata to perform simple data checks. We can expand on that macro by making use of the type of metadata shown here. In this example the checks are performed across all data sets, and the checks themselves are constructed from the information in the metadata.

Any number of different types of checks is possible; shown here are just a few to give you an idea of the possibilities (variable CHKTYPE in the metadata):

  • notmiss

the variable may not contain missing values.

  • list

the value must be in the list of values in CHKTEXT.

  • datefmt

the formatted value of the variable (using the format in CHKTEXT) must not be missing.

  • maximum

the value must be less than or equal to the value in CHKTEXT.

Using this approach any number of checks can be performed against a given variable or data set. Adding and changing checks does not require coding changes, unless a brand new check is introduced. Here a slight coding modification would be required if we wanted to introduce a check for minimum values.

%macro errrpt(dsn=, keyvars=subject); Callout 1
%local i;
data _null_; Callout 2
  set advrpt.fldchk(where=(upcase(dsn)=upcase("&dsn")));
  fldcnt+1;
  cnt = left(put(fldcnt,6.));
  call symputx('errdsn'||cnt,dsn,'l'),
  call symputx('errvar'||cnt,var,'l'),
  call symputx('errrating'||cnt,chkrating,'l'),
  call symputx('errtype'||cnt,chktype,'l'),
  call symputx('errtext'||cnt,chktext,'l'),
  call symputx('chkcnt',cnt,'l'),
  run;  
data errrpt&dsn Callout 3
     (keep=dsn 
           &keyvars
           errvar errval errtxt errrating);
  length dsn    $25
     errvar   $15
     errval   $25
     errtxt   $25
     errrating 8;
set advrpt.&dsn;
%do i = 1 %to &chkcnt; Callout 4
  %* Write as many error checks as are needed;
  if Callout 5
  %* Determine the error expression;
  %if %upcase(&&errtype&i)    = NOTMISS %then Callout 6
        missing(&&errvar&i); Callout 7
  %else %if %upcase(&&errtype&i) = LIST %then 
        &&errvar&i not in(&&errtext&i); Callout 7
  %else %if %upcase(&&errtype&i) = DATEFMT %then
        input(&&errvar&i,&&errtext&i) eq .; Callout 7
  %else %if %upcase(&&errtype&i) = MAXIMUM %then
        &&errvar&i gt &&errtext&i; Callout 7
  then do; Callout 8
   dsn = "&dsn";
   errvar = "&&errvar&i";
   errval = &&errvar&i;
   errtxt = "&&errtext&i";
   errrating= &&errrating&i;
   output errrpt&dsn;
   end;
  %end;
  run;
title2 "Data Errors for the &dsn data set";
proc print data=errrpt&dsn;
  run;
%mend errrpt;

Callout 1 The %ERRRPT macro is passed the name of the data set to be checked and the key variables for that data set.

Callout 2 A DATA _NULL_ step is used to read the error metadata appropriate for the data set to be checked.

Callout 3 A data set containing the data errors is defined. Here it is written to the WORK directory and its name includes the name of the data set being checked.

Callout 4 A macro %DO loop is used to cycle across the checks that have been requested in the metadata for this data set.

Callout 5 The expression used to detect the data error will be written for the DATA step IF statement by a macro %IF and will be based on the metadata. The IF statement terminates with a THEN DO/END at Callout 8.

Callout 6 A %IF statement is used to determine the type of error comparison that is to be written.

Callout 7 The error condition specified in the metadata is written.

Callout 8 The error information is written to the error reporting data set. This THEN DO/END terminates the IF statement started at Callout 5.

The macro %ERRRPT is called once for each data set that is to have its data validated. The list of data sets used in the study, along with their key variables, can also be placed in metadata. For the study data being checked in this example, the metadata used to describe the study data sets can be found in ADVRPT.DSNCONTROL (see Section 13.5.1).

For the checks on the DEMOG data set the following DATA step is written by the %ERRRPT macro. Notice that only the two checks associated with this data set in the metadata have been included and that the appropriate variables have been used in the checks.

data errrptdemog (keep=dsn subject errvar errval errtxt errrating);
length dsn $25 errvar $15 errval $25 errtxt $25 errrating 8;
set advrpt.demog;
if missing(subject) Callout 7 then do;
dsn = "demog";
errvar = "subject";
errval = subject;
errtxt = "";
errrating= 1;
output errrptdemog;
end;
if RACE not in(('1','2','3','4','5','6')) Callout 7 then do;
dsn = "demog";
errvar = "RACE";
errval = RACE;
errtxt = "('1','2','3','4','5','6')";
errrating= 2;
output errrptdemog;
end;
run;

The ERRRPTDEMOG data set will contain any detected errors (in this case a single error was found—a missing value for the variable RACE).

13.5.2 Metadata Driven Field Checks
Data Errors for the demog data set
Obs  dsn  errvar  errval          errtxt          errrating   subject
 1  demog  RACE          ('1','2','3','4','5','6')    2         204

The macro %DATAVAL reads the DSNCONTROL metadata and then builds macro variable lists of data set names and BY variables. Those macro variable lists are then used to call the %ERRRPT macro (shown above) for each data set.

%macro dataval;
%local i;
* Determine list of data sets to check;
data _null_; Callout 9
  set advrpt.dsncontrol;
  cnt = left(put(_n_,5.));
  call symputx('dsn'||cnt,dsn,'l'),
  call symputx('keyvars'||cnt,keyvars,'l'),
  call symputx('dsncnt',cnt,'l'),
  run;
%* Perform data validation checks on 
%* each data set;
%do i = 1 %to &dsncnt;
  %errrpt(dsn=&&dsn&i, keyvars=&&keyvars&i) Callout 10
%end;
%mend dataval;
%dataval

Callout 9 A DATA step is used to create the lists of macro variables. This type of list is generally easier to create using an SQL step.

Callout 10 An iterative %DO loop is used to process across the list of data sets.

In some instances you may want to store the validation formula itself in the metadata. Although technically this can be more challenging, the methodology is an expansion of the techniques shown above.

The DATA _NULL_ step in the %DATAVAL macro could have been replaced with a PROC SQL step. The end result is the same with the exception that we cannot as easily control the symbol table for the derived macro variables.

%macro dataval2;
* Determine list of data sets to check;
proc sql noprint;
  select dsn,keyvars
   into :dsn1-:dsn999,
      :keyvars1-:keyvars999
   from advrpt.dsncontrol;
  %let dsncnt=&sqlobs;
  quit;
%* Perform data validation checks;
%* on each data set;
%do i = 1 %to &dsncnt;
  %errrpt(dsn=&&dsn&i, bylst=&&keyvars&i)
%end;
%mend dataval2;

The generation of the list of macro variables can be avoided altogether by using the CALL EXECUTE routine.

Since CALL EXECUTE is a DATA step routine, the %ERRRPT macro call can be generated directly for each observation using the DATA step variables. CALL EXECUTE places the macro call in a stack which executes after the execution of the DATA step.

%macro dataval3;
* Determine list of data sets to check;
data _null_;                              
  set advrpt.dsncontrol;                       
  call execute('%nrstr(%errrpt(dsn='||dsn||', keyvars='||keyvars||'))'),
run;  
%mend dataval3;
%dataval3

MORE INFORMATION

Section 2.3.3 introduces an example that uses metadata to drive a data validation macro. Section 13.5.1 introduces the use of the ADVRPT.DSNCONTROL data set.

SEE ALSO

Fehd and Carpenter (2007) and Rozhetskin (2010) discuss several different ways to process a list of metadata values.

Although not directly applicable to the use of metadata, discussions on how to store a formula as a data value and have it executed dynamically in a later DATA step can be found in the SAS Forum threads http://communities.sas.com/message/48498 and

13.6 Hard Coding—Just Don’t Do It

Hard coding takes place when study or data-specific information is inserted directly as code in our programs. Unfortunately this is an all too common practice that can cause a number of problems for the researcher:

  • Code has embedded data dependencies.
  • Changes to the dependencies requires coding changes in all programs, which have the dependency.
  • Each modified program must be revalidated.

A simple example of a hard coded data dependency is the exclusion of a subject from an analysis. In this case we need to exclude subject 202 when data is read from ADVRPT.CONMED. Creating a WHERE clause through the use of a WHERE= data set option to do the exclusion is quite easy Callout 1; however, for consistency the exclusion must take place in each program that utilizes data that contains that subject, and it is likely that the exclusion list will not remain constant. Keeping track of which programs utilize what data dependent exclusions can become tedious and error prone. By consciously developing tools for avoiding the use of hard coding, we can avoid the hard coding nightmare.

data conmed;
  set advrpt.conmed(where=(subject ne '202')); Callout 1
 . .       . . code not shown . . . .
  run;

The macro language can be used to replace hard coded exception lists. The simplest solution is to just move the exception coding to a macro Callout 2 that can be called from any program that needs to account for the data exceptions Callout 3. While not very flexible, placing the macro in a macro library makes the exceptions available to all programs from a single, changeable source.

%macro exceptions;
where=(subject ne '202') Callout 2
%mend exceptions;
data conmed;
  set advrpt.conmed(%exceptions Callout 3);
  run;

Of course real life is rarely this simple Exceptions may only be appropriate for some data sets and there may also need to be data adjustments that need to be applied in only certain situations. Both of these cases lend themselves well to the creation and use of metadata to control the process. The use of metadata to drive a process is described in Section 13.5.

A simple extension of the previous example might include a metadata file such as the one shown here. This data set includes only the data set name and one or more exceptions. Other expressions are easily implemented using this approach.

data advrpt.DataExceptions;
  length dsn $12 exception $35;
  dsn='AE';     exception="(subject le '204')"; output;
  dsn='conmed'; exception="(subject ne '202')"; output;
  dsn='conmed'; exception="(subject ne '208')"; output;
  run;
%macro exceptions(dsn=ae);
  * Build exception list;
  proc sql noprint;
   select exception into :explist separated by '&' Callout 6
     from advrpt.dataexceptions Callout 4
      where upcase(dsn)=upcase("&dsn"); Callout 5
   quit;
  %if &explist ne %then %let explist=where=(&explist); Callout 7
%mend exceptions;
%let explist = ; Callout 8
%exceptions(dsn=conmed) Callout 9
%put &explist;
proc print data=advrpt.conmed(&explist) Callout 10;
  run;

In this version of the %EXCEPTIONS macro, the metadata are used to build a WHERE= data set option that can be used to subset the incoming data. Callout 4 The data exceptions metadata is read using an SQL step.

Callout 5 Observations associated with the data set of interest are selected, and the values of the variable EXCEPTION are added to the macro variable &EXPLIST Callout 6.

Callout 6 You need to be careful when using an ampersand within a macro variable, as was done here. In this macro the individual clauses are surrounded by parentheses, consequently the & will not be seen as a macro language trigger.

Callout 7 The list of data exceptions are stored in the form of a WHERE= data set option. The macro variable &EXPLIST will NOT be written to the local symbol table for the %EXCEPTIONS macro, but will be written to the next higher table (where &EXPLIST already has been established with a null value) Callout 8.

Callout 8 The macro variable is initialized to a null value. This not only ensures that the macro variable does not contain a value from a previous execution of %EXCEPTIONS, it also adds the macro variable to the most local symbol table (given the code that we see here, this may be the global symbol table). The value of &EXPLIST generated within %EXCEPTIONS will, therefore, be written to this higher symbol table and not to the local table for %EXCEPTIONS. This helps to control the possibility of a macro variable collision with the value of the macro variable &EXPLIST in a higher symbol table.

Callout 9 The %EXCEPTIONS macro is executed and a value is assigned to the macro variable &EXPLIST. The LOG shows the resulting WHERE clause.

15  %put &explist;
where=((subject ne '202')&(subject ne '208'))

Callout 10 The WHERE= data set option with the exceptions is added when the data set is used. When there are no exceptions for a given data set, &EXPLIST will have a null value and no observations will be excluded.

MORE INFORMATION

Section 13.1 specifically discusses macro variable collisions.

13.7 Writing Macro Functions

A macro function (Chung and Whitlock (2006) use the terminology Function-Style macros) is a macro that is written so that it mimics the behavior of a function. Several of the Autocall macros supplied by SAS (including %LEFT, %QTRIM, and %VERIFY) are actually macro functions. It is not all that difficult to write a macro function, but there are three rules that you need to follow to successfully cause the macro to work like a function.

A function returns a specific value of interest, and only that value of interest. Since the macro language is first and foremost a code generator, we want to make sure that the only code generated by our function is the value that is to be returned. Of course we would also like our macro to be robust and to not interfere with any other code that we use in conjunction with our macro function. The following three rules ensure that your macro will operate like a function and will not interfere with other code.

Your macro function should:

  • Use only macro language elements: no DATA steps or PROC steps.
  • Create no macro variables that are not local to the macro.
  • Resolve to the value that is to be passed out of the function.

When written following these rules, your macro function can be used in both DATA steps and with macro language elements. Here is a fairly classic macro function, which is a slightly modified version of a macro of the same name that appears in Carpenter, 2004 (Section 11.5.1). This macro function returns the number of observations in a SAS data set by opening and examining the data set’s metadata.

%macro obscnt(dsn);
%local nobs dsnid rc; Callout 1
%let nobs=.;
%* Open the data set of interest;
%let dsnid = %sysfunc(open(&dsn));
%* If the open was successful get the;
%* number of observations and CLOSE &dsn;
%if &dsnid %then %do;
   %let nobs=%sysfunc(attrn(&dsnid,nlobs));
   %let rc =%sysfunc(close(&dsnid));
%end;
%else %do;
   %put Unable to open &dsn - %sysfunc(sysmsg());
%end;
%* Return the number of observations;
&nobs Callout 2
%mend obscnt;

Notice that the %OBSCNT macro contains only macro statements, that all the macro variables created in the macro are forced onto the local symbol table Callout 1, and that the value of the number of observations, &NOBS, is passed out of the macro as resolved text Callout 2. As occurs here, it is quite common that the value to be passed back stands alone as a macro language element–not as a complete statement. Here the macro variable &NOBS Callout 2 will resolve to the number of observations during the execution of the macro. This becomes the only non-macro language element in the macro and, therefore, becomes the resolved value of the macro. If we assume that the data set WORK.CLINICS has 88 observations, the %IF statement in the code box on the left resolves to the one in the code box on the right.

%if 88 > 5 %then %do;
%if %obscnt(clinics) > 5 %then %do;

The first rule requires all statements to be macro language statements. This includes comments. In this example macro comments have been used; however, they could have been replaced with the SAS recommended /* */ style comments. Although the /* */ style comment is not a macro language element, as is the %* style comment, the /* */ style comment is stripped out even earlier in the parsing process and, consequently, will not interfere with the macro function as would an asterisk style comment.

Very often the macro function can be written to contain only a single macro language phrase. This code segment is executed and the result is passed out of the macro to the calling program. It is important to remember that code segments are handled differently in the macro language than are code segments in the DATA step. Macro variables are commonly resolved without being a part of a complete macro statement Callout 2, and a macro function, especially %SYSFUNC, can be a complete element in and of itself Callout 3.

%macro wordcount(list);
  %sysfunc(countw(&list,%str( ))) Callout 3
%mend wordcount;
%let list = a  Bb c d;
%put %wordcount(&list);

The %WORDCOUNT macro function, shown here only contains a %SYSFUNC macro function. Notice that the %SYSFUNC Callout 3 is not a part of a complete statement, and is not followed by a semicolon. The %SYSFUNC function call will resolve to the number of words in &LIST (4 in the example shown here).

The following macro, %AGE, which was written by Ian Whitlock and appears in Chung and Whitlock (2006), is a macro function that returns a person’s age in years. This macro function is designed to be used either in a DATA step or in a PROC step WHERE clause. The macro assumes that &BEGDATE and &ENDDATE are either SAS date values or variables that hold the date values. Here the %AGE macro is used to list those subjects over 45 as of the specified date.

%macro age(begdate,enddate); 
 (floor((intck('month',&begdate,&enddate)-(day(&enddate)<day(&begdate)))/12))
%mend age;

The value returned by this function is the expression itself (not its resolved value—which is determined at the time of execution). As in the previous example, %WORDCOUNT, the macro contains only an expression and not a complete statement.

proc print data=advrpt.demog;
  * select subjects over 45 as of Feb 18, 1998;
  where %age(dob,'18feb1998'd) gt 45;
  var fname lname dob;
  run;

MORE INFORMATION

Alternative methods of calculating age can be found in Section 3.2.

Because a macro function can also be used to return a value, the macro call itself can be used as a part of a macro statement. The macro %NEXTDOG determines the next available macro variable name that starts with the letters DOG.

%let dog=scott;
%let dog1=bill;
%let dog2=george;
%let dog3=notsue; Callout 4
%macro nextdog;
%local cnt;
%let cnt=;
%do %while(%symexist(dog&cnt)); Callout 5 
  %let cnt=%eval(&cnt+1); Callout 6
%end;
&cnt Callout 7
%mend nextdog;
%put nextdog is %nextdog;
%let dog%nextdog=Johnny; Callout 8
%put nextdog is %nextdog;

Callout 4 The macro variable &DOG3 has been defined. The next available name will be &DOG4.

Callout 5 The %SYMEXIST function is used to determine if a given macro variable currently exists on the global symbol table.

Callout 6 Increment the counter and check for the next macro variable.

Callout 7 When a given macro variable is not
found Callout 5 the %DO %WHILE loop terminates and the next available value is passed out of the macro.

Callout 8 The next available macro variable is automatically assigned using the next available number.

SEE ALSO

Carpenter (2002) and Carpenter (2004: Section 7.5.2) both cover the rules associated with the creation of macro functions.

The original age formula used by Chung and Whitlock (2006) was devised by Kreuter (2004).

The %NEXTDOG macro was used to demonstrate a concept in a SAS Forum thread http://communities.sas.com/thread/14805.

13.8 Macro Information Sources

Macros that are sophisticated enough to seek out and utilize information that they need without resorting to user input have a great advantage in both flexibility and power. In order for us to write these macros, we must be aware of these information sources and how and when to use them.

Fortunately, there is a great deal of information that is easily accessible to the macro language.

13.8.1 Using SASHELP and Dictionary tables

A series of views and on-demand tables have been constructed to provide a great deal of information about SAS and the environment in which it is running. These come in two basic flavors:

  • DICTIONARY tables available only within an SQL step.
  • SASHELP views can be used anywhere a data set or a view can be used.

The full list of SASHELP views and DICTIONARY tables can be found in the SAS documentation. The following list is selection of some of these that I have found to be most helpful.

DICTIONARY Tables and Associated SASHELP Views

DICTIONARY Table

SASHELP View

Description

CATALOGS

VCATALG

Contains information about known SAS catalogs.

COLUMNS

VCOLUMN

Contains information about columns in all known tables.

DICTIONARIES

VDCTNRY

Contains information about all DICTIONARY tables.

ENGINES

VENGINE

Contains information about SAS engines.

EXTFILES

VEXTFL

Contains information about known external files.

FORMATS

VFORMAT

VCFORMAT

Contains information about currently accessible formats and informats.

GOPTIONS

VGOPT

VALLOPT

Contains information about currently defined graphics options (SAS/GRAPH software). SASHELP.VALLOPT includes SAS system options as well as graphics options.

INDEXES

VINDEX

Contains information about known indexes.

LIBNAMES

VLIBNAM

Contains information about currently defined SAS libraries.

MACROS

VMACRO

Contains information about currently defined macro variables.

(continued)

DICTIONARY Tables and Associated SASHELP Views (continued)

DICTIONARY Table

SASHELP View

Description

MEMBERS

VMEMBER

VSACCES

VSCATLG

VSLIB

VSTABLE

VSTABVW

VSVIEW

Contains information about all objects that are in currently defined SAS libraries. SASHELP.VMEMBER contains information for all member types; the other SASHELP views are specific to particular member types (such as tables or views).

OPTIONS

VOPTION

VALLOPT

Contains information about SAS system options. SASHELP.VALLOPT includes graphics options as well as SAS system options.

STYLES

VSTYLE

Contains information about known ODS styles.

TABLES

VTABLE

Contains information about known tables.

TITLES

VTITLE

Contains information about currently defined titles and footnotes.

VIEWS

VVIEW

Contains information about known data views.

To learn more about a given SASHELP view simply explore it like you would any data set. A quick look at the view with PROC CONTENTS or VIEWTABLE is generally sufficient for you to understand what the view contains.

The DICTIONARY tables must be explored using an SQL step. The DESCRIBE statement can be used to write the column names and attributes to the LOG, while the SELECT statement will write the contents of the table to any open ODS destinations.

proc sql;
describe table dictionary.tables;
select * from dictionary.members;

The information in these tables can then be transferred to macro variables for processing by the macro language. This can be done in either the DATA or SQL steps. In this PROC SQL step a comma-separated list of the names of all of the data sets in the ADVRPT library is written to the macro variable &TABLELIST.

title2 'Build a list of data sets';
proc sql noprint;
select memname 
  into :tablelist separated by ','
   from dictionary.members
     where libname='ADVRPT';
%put &tablelist;
quit;

MORE INFORMATION

Examples of the use of SASHELP.VTABLE and DICTIONARY.MEMBERS can be found in Section 1.1.5.

13.8.2 Retrieving System Options and Settings

It is not unusual for a macro to need to adjust the value of a system option or setting during macro execution. If a macro changes system settings, such as options, during execution, these settings should be returned to their original values at the completion of the macro’s execution. This means that the original settings must be captured, saved, and restored.

The SASHELP views and DICTIONARY tables mentioned in Section 13.8.1 are one source of this type of information. Sources for SAS system option settings include:

  • SASHELP.VOPTIONS
  • SASHELP.VALLOPT
  • SASHELP.VGOPT
  • DICTIONARY.GOPTIONS
  • DICTIONARY.OPTIONS
  • GETOPTION function

The portion of the macro %SECURECODE shown here grabs the current settings for the system

options MPRINT, SYMBOLGEN, and MLOGIC Callout 1, saves them in macro variables whose names start with HOLD Callout 2, and then turns these options off Callout 3 so that the code used in the macro Callout 4 will not be revealed in the LOG. At the conclusion of the macro these three options are reset to their original values Callout 5.

%macro securecode;
data _null_;
  set sashelp.voption
    (where=(optname in('MPRINT','MLOGIC','SYMBOLGEN'))); Callout 1
  call symputx('hold'||left(optname),optname, 'l'), Callout 2
  run; 
options nomprint nomlogic nosymbolgen; Callout 3
/* secure code goes here*/Callout 4
options &holdmprint &holdmlogic &holdsymbolgen; Callout 5
%mend securecode;

It is also possible to collect setting values through the use of functions. There are a great many functions that can be used to obtain this kind of information and it is essential for an advanced macro programmer to be well versed in which ones can be useful.

The documentation groups functions by category and some of these categories contain functions that are especially useful for obtaining information about system settings. These include:

  • External files
  • SAS File I/O
  • Special

The following example detects the location of a file using the PATHNAME function Callout 6 (the assumption being that the macro programmer only knows the fileref—and not the actual physical location when calling the macro). The macro uses this information to create a new fileref using the FILENAME Callout 7 function, which points to a different file (&NEWNAME) in the same location. Notice that the first argument of the FILENAME function is expected to be a macro variable when used with %SYSFUNC and that the & is not used.

filename super 'c:	empsuper.pdf';
%macro newref(locref=, newlocref=, newname=);
 %local origref origname nameloc newloc rc;
 %let origref = %sysfunc(pathname(&locref)); Callout 6
 %let origname= %scan(&origref,-1,);
 %let nameloc = %sysfunc(indexw(&origref,&origname,));
 %let newloc = %substr(&origref,1,&nameloc-1)&newname;
 %let rc   = %sysfunc(filename(newlocref,&newloc)); Callout 7
 %put %sysfunc(fileexist(&newlocref));
%mend newref;
%newref(locref=super,newlocref=silly,newname=freqplot.pdf)

Once the physical path has been retrieved Callout 6, it can be dissected. The filename is extracted from the end of the string (&ORIGNAME) and its starting location noted (&NAMELOC). Using this location the new name (&NEWNAME) can then be appended onto the location portion of the path. Once the new path has been constructed, the new fileref (&NEWLOCREF) can be established using the FILENAME function Callout 7.

The GETOPTION function (in the Special Category of the list of functions), can be used to retrieve current system option settings. The %FINDAUTOS macro shown here is used to retrieve the physical locations of the autocall macro libraries and to write them to the LOG. The filerefs associated with the autocall library are stored in the SASAUTOS system option. Because the %QSCAN is used to parse the list of locations, this macro assumes that filerefs, and not physical names of files (which could contain characters that would be interpreted as word delimiters), are used in the definition of the SASAUTOS system option.

%macro findautos;
%local autoref i ref refpath;
%let autoref = %sysfunc(getoption(sasautos));Callout 8
%let i=0;
%do %until(&ref eq);
  %let ref = %qscan(&autoref,&i+1);      
  %if &ref eq %then %return;    
  %let refpath=%qsysfunc(pathname(&ref)); Callout 9
  %let i = %eval(&i + 1);
  %put &i &ref &refpath; Callout 10
%end;
%mend findautos;

Callout 8 The value of this option, or any other system option, can be retrieved with the GETOPTION function.

Callout 9 The list of filerefs can then be passed, one at a time, through the PATHNAME function.

Callout 10 In this macro the resulting path, including in this case a composite location, is written to the LOG.


				
%put %sysfunc(getoption(sasautos)); 
%findautos  
44  %put %sysfunc(getoption(sasautos));
(advmac, sasautos)
45  %findautos
1 advmac C:AdvTechniquessascodesasmacros
2 sasautos (
'C:Program FilesSASSASFoundation9.2coresasmacro' 
'C:Program FilesSASSASFoundation9.2accelmvasasmacro' 
. . . . portions of the SASAUTOS definition are not shown . . . .

MORE INFORMATION

The DATA step SCAN function is used in a related example in Section 3.6.6. Examples related to determining the location of executing programs can be found in Section 14.6.

SEE ALSO

Carpenter (2008b) demonstrates various ways to retrieve the physical location of a file, even when it is on a server with a mapped drive.

13.8.3 Accessing the Metadata of a SAS Data Set

Several of the SASHELP views and DICTIONARY tables described in Section 13.8.1 provide information about the attributes of data sets (e.g., variable names, formats, variable type). Much of this same information can be obtained through the use of either PROC CONTENTS or DATA step functions. Depending on what you intend to do there can be decided performance differences between these approaches. Experiment—results will likely vary from one situation to another.

Using PROC CONTENTS

The OUT= option on the CONTENTS procedure can be used to create a data set that contains the information of interest. Like the examples in Section 13.8.1, once this information is in data set form it can be harvested and used by a number of techniques.

In the macro %VARLIST, the user may request that a list of either numeric or character variables be written to the LOG. The CONTENTS procedure returns the column TYPE as 1 for numeric variables and 2 for character variables.

%macro varlist(dsn=sashelp.class, type=1);
%* TYPE 1=numeric
%*      2=character;
%local varlist;
proc contents data=&dsn 
              out=cont(keep=name type Callout 1
                       where=(type=&type))
              noprint;
  run;
proc sql noprint;
  select name
   into :varlist separated by ' ' Callout 2
     from cont;
  quit;
  %put The list of type &type variables is: 
   &varlist; Callout 3
%mend varlist;
%varlist(dsn=advrpt.demog,type=1)

Callout 1 The OUT= option is used to write a data set containing the metadata. The data set is in the form of one observation for each variable in the data set.

Callout 2 The list of space separated variable names that meet the numeric/character attribute is written to the macro variable &VARLIST.

Callout 3 The list of variable names is then written to the LOG.

If you want to create a summary of all of the data sets in a library the keyword _ALL_ can be used. This PROC CONTENTS step will create a table (CONT) that contains one observation for each variable by data set combination in the library ADVRPT.

proc contents data=advrpt._all_
       out=cont 
       noprint;
  run;

PROC CONTENTS is a fast method for generating a list of metadata attributes. However, there are limitations. Because of the use of the two PROC steps it is not possible to use this technique in a macro that will mimic a macro function (see Section 13.7). The use of DATA step functions along with the %SYSFUNC macro function can eliminate this limitation, improve performance, and generally simplify the macro coding.

Using DATA Step Functions

The metadata of a SAS data set can be accessed directly using DATA step functions. Although virtually never used within a DATA step, these functions are extraordinarily helpful when accessing metadata from within the macro language.

These functions allow us to open and close the data set as well as to query all sorts of things about the metadata itself. We can even manipulate the data itself; however, that is rarely necessary.

In the %MAKELIST macro shown here, we again need to return a list of either numeric or character variables. Rather than use either a PROC or DATA step to access the metadata, this macro goes directly to the source.

%macro makelist(dsn=sashelp.class, type=N);
%* TYPE = N for numeric
%*        C for character;
%local dsid i varlist rc;
%let dsid = %sysfunc(open(&dsn)); Callout 4
%do i = 1 %to %sysfunc(attrn(&dsid,nvar)); Callout 5
  %if %sysfunc(vartype(&dsid,&i))=%upcase(&type) %then Callout 6
    %let varlist=&varlist %sysfunc(varname(&dsid,&i)); Callout 7
%end;
%let rc = %sysfunc(close(&dsid)); Callout 8
&varlist Callout 9
%mend makelist;
%put Char vars are: %makelist(dsn=advrpt.demog,type=c); Callout 10

Callout 4 The data set of interest is opened for inspection. The opened data set is assigned a non-zero identification number (saved here in &DSID), which is used by a number of other functions. Once opened a series of functions can be applied to the metadata or even to the data itself.

Callout 5 The ATTRN function is especially useful. It retrieves numeric attributes from the metadata. Its first argument is the identification number of the opened data set, and the second argument is used to select the attribute of interest. Here the NVAR argument is used to select the number of variables stored in the data set. This number becomes the upper bound for the %DO loop which processes across the variables in the data set.

Callout 6 The VARTYPE function returns the type (N=numeric, C=character) of the &ith variable. This value is compared to the requested variable type.

Callout 7 The VARNAME function returns the name of the &ith variable.

Callout 8 After the information has been retrieved from the metadata, the data set is closed.

Callout 9 This is a macro function and the list of variable names is returned.

Callout 10 The %MAKELIST macro is called with a request for the names of character variables (type=c).

MORE INFORMATION

DATA step functions that return variable characteristics can be found in Section 3.6.5.

13.9 Macro Security and Protection

At times, such as when executing macros under a controlled environment or as part of a larger application, it may be necessary to limit the user’s access to various aspects of the coding of our macros. Sometimes we need to prevent the dissemination of proprietary code. Other times we need to force the use of a particular version of a macro.

When control-related issues are discussed by experienced SAS programmers, common topics include:

  • What version of a macro is being executed?
  • How can we control for the correct version?
  • How do we avoid macro variable collisions and protect our macro variables, compiled macros, and the source code?

MORE INFORMATION

Sherman and Carpenter (2007) discuss the protection of user IDs and passwords when accessing external databases from within SAS. This topic is also discussed in less detail in Section 5.4.2.

SEE ALSO

Sun and Carpenter (2011) discuss a number of aspects of the control and protection of macro code, macro operation, and macro variables.

13.9.1 Hiding Macro Code

When using stored compiled macros, the SOURCE option on the %MACRO statement can be used to store your macro’s definition, the code itself, in the catalog along with the compiled macro. This code can then be reclaimed from the catalog using the %COPY statement. Of course if your users can see the source code for your macro, they can then re-engineer your macro. Obviously this is definitely not the best way to hide your macro source code.

%macro abc/store source;

Regardless of whether or not the SOURCE option was used on the %MACRO statement it is still to some extent possible to reclaim some of the original macro definition. A DATA _NULL_ step can be used to write the hex codes that are associated with the macro’s compiled definition to the LOG. Another technique which has been attributed to Ian Whitlock uses the %QUOTE function to surface the macro code.

filename maccat catalog 'advrpt.sasmacr.abc.macro';
data _null_;
  infile maccat;
  input;
  list;
  run;
%put %quote(%abc);

The SECURE option on the %MACRO statement prevents even this partial recovery of the code by encrypting the compiled macro definition.

%macro def/store secure;

Use of the SECURE option (with or without the STORE option) causes the system option values of NOMPRINT, NOMLOGIC, and NOSYMBOLGEN to be temporarily set during the execution of the macro. The SECURE option in the example to the left resets the MPRINT system option to NOMPRINT during the execution of %DTEST. After %DTEST has completed execution, the value of MPRINT is restored as the system option value.

options mprint symbolgen mlogic;
%macro dtest/secure;
proc print data=sashelp.class;
  run;
%mend dtest;
%dtest

The SECURE option allows us to keep the source code of our validated macro out of the hands of those who may want to re-engineer our code. However, since one of the primary functions of a macro is to serve as a code generator any code generated by the macro, even with the SECURE option in effect, can still be seen by those executing the macro.

13.9.2 Executing a Specific Macro Version

In addition to hiding the macro source code, we may also want to control which version of a given macro is to be executed. If we have written and validated a given macro, we may need to make sure that our version is the one executed by our users. Nominally we do this by placing our version of the macro in the Autocall library and/or in the stored compiled macro library.

The user could still circumvent the use of our version of the macro by writing his/her own macro from scratch and then force its use in preference to the validated version using any of several techniques. Each of these techniques results in the compiled version of their macro being written to the WORK.SASMACR catalog. Since the WORK.SASMACR catalog is always searched first, their version will then be seen (and executed) in preference to ours.

The user’s version may also be inadvertently written to either the autocall library or to the stored compiled macro catalog. You can provide some protection by making each of these locations READ ONLY to all but the developers of the macros. Still the user can compile and execute a macro from the WORK.SASMACR catalog.

The system options NOMCOMPILE and NOMREPLACE are partial solutions to these circumventions. However, they are not without side effects, and of course like other system options, protection of the option’s value itself is outside of our control. NOMCOMPILE prevents the compilation of new macro definitions and NOMREPLACE restricts the storage of a new compiled version of a macro that has already been compiled.

options nomcompile;
%* attempt to compile an autocall macro (%OBSCNT);
%* The macro compiles!!!; Callout 2
%put OBS count for DEMOG is %obscnt(advrpt.demog);
* Because of NOMCOMPILE
* macro MYGHI does not compile; Callout 1
%macro myghi;
%put compile from within program;
%mend myghi;
%myghi
* Because of NOMCOMPILE
* Included macro definitions do not compile; Callout 1
%inc "&pathsascodeChapter13frominc13_9_2.sas";
%frominc13_9_2
* Because of NOMCOMPILE
* The macro is not stored or compiled; Callout 3
%macro storeghi / store;
%put macro was compiled and stored;
%mend storeghi;
%storeghi

NOMCOMPILE prevents the compilation of new macros, but does not prevent the use of macros that are already stored in a library. This option is a solution to the user’s macro which was written to replace one of ours Callout 1 Callout 3. However, it also prevents the user from compiling any new macro definitions. Notice also that macro definitions stored in the autocall library Callout 2 are not affected by NOMCOMPILE.

A portion of the LOG shows that, because of the NOMCOMPILE option, the macro %MYGHI was not compiled, and was not available for execution.

1563 %macro myghi;
ERROR: Macro compilation has been disabled by the NOMCOMPILE option. Source code
    will be discarded until a corresponding %MEND statement is encountered.
1564 %put compile from within program;
1565 %mend myghi;
1566 %myghi
   -
   180
WARNING: Apparent invocation of macro MYGHI not resolved.

The NOMREPLACE system option is used to prevent the replacement of a macro definition that has already been compiled and resides in WORK.SASMACR. It has no affect on stored compiled macro libraries and, therefore, offers us no protection against a scenario where the user overwrites the official version of the macro in the stored compiled macro library Callout 3.

options mcompile nomreplace;
* Compile the autocall macro GHI;
* Definition is stored in WORK.SASMACR; Callout 2
* (entry GHI.MACRO does not already exist);
%ghi
* Unauthorized version of GHI
* does not replace version from the 
* autocall library; 
%macro ghi; Callout 1
%put Unauthorized version of GHI;
%mend ghi;
%ghi

When used together these two system options give us some protection for our authorized macro versions from the three cases shown above. Unfortunately the use of NOMCOMPILE can severely limit the use of user written macros.

ERROR: The macro GHI will not be compiled because the NOMREPLACE option is set. Source code
    will be discarded until a corresponding %MEND statement is encountered. Callout 1

Since one of the biggest issues is that the WORK.SASMACR catalog is searched first, this is the catalog on which we need to focus. If we were to copy all the macro definitions in our stored compiled macro library to WORK.SASMACR at the very start of the program/application, the NOMREPLACE option would be protecting our authorized macro definitions! The macro %COPYSASMACR can be placed in our library and then called in the autoexec.sas program, before WORK.SASMACR exists. With the NOMREPLACE option in effect, but not NOMCOMPILE, users will be free to create their own macros, but not macros with the same name as any of our validated macros.

%macro copysasmacr/store;
proc catalog catalog=advrpt.sasmacr;
  copy out=work.sasmacr;
  quit;
%mend copysasmacr;

The %COPYSASMACR macro will not work if the WORK.SASMACR catalog already exists. Since compiling any macro, even one that is to be saved as a stored compiled macro creates WORK.SASMACR, the call to %COPYSASMACR needs to be one of the first things executed in the AUTOEXEC program.

As an aside, SAS uses an internal pointer to track whether or not WORK.SASMACR exists. Using the %COPYSASMACR does not reset this pointer. This generally does not matter, because any macro executed by the user will exist in the stored compiled library as well, and it is the version (ADVRPT.SASMACR) that will actually be executed (not the version in the WORK.SASMACR catalog). As soon as the user compiles or attempts to compile any macro, the pointer is reset and the macro facility will start checking WORK.SASMACR first for macro definitions.

Another approach is to purge the WORK.SASMACR catalog of any macros with a name of one of your protected macros prior to running your application. Although the WORK.SASMACR catalog cannot be deleted its members can be deleted. The macro %PURGEWORK deletes one or more entries from the WORK.SASMACR catalog. The list of entries to be deleted may contain macros that are not in the catalog, these generate an ERROR in the LOG; however, the remaining macro entries are still deleted. It should be noted that while this technique seems to work, deleting macros from the WORK.SASMACR catalog is NOT a technique that is supported by SAS Institute. Starting in SAS 9.3 there is a new statement called %SYSMACDELETE that allows you to delete macros from the WORK.SASMACR catalog.

%macro purgework(macname=);
proc catalog cat=work.sasmacr 
       entrytype=macro;
  delete &macname;
  quit;
%mend purgework;
%purgework(macname=abc def ghi myghi)

Incorporation of the FORCE option on the PROC CATALOG statement allows you to overwrite an existing compiled macro in the WORK.SASMACR catalog. Here the %COPYSASMACR macro has been enhanced so that one or more selected macro entries can be copied from the stored compiled macro library to the WORK.SASMACR catalog. Unlike %COPYSASMACR, the %VERCOPY macro can be executed after the WORK.SASMACR catalog has been established. If it is called just before using one of your controlled macros, you can be sure that you will be using your own version of the macro that is to be called.

%macro vercopy(verlist=)/store;
proc catalog c=complib.sasmacr 
       force 
       et=macro; 
  copy out=work.sasmacr ;
  select &verlist;
  quit;
%mend vercopy;

Here the %BIGSTEP macro will be calling %CLEANUP and %GHI. In order to make sure that our versions of these two macros exist in the WORK.SASMACR catalog, we call %VERCOPY.

%macro bigstep;
  %vercopy(verlist=cleanup ghi)
  %cleanup 
  %* do other things;
  %ghi 
%mend bigstep;

SEE ALSO

Sun and Carpenter (2011) offer more detail on a number of additional techniques for the protection of macros and macro code.

13.10 Using the Macro Language IN Operator

The IN comparison operator can be used in the DATA step to form lists of values that are to be checked against. For the macro language, the IN operator was briefly available in the initial release of SAS®9, and has returned with some differences in SAS 9.2.

Although the syntax is a bit different, the macro language IN comparison operator is similar in function to the IN comparison operator that can be used elsewhere, as in the DATA or SQL steps. The operator symbol is the pound sign (#), and the mnemonic IN can also be used. By default this comparison operator is not available in SAS 9.2, but can be made available through the use of the MINOPERATOR option.

SEE ALSO

Usage Note 35591 http://support.sas.com/kb/35/591.html discusses the IN operator, as does Usage Note 31322 http://support.sas.com/kb/31322, which shows how to use the IN operator with a NOT. Examples in the documentation can be found at http://support.sas.com/documentation/cdl/en//mcrolref/61885/HTML/default/viewer.htm#a003092012.htm.

Warren Repole has an example of the IN operator as a part of his series of articles titled “Don't Be a SAS Dinosaur: Modernize Your SAS Programs,” http://www.repole.com/dinosaur/separatedby.html.

13.10.1 What Can Go Wrong

One of the reasons that the IN operator was unavailable in SAS 9.1 was because of a confusion between the mnemonic IN and the postal code abbreviation for the state of Indiana, which is also IN. In the initial release of SAS®9 the macro %BROKENIN shown here would fail, because &STATE resolves to IN before the expression is evaluated. During the evaluation of the expression the IN is seen as mnemonic for the IN operator. In SAS 9.1 there is no confusion, because the IN operator is not available. In SAS 9.2 the operator returns, but with options that help to remove the confusion. In SAS 9.2 the IN operator is, by default, not available, and %BROKENIN will execute correctly.

%macro BrokenIN;
%let state=in;
%if &state=CA %then %put California;
%else %put Not California;
%mend brokenin;
%brokenin

Regardless of the release of SAS or the status of the MINOPERATOR option, the IN can be masked from being interpreted as the mnemonic for the IN operator by using a quoting function. Here %BQUOTE is used to mask the resolved value of &STATE until after the evaluation of the expression when it will not cause a problem. For this particular example the %BQUOTE will also prevent parsing errors for the states of Oregon (OR) and Nebraska (NE).

%if %bquote(&state)=CA %then %put California;

13.10.2 Using the MINOPERATOR Option

Starting in SAS 9.2 you can choose whether or not to make the IN operator available. The selection is done using the MINOPERATOR option (the default is NOMINOPERATOR). This option can be applied as a system option or, and this is my recommendation, as an option on the macro statement.

In Section 13.10.1 the macro %BROKENIN was shown to work correctly in SAS 9.2; however, it will fail in SAS 9.2 when the MINOPERATOR option is turned on. Instead of applying the MINOPERATOR at the system level, if it is applied at the macro level you gain a finer degree of control. In the remaining examples in this section this option will always be set at the macro level and the assumption will be that the system option remains turned off (NOMINOPERATOR).

option minoperator;
%macro BrokenIN;
%let state=in;
%if &state=CA %then %put California;
%else %put Not California;
%mend brokenin;
%brokenin

Although we commonly refer to the letters (IN) as the operator symbol, the IN is actually the mnemonic, and the pound sign (#) is the actual symbol for the operator. Some of the confusion discussed above would go away if we could turn off the mnemonic and just use the # sign, but that choice is not currently an option.

* system option default value;
option nominoperator; Callout 1
%macro testIN(dsn=demog)/minoperator; Callout 2
%if %upcase(&dsn) # AE CONMED DEMOG Callout 3 %then %do;
  %put &dsn count %obscnt(advrpt.&dsn);
%end;
%mend testin;
%testin(dsn=ae)
%upcase(&dsn) in (AE CONMED DEMOG)

The macro %TESTIN can be used to demonstrate the use of the IN operator. Here the NOMINOPERATOR system option Callout 1 has been set to mimic the default value (SAS 9.2). Since we want to use the operator in the macro it is turned on, just for the execution of %TESTIN, through the use of the MINOPERATOR option Callout 2 on the %MACRO statement. In this comparison, the expression checks to see if the resolved value of &DSN is in the list of acceptable values Callout 3 to the right of the operator. The expression is often easier to read when the list of values is enclosed in parentheses. The # can be replaced with the mnemonic. The IN operator resolves to true or false (1 or 0) and, therefore, uses an implied %EVAL function.

13.10.3 Using the MINDELIMITER= Option

By default the IN operator expects the list of values to be space separated; however, it is possible to use the MINDELIMITER= option to specify an alternate delimiter for the list. Like the MINOPERATOR option, the MINDELIMITER= option can be specified at the system option level or at the macro level, and again my recommendation is to always apply it on the macro statement and not at the system level. Here the MINDELIMITER= option has been added to the %MACRO statement Callout 4 to allow a comma separated list Callout 5 of values.

%macro testIN(dsn=demog)/minoperator 
             mindelimiter=','; Callout 4
%if %upcase(&dsn) in(AE,CONMED,DEMOG) Callout 5 %then %do;
  %put &dsn count %obscnt(advrpt.&dsn);
%end;
%mend testin;
%testin(dsn=demog)

By enclosing the list in parentheses and by using the IN mnemonic, as was done here, the macro expression more closely mimics the syntax used with the DATA step’s IN operator.

13.10.4 Compilation vs. Execution for these Options

When using the system option versions of MINOPERATOR and MINDELIMITER=, it is important to understand the difference between compilation and execution of the macro. If you intend to use these options on the %MACRO statement (as I have suggested), rather than as system options, then the issues discussed below will not affect you, as the values declared on the %MACRO statement will override the system options during both compilation and execution.

The MINDELIMITER= System Option

The value of the MINDELIMITER= option is set when the macro is compiled. Subsequent changes to this option will not affect the execution of the macro.

options minoperator mindelimiter=','; Callout 6
%macro testIN(dsn=demog);
%if %upcase(&dsn) in(AE,CONMED,DEMOG) %then %do;
  %put &dsn count %obscnt(advrpt.&dsn);
%end;
%else %PUT &DSN not on the list;
%mend testin;
. . . . 
* change in mindelimiter does not break the macro;
options minoperator mindelimiter=' '; Callout 7
%testin(dsn=conmed)

Callout 6 The MINDELIMITER= option has been set prior to the compilation of the macro %TESTIN.

Callout 7 Prior to execution, the value of the MINDELIMITER= option has been changed to a blank. Although the value of the MINDELIMITER= option has been changed Callout 7, the %TESTIN macro will still operate correctly. This means that before you compile a macro that depends on the MINDELIMITER= system option, you must either know the current value of the option or explicitly set this system option prior to compiling the macro. You do not, however, need to know the current setting when executing the macro.

The MINOPERATOR System Option

For the MINOPERATOR system option, it is the value that is in effect at the time of macro execution that is applied. It does not matter whether MINOPERATOR or NOMINOPERATOR is set during the compilation of the macro; however, if NOMINOPERATOR is in effect when the macro is called, the macro will fail. The LOG shown here shows that the macro %TESTIN, which executed successfully in the previous example Callout 7, fails with the MINOPERATOR option turned off Callout 8.

77  options nominoperator; Callout 8
78  %testin(dsn=conmed)
ERROR: Required operator not found in expression: %upcase(&dsn) in(AE,CONMED,DEMOG)
ERROR: The macro TESTIN will stop executing.

Remember you will not need to worry about the current settings of these system options if you always set their values as options on the %MACRO statement.

13.11 Making Use of the MFILE System Option

Generally when debugging a macro the options MPRINT, SYMBOLGEN, and MLOGIC are sufficient to find the problem with the code. You can also use the MLOGICNEST and MPRINTNEST options when the macros are nested. Sometimes, however, you just need a bigger hammer. The MFILE system option gives us another way of looking at the results of the macro.

The macro language is primarily a code generator, and the MFILE system option allows us to save the code generated by the macro. This saved code will be completely free from all macro references. Here for demonstration purposes the macro %PRINTIT performs a simple PROC PRINT.

%Macro PRINTIT(dsn=,varlist= ,obs=);                            
proc print data=&dsn                                            
  %if &obs ne %then (obs=&obs);;                                     
  %if &varlist ne %then var &varlist;;
  run;
%mend printit;
options mprint mfile; Callout 1
filename mprint Callout 2 "&pathsascodeChapter13E13_11_mfile.sas" Callout 3;                   
%printit(dsn=advrpt.demog,varlist=subject lname fname dob,obs=4)                  

Callout 1 In order to use the MFILE system option, the MPRINT option must also be turned on.

Callout 2 The resulting SAS code is written to the file associated with the fileref MPRINT (it must be MPRINT; you do not get to choose the name for this fileref).

The generated SAS code is written to the file (E13_11_mfile.sas Callout 3), and contains the PROC PRINT after all the macro references and logic have been resolved and executed.

proc print data=advrpt.demog (obs=4);
var subject lname fname dob;
run;

13.12 A Bit on Macro Quoting

Outside of the macro language quote marks (double and single) are used by the parser to distinguish character strings from things like options and variable names. In the macro language quotes are not seen as parsing characters because the & and % characters serve as macro language triggers. While quote marks are not parsing characters, they and other special characters can have special meaning in the macro language. As a consequence when we must work with these special characters; which may also include the comma, semicolon, colon, and Boolean operators, we need to take precautions to make sure that they are interpreted correctly.

Fortunately the macro language includes a number of functions that mask these special characters. However, these various functions do not all mask all of the same special characters in the same way. There are not only several quoting functions that are supplied with the macro language, but there are also a number of text functions that can be used to return quoted text.

It is not always obvious when macro quoting is going to be required. In this simple example all we want to do is write the macro variable &LIST to the LOG and we would like to make sure that the list is left justified.

Here the %LEFT function fails because it has too many positional arguments. The message comes from the fact that %LEFT is really a macro with a single positional parameter. But the real problem is that the commas in &LIST are being interpreted as parameter separators. The commas have special meaning to the macro parser. This meaning can be masked by using a quoting function. Here the %STR function is used to mask the commas. The %LEFT now executes as we would expect.

%let list = butter, cheese, milk;
%put %left(&list);
%let list = %str(butter, cheese, milk);
%put %left(&list);

Internally the macro quoting functions insert an invisible character into the text string that allows the parser to ignore the special characters. Effectively the quoted text stored in &LIST becomes something like is shown here. Where the symbol stands for the invisible masking character. Under Windows it is sometimes possible to surface this invisible character in the LOG, and it shows up as the symbol.

%let list =butter, cheese, milk;

In the following TITLE statement we would like to insert the run date using the WORDDATE18. format. Since this numeric format will be right justified we want to left justify it in the title. This fails for the same reason as the %PUT failed in the previous example. On May 24th the %SYSFUNC returns a date string which is to be left justified. The comma inserted by the WORDDATE18. format is the culprit.

title1 "13.12 %left(%sysfunc(date(),worddate18.))";
title1 "13.12 %left(   May 24, 2011)";
title1 "13.12 %left(%qsysfunc(date(),worddate18.))";

A quoting function is needed to mask the comma in the date. In this case the %SYSFUNC function has a quoting analogue %QSYSFUNC which can be used to mask the comma.

There are several macro quoting functions. Each is slightly different and the differences are often quite subtle. Fortunately you do not need a full understanding of each of the various quoting functions in order to mask special characters. Almost all of your quoting needs can be met with the %BQUOTE and %NRSTR quoting functions. The following table gives a brief summary of some of these functions.

Quoting Function

This Function’s Super Power

%STR

Easy to type—quotes most of the usual suspects.

%BQUOTE

Quotes even more characters than %STR.

%NRSTR

Masks & and %, and prevents their resolution or execution.

%UNQUOTE

Removes macro masking characters.

Each of the macro functions that returns text (such as; %LEFT, %UPCASE, %TRIM, %SYSFUNC) has a quoting analog (same name preceded by a Q) that returns text with special characters masked. This includes the macro language triggers & and %. For instance the function call %LEFT(A&P) would attempt to resolve the &P as a macro variable, whereas the quoting analog (%QLEFT) will not. Regardless of whether or not characters have been masked before the use of these functions, they will be unmasked in the returned text, unless a quoting analog is used.

%let p = proc;
%let store= 
  %nrstr( My favorite store is the A&P. ); Callout 1
%put |&store|;Callout 2
%put %left(&store); Callout 3
%put %qleft(&store); Callout 4

Callout 1 The %NRSTR allows the preservation of leading and trailing blanks and also masks the &P so that no attempt is made to resolve it as a macro variable.

7  %put |&store|;
| My favorite store is the A&P. |  
8  %put %left(&store);
My favorite store is the Aproc Ž
9  %put %qleft(&store);
My favorite store is the A&P.  

Callout 2 The value of &STORE contains leading and trailing blanks as well as a masked &P.

Callout 3 The %LEFT left justifies the text and allows the resolution of &P. (&P. is replaced by the letters proc). Notice that the period is seen as a part of the macro variable name and is also replaced.

Callout 4 The %QLEFT left justifies the text without removing the masking characters around the A&P.

For my work I usually tend to use the %BQUOTE and %NRSTR quoting functions almost to the exclusion of the others. For text functions my general rule is to use the Q analogue version unless there is a reason not to do so.

SEE ALSO

Whitlock (2003) introduces and discusses the ins and outs of macro quoting—start here if you are new to macro quoting. Rosenbloom and Carpenter (2011b) specifically address issues associated with macro variables that contain special characters. Macro quoting is discussed in detail in Carpenter (2004, Section 7.1).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset