Thursday, July 08, 2010

Macros

For a long time i wondered if string substitution is all that the macros offer for until i realized the power of macros when it comes to a programming language.

Macros are strings which are substituted by the pre-compiler before they reach the compiler and it is this behavior which makes macros extremely powerful.

Hence macro substitutes a string in the source code which is then compiled and processed by the compiler.

A classic example of this behavior is the pack unpack pattern applied in AX. It starts with the definition of a macro which refers to all the memory variables we need to maintain state for. The pack method returns a container effectively before this source reaches the compiler the macro is substituted and the container list contains all the memory variables which are processed by the compiler and the actual memory values are composed into the container.

similarly in the unpack method when we say
[version,#CurrentList] = _packedClass;
before the above statement is processed by the compiler the macro is substituted by all the memory variables which then get initialized as this statement is executed.


Ideally the source code written once should not be repeated elsewhere coz as per the best practices it should be reused however if for some reason a piece of code needs to be duplicated through a routine the macros are the ideal candidates to maintain a single central copy of this source.


MorphX has a built-in macro-preprocessor. The purpose of macros is to make statements easy to reuse and you can declare macros wherever you can write X++ statements.

There are basically three kinds of macros: macros in macro libraries, stand-alone macros and (local) macros inside methods. All three types of macros have identical syntax and functionality. The only differences are that

  • A stand-alone macro is created using the macro-node of the Application Object Tree

  • A macro library is a stand-alone macro, which contains (local) macros

  • A local macro is declared within a method, or stand-alone macro

X++ has these macro constructs:

#DEFINE

This statement defines a macro variable, which can be used as a constant or to make macros perform differently according to the macro variable value.

#UNDEF

This statement cancels a macro variable.

#MACROLIB

Loads a macro library. Libraries must be loaded before the macros within them can be used.

#GLOBALMACRO

Declares a global macro (header). A global macro can be used everywhere in MorphX after declaration.

#LOCALMACRO

Declares a local macro (header).

#ENDMACRO

Signals the end of a macro declaration.

#IF

Tests if the macro variable following the #IF has as given value. Includes the statements between #IF and #ENDIF.

#IF.EMPTY()

Tests the argument within the brackets, and includes the statements between #IF and #ENDIF if the argument is empty.

#IFNOT.EMPTY()

Tests the argument within the brackets, and includes the statements between #IF and #ENDIF if the argument is not empty.

#ENDIF

Signals the end of a conditional macro declaration (#IF).

#LINENUMBER

Returns the current line number. Mainly useful for debugging

Using define

You can use the define macro construct to declare symbolic constants in your program as follows:

#Define.MaxLength(100)

int intarray[#MaxLength]

Macro declaration

Macros are declared by this syntax:

Macrodeclaration = #localmacro. Macroname { text } #endmacro

Where text can be anything. Within the macro declaration, it is possible to use parameters. The parameters are named %1, %2, %3 etc. All macros are considered as text, and are unfolded before compilation. This means that there is no means of checking syntax or functionality within macros (all that appears is a compiler error in the macro with no specification of error location).

A local macro, called AnExample is declared as follows:

#localmacro.AnExample

// Some statements or text

#endmacro

A more complex macro using parameters is declared as follows:

#localmacro.ComplexMacro

#DEFINE.ARG(%1); // Defines a macro variable ARG

#IF.EMPTY(%1) // If parameter1 is empty

print “The first parameter is empty (not used)”;

#ENDIF

#IFNOT.EMPTY(%1) // If it is not empty

print “The first parameter is NOT empty, it is %1”;

#ENDIF

%2 = %3 + %4; // param2 is assigned

// param3+param4

#IF.ARG(1) // IF parameter1 is 1

print “Parameter1 has the value 1”;

#ENDIF

#UNDEF.ARG // The macro variable ARG is undefined

#endmacro

Referencing macros

You reference macros by their name prefixed with a hash mark (#). To reference the complex macro shown above, you would write:

#complexMacro(parameter1, parameter2, parameter3, parameter4)

The parameters (1-4) have a story: Parameter 1 is optional, as it is only used in the macro, if it exists (#IFNOT.EMPTY) whereas parameters 2, 3 and 4 are used unconditionally (in the statement %2 = %3 + %4) - and therefore must be included in the call. Assuming you have declared a variable called A, a valid reference would be):

ComplexMacro(1,a,3,4);

Which would expand the macro into the statements below:

#DEFINE.ARG(1); // Defines a macro variable

#IF.EMPTY(1) // If parameter1 is empty

print “The first parameter is empty (not used)”;

#ENDIF

#IFNOT.EMPTY(1) // If it is not empty

print “The first parameter is NOT empty, it is 1”;

#ENDIF

A = 3 + 4; // param2 is assigned param3+param4

#IF.ARG(1) // IF parameter1 is 1

print “Parameter1 has the value 1”;

#ENDIF

#UNDEF.ARG // The macrovariable ARG is undefined

The results of the above statements are two prints and an assignment.

A is assigned the value 7 and two lines are printed on the screen: “The first parameter is NOT empty, it is 1” and “Parameter1 has the value 1”.

If you referenced the complex macro without parameters 2-4, the result would have been:

= + ; // param2 is assigned param3+param4

And hence a compilation error occurs though you cannot see that the macro is actually expanded in this way (which makes the error harder to find).

Using macro libraries

To use a macro library, you must create the library (say TestMe) using the Application Object Tree and then load the macro using the following construct:

#TestMe;

A more preferred way of referring macro libraries is using the #macrolib keyword the advantage of using this keyword is that you will be able to reference macors with special characters and multiple lines look at the example below where a special character (") doube quote had to be used in a macro
1. #define.TableRead(select * from EmplTable where EmplTable.EmplCode == %1;)
#TableRead("E0006")
info( emplTable.EmplCode );

2. #localmacro.TableRead
select * from EmplTable where EmplTable.EmplCode == "%1";
#endmacro

#macrolib.training
#TableRead(E0006)
info( emplTable.EmplCode );

The second way is the preferred way of creating multi line macros also if the macro includes some special symbols like ) or " then the second method is preferred if you notice in the first method the double quote are passed in the parameter to the macro as they could not be a part of the macro as in the macro definition the macro would end at the first instance of the double quote.

Inheritance and Macros
The macros defined in the class declaration of the base class are available in the sub classes however if one of the class in the hierarcy undefines the macro then that point onwards the macro are not available in the child classes.

No comments: