Differences between revisions 3 and 4
Revision 3 as of 2020-02-15 23:15:13
Size: 12051
Comment:
Revision 4 as of 2020-02-15 23:38:11
Size: 12928
Comment:
Deletions are marked like this. Additions are marked like this.
Line 91: Line 91:
Many macro functions have two versions: one that obeys syntax, and one that masks its value from interpretation. For example: Because macros fundamentally are creating syntax, they often need to contain characters that affect processing. In order to defer the interpretation of these characters, special masking functions are available.

Special characters include (but are not limited to):
 * semicolons (`;`)
 * commas (`,`)
 * operators, including...
   * arithmetic (`+`, `-`, etc.)
   * expression (`<`, `|`, etc.)
   * mnemonic expression (`LE`, `OR`, etc.)
 * macro triggers (`&`, `%`)

`%str` and `%nrstr` mask text until compilation, then are interpreted into the syntax. `%quote` and `%nrquote` mask text until execution. The `nr` versions mask all special characters, while the non-`nr` versions do not mask macro triggers.

Many macro functions have two versions: one that is interpreted immediately, and one that automatically is masked until execution.
Line 106: Line 119:

There are also a number of computational macro functions.
Line 117: Line 132:
||'''Name''' ||'''Action''' ||'''Example Input''' ||'''Example Result''' ||
||`%nrstr(value)` ||Masks special characters inside value || || ||
||'''Name''' ||'''Action''' ||'''Example Input'''  ||'''Example Result''' ||
Line 120: Line 134:
||`%qsysfunc(function call)` ||Same, but masks function call || || || ||`%qsysfunc(function call)` ||Same, but masks value || || ||
Line 172: Line 186:
== Macro Variables as Macro Programs ==

Disclosure: this is a terrible idea.

{{{
%let mymacro=%str(proc means data=LIBREF.TABLE; var foo bar; run;);

&mymacro
}}}

----


SAS Macros

SAS supports a macro feature. For more options for extending and programming in SAS (or related software), see this crosswalk of programming features.


Macro Variables

Macro variables expand to text values that can be substituted into a program, in any location other than data input. They are identified by a label up to 32 characters, beginning with a letter or underscore and continuing with letters, numbers, and underscores. A macro label is prepended with an ampersand (&) to trigger macro processing.

Note that, similar to Unix shells, variables are not substituted in single-quoted strings.

To delimit a macro variable label from immediately-appended text, use a period. That is, &prefix1 expects a macro under the name prefix1, while &prefix.1 expects a macro under the name prefix. (To 'escape' the period, use two.)

Built-in Macro Variables

There are several built-in macro variables including %sysdate (system date as DATE7), or %systime (system time as TIME5).

Custom Macros

A macro variable can be defined as:

%let LABEL = VALUE;

Macros are referenced using &LABEL. A macro definition can reference another macro. For a macro definition to reference a SAS function, the %sysfunc macro function must be used.

%let mon       = 1;
%let curr_yr   = %sysfunc(year("&sysdate"d));
%let mon_title = %sysfunc(mdy(&mon,1,&curr_yr), monname9.);
%let mon_title = %sysfunc(left(%qsysfunc(mdy(&mon,1,&curr_yr), monname9.)));

For considerations on using macro functions including %sysfunc, see Macro Functions.

Program-Set Macros

A macro variable can also be set by symputx calls. This enables programmatic creation of macros, for example to store aggregated values from a data step.

data _NULL_;
  set LIBREF.TABLE end=lastobs;
  retain sum_my_var 0;
  sum_my_var = sum_my_var + my_var;
  if lastobs then call symputx('sum_of_my_var', put(sum_my_var, dollar10.2));
run;
proc means data=LIBREF.TABLE;
  title "Allocation of &sum_of_my_var"
run;

Note: symputx and symput are very similar. The former is silent (i.e., does not log) and trims leading spaces when converting a value into character data (which all macros must be reduced into.

Scope

A macro variable defined by open code is stored in the global table. (And therefore: a macro variable defined in a separate program is local.) By the nature of SAS, most custom macros will be global.

If necessary, it is possible to force local resolution of a label.

%local A;
%let A=B;


Macro Functions

Macro functions are identified by a leading percent sign (%). They take one or more positional arguments within parentheses. They otherwise are used like macro variables.

Masking Special Characters

Because macros fundamentally are creating syntax, they often need to contain characters that affect processing. In order to defer the interpretation of these characters, special masking functions are available.

Special characters include (but are not limited to):

  • semicolons (;)

  • commas (,)

  • operators, including...
    • arithmetic (+, -, etc.)

    • expression (<, |, etc.)

    • mnemonic expression (LE, OR, etc.)

  • macro triggers (&, %)

%str and %nrstr mask text until compilation, then are interpreted into the syntax. %quote and %nrquote mask text until execution. The nr versions mask all special characters, while the non-nr versions do not mask macro triggers.

Many macro functions have two versions: one that is interpreted immediately, and one that automatically is masked until execution.

%let a=foo;
%let b=%nrstr(&a bar);

%let substring1 = %substr(&b,1,2);  /* => 'foo', **even though a substring of length 2 was requested** */
%let substring2 = %qsubstr(&b,1,2); /* => '&a' */

If special characters are at all possible within a macro function, consider using the masked version.

Built-in Macro Functions

There are also a number of computational macro functions.

Given:

%let a=Foo;
%let b=%nrstr(&a bar);
%let c=%nrstr(&a);

The following functions can be used as:

Name

Action

Example Input

Example Result

%sysfunc(function call)

Evaluates a function call

%qsysfunc(function call)

Same, but masks value

%substr(value,position,length)

Copies a substring from a value by literal position

%substr(&c,1,2)

'Foo'

%qsubstr(value,position,length)

Same, but masks value

%qsubstr(&c,1,2)

'&a'

%scan(value,index,delimiter)

Copies a substring from a value by parsed position

%scan(&c,1,' ')

'Foo'

%qscan(value,index,delimiter)

Same, but masks value

%qscan(&c,1,' ')

'&a'

%upcase(value)

Translates lowercase into uppercase

%upcase(&c)

'Foo'

%qupcase(value)

Same, but masks value

%qupcase(&c)

'&A'

%lowcase(value) position

Translates uppercase into lowercase

%lowcase(&c)

'Foo'

%qlowcase(value)

Same, but masks value

%qlowcase(&c)

'&a'

%cmpres(value)

Strips leading/trailing and multiple whitespace

%qcmpres(value)

Same, but masks value

%left(value)

Strips leading whitespace

%qleft(value)

Same, but masks value

%eval(expression)

Evaluates expression using integer arithmetic

%eval(10/3)

'3'

%sysevalf(expression)

Evaluates expression using floating point arithmetic

%sysevalf(10/3)

'3.333333'

sysfunc

The macro functions %sysfunc and qsysfunc cannot take nested functions, but macros can be nested without issue.

%let mon_title = %sysfunc(left(%qsysfunc(mdy(&mon,1,&curr_yr), monname9.)));

The difference between sysfunc and qsysfunc is masking of special characters. In this context, the most contentious special character is the comma, which can be confused for an argument delimiter.

This results in an error:

%let date=%sysfunc(left(%sysfunc(today(),worddate.)));
/*                      ^
                        June 7, 2002
          ^
          left(June 7, 2002)
                     ^
                     Error: `left` takes only one argument
*/

This will not:

%let date=%sysfunc(left(%qsysfunc(today(),worddate.)));


Macro Variables as Macro Programs

Disclosure: this is a terrible idea.

%let mymacro=%str(proc means data=LIBREF.TABLE; var foo bar; run;);

&mymacro


Macro Programs

Macro programs expand to syntax that is immediately executed in any location other than data input. They are identified by a label up to 32 characters, beginning with a letter or underscore and continuing with letters, numbers, and underscores. A macro label is prepended with an ampersand (%) to trigger macro processing.

Macro programs can be called alone, or can be called with arguments within parentheses.

Do not add a semicolon (;) after a macro program call to terminate the line--the macro expands out to syntax that is correctly terminated.

Comments

Generally, use the /* comment */ style. The alternate options have tricky limitations.

%macro mymacro;
  /* This is a comment that will be treated literally by the macro processor */
  %* This is a comment that the macro processor will interpret, so don t use any special characters like quotes in me;
  *  This is a comment that the macro processor will tokenize, so don't use any macro triggers in me;
%mend mymacro;

Arguments and Options

A macro can take both positional arguments and keyword options. Keyword options can also take default values, or be left null.

%macro mymacro(start, stop, opts=, stats=N SUM MIN MAX);
  proc means data=LIBREF.TABLE &stats &opts;  /* default stats are "N SUM MIN MAX", default opts are "" */
    where "&start"d <= date <= "&stop"d;
  run;
%mend mymacro;

%mymacro(01JAN2020, 01JAN2021)

Looping

Looping a SAS macro is accomplished through one of three structures. The most common structure is iterating over ranges. This is done %do ... %to ... %by ....

%macro mymacro;
  %* Note: '&year.TABLE' becomes '2000TABLE'-the period is indicating the end of the macro label;
  %do year=2000 %to 2020 %by 1;
    /* do something */
  %end;
%mend mymacro;

%mymacro

Note that %eval is automatically called on the values supplied to loops.

Other structures are %do %until ... and %do %while ....

Variable Number of Arguments

It is also possible to use a variable number of arguments. This is most feasibly used with a %do %until ... loop.

Enable the parmbuff option and access the arguments from the &syspbuff array.

%macro mymacro / parmbuff;
  %let index=1;                    /* initialize the index */
  %do %until (%scan(&syspbuff,&index) = '');
    /* do something like "%let month = %scan(&syspbuff,&index);" */

    %let index=%eval(&index + 1);  /* increment the index */
  %end;
%mend mymacro;

%mymacro(1, 2, 3)

Conditional Processing

To process a macro statement conditionally, use the %if statement.

if EXPR then ASSIGNMENT;
else if EXPR then ASSIGNMENT;
else ASSIGNMENT;

Similar to conditional processing in data steps, an %if statement can be followed by %else (or %else %if) statements. And a %do block can be used to conditionally process multiple macro statements.

Similar to looping statements, %eval is automatically called on the expression.

%macro mymacro(dsname);
  %let dsid = %sysfunc(open(&dsname));  /* open file, and do something different if the id=0 (indicates an error) */

  %if (&dsid > 0) %then %do
    /* do something like "%let nobs = %sysfunc(attrn(&dsid, nobs));" */

    %let rc = %sysfunc(close(&dsid));   /* close file, though we can't do anything if the return code is an error */
  %end;
  %else %if (&dsname = "") %then %put You forgot to set the dataset name!;
  %else %put Could not open the dataset &dsname..;
%mend;

%mymacro(myds)

Branching

Lastly, SAS macros can use %goto to branch out.

%macro mymacro(dsname);
  %if (&dsname = "") %then %goto error;
  /* do something */
  %goto success;

  %error:
    %put You forgot to set the dataset name!;
    quit;
  %success:
%mend mymacro;

%mymacro("")


Resolution Order of Macros

The macro processor scans from right to left in each token. It looks up every valid label (i.e., an ampersand followed by a character or underscore) it encounters, and also substitutes double ampersands with single ampersands. However, it will re-scan until all ampersands have been cleared. Therefore, double ampersands can be used to 'defer' processing.

%let section1=Math;
%let section2=Science;
%let section3=Literature;

%let n = 2;
proc means data=LIBREF.TABLE;
  where section=&&section&n;
run;

The rabbit hole does not end here.

%let wherevar=section;
proc means data=LIBREF.TABLE;
  where &wherevar=&&&wherevar&n;
run;


Debugging Macros

Set the mcompilenote option to see compilation errors in macro programs logged.

options mcompilenote=all;
/* do something */
options mcompilenote=none;

Enable the symbolgen option to see macro variable substitutions logged as they are processed. Enable the mprint option to see macro program commands as they are submitted.

options symbolgen mprint;
/* do something */
options nosymbolgen nomprint;

The %put macro command can log arbitrary text.

%if (&dsid=0) %then %put Could not open file &dsname;


CategoryRicottone

SAS/Macros (last edited 2022-06-08 20:13:15 by DominicRicottone)