BookmarkSubscribeRSS Feed
☑ This topic is solved. Need further help from the community? Please sign in and ask a new question.
pblls
Calcite | Level 5

This might not be a problem that absolutely needs solving, but it left me scratching my head & I'm wondering how I can prevent this.

 

When I write a macro that executes actual statements (not just macro code), I often include a bogus option statement or even just a semicolon at the start - setting the page size to its current setting or so. The goal here is that if the macro were assigned via %let, the first statement would get gobbled but the rest of the code executes as normal (and you don't lose the first actually important statement inside the macro). As a simple example:

 

options ps=100;
%MACRO PS;
   %do i = 1 %to 3;
      options ps=10&i;
      %put %sysfunc(getoption(ps, keyword));
   %end;
%MEND;

%let V1 = %PS;
%put &=V1;

PS=100
PS=102
PS=103
V1=options ps=101

Notice that the first options statement never executes; it gets assigned to V1 instead. The other statements go through just fine, this is what I would expect.

 

The issue is that when I do the same inside a macro, the following happens (continuing the example):

%MACRO PS2;
   options ps=100;
   %let V2 = %PS;
   %put &=V2;
%MEND;

%PS2;

PS=100
PS=100
PS=100
V2=options ps=101;       options ps=102;       options ps=103;

Suddenly, %let gobbles all statements from the inner macro, while macro code still executes as expected. This happens on all OSes/versions that I have available to me, it doesn't matter what the scope of the variable being assigned is, and no amount of %unquote at any level can stop this.

 

Again, not really a problem that needs fixing (rather to prevent a macro from breaking if it's being used function-style when in reality it isn't), but... what is going on here?

1 ACCEPTED SOLUTION

Accepted Solutions
Quentin
Super User

I would encourage you to re-think this idea of  adding a bogus statement to your macros.  I would be interested to see an example that shows the useful functionality you are trying to achieve.

 

In your first example:

options ps=100;
%MACRO PS;
   %do i = 1 %to 3;
      options ps=10&i;
      %put %sysfunc(getoption(ps, keyword));
   %end;
%MEND;

%let V1 = %PS;
%put &=V1;

When you invoke the macro %PS it will generate 3 options statements, and execute 3 %PUT statements.  

 

When you invoke %PS on the right had side of a %LET statement (it's not clear why you would want to do this), you generate:

%let V1 = options ps=101; options ps=102; options ps=103;;

So the text for first the first options statement is assigned to the macro variable V1 because the first semicolon generated by the macro PS ends the %LET statement. The second two options statements will be executed by SAS.  Note there is an extra semicolon at the end. This "works" (i.e. runs without errors), but I don't see how adding the OPTIONS statement has added any functionality.

 

Now look at your second example:

%MACRO PS2;
   options ps=100;
   %let V2 = %PS;
   %put &=V2;
%MEND;

%PS2;

When the macro PS2 compiles, the %LET statement is compiled. The semicolon inside of the macro definition ends the %LET statement. The macro invocation of %PS has not been executed.  Thus the %LET statement compiles as something like: %let V2= whatever text results from invoking the macro PS ;

 

When you call %PS2, the precompiled %LET statement executes, and %PS resolves to:

options ps=101; options ps=102; options ps=103;

Note that those semicolons generated by %PS cannot end the %LET statement, because the %LET statement was already ended when the macro was compiled.

 

The difference you have illustrated is not a problem or bug, it's a result of the way that SAS compiles macros.  It is an instructive example, to help think about how the macro language works. 

 

But to the big picture, I don't think you're accomplishing any useful functionality with this approach, and I expect it will be confusing to anyone who inherits such code.

 

 

 

BASUG is hosting free webinars Next up: Don Henderson presenting on using hash functions (not hash tables!) to segment data on June 12. Register now at the Boston Area SAS Users Group event page: https://www.basug.org/events.

View solution in original post

5 REPLIES 5
ballardw
Super User

Set Options mprint symbolgen; before running the macro and read the log. See if that gives you some idea what is happening.

Tom
Super User Tom
Super User

Huh? You seem to using the word MACRO for both an actual macro and for a symbol (what most users call macro variables).

Perhaps that confusion is part your problem?

You define an actual macro with the %MACRO statement (definition ends with the %MEND statement).  You EXECUTE the macro by placing a % character in front of its name.

You can define a macro variable using a %LET statement.  Everything between the equal sign and semicolon is the value to set to the macro variable.  To reference the value of a macro variable you place an & character in front of its name.

 

If you define a macro that generates one or more actual SAS statements then do not attempt to execute that macro in the middle of another statement, like the %LET macro statement.  Just execute it like you would any other statement.

%ps;

 

Quentin
Super User

I would encourage you to re-think this idea of  adding a bogus statement to your macros.  I would be interested to see an example that shows the useful functionality you are trying to achieve.

 

In your first example:

options ps=100;
%MACRO PS;
   %do i = 1 %to 3;
      options ps=10&i;
      %put %sysfunc(getoption(ps, keyword));
   %end;
%MEND;

%let V1 = %PS;
%put &=V1;

When you invoke the macro %PS it will generate 3 options statements, and execute 3 %PUT statements.  

 

When you invoke %PS on the right had side of a %LET statement (it's not clear why you would want to do this), you generate:

%let V1 = options ps=101; options ps=102; options ps=103;;

So the text for first the first options statement is assigned to the macro variable V1 because the first semicolon generated by the macro PS ends the %LET statement. The second two options statements will be executed by SAS.  Note there is an extra semicolon at the end. This "works" (i.e. runs without errors), but I don't see how adding the OPTIONS statement has added any functionality.

 

Now look at your second example:

%MACRO PS2;
   options ps=100;
   %let V2 = %PS;
   %put &=V2;
%MEND;

%PS2;

When the macro PS2 compiles, the %LET statement is compiled. The semicolon inside of the macro definition ends the %LET statement. The macro invocation of %PS has not been executed.  Thus the %LET statement compiles as something like: %let V2= whatever text results from invoking the macro PS ;

 

When you call %PS2, the precompiled %LET statement executes, and %PS resolves to:

options ps=101; options ps=102; options ps=103;

Note that those semicolons generated by %PS cannot end the %LET statement, because the %LET statement was already ended when the macro was compiled.

 

The difference you have illustrated is not a problem or bug, it's a result of the way that SAS compiles macros.  It is an instructive example, to help think about how the macro language works. 

 

But to the big picture, I don't think you're accomplishing any useful functionality with this approach, and I expect it will be confusing to anyone who inherits such code.

 

 

 

BASUG is hosting free webinars Next up: Don Henderson presenting on using hash functions (not hash tables!) to segment data on June 12. Register now at the Boston Area SAS Users Group event page: https://www.basug.org/events.
pblls
Calcite | Level 5

Thank you, this is exactly the information I was looking for, clearly I haven't given much thought to macro compilation.

 

I concur that this is not exactly a highly productive design pattern, I figured it also doesn't harm to have it make no difference if the first statement gets executed or not. In my example all three statements are more or less the same, but in reality the second would be a data step, proc, library assignment... on which downstream statements rely. If the macro is then assigned to a macro variable even when it isn't designed that way (returning nothing of use to the macro processor), it'll still work.

 

Then again, it still breaks when nested, so it's indeed very limited in its usefulness - and in that sense perhaps even harmful if it gives the impression that the macro can be used in macro variable assignment.

Tom
Super User Tom
Super User

If you want to capture all of the text that the macro emits you could just enclose the macro call in quotes.

7    %macro two_statements;
8      x=1;
9      y=x*x;
10   %mend;
11
12   %put "%two_statements" ;
"x=1;   y=x*x;"

But that really just converts the problem caused by a macro that generates semicolons to a problem with a macro that generates double quote characters.  In which case you might want to use a data step and RESOLVE() function instead.

data want;
  length string $200;
  string=resolve('%two_statements');
run;

sas-innovate-2024.png

Available on demand!

Missed SAS Innovate Las Vegas? Watch all the action for free! View the keynotes, general sessions and 22 breakouts on demand.

 

Register now!

How to Concatenate Values

Learn how use the CAT functions in SAS to join values from multiple variables into a single value.

Find more tutorials on the SAS Users YouTube channel.

Click image to register for webinarClick image to register for webinar

Classroom Training Available!

Select SAS Training centers are offering in-person courses. View upcoming courses for:

View all other training opportunities.

Discussion stats
  • 5 replies
  • 828 views
  • 2 likes
  • 4 in conversation