📜 ⬆️ ⬇️

Macros in InterSystems Caché

Monet tulips in holland

Introduction


I want to talk about the use of macros in InterSystems Caché. A macro is a symbolic name that is replaced when the source code is compiled with a sequence of program instructions. A macro can “unfold” into different sequences of instructions for each call, depending on the branches that have been triggered inside the macro and the arguments passed to it. This can be either a static code or the result of running COS. Consider how you can use them in your application.

Compilation



To begin with, to understand where macros are used, a few words about how ObjectScript code is compiled:

Macros


As already mentioned, a macro is a symbolic name that is replaced when a preprocessor is processed with a sequence of program instructions. It is determined using the #Define command, followed by the name of the macro first (possibly with an argument list) and then the value of the macro:
#Define Macro [(Args)] [Value]
Where can macros be defined? Either directly in the code, or in separate INC files containing only macros. Include the necessary files to the classes with the Include MacroFileName command at the very beginning of the class definition — this is the main and preferred method for connecting macros to the class, thus attaching macros to them can be used in any part of the class definition. You can attach the INC file of macros with the #Include MacroFileName command to the MAC routines or code of individual class methods.

Examples


Example 1


Let us turn to examples of use, we begin with the output line "Hello World". COS code: Write "Hello, World!"
Now we will write a macro HW that displays this line: #define HW Write "Hello, World!"
It is enough to write in the $$$ HW code ( $$$ to call the macro, then the macro name follows):

ClassMethod Test ()
{
#define HW Write "Hello World!"
$$$ HW
}
')
And when compiled, it is converted to the following INT code:

zTest1 ( ) public {
Write "Hello, World!" }

In the terminal when you run this method will be displayed:
Hello, World! 

Example 2


In the following example, we use variables:

ClassMethod Test2 ()
{
#define WriteLn (% str,% cnt) For ## Unique (new) = 1: 1:% cnt {## Continue
Write% str ,! ## Continue
}

$$$ WriteLn ( "Hello, World!" , 5)
}

Here the string% str is printed% cnt times. Variable names must begin with%. The ## Unique (new) command creates a new unique variable in the generated code, and the ## Continue command allows you to continue defining the macro on the next line. This code is converted to the following INT code:

zTest2 ( ) public {
For % mmmu1 = 1: 1: 5 {
Write "Hello, World!" !
} }

In the terminal when you run this method will be displayed:
 Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! 

Example 3


Let us turn to more complex examples. The ForEach operator is very useful when iterating over globals; add it:

ClassMethod Test3 ()
{
#define ForEach (% key,% gn) Set ## Unique (new) = $ name (% gn) ## Continue
Set% key = "" ## Continue
For {## Continue
Set% key = $ o (@ ## Unique (old) @ (% key)) ## Continue
Quit:% key = ""

#define EndFor }

Set ^ test (1) = 111
Set ^ test (2) = 222
Set ^ test (3) = 333

$$$ ForEach ( key , ^ test)
Write "key:" , key,!
Write "value:" , ^ test ( key ) ,!
$$$ EndFor
}

Here is how it looks in int code:

zTest3 ( ) public {
Set ^ test (1) = 111
Set ^ test (2) = 222
Set ^ test (3) = 333
Set % mmmu1 = $ name ( ^ test)
Set key = ""
For {
Set key = $ o ( @ % mmmu1 @ ( key ) )
Quit : key = ""
Write "key:" , key,!
Write "value:" , ^ test ( key ) ,!
} }

What happens in these macros?

In the terminal when you run this method will be displayed:
 key: 1 value: 111 key: 2 value: 222 key: 3 value: 333 

If you use lists and arrays - the heirs of the % Collection.AbstractIterator class, you can write a similar iterator already for it.

Example 4


Another possibility of macros is to execute arbitrary COS code at compile time and substitute the result of execution instead of a macro. Create a macro with compile time:

ClassMethod Test4 ()
{
#Define CompTS ## Expression ( "" "Compiled:" _ $ ZDATETIME ($ HOROLOG) _ "" ",!" )
Write $$$ CompTS
}

Which is converted to the following int code:

zTest4 ( ) public {
Write "Compiled: 05/19/2015 15:28:45",! }

In the terminal when you run this method will be displayed:
 Compiled: 05/19/2015 15:28:45 

The expression ## Expression executes the code and substitutes the result, the input can be the following elements of the COS language:

Example 5


The preprocessor directives #If , #ElseIf , #Else , #EndIf are used to select the source code when compiling depending on the value of the expression after the directive, for example this method:

ClassMethod Test5 ()
{
#If $ SYSTEM .Version . GetNumber () = "2015.1.1" && $ SYSTEM .Version . GetBuildNumber () = "505"
Write "You are using the latest version of Caché"
#ElseIf $ SYSTEM .Version . GetNumber () = "2015.2.0"
Write "You are using the latest beta version of Caché"
#Else
Write "Please consider an upgrade"
#EndIf
}

In Caché, version 2015.1.1.505 will be compiled into the following INT code:

zTest5 () public {
Write "You are using the latest version of Caché"
}

And in the terminal will display:
 You are using the latest released version of Caché 

In Caché, the download from the beta portal will be compiled into another INT code:

zTest5 () public {
Write "You are using the latest beta version of Caché"
}

And in the terminal will display:
 You are using the latest beta version of Caché 

And previous versions of Caché will compile the following INT code with a suggestion to upgrade:

zTest5 () public {
Write "Please consider an upgrade"
}

And in the terminal will display:
 Please consider an upgrade 

This feature can be used, for example, to preserve the compatibility of the client application between the old versions and the new ones, where the new Caché DBMS functionality can be used. This purpose is also served by the preprocessor directives #IfDef , #IfNDef which check for the existence or absence of a macro, respectively.

findings


Macros can simply make your code more understandable, simplifying constructions that are often repeated in code, and also implement part of the application logic at the compilation stage, thus reducing the load in runtime.

What's next?


In the next article I will talk about a more practical example of using macros - the logging system.

Links


About compilation
List of preprocessor directives
List of system macros
Class with examples
Part II. Logging system

The author is grateful to Daimor Habrawers , Greyder, and another very competent engineer who wished to remain anonymous, for help in writing the code.

Source: https://habr.com/ru/post/258081/


All Articles