SAS macro that calls other macro with unspecified parameters

I am trying to do a macro that takes other macros as an input, and these macros can have a different number of parameters. So imagine I have a macro:

%MACRO foo(text = );
%PUT &text.;
%MEND foo;

And I want to create a call_macro macro that calls foo(), specifying the text. The issue is that I might want to call another macro fuu() with other parameters.

I've thought of something like that:

%MACRO call_macro(macro = , args = );
%&macro.(&args.);
%MEND call_macro;
%call_macro(macro=foo, args='text=Hello');

Which obviouslly does not work, because I am calling %foo('text=Hello') instead of %foo(text=Hello). Is there a way out of this?

(For context, I am trying to create a map function, used in R, for SAS. The objective would be to have a function map that takes a list of datasets, a function to be applied, and the parameters of that function, performs that function on all datasets)

Upvotes: 0

Views: 736

Answers (5)

Dirk Horsten
Dirk Horsten

Reputation: 3845

Just omit the quotes.

%MACRO call_macro(macro = , args = );
%&macro.(&args.);
%MEND call_macro;
%call_macro(macro=foo, args=text=Hello);

it would not work if you would have called your macro without equality signs because in your call

%call_macro(foo, text=Hello);

the second argument would not be interpreted as having a value text=Hello, but as a parameter text with value Hello.

Upvotes: 2

Tom
Tom

Reputation: 51566

If you did want to do this then you need to protect the commas in the arguments to the submacro. You could try macro quoting or actual quoting. But it would be simplest to take advantage of the fact that commas inside of parentheses in a macro call do not get treated as indicating start of a new parameter.

So just pass the macro call without the % as the single argument to the CALL_MACRO macro.

%macro call_macro(arg);
  %put &=sysmacroname: &=arg ;
  %&arg.
%mend;

%macro test1(a,b=);
  %put &=sysmacroname: &=a &=b ;
%mend;

%macro test2(c,d,e);
  %put &=sysmacroname: &=c &=d &=e;
%mend;

%call_macro(test1(b=xyz,a=123))
%call_macro(test2(456,e=abc,d=89))
%call_macro(foo(text=Hello))

If for some reason CALL_MACRO needed to know the name of the macro it is going to call that could be extracted with a simple %SCAN() function call.

%local macroname;
%let macroname=%scan(&arg,1,());

Upvotes: 1

Richard
Richard

Reputation: 27508

If you code your dispatching macro with the PARMBUFF option, you do not need to embed your arguments within another parameter. PARMBUFF supplies the macro the parameters, including any bounding parentheses.

Example:

Supply the dispatched macros arguments as first-order arguments to the dispatching macro.

%macro one (data=);
  %put INFO:   Macro:&SYSMACRONAME.  &=data;
%mend;

%macro two (data=, vars=);
  %put INFO:   Macro:&SYSMACRONAME. &=data &=vars;
%mend;

%macro three (data=, vars=, out=);
  %put INFO:   Macro:&SYSMACRONAME. &=data &=vars &=out;
%mend;

%macro dispatch (macro=) / PARMBUFF;
  %put INFO: -----;
  %put INFO: &SYSMACRONAME. &=SYSPBUFF; 
  %put INFO: &=macro;
  %if %length(&macro) %then %do;
    %let syspbuff = %sysfunc(prxchange(%str(s/,?\s*macro\s*=\s*\w+,?//i),-1,&syspbuff));
    %local invoke;
    %let invoke = &macro &syspbuff;
    %&invoke
  %end;
  %put INFO: -----;
%mend;

%dispatch(macro=one, data=foobar)
%dispatch(data=foobar, macro = one)
%dispatch(macro=two, data=foobar, vars=p q r)
%dispatch(macro=three, data=foobar, vars=p q r, out=snafu)

%one (data=x)

will log

INFO: -----
INFO: DISPATCH SYSPBUFF=(macro=one, data=foobar)
INFO: MACRO=one
INFO:   Macro:ONE  DATA=foobar
INFO:
INFO: -----
INFO: DISPATCH SYSPBUFF=(data=foobar, macro = one)
INFO: MACRO=one
INFO:   Macro:ONE  DATA=foobar
INFO:
INFO: -----
INFO: DISPATCH SYSPBUFF=(macro=two, data=foobar, vars=p q r)
INFO: MACRO=two
INFO:   Macro:TWO DATA=foobar VARS=p q r
INFO:
INFO: -----
INFO: DISPATCH SYSPBUFF=(macro=three, data=foobar, vars=p q r, out=snafu)
INFO: MACRO=three
INFO:   Macro:THREE DATA=foobar VARS=p q r OUT=snafu
INFO:
INFO:   Macro:ONE  DATA=x

Upvotes: 1

I worked it out. Set args="par_1=val_1, par_2=val_2" and then inside the macro, use args=%sysfunc(COMPRESS(&args.,'"');

Upvotes: -1

Tom
Tom

Reputation: 51566

If you want to work with DATA in SAS then use SAS data steps and procedures. This is true even if the data is actually metadata about code you want to run.

So if you have a dataset with three variables, REMOTE, MACRO and ARGS you can use that to generate a series of RSUBMIT/ENDRSUBMIT blocks to send the macro calls to each of the different parallel processes. Then just use %INCLUDE to run the generated code.

filename code temp;
data _null_;
  set metadata ;
  by remote ;
  file code;
  if first.remote then put 'rsubmit ' remote ';' ;
  put '%' macro '(' args ');';
  if last.remote then put 'endrsubmit;' ;
run;
%include code / source2;

In reality the RSUBMIT statement might need to be more complex to really implement parallel execution, but the idea of how to generate the code from the metadata is the point.

Upvotes: 3

Related Questions