4

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
Quentin
  • 5,960
  • 1
  • 13
  • 21

4 Answers4

1

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.

Robert Penridge
  • 8,424
  • 2
  • 34
  • 55
  • Thanks @Robert. Yes, you've guessed correctly that my real code is CATX(). And my real code is not a date (I agree this is not the way to handle dates, just seemed the easiest example). I will probably take your approach of adding and removing string, or just avoid CATX(). It's an interesting problem, though. Guess it's partly a side effect of CATX being happy to deal with numeric arguments. And of course only a problem with %SYSFUNC invocations, since there isn't a good way to say "This value is a string". – Quentin Oct 13 '15 at 19:56
1

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"
Tom
  • 47,574
  • 2
  • 16
  • 29
  • Understood, @Tom, but this is a simplified example. In the real setting I was hoping to use CATX() in a macro setting, and stumbled into this surprise (to me at least) that it would evaluate items in the list which look like expressions. – Quentin Oct 13 '15 at 19:51
  • That is a problem of %SYSFUNC() and not just the CAT() functions. – Tom Oct 13 '15 at 19:52
  • I think it's more CAT(). That is, CAT() is deciding the value is an expression, and evaluating it. UPCASE() does not. Compare: %put %sysfunc(upcase(1 or 0)); %put %sysfunc(catt(1 or 0)); I think it's the flexibility of CAT, being happy with character or numeric arguments and expressions, that causes this problem in macro setting. In DATA step language, could just add quotes to indicate it's a string. – Quentin Oct 13 '15 at 19:59
1

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.

Joe
  • 62,789
  • 6
  • 49
  • 67
  • I like this solution. At least the mess is hidden away. =) – Robert Penridge Oct 13 '15 at 21:43
  • Perhaps the way the parameters are being passed could be improved though? Assuming @Quentin wants a `catx()` function, keep the first parameter as the string to use as the separator, and just `%bquote` the second parameter (when calling it) which would allow you to use a comma separated list of values? – Robert Penridge Oct 13 '15 at 21:46
  • I think if `%bquote` worked like that, it would also work for the original `catx`, unfortunately. – Joe Oct 13 '15 at 21:48
  • I think perhaps I didn't explain it clear enough. What I was saying was that with some slight changes to the `proc fcmp` parameters you could end up with a call very similar to calling `%sysfunc(catx())`... It would be nice to be able to call it like so: `%let want = %sysfunc(mycatx(myDelimiter,%bquote(param1,param2,etc,paramX) ));` – Robert Penridge Oct 13 '15 at 22:08
  • Thanks @Joe, this is a good reminder that I need to learn FCMP some time. Sadly, `%put %sysfunc(dequote(%sysfunc(catt(The date Is:,"&date"))));` does not work. It returns `The date Is:"10-13-2015"` I think DEQUOTE() can't remove internal quote marks. But @Robert's TRANWRD() workaround is a similar approach. – Quentin Oct 14 '15 at 00:31
0

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(...)).

user667489
  • 9,501
  • 2
  • 24
  • 35
  • Ahh, some DOSUBL macro function magic! Nifty approach. I'm still staying away from DOSUBL in production work, because of an odd scoping collision bug. Note if you add `%let outstr=Hi Mom;` in open code before your macro call, the macro will return Hi Mom. Even though you have appropriately declared OUTSTR to be local to your macro. But that said, I think there is a lot of promise in DOSUBL for just this sort of approach, so hopefully they will be able to fix this bug soon. : ) – Quentin Oct 13 '15 at 23:52
  • If you want to make a macro version of CATX() there is no need to use `dosubl` or `%sysfunc(catx())`. – Tom Oct 14 '15 at 02:10
  • @Tom that did occur to me, but only after the fact. I might have another go. – user667489 Oct 14 '15 at 07:45
  • I've added a slightly more sensible option. – user667489 Oct 14 '15 at 13:14