SAS Studio - parentheses affecting how sysfunc function resolves macro variable

66 Views Asked by At

I have a counter in my code that is as follows: %let counter = %sysfunc(countw(&&CLMNS&j.)); However this does not work properly for some macro variables. For variable_1, which contains the values Parent_Name, Name the counter returns as 3 (should be 2). For variable_2, which contains Alt_Parent_Name, Alt_Name the counter correctly returns a value of 2.

If I add a pair of parentheses, so it now is: %let counter = %sysfunc(countw((&&CLMNS&j.)));, it now correctly outputs a value of 2 for both variable_1 and variable_2.

I have read through the help cards to no avail and would like to figure out why this occurs and what the extra parentheses do. I am mostly confused by the inconsistency of the result in the first instance.

3

There are 3 best solutions below

0
Therkel On BEST ANSWER

Perhaps this helps:

SAS will break it down in the following order:

%let counter = %sysfunc(countw(&&CLMNS&j.));

  1. Two ampersands (&&) resolves to one ampersand (&) and the scanner continues.
  2. Resolve &j. in the global symbol table. E.g. to 1.
  3. Then resolve &CLMNS1 in the global symbol table. E.g. to variable_1 (which would have to be resolved with another ampersand) or just Parent_Name, Name.
  4. Replace the values in the input stack:
    • %let counter = %sysfunc(countw(Parent_Name, Name));

Now you can perhaps see what is going wrong. When the compiler continues, it will understand Name as the optional argument <character(s)> in countw.

What you want is instead to mask the output of &&CLMNS&j. for the macro processor. I.e. use %NRSTR:

%let counter = %sysfunc(countw(%NRSTR(&&CLMNS&j.)));

Expected global symbol table:

%PUT _ALL_;
GLOBAL J 1
GLOBAL CLMNS1 parent_name, name
GLOBAL CLMNS2 alt_parent_name, alt_name
AUTOMATIC ....

These resources might be relevant for you:

  1. Going Under the Hood: How Does the Macro Processor Really Work?
  2. The Ampersand (&) Challenge, Single, Double or more?
  3. SAS Help Center: COUNTW
  4. SAS Help Center: %STR and %NRSTR Functions
0
Richard On

You can use %SUPERQ to resolve a macro symbol in a quoted way.

So instead of &&CLMNS&j. you would use %superq(CLMNS&j)

Example:

An alternative delimiter # is allowed.

%macro example;
  %local list1 list2 list_index item_index item;

  %let list1 = a#b#c;
  %let list2 = d,e,f;

  %do list_index = 1 %to 2 ;
    %put NOTE: Logging items of list&list_index: ;
    %do item_index = 1 %to %sysfunc(countw(%superq(list&list_index),%str(#,))) ;
      %let item = %qscan(%superq(list&list_index),&item_index,%str(#,));
      %put NOTE:   &item_index: &item ;
    %end ;
  %end ;
%mend ;

%example

Will log

NOTE: Logging items of list1:
NOTE:   1: a
NOTE:   2: b
NOTE:   3: c
NOTE: Logging items of list2:
NOTE:   1: d
NOTE:   2: e
NOTE:   3: f
1
Tom On

The extra parentheses prevent the comma in the value of the macro variable from being seen as delimiting the arguments to the function call.

If you pass COUNTW() two arguments the second is the list of characters that are used as delimiters. So 3 is the right answer when counting the words in Parent_name using the letters Name as the delimiters. Replacing the delimiters with periods you get: P.r.nt_n...

108  %put %sysfunc(countw(Parent_name,Name));
3
109  %put %scan(Parent_name,1,Name);
P
110  %put %scan(Parent_name,2,Name);
r
111  %put %scan(Parent_name,3,Name);
nt_n
112  %put %scan(Parent_name,4,Name);

But if you add the parentheses the comma is part of the string (the parentheses are also part of the string but since they are in the list of default delimiters they are ignored).

126  %put %sysfunc(countw((Parent_name,Name)));
2
127  %put %scan((Parent_name,Name),1);
Parent_name
128  %put %scan((Parent_name,Name),2);
Name
129  %put %scan((Parent_name,Name),3);

You can add macro quoting to prevent the comma from being seen as delimiting the arguments.

151  %let clmns=variable;
152  %let j=1;
153  %let &clmns&j=Parent_name,Name ;
154
155  %put %sysfunc(countw(%superq(&CLMNS&j)));
2
156  %put %scan(%superq(&CLMNS&j),1);
Parent_name
157  %put %scan(%superq(&CLMNS&j),2);
Name

But the easiest solution is to NOT use comma as the delimiter in your string and explicitly tell COUNTW() and SCAN() what delimiter you are using.

130  %let clmns=variable;
131  %let j=1;
132  %let &clmns&j=Parent_name|Name ;
133
134  %put %sysfunc(countw(&&&CLMNS&j,|));
2
135  %put %scan(&&&clmns&j,1,|);
Parent_name
136  %put %scan(&&&clmns&j,2,|);
Name