Skip to content

How the macro processor works

The macro language is a text generator. It runs first, writes SAS code, and only then does SAS compile and run that code. Almost every macro surprise comes from forgetting this order. This chapter shows the order step by step.

When you submit a program, the word scanner reads it token by token. A token that starts with & or % goes to the macro processor, which replaces it with text. Everything else goes to the DATA step or PROC compiler. So the macro processor has finished its work on a step before that step runs:

your program ──► word scanner ──► & and % ──► macro processor ──► text ─┐
│ │
└──────────► other tokens ◄────────────────────────┘
│
▼
compiler, then execution
Animation The macro processor, and the loop counter that leaks · 39 s, no sound
17_1_resolve.sas
/* Chapter 17: how the macro processor resolves names */
%LET trt1 = Placebo;
%LET trt2 = Xanomeline Low Dose;
%LET trt3 = Xanomeline High Dose;
%LET i = 2;
%PUT NOTE: &&trt&i.;
%LET name = trt3;
%PUT NOTE: &&&name.;
/* Integer and decimal arithmetic */
%PUT NOTE: EVAL gives %EVAL(7 / 2), SYSEVALF gives %SYSEVALF(7 / 2);
/* Data step functions in macro code */
%PUT NOTE: %SYSFUNC(PUTN(0, DATE9.)) %SYSFUNC(COUNTW(sex race agegr1)) %SYSFUNC(PROPCASE(xanomeline high dose));

What you should see:

NOTE: Xanomeline Low Dose
NOTE: Xanomeline High Dose
NOTE: EVAL gives 3, SYSEVALF gives 3.5
NOTE: 01JAN1960 3 Xanomeline High Dose
  • &&trt&i. is read in passes. Pass 1: && becomes &, &i. becomes 2, so the text is &trt2. Pass 2: &trt2 becomes Xanomeline Low Dose. This is an indirect reference, the usual way to walk through trt1, trt2, trt3 in a loop.
  • &&&name.: && becomes &, &name. becomes trt3, then &trt3 resolves.
  • %EVAL does whole-number arithmetic: 7 / 2 is 3. %SYSEVALF does decimal arithmetic: 3.5.
  • %SYSFUNC calls a DATA step function from macro code: a format, a word count, a case change.

%EVAL cannot read a decimal at all:

17_5_eval_error.sas
/* EXPECT: ERROR A character operand was found in the %EVAL function */
/* Chapter 17: %EVAL knows only whole numbers */
%PUT NOTE: %EVAL(1.5 + 1);

What you should see: ERROR: A character operand was found in the %EVAL function or %IF condition where a numeric operand is required. The condition was: 1.5 + 1. %IF uses %EVAL too, so %IF &dose. > 1.5 fails the same way. Use %SYSEVALF for decimals.

17_2_timing.sas
/* EXPECT: WARNING Apparent symbolic reference WHEN not resolved */
/* Chapter 17: macro code runs before the DATA step runs */
%SYMDEL when / NOWARN;
DATA _NULL_;
CALL SYMPUTX("when", "set while the step runs");
PUT "inside the step: &when.";
RUN;
%PUT NOTE: after the step: &=when.;

What you should see:

WARNING: Apparent symbolic reference WHEN not resolved.
inside the step: &when.
NOTE: after the step: WHEN=set while the step runs

The macro processor met &when. while it read the step, before the step ran and before CALL SYMPUTX created the variable. So the reference stayed unresolved. After RUN; the variable exists. Rule: a macro variable created by CALL SYMPUTX (or SELECT INTO) can be used only in a later step.

Some characters mean something to the macro processor or to SAS: ; ends a statement, & and % start macro references, an unmatched quote opens a string. Quoting functions hide them.

17_3_quoting.sas
/* Chapter 17: quoting functions protect special characters */
%LET code = %STR(TITLE "Demography"; FOOTNOTE "Safety population";);
%PUT NOTE: &=code.;
%LET amp = %NRSTR(R&D department);
%PUT NOTE: &=amp.;
DATA _NULL_;
CALL SYMPUTX("site", "O'Brien Clinic");
RUN;
%MACRO show(text);
%PUT NOTE: show received [&text.];
%MEND show;
%show(%BQUOTE(&site.))
%PUT NOTE: SUPERQ gives %SUPERQ(site);

What you should see:

NOTE: CODE=TITLE "Demography"; FOOTNOTE "Safety population";
NOTE: AMP=R&D department
NOTE: show received [O'Brien Clinic]
NOTE: SUPERQ gives O'Brien Clinic
FunctionWhenExample
%STRtext you type, with ; or blanks to keep%STR(TITLE "a"; FOOTNOTE "b";)
%NRSTRtext you type, with & or % that must not resolve%NRSTR(R&D)
%BQUOTEa value from data, maybe with an unmatched quote%BQUOTE(&site.)
%SUPERQa value from data, never resolved again%SUPERQ(site) (no &)

The value O'Brien Clinic came from data. Passed to a macro without %BQUOTE, the single quote stays unmatched and the rest of the program becomes one long string. Quote every value that comes from data.

The same rule explains the trap of Chapter 6: in LIKE "%FL" the macro processor sees %FL inside double quotes and looks for a macro. Single quotes stop the macro processor; double quotes do not.

17.5 The classic log options, and something better

Section titled “17.5 The classic log options, and something better”
17_4_options.sas
/* Chapter 17: the classic log options */
%MACRO freq_each(data=, vars=);
%LOCAL i var;
%DO i = 1 %TO %SYSFUNC(COUNTW(&vars., %STR( )));
%LET var = %SCAN(&vars., &i., %STR( ));
PROC FREQ DATA=&data.; TABLES &var. / NOCUM; RUN;
%END;
%MEND freq_each;
OPTIONS MPRINT MLOGIC SYMBOLGEN;
%freq_each(data=refadm.adsl, vars=sex)
OPTIONS NOMPRINT NOMLOGIC NOSYMBOLGEN;

What you should see (part of the log):

MLOGIC(FREQ_EACH): Beginning execution.
MLOGIC(FREQ_EACH): Parameter DATA has value refadm.adsl
SYMBOLGEN: Macro variable VARS resolves to sex
MLOGIC(FREQ_EACH): %DO loop beginning; index variable I; start value is 1; stop value is 1; by value is 1.
MPRINT(FREQ_EACH): PROC FREQ DATA=refadm.adsl;
MPRINT(FREQ_EACH): TABLES sex / NOCUM;
MLOGIC(FREQ_EACH): Ending execution.

MPRINT shows the code a macro writes, MLOGIC the macro’s own logic, SYMBOLGEN every resolution. For one variable that is readable; for a real macro it is thousands of lines.

The same stop for this chapter's program

The same stop for this chapter’s program: the Variables view shows what MLOGIC and SYMBOLGEN write to the log, without searching the log.

Write macros you want to debug with empty parentheses, %MACRO name();, because of a known call-pane issue.

Exercise 17-1. Create trt1 to trt3 with PROC SQL INTO :trt1- from ADSL, then print all three in a %DO loop with &&trt&i..

Exercise 17-2. What does %PUT %EVAL(10 / 4) %SYSEVALF(10 / 4, CEIL); print? Check in the log.

Exercise 17-3. Store Smith & Jones in a macro variable so that %PUT &=x.; prints it without a WARNING.

Exercise 17-4. Fix 17_2_timing.sas so that the PUT statement prints the value: move the use of &when. to where it is allowed.

Answers: Chapter 17 answers.