[resulttype] macroname ( parameterlist ) := MACRO
tokenstream;
resulttype | Optional. The result type of the macro. The only valid type is DATASET. If omitted and the tokenstream contains no Attribute definitions, then the macro is treated as returning a value (typically INTEGER or STRING). |
macroname | The name of the function the MACRO structure defines. |
parameterlist | A comma separated list of names (tokens) of the parameters that will be passed to the macro. These names are used in the tokenstream to indicate where the passed parameters are substituted when the macro is used. Value types for these parameters are not allowed, but default values may be specified as string constants. |
tokenstream | The Attribute definitions or Actions that the macro will perform. |
The MACRO structure makes it possible to create a function without knowing the value types of the parameters that will eventually be passed to it. The most common use would be performing functions upon arbitrary datasets.
A macro behaves as if you had typed the tokenstream into the exact position you use it, using lexical substitution--the tokens defined in the parameterlist are substituted everywhere they appear in the tokenstream by the text passed to the macro. This makes it entirely possible to write a valid MACRO definition that could be called with a set of parameters that result in obscure compile time errors.
There are two basic type of macros: Value or Attribute. A Value macro does not contain any Attribute definitions, and may therefore be used wherever the value type it will generate would be appropriate to use. An Attribute macro does contain Attribute definitions (detected by the presence of the := in the tokenstream) and may therefore only be used where an Attribute definition is valid (a line by itself) and one item in the parameterlist should generally name the Attribute to be used to contain the result of the macro (so any code following the macro call can make use of the result).
Example:
// This is a DATASET Value macro that results in a crosstab
DATASET CrossTab(File,X,Y) := MACRO
TABLE(File,{X, Y, COUNT(GROUP)},X,Y)
ENDMACRO;
// and would be used something like this:
OUTPUT(CrossTab(Person,person.per_st,Person.per_sex))
// this macro usage is the equivalent of:
// OUTPUT(TABLE(Person,{person.per_st,Person.per_sex,COUNT(GROUP)},
// person.per_st,Person.per_sex)
//The advantage of using this macro is that it can be re-used to
// produce another cross-tab without recoding
// The following macro takes a LeftFile and looks up a field of it in
// the RightFile and then sets a field in the LeftFile indicating if
// the lookup worked.
IsThere(OutFile ,RecType,LeftFile,RightFile,LinkId ,SetField ) := MACRO
RecType Trans(RecType L, RecType R) := TRANSFORM
SELF.SetField := IF(NOT R.LinkId,0,1);
SELF := L;
END;
OutFile := JOIN(LeftFile,
RightFile,
LEFT.LinkId=RIGHT.LinkId,
Trans(LEFT,RIGHT),LEFT OUTER);
ENDMACRO;
// and would be used something like this:
MyRec := RECORD
Person.per_cid;
Person.per_st;
Person.per_sex;
Flag:=FALSE;
END;
MyTable1 := TABLE(Person(per_first_name[1]='R'),MyRec);
MyTable2 := TABLE(Person(per_first_name[1]='R',per_sex='F'),MyRec);
IsThere(MyOutTable,MyRec,MyTable1,MyTable2,per_cid,Flag)
// This macro call generates the following code:
// MyRec Trans(MyRec L, MyRec R) := TRANSFORM
// SELF.Flag := IF(NOT R.per_cid ,0,1);
// SELF := L;
// END;
// MyOutTable := JOIN(MyTable1,
// MyTable2,
// LEFT.per_cid=RIGHT.per_cid,
// Trans(LEFT,RIGHT),
// LEFT OUTER );
OUTPUT(MyOutTable);
//***********************************************************
//This macro has defaults for its second and third parameters
MyMac(FirstParm,yParm='22',zParm='42') := MACRO
FirstParm := yParm + zParm;
ENDMACRO;
// and would be used something like this:
MyMac(Fred)
// This macro call generates the following code:
// Fred := 22 + 42;
//***********************************************************
//This macro uses #EXPAND
MAC_join(attrname, leftDS, rightDS, linkflags) := MACRO
attrname := JOIN(leftDS,rightDS,#EXPAND(linkflags));
ENDMACRO;
MAC_join(J1,People,Property,'LEFT.ID=RIGHT.PeopleID,LEFT OUTER')
//expands out to:
// J1 := JOIN(People,Property,LEFT.ID=RIGHT.PeopleID,LEFT OUTER);
See Also: TRANSFORM Structure, RECORD Structure, #UNIQUENAME, #EXPAND