Quentin
Quentin

Reputation: 6378

How to prevent CATx functions from evaluating expression

When calling CATT() function with %sysfunc, is there a way to stop it from evaluating an expression?

For example given the code:

%let date=10-13-2015;
%put %sysfunc(catt(The date Is:,&date));

I would like it to return:

The date Is:10-13-2015

Because 10-13-2015 is just a text string. But instead CATT() sees hyphen as a subtraction sign and evaluates it as a numeric expression, returning:

The date Is:-2018

I have tried macro quoting, but doesn't change anything, I suppose because I need to somehow hide the values from CATT(). Seems if any argument to CATT looks like an expression, it will be treated as such.

Another example:

%let value=2 and 3;
%put %sysfunc(catt(The value Is:,&value));
The value Is:1

Upvotes: 4

Views: 936

Answers (4)

user667489
user667489

Reputation: 9569

Slightly less insane function-style macro without the dosubl:

%macro catx() /parmbuff;
%local rc dlm i params OUTSTR QWORD outstr;
%let SYSPBUFF = %qsubstr(&SYSPBUFF,2,%length(&SYSPBUFF)-2);
%let dlm = %qscan(&SYSPBUFF,1,%str(,));
%let params = %qsubstr(&SYSPBUFF,%index(&SYSPBUFF,%str(,))+1);

%let i = 1;
%let QWORD = %scan(&PARAMS,&i,%str(,));
%let OUTSTR = &QWORD;
%do %while(&QWORD ne);
    %let i = %eval(&i + 1);
    %let QWORD = %scan(&PARAMS,&i,%str(,));
    %if &QWORD ne %then %let OUTSTR = &OUTSTR.&DLM.&QWORD;
%end;

%unquote(&OUTSTR)
%mend catx;

%put %catx(%str( ),abc,10 - 1 + 2,def);

Somewhat more insane but apparently working option - use %sysfunc(dosubl(...)) and lots of macro logic to create a function-style macro that takes input in the same way as %sysfunc(catx(...)), but forces catx to treat all input as text by quoting it and calling it in a data step.

%macro catxt() /parmbuff;
%local rc dlm i params QPARAMS QWORD outstr;
%let SYSPBUFF = %qsubstr(&SYSPBUFF,2,%length(&SYSPBUFF)-2);
%let dlm = %qscan(&SYSPBUFF,1,%str(,));
%let params = %qsubstr(&SYSPBUFF,%index(&SYSPBUFF,%str(,))+1);

%let i = 1;
%let QWORD = "%scan(&PARAMS,&i,%str(,))";
%let QPARAMS = &QWORD;
%do %while(&QWORD ne "");
    %let i = %eval(&i + 1);
    %let QWORD = "%scan(&PARAMS,&i,%str(,))";
    %if &QWORD ne "" %then %let QPARAMS = &QPARAMS,&QWORD;
%end;

%let rc = %sysfunc(dosubl(%str(
    data _null_;
        call symput("OUTSTR",catx("&dlm",%unquote(&QPARAMS)));
    run;
)));

&OUTSTR
%mend catxt;

%put %catxt(%str( ),abc,10 - 1 + 2,def);

Although this uses a data step to execute catx, dosubl allows the whole thing to be run in any place where you could normally use %sysfunc(catx(...)).

Upvotes: 0

Tom
Tom

Reputation: 51611

The problem with %SYSFUNC() evaluating the arguments is not limited to the CAT() series of functions. Any function that accepts numeric values will result in SAS attempting to evaluate the expression provided.

This can be a useful feature. For example:

%let start_dt=10OCT2012 ;
%put %sysfunc(putn("&start_dt"d +1,date9));

You don't need to use CAT() functions to work with macro variables. Just expand the values next to each other and the are "concatenated".

%let date=10-13-2015;
%put The date Is:&date;

If you want to make a macro that works like the CATX() function then that is also not hard to do.

%macro catx /parmbuff ;
%local dlm return i ;
%if %length(&syspbuff) > 2 %then %do;
  %let syspbuff = %qsubstr(&syspbuff,2,%length(&syspbuff)-2);
  %let dlm=%qscan(&syspbuff,1,%str(,),q);
  %let return=%qscan(&syspbuff,2,%str(,),q);
  %do i=3 %to %sysfunc(countw(&syspbuff,%str(,),q));
    %let return=&return.&dlm.%qscan(&syspbuff,&i,%str(,),q);
  %end;
%end;
&return.
%mend catx;

%put %catx(|,a,b,c);
a|b|c
%put "%catx(",",a,b,c,d)";
"a","b","c","d"

Upvotes: 1

Joe
Joe

Reputation: 63424

I don't see an easy way around this, unfortunately. I do see that you could in theory pass this through an FCMP function, though since FCMP doesn't allow true variable arguments, that isn't ideal either, but...

proc fcmp outlib=work.funcs.funcs;
  function catme(delim $, in_string $) $;
    length _result $1024;
    length _new_delim $1;
    _new_delim = scan(in_string,1,delim);
    do _i = 1 to countc(in_string,delim);
      _result = catx(_new_delim, _result, scan(in_string,_i+1,delim));
    end;
    return(_result);
  endfunc;
quit;

options cmplib=work.funcs;

%let date=10-13-2015;
%put %sysfunc(catme(|,:|The date Is| &date.));

Or add quotes to the argument and then remove them after the CATx.

%sysfunc(dequote(%sysfunc(catt(.... ,"&date."))))

All messy.

Upvotes: 1

Robert Penridge
Robert Penridge

Reputation: 8513

Provided you can do so, just remove the comma - there's no need to separate it into an individual parameter (unless you're using catx() rather than catt():

%let date=10-13-2015;
%put %sysfunc(catt(The date Is: &date));

Personally, I think the best way to work is to store the date as a SAS date value and then use the second (optional) parameter of %sysfunc to apply the formatting. This provides better flexibility.

%let date = %sysfunc(mdy(10,13,2015));
%put The date Is: %sysfunc(sum(&date),mmddyyd10.);

If you are insistent on the original approach and are using catx(), then I don't know how to do it exactly. The closest I could get was to insert a piece of text so it couldn't be interpreted as an expression, and then remove that text afterwards using tranwrd. Pretty, ugly, and it leaves a space:

%let date=10-13-2015;
%let tmp=%sysfunc(catx(#, The date Is: , UNIQUE_STRING_TO_REMOVE&date ));
%let want=%sysfunc(tranwrd(&tmp, UNIQUE_STRING_TO_REMOVE, ));

%put &want;

Gives:

The date Is:# 10-13-2015

I also tried every combination of macro quoting, and scanned through the entire SAS function list and couldn't see any other viable options.

Upvotes: 1

Related Questions