BookmarkSubscribeRSS Feed
ahuige
Obsidian | Level 7

Nested macro definition causes :Open code statement recursion detected.

however move the nested macro definition to outside of main macro will cure the code.

But why? Syntax-wise they are both valid.

Anyone can explain to me? Thank you.

 

#######

%macro upcaseDSN(dsn);
  %macro NameWithoutLibOpt(dsn);
  	%if %index(&dsn,.) %then %scan(&dsn,2,%str(.%());
  	%else %scan(&dsn,1,%str(.%());
  %mend;
  %upcase(%NameWithoutLibOpt(&dsn))
%mend;
%put %upcaseDSN(%str(sashelp.class(keep=age)));
%* expect to see CLASS but failed;

Error message

252
253
254  %macro upcaseDSN(dsn);
255    %macro NameWithoutLibOpt(dsn);
256      %if %index(&dsn,.) %then %scan(&dsn,2,%str(.%());
257      %else %scan(&dsn,1,%str(.%());
258    %mend;
259    %upcase(%NameWithoutLibOpt(&dsn))
260  %mend;
261  %put %upcaseDSN(%str(sashelp.class(keep=age)));
MLOGIC(UPCASEDSN):  Beginning execution.
MLOGIC(UPCASEDSN):  Parameter DSN has value sashelp.class(keepage)
ERROR: Open code statement recursion detected.
SYMBOLGEN:  Macro variable DSN resolves to sashelp.class(keep=age)
SYMBOLGEN:  Some characters in the above value which were subject to macro quoting have been
            unquoted for printing.
ERROR: Expected %DO not found.
ERROR: Skipping to next %END statement.

modified code are fine. but why?

%macro NameWithoutLibOpt(dsn);
  	%if %index(&dsn,.) %then %scan(&dsn,2,%str(.%());
  	%else %scan(&dsn,1,%str(.%());
  %mend;

%macro upcaseDSN(dsn);

  %upcase(%NameWithoutLibOpt(&dsn))
%mend;
%put %upcaseDSN(%str(sashelp.class(keep=age)));
%* get CLASS by moving nested macro out ;
273  %* get CLASS by moving nested macro out ;
274  option nosymbolgen nomlogic;
275    %macro NameWithoutLibOpt(dsn);
276      %if %index(&dsn,.) %then %scan(&dsn,2,%str(.%());
277      %else %scan(&dsn,1,%str(.%());
278    %mend;
279
280  %macro upcaseDSN(dsn);
281
282    %upcase(%NameWithoutLibOpt(&dsn))
283  %mend;
284  %put %upcaseDSN(%str(sashelp.class(keep=age)));
CLASS
285  %* get CLASS by moving nested macro out ;
14 REPLIES 14
Oligolas
Barite | Level 11

Hi,

have a look here

________________________

- Cheers -

Ksharp
Super User
%let dsn=sashelp.class(keep=age) ;
%let want=%scan(%sysfunc(prxchange(s/\(.*\)//,1,&dsn.)) ,-1,.);

%put &=dsn. &=want. ;
ahuige
Obsidian | Level 7

Thank everyone who replied. I want to emphasize one thing here. I am not looking for another solution of this example of dataset name extraction. It is not the point but an example to show what the issue is. The actual code is more complicated and with some other purposes. I am also not arguing the nested style is a good way or not. Occasionally we do need to have small submacros go with main macro for some purpose. Here i need to know the key point of the reason why it is working/not working without any difference but the location of submacro. Let me know if you get the answer to this particular point. I have a remote guess of resolving phase/execution phase difference but if someone got a better and deeper understanding is much helpful. Thank you.

Oligolas
Barite | Level 11

The open code environment in which you call the %put statement that executes the macro code seems to be the issue.

I do not figure exactly why neither.

If you nest the whole code one more time it works:

%macro doit();
   %macro upcaseDSN(dsn);
      %local v;
      %macro NameWithoutLibOpt(ds=);
         %if %index(%superq(ds),.) %then %scan(%superq(ds),2,.);
         %else %scan(%superq(ds),1,.);
      %mend NameWithoutLibOpt;
      %NameWithoutLibOpt(ds=%superq(dsn));
   %mend upcaseDSN;
%put %upcaseDSN(sashelp.class2);
%mend doit;
%doit;

or

%macro doit();
%macro upcaseDSN(dsn);
  %macro NameWithoutLibOpt(dsn);
  	%if %index(&dsn,.) %then %scan(&dsn,2,%str(.%());
  	%else %scan(&dsn,1,%str(.%());
  %mend;
  %upcase(%NameWithoutLibOpt(&dsn))
%mend;
%put %upcaseDSN(%str(sashelp.class(keep=age)));
%* expect to see CLASS but failed;
%mend doit;
%doit;
________________________

- Cheers -

ahuige
Obsidian | Level 7

Thanks, it turns out the problem is the resolving precedence of macro/plain statement. %put and %mcr has the same priority on execution and finally the delayed resolving of macro definition such as '%put %inside(%macro inside).....' is invalid. But if I use 'put' in data step it is fine. It is tricky but it is how SAS was built on the past decades. it is hard to give away the worked tech to other new tech even it could be better.

 

%macro outside();

%macro inside();
    Some Text
%mend inside;
  %inside()
%mend outside;

data ahuige;
put "%outside";
run;

 

Quentin
Super User

Nesting macro definitions is never a good idea.  When you do this, each time the outer macro executes, the inner macro has to be compiled.  I'm not sure I would call nested macro definitions "valid syntax".  

 

That said, I agree your question is interesting.  Here's a simpler example that replicates the issue.  Note that the %inside macro is never even called:

 

%macro outside();
  %macro inside();
    Some Text
  %mend inside;
%mend outside;

If you call the macro outside, it correctly returns nothing, but it works fine. And you can see that %INSIDE is compiled when %OUTSIDE executes:

1    options mcompilenote=all ;
2    %macro outside();
3      %macro inside();
4        Some Text
5      %mend inside;
6    %mend outside;
NOTE: The macro OUTSIDE completed compilation without errors.
      5 instructions 100 bytes.
7
8    %outside()
NOTE: The macro INSIDE completed compilation without errors.
      5 instructions 64 bytes.

But note that above I called %OUTSIDE as a macro statement.  You have a function-style macro, which you want to use within a macro statement.  If I do that with %OUTSIDE, I get similar errors:

10   %put %outside();
ERROR: Open code statement recursion detected.
ERROR: Macro keyword MEND appears as text.
Some Text

It looks to me like when the macro processor is in the middle of executing the %PUT statement, and executes %OUTSIDE, it can't pause to compile %INSIDE, and it gets confused.

 

I think the best solution is to avoid nesting macro definitions.

The Boston Area SAS Users Group is hosting free webinars!
Next webinar will be in January 2025. Until then, check out our archives: https://www.basug.org/videos. And be sure to subscribe to our our email list.
ahuige
Obsidian | Level 7

Hi, I do like your way of testing this. it is interesting because I do use nested macro in my own SAS kit library in some code for a decade and as I said some sub-macros are only serving the purpose for a main macro and separating them might be meaningless since no other macro will call it anyway. one straight example is if I have a heavy code macro I might have it as part I and Part II... just for a reviewing purpose so a %macro_part1 and %macro_part2 are used as organizing purpose only and this way works fine. Never a fatal error like this occured before.  so, I might avoid nesting as necessary but by discussing and getting a nailed answer would be even better. I will welcome more suggestions on this topic. Thank you for your insight on this.

Quentin
Super User

I'm definitely a fan of using macros to create modular code, but still opposed to nesting macro definitions.  If you do:

%macro main() ;
   %macro part1() ;
     %put part1 running ;
   %mend part1 ;
   %macro part2() ;
     %put part2 running ;
   %mend part2 ;

   %part1()
   %part2()
%mend main ;

%main()

 

 

The code works, but you haven't achieved any real benefit from the nested definitions.  The macro %PART2 can still be called outside of %MAIN.   And every time you run %MAIN, both %PART1 and  %PART2 are re-compiled.

 

I think the only benefit you gain from this is that it looks like %PART1 and %PART2 are part of %MAIN.  But that appearance is misleading.

 

I would code this as:

 

%macro main() ;
   %part1()
   %part2()
%mend main ;

%macro part1() ;
  %put part1 running ;
%mend part1 ;
%macro part2() ;
  %put part2 running ;
%mend part2 ;

%main()

If the definition of main is stored in an autocall library as main.sas, you can still put helper macros in that same file.  All of the code in main.sas will be executed the first time you call %main, which will compile all the macros.  You just need to be careful to avoid naming collisions across helper macros.  I've seen people do stuff like:

*main.sas stored in autocall library;

%macro main() ;
  %main_part1()
  %main_part2()
%mend main ;

%macro main_part1() ;
  %put part1 running ;
%mend main_part1 ;
%macro main_part2() ;
  %put part2 running ;
%mend main_part2 ;

So the helper macros get named with a prefix of the main macro.

 

The Boston Area SAS Users Group is hosting free webinars!
Next webinar will be in January 2025. Until then, check out our archives: https://www.basug.org/videos. And be sure to subscribe to our our email list.
Oligolas
Barite | Level 11

I think it can make sense to have the code in place

For example to ease the review by another programmer

I would definetely prefer such a nesting compared to a modular structure

Particularly if the nested macro is unable to run alone somewhere else

I've seen too much useless copy N paste

%macro main() ;
   <...code...>
   %macro sub(param=);
      <...code...>
   %mend sub;
   %subsub(param=a);
   %subsub(param=b);
   %subsub(param=c);
%mend main ;
%main()

 

________________________

- Cheers -

Quentin
Super User

One reason I don't like the idea of nested macro definitions is the fact that the macros themselves are not actually nested.  That is, even if you nest the definition of %sub within the definition of %main, when it is compiled %sub exists as a compiled macro separate from %main, and there is no nesting of the compiled macros.  You could even delete %main and %sub would still exist, because they are fully independent. So the code suggests an organization of the macros which is not accurate. Some people mistakenly believe that %sub is "local to" %main, i.e. only exists within %main, and can only be used within %main.  They think that by nesting macro definitions they have created a "scope" for a macro, but SAS does not have a scope for macros.  It only has a scope for macro variables.

 

I feel the same way about placing global statements inside of a step, e.g. title statement:

 

proc print data=sashelp.class ;
  title1 "My Title" ;
run;
title1 ;

You see that a lot in SAS documentation, and it's how I was trained to do it.  But a wise mentor pointed out to me that TITLE is a global statement, why would you put it "inside" a step when it is not part of the step, and it has a global effect.  I think it's clearer to code as:

title1 "My Title" ;
proc print data=sashelp.class ;
run;
title1 ;

 

The Boston Area SAS Users Group is hosting free webinars!
Next webinar will be in January 2025. Until then, check out our archives: https://www.basug.org/videos. And be sure to subscribe to our our email list.
ahuige
Obsidian | Level 7
Thank you all for your input. After identified the reason I am just giving some background here of why I am doing it. I do maintain my SAS macro library mostly single macro definition one single file. What I am doing now is I sometimes want to wrap all called macros together with the main call program as a single workable program file. For this program it is not supposed to be called by anther program and all sub macros can be put within the %main part. So, that is how I have had this interesting problem appeared. so, I agree it is best to have them separated and maintained. the root cause is that SAS does not have name-space/module concept as other languages. It is always tricky as any macro/dataset/are public to any layer of program that is why the less the better and if any is needed I always keep MD5/RANDOM string as prefix for temporary macro variables/datasets to avoid conflict with others. Anyway, thank you all for the wonderful discussion.
Tom
Super User Tom
Super User

Don't see why you need to nest them to put them together.  In fact it just makes it HARDER to put them together.

If they are separate files for each macro and you want to make one large program just concatenate the macro definitions (in any order) and append the open code that uses them to the bottom.

ahuige
Obsidian | Level 7

hi @Quentin , after your inspiring hint I think now I have a solid explanation:

the nested macro [inside] was not complied until %outside is called. that means the compiling is delayed till called but the %put. But when it is called , the "%put %macro xxxxx  ....." is not a valid syntax.  That means an error. however if it is put outside as a separated macro its compilting was done already and the call %put [compiled context] is %put some text; this is totally fine. oh, it is very interesting. now I know how to nest macros and not to call with other partial code with it. thank you and others for testing and give me the hints. thank you all. 🙂

 

%macro outside();

%macro inside();
    Some Text
%mend inside;
  %inside()
%mend outside;


%put %outside;

%*###############

next phase the code is evolved as  
%put [code inside including definition of macro inside]

it is now syntax-wise incorrect.
;


%put 

[
%macro inside();
    Some Text
%mend inside;
  %inside()
]
;
Tom
Super User Tom
Super User

There is no reason to nest the DEFINITION of macros.  The address space for compiled macros is FLAT.  You cannot have a "local" version of a macro.  Nesting the definitions will just confuse the poor programmer who has to try to understand your code.

 

If your issue is indeed caused by nesting the macro definition then that is just another reason to not nest the definitions.

SAS Innovate 2025: Register Now

Registration is now open for SAS Innovate 2025 , our biggest and most exciting global event of the year! Join us in Orlando, FL, May 6-9.
Sign up by Dec. 31 to get the 2024 rate of just $495.
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.

SAS Training: Just a Click Away

 Ready to level-up your skills? Choose your own adventure.

Browse our catalog!

Discussion stats
  • 14 replies
  • 3794 views
  • 8 likes
  • 5 in conversation