Search code examples
sassas-macro

macro seems stuck in infinite loop, don't know how to debug


I'm trying to define a macro function that returns unique list items from a space separated list. This macro itself uses other macros that I tested and seem to work fine by themselves (see examples below), it's all very simple code.

However for some reason the code runs indefinitely and I don't know how to debug it properly. I usually debug using %put statements but they don't print here as there's no error and I stop the code manually.

Here is the main macro, followed by my other convenience macros that I use, you can execute the whole batch to load the macros and then check out given examples.

*---------------------------------------------------------------;
* LIST_UNIQUE                                                   ;
* Return only unique items from list,                           ;
* in order of first appearance                                  ;
*---------------------------------------------------------------;
/* EXAMPLE
%put %list_unique();    ** (nothing)
%put %list_unique(a);   ** a
%put %list_unique(a a); ** doesn't work (should be a)
%put %list_unique(a b); ** doesn't work (should be a b)
*/
%macro list_unique(data);
%local out curr_item;
%do i=1 %to %list_length(&data);
   %let curr_item = %extract(&data,&i);
   %if not %list_in(&curr_item,&out) %then %let out = &out &curr_item;
%end;
&out
%mend;

*---------------------------------------------------------------;
* LIST_LENGTH                                                   ;
* Length of space separated list                                ;
*---------------------------------------------------------------;
/* EXAMPLES :   
   %put %list_length(); ** 0
   %put %list_length(item1 item2 item3); ** 3
*/
%macro list_length(data);
%sysfunc(countw(&data,%str( )))
%mend;

*---------------------------------------------------------------;
* LIST_IN                                                       ;
* check if item is in list                                      ;
*---------------------------------------------------------------;
/* EXAMPLE
%put %list_in(,a);        ** 0
%put %list_in(a,);        ** 0
%put %list_in(a,a);       ** 1
%put %list_in(a,a a);     ** 1
%put %list_in(b,a b c d); ** 1
%put %list_in(e,a b c d); ** 0
*/
%macro list_in
(item /* item to search in list */
,list /* space separated list to quote */
);
/* exception when list has null length */
%if not %length(&list) %then 0%return; 
/* general case */
%do i=1 %to %list_length(&list);
   %if %extract_pos(&list,&i) = &item %then 1%return;
%end;
0
%mend;

*-------------------------------------------------------------------------------;
* EXTRACT_POS                                                                   ;
* Extracts subset of values from space separated list                           ;
*-------------------------------------------------------------------------------;
/* EXAMPLES
%put %extract_pos(,1);        ** (nothing)
%put %extract_pos(a b c d,);  ** (nothing)
%put %extract_pos(a b c d,1); ** a
%put %extract_pos(a b c d,2 1:3 1); ** b a b c a
*/
%macro extract_pos
(data
,ind
);
%local i j token output new_ind;
%do i=1 %to %sysfunc(countw(&ind,%str( )));
  %let token = %scan(&ind,&i,%str( ));
  %if %index(&token,:) %then %do; /* if token with ':' */
    %do j=%scan(&token,1,:) %to %scan(&token,2,:);
      %let output = &output %scan(&data,&j,%str( ));
    %end;
  %end;
  %else %do;                      /* if simple token */
      %let output = &output %scan(&data,&token,%str( ));
  %end;
%end;
&output
%mend;

Solution

  • You cannot protect macros you call from modifying your macro variables, but if the macros are designed properly they will NOT. Unless you are INTENDING to modify any existing macro variable you need to define your macro variables as local. One or more of your macros were using the macro variable I without defining it as local. So if there already existed a macro variable named I then the macro modified the existing variable's value.

    Also one of your macros was calling %extract() instead of %extract_pos().

    I also simplified your %list_in() macro to just be a call to an existing SAS function, like your %list_length() macro.

    %macro list_unique
    /*---------------------------------------------------------------
    Return only unique items from list
    ---------------------------------------------------------------*/
    (data  /* Space delimited list of items */
    );
    %local i curr_item out ;
    %do i=1 %to %list_length(&data);
      %let curr_item = %extract_pos(&data,&i);
      %if not %list_in(&curr_item,&out) %then %let out=&out &curr_item;
    %end;
    &out
    %mend list_unique;
    
    %macro list_length(data);
    %sysfunc(countw(&data,%str( )))
    %mend list_length;
    
    %macro list_in
    /*---------------------------------------------------------------
    Check if item is in list
    ---------------------------------------------------------------*/
    (item /* item to find in list */
    ,list /* space separated list to search */
    );
    %sysevalf(%sysfunc(indexw(&list,&item,%str( ))),boolean)
    %mend list_in;
    
    %macro extract_pos
    /*-------------------------------------------------------------------------------
    Extracts subset of values from space separated list
    -------------------------------------------------------------------------------*/
    (data   /* Space delimited list of values */
    ,ind    /* Space delimited list of positions or position ranges */
    );
    %local i j token output;
    %do i=1 %to %sysfunc(countw(&ind,%str( )));
      %let token = %scan(&ind,&i,%str( ));
      %do j=%scan(&token,1,:) %to %scan(&token,-1,:);
    /* Token is single position or range in format start:end */
        %let output = &output %scan(&data,&j,%str( ));
      %end;
    %end;
    &output
    %mend extract_pos;
    

    Test

    831  %put %list_unique();    %** (nothing);
    
    832  %put %list_unique(a);   %** a ;
    a
    833  %put %list_unique(a a); %** doesnot work (should be a);
    a
    834  %put %list_unique(a b); %** doesnot work (should be a b);
    a b