/************************************************************************************ * * RUNONCE: Make sure SAS code is run only once or when a rerun is needed * * Definition: * runonce(id,runid=,after=,rerun=0,code=,mod=2**50,mult=11) * * Parameters: * id code identifier, should be unique * runid run identifier, i.e. rerun when changed * after list of RunOnce identifiers that should should trigger rerun * rerun set to 1 to force a rerun (default=0) * code SAS code to be run (encompass with %QUOTE, %BQUOTE or %NRSTR) * * mod,mult are used to generate id if not given * * The macro checks the macrovariable &runonce_&id. to check if the code has already * been executed: when excecuted, this will be set to datetime(). It also checks if * the identifiers in after have been defined, indicating that the corresponding code * segments have been run, or have been rerun requiring a rerun of this code segment. * * If the code is changed, it will automatically be rerun. If a runid value is given, * a rerun will also be triggered by a change in this even when the code is unchanged. * You can also give a list of RunOnce IDs in the after option, and the code will be * rerun if any of these have been rerun. * * In case of errors, &runonce_&id. is set to "." to distinguish it from blank. * * If no id is given, one is generated from the code: i.e. this will change if the * code is changed. * * Example (will rerun is samplesize is changed): * %LET samplesize=10; * %runonce(init,code=%bquote(; * data t; * do i=1 to &samplesize.; * x=ranuni(0); * output; * end; * run; * )); * * Requires: %ComputeHash * ************************************************************************************/ %MACRO runonce(id,runid=,after=,rerun=0,code=,mod=2**50,mult=11); %LOCAL T TR i z flag codeid; %LET T=runonce; %LET TR=runonce_run; %ComputeHash(codeid,%quote(&code.)); %LET runid=&codeid.-&runid.; %IF %quote(&id)=%str() %THEN %DO; %LET id=&codeid.; %PUT NOTE: Generated id is &id.; %END; %GLOBAL &T&id. &TR&id.; %LET flag=&rerun.; %IF &&&T&id<0 %THEN %LET flag=1; %ELSE %IF %quote(&runid.)^=%quote(&&&TR&id.) %THEN %LET flag=1; %LET i=1; %DO %WHILE (%scan(&after.,&i.,%str( )) ne %str()); %LET z=%scan(&after.,&i.,%str( )); %GLOBAL &T&z.; %IF &&&T&z.<0 %THEN %DO; %PUT ERROR: Requires code segment &z. to be run first!; %GOTO error; %END; %IF &&&T&z.>&&&T&id. %THEN %LET flag=1; %LET i=%eval(&i.+1); %END; %IF &flag %THEN %GOTO runcode; %PUT Code &id. has already been run.; %GOTO final; %runcode: %PUT Running &id.!; %unquote(&code); %IF &syserr.>0 %THEN %GOTO error; %LET &T&id.=%sysfunc(datetime(),z16.3); %LET &TR&id.=&runid.; %GOTO final; %error: %PUT ERROR: Code terminated by system error &syserr.!; %LET &T&id.=.; %final: %MEND;