Using Caché ObjectScript
ObjectScript Macros and the Macro Preprocessor
[Home] [Back] [Next]
InterSystems: The power behind what matters   
Class Reference   
Search:    

The ObjectScript compiler includes a preprocessor and ObjectScript includes support for preprocessor directives. These directives allow you to create macros for use in applications — both in methods and routines. These macros provide the functionality for simple textual substitutions in code. Caché itself also includes various predefined macros, which are described in the relevant contexts within the Caché documentation set.

This chapter has three parts:
Note:
The preprocessor expands macros before the ObjectScript parser handles any Embedded SQL or Embedded HTML code. This means that there is no support for macros within Embedded HTML. The preprocessor supports Embedded SQL in either embedded or deferred compilation mode; the preprocessor does not expand macros within Dynamic SQL.
The ObjectScript parser removes multiple line comments before parsing preprocessor directives. Therefore, any macro preprocessor directive specified within a /* . . . */ multiple line comment is not executed.
Using Macros
This section covers the following topics:
Creating Custom Macros
Macros are one-line definitions of substitutions that can support many aspects of ObjectScript functionality. In their basic form, they are created with a #Define directive. For instance, the following code creates a macro called StringMacro and makes it a substitution for the string “Hello, World!”:
#Define StringMacro "Hello, World!"
ObjectScript allows you to invoke a macro using the “$$$” syntax, such as:
  WRITE $$$StringMacro
which, in this case, displays the string “Hello, World!” Here is the entire sample:
#Define StringMacro "Hello, World!"
   WRITE $$$StringMacro
 
Supported functionality includes:
Macro Naming Conventions
Macro Whitespace Conventions
Macro Comments and Studio Assist
Macros can include comments, which are passed through as part of their definition. Comments delimited with /* and */, //, #;, ;, and ;; all behave in their usual way. See the Comments section in the “Syntax Rules” chapter of Using Caché ObjectScript for basic information on comments.
Comments that begin with the /// indicator have a special functionality. If you wish to use Studio Assist with a macro that is in an include file, then place a /// comment on the line that immediately precedes its definition; this causes its name to appear in the Studio Assist popup. (All macros in the current file appear in the Studio Assist popup.) For example, if the following code were referenced through an #Include directive, then the first macro would appear in the Studio Assist popup and the second would not:
/// A macro that is visible with Studio Assist
#Define MyAssistMacro 100
 //
 // ...
 // 
 // A macro that is not visible with Studio Assist
#Define MyOtherMacro -100
For information on making macros available through include files, see Referring to External Macros (Include Files).” For information on Studio Assist, see the Editor Options section of the “Setting Studio Options” chapter of Using Studio.
Saving Custom Macros
Macros can either appear in the file where they are invoked or, as is more common, in a separate include file. For a macro to be available class-wide (that is, available for any member to invoke), place its definition in an include file and include it in the class.
To place macros in an include file, the procedure in Studio is:
  1. Select Save or Save As from the File menu.
  2. In the Save As dialog, specify that the Files of type field has a value of Include File (*.inc).
    Note:
    The Macro Routine (*.mac) value for this field is not the correct file type for ObjectScript macros.
  3. Enter the directory and file names, and save the macros.
Invoking Macros
You can invoke a macro either when its definition is part of the method or routine being defined or when that method or routine uses the #Include directive to refer to a definition in an external source. For information on #Include, see the section Referring to External Macros or the reference section on #Include.
To invoke a macro from within ObjectScript code, refer to it by its name prefixed with “$$$”. Hence, if you have defined a macro called MyMacro, you can call it by referring to $$$MyMacro. Note that macro names are case-sensitive.
Remember that macros are text substitutions. After a macro is substituted in, the syntax for the resulting statement is checked for correctness. Therefore, the macro defining an expression should be invoked in a context requiring an expression; the macro for a command and its argument can stand as an independent line of ObjectScript; and so on.
Referring to External Macros (Include Files)
When you have saved macros to a separate file, you can make them available with the #include directive. The directive is not case-sensitive, so it can appear as #Include.
When including macros within a class or at the beginning of a routine, the directives are of the form:
#include MacroIncFile
where MacroIncFile refers to an included file containing macros that is called MacroIncFile.inc. Note that the .inc suffix is not included in the name of the referenced file when it is an argument of #include.
For example, if you have one or more macros in the file MyMacros.inc, you can include them with the following call:
#include MyMacros
If there are other macros that are in the file YourMacros.inc, you can include all of them with the following calls:
#include MyMacros
#include YourMacros
When you use #include in a routine you must specify a separate #include statement on a separate line for each include file.
To include an include file at the beginning of a class definition, the syntax is of the form:
include MyMacros
To include multiple include files at the beginning of a class definition, the syntax is of the form:
include (MyMacros, YourMacros) 
Note that this include syntax does not have a leading pound sign; this syntax cannot be used for #include. Also, compilation in Studio converts the form of the word so that its first letter is capitalized and subsequent letters are lower case.
The ObjectScript compiler provides a /defines qualifier that permits including external macros. For further details refer to the Compiler Qualifiers table in the $SYSTEM special variable reference page in the Caché ObjectScript Reference.
See also the reference section on #Include.
Preprocessor Directives Reference
Caché includes support for the following system preprocessor directives:
Note:
The macro preprocessor directives are not case-sensitive. This document displays their names in title case for clarity, but this is not required.
#;
The #; preprocessor directive creates a single line comment that does not appear in .int code. The comment appears only in either .mac code or in an include file. The #; appears at the beginning (column 1) of the line. The comment continues for the remainder of the current line. It has the form:
#; Comment here...
where the comment follows the “#;”.
Compare with the ##; preprocessor directive.
#Def1Arg
The #Def1Arg preprocessor directive defines a macro with only one argument, where that argument can have commas in it. #Def1Arg is a special version of #Define, since #Define treats commas within arguments as delimiters between arguments. It has the form:
#Def1Arg Macro(%Arg) Value
where
A #Def1Arg line can include a ##; comment.
For more information on defining macros generally, see the entry for #Define.
For example, the following MarxBros macro accepts the comma-separated list of the names of the Marx brothers as its argument:
#Def1Arg MarxBros(%MBNames) WRITE "%MBNames:",!,"The Marx Brothers!",!
 // some movies have all four of them
 $$$MarxBros(Groucho, Chico, Harpo, and Zeppo)
 WRITE !
 // some movies only have three of them
 $$$MarxBros(Groucho, Chico, and Harpo)
 
where the MarxBros macro takes an argument, %MBNames argument, which accepts a comma-delimited list of the names of the Marx brothers.
#Define
The #Define preprocessor directive defines a macro. It has the form:
#Define Macro[(Args)] [Value]
where
If a macro is defined with a value, then that value replaces the macro in ObjectScript code. If a macro is defined without a value, then code can use other preprocessor directives to test for the existence of the macro and then perform actions accordingly.
A #Define line can include a ##; comment.
Macros with Values
Macros with values provide a mechanism for simple textual substitutions in ObjectScript code. Wherever the ObjectScript compiler encounters the invocation of a macro (in the form $$$MacroName), it replaces the value specified for the macro at the current position in ObjectScript code. The value of a macro can be any valid ObjectScript code. This includes:
Macro arguments cannot include commas. If commas are required, the #Def1Arg directive is available.
The following are examples of definitions for macros being used in various ways.
#Define Macro1 22
#Define Macro2 "Wilma"
#Define Macro3 x+y
#Define Macro4 $Length(x)
#Define Macro5 film.Title
#Define Macro6 +$h
#Define Macro7 SET x = 4
#Define Macro8 DO ##class(%Library.PopulateUtils).Name()
#Define Macro9 READ !,"Name: ",name  WRITE !,"Nice to meet you, ",name,!

#Define Macro1A(%x) 22+%x
#Define Macro2A(%x) "Wilma" _ ": %x"
#Define Macro3A(%x) (x+y)*%x
#Define Macro4A(%x) $Length(x) + $Length(%x)
#Define Macro5A(%x) film.Title _ ": " _ film.%x
#Define Macro6A(%x) +$h - %x
#Define Macro7A(%x) SET x = 4+%x
#Define Macro8A(%x) DO ##class(%Library.PopulateUtils).Name(%x) 
#Define Macro9A(%x) READ !,"Name: ",name  WRITE !,"%x ",name,!
#Define Macro9B(%x,%y) READ !,"Name: ",name  WRITE !,"%x %y",name,!
Conventions for Macro Values
Though a macro can have any value, the convention is a macro is literal expression or complete executable line. For example, the following is valid ObjectScript syntax:
#Define Macro7 SET x =
where the macro might be invoked with code such as:
 $$$Macro7 22
which the preprocessor would expand to
 SET x = 22
Though this is clearly valid ObjectScript syntax, this use of macros is discouraged.
Macros without Values
A macro can be defined without a value. In this case, the existence (or not) of the macro specifies that a particular condition exists. You can then use other preprocessor directives to test if the macro exists and perform actions accordingly. For example, if an application is compiled either as a Unicode executable or an 8-bit executable, the code might be:
#Define Unicode

#IfDef Unicode
   // perform actions here to compile a Unicode
   // version of a program
#Else
   // perform actions here to compile an 8-bit
   // version of a program
#EndIf
JSON Escaped Backslash Restriction
A macro should not attempt to accept a JSON string containing the \" escape convention. A macro value or argument cannot use the JSON \" escape sequence for a literal backslash. This escape sequence is not permitted in the body of a macro or in the formal arguments passed to a macro expansion. As an alternative, the \" escape can be changed to \u0022. This alternative works for JSON syntax strings used as both key names and element values. In the case where the JSON string containing a literal backslash is used as an element value of a JSON array or a JSON object, you can alternatively replace the JSON string containing \" with an ObjectScript string expression enclosed in parentheses that evaluates to the same string value.
#Dim
The #Dim preprocessor directive specifies the intended type of a variable. It has the form:
#Dim VariableName As DataTypeName
#Dim VariableName As DataTypeName = InitialValue
#Dim VariableName As List Of DataTypeName
#Dim VariableName As Array Of DataTypeName
where:
#Else
The #Else preprocessor directive specifies the beginning of the fall-through case in a set of preprocessor conditions. It can follow #IfDef, #If, or #ElseIf. It is followed by #EndIf. It has the form:
#Else
  // subsequent indented lines for specified actions
#EndIf
The #Else directive keyword should appear on a line by itself. Anything following #Else on the same line is considered a comment and is not parsed.
For an example of #Else with #If, see that directive; for an example with #EndIf, see that directive.
Note:
If #Else appears in method code and has an argument other than a literal value of 0 or 1, the compiler generates code in subclasses (rather than invoking the method in the superclass). To avoid generating this code, test conditions for a value of 0 or 1, which results in simpler code and optimizes performance.
#ElseIf
The #ElseIf preprocessor directive specifies the beginning of a secondary case in a set of preprocessor conditions that begin with #If. Hence, it can follow #If or another #ElseIf. It is followed by another #ElseIf, #Else or #EndIf. (The #ElseIf directive is not for use with #IfDef or #IfNDef.) It has the form:
#ElseIf <expression>
  // subsequent indented lines for specified actions

  // next preprocessor directive
where <expression> is a valid ObjectScript expression. If <expression> evaluates to a non-zero value, it is true.
Any number of spaces may separate #ElseIf and <expression>. However, no spaces are permitted within <expression>. Anything following <expression> on the same line is considered a comment and is not parsed.
For an example, see #If.
Note:
#ElseIf has an alternate name of #ElIf. The two names behave identically.
#EndIf
The #EndIf preprocessor directive concludes a set of preprocessor conditions. It can follow #IfDef, #IfUnDef, #If, #ElseIf, and #Else. It has the form:
  // #IfDef, #If, or #Else specifying the beginning of a condition
  // subsequent indented lines for specified actions
#EndIf
The #EndIf directive keyword should appear on a line by itself. Anything following #EndIf on the same line is considered a comment and is not parsed.
For an example, see #If.
#Execute
The #Execute preprocessor directive executes a line of ObjectScript at compile time. It has the form:
#Execute <ObjectScript code>
where the content that follows #Execute is valid ObjectScript code. This code can refer to any variable or property that has a value at compile time; it can also invoke any method or routine that is available at compile time. ObjectScript commands and functions are always available to be invoked.
#Execute does not return any value indicating if the code has successfully run or not. Application code is responsible for checking a status code or other information of this kind; this can use additional #Execute directives or other code.
Note:
There may be unexpected results if you use #Execute with local variables. Reasons for this include:
For example, you can determine the day of the week at compile time and save it using the following code:
#Execute KILL ^DayOfWeek
#Execute SET ^DayOfWeek = $ZDate($H,12)

 WRITE "Today is ",^DayOfWeek,".",!
where the ^DayOfWeek global is updated each time compilation occurs.
#If
The #If preprocessor directive begins a block of conditional text. It takes an ObjectScript expression as an argument, tests the truth value of the argument, and compiles a block of code if the truth value of its argument is true. The block of code concludes with a #Else, #ElseIf, or #EndIf directive.
#If <expression>
  // subsequent indented lines for specified actions

  // next preprocessor directive
where <expression> is a valid ObjectScript expression. If <expression> evaluates to a non-zero value, it is true.
For example:
 KILL ^MyColor, ^MyNumber
#Define ColorDay $ZDate($H,12)
#If $$$ColorDay="Monday"
 SET ^MyColor = "Red"
 SET ^MyNumber = 1
#ElseIf $$$ColorDay="Tuesday"
 SET ^MyColor = "Orange"
 SET ^MyNumber = 2
#ElseIf $$$ColorDay="Wednesday"
 SET ^MyColor = "Yellow"
 SET ^MyNumber = 3
#ElseIf $$$ColorDay="Thursday"
 SET ^MyColor = "Green"
 SET ^MyNumber = 4
#ElseIf $$$ColorDay="Friday"
 SET ^MyColor = "Blue"
 SET ^MyNumber = 5
#Else
 SET ^MyColor = "Purple"
 SET ^MyNumber = -1
#EndIf
 WRITE ^MyColor, ", ", ^MyNumber
This code sets the value of the ColorDay macro to the name of the day at compile time. The conditional statement that begins with #If then uses the value of ColorDay to determine how to set the value of the ^MyColor variable. This code has multiple conditions that can apply to ColorDay — one for each weekday after Monday; the code uses the #ElseIf directive to check these. The fall-through case is the code that follow the #Else directive. The #EndIf closes the conditional.
Any number of spaces may separate #If and <expression>. However, no spaces are permitted within <expression>. Anything following <expression> on the same line is considered a comment and is not parsed.
Note:
If #If appears in method code and has an argument other than a literal value of 0 or 1, the compiler generates code in subclasses (rather than invoking the method in the superclass). To avoid generating this code, test conditions for a value of 0 or 1, which results in simpler code and optimizes performance.
#IfDef
The #IfDef preprocessor directive marks the beginning of a block of conditional code where execution depends on a macro having been defined. It has the form:
#IfDef macro-name
where macro-name appears without any leading “$$$” characters. Anything following macro-name on the same line is considered a comment and is not parsed.
Execution of the code is contingent on the macro having been defined. Execution continues until reaching a #Else directive or a closing #EndIf directive.
#IfDef checks only if a macro has been defined, not what its value is. Hence, if a macro exists and has a value of 0 (zero), #IfDef still executes the conditional code (since the macro does exist).
Also, since #IfDef checks only the existence of a macro, there is only one alternate case (if the macro is not defined), which the #Else directive handles. The #ElseIf directive is not for use with #IfDef.
For example, the following provides a simple binary switch based on a macro’s existence:
#Define Heads
 
#IfDef Heads
  WRITE "The coin landed heads up.",!
#Else
  WRITE "The coin landed tails up.",!
#EndIf
 
#IfNDef
The #IfNDef preprocessor directive marks the beginning of a block of conditional code where execution depends on a macro having not been defined. It has the form:
#IfNDef macro-name
where macro-name appears without any leading “$$$” characters. Anything following macro-name on the same line is considered a comment and is not parsed.
Execution of the code is contingent on the macro having not been defined. Execution continues until reaching a #Else directive or a closing #EndIf directive. The #ElseIf directive is not for use with #IfNDef.
Note:
#IfNDef has an alternate name of #IfUnDef. The two names behave identically.
For example, the following provides a simple binary switch based on a macro not having been defined:
#Define Multicolor 256
 
#IfNDef Multicolor
  SET NumberOfColors = 2
#Else
  SET NumberOfColors = $$$Multicolor
#EndIf
  WRITE "There are ",NumberOfColors," colors in use.",!
 
#Import
The #Import preprocessor directive specifies one or more schema names to search to supply the schema name for an unqualified table name in an Embedded SQL query. You can specify a single schema name, or a comma-separated list of schema names. All schemas must be in the current namespace. This is shown in the following example, which locates the Sample.Person table:
#Import Voters,Sample
  &sql(SELECT Name,DOB INTO :n,:date FROM Person)
  WRITE "name: ",n," birthdate: ",date,!
  WRITE "SQLCODE=",SQLCODE
 
Specifying an #Import directive prevents automatic searching of the system-wide default schema. If you wish to search the system-wide default, you must specify it by name in the #Import list of schema names.
Both #Import and #SQLCompile Path specify one or more schema names used to resolve an unqualified table name. Some of the differences between these two directives are as follows:
#Import directives are additive. Specifying a second #Import does not inactivate the list of schema names specified in a prior #Import. Specifying an #SQLCompile Path directive overwrites the path specified in a prior #SQLCompile Path directive; it does not overwrite schema names specified in prior #Import directives.
#Import detects ambiguous table names. #Import searches all specified schemas; #SQLCompile Path searches the specified list of schemas in left-to-right order until it finds the first match. Therefore, #Import can detect ambiguous table names; #SQLCompile Path cannot. For example, #Import must find exactly one occurrence of Person in Voters and Sample; if it finds more than one occurrence of this table name an SQLCODE -43 error occurs: “Table 'PERSON' is ambiguous within schemas”. If the Person table is not found in either Voters or Sample, an SQLCODE -30 error occurs.
Caché ignores non-existent schema names in #Import directives. Caché ignores duplicate schema names in #Import directives.
If the table name is already qualified, the #Import directives do not apply. For example:
  #Import Voters
  #Import Bloggers
  &sql(SELECT Name,DOB INTO :n,:date FROM Sample.Person)
  WRITE "name: ",n," birthdate: ",date,!
  WRITE "SQLCODE=",SQLCODE
 
In this case, Caché searches the Sample schema for the Person table. It does not search the Voters or Bloggers schemas.
If you specify an #Import directive with one or more schema names, Caché does not search the system-wide default schema; if it does not find the table in the specified schemas, it generates an SQLCODE -30 error. If no #Import directive is specified, Caché searches the system-wide default schema.
#Import has no effect on SQL DDL statements. If you create an unqualified table or view, it will be created in the system-wide default schema, as defined in the Management Portal System Administration, Configuration, SQL and Object Settings, General SQL Settings ([Home] > [Configuration] > [General SQL Settings]).
Compare with the #SQLCompile Path preprocessor directive.
#Include
The #Include preprocessor directive loads a specified file name that contains preprocessor directives. It has the form:
#Include <filename>
where filename is the name of the include file, not including the .inc suffix. Include files are typically located in the same directory as the file calling them. Their names are case sensitive.
For example, suppose there is an OS.inc header file that contains macros
#Include OS
 
#IfDef Windows
  WRITE "The operating system is not case-sensitive.",!
#Else
  WRITE "The operating system is case-sensitive.",!
#EndIf
Note:
When using #Include in stored procedure code, it must be preceded by a colon character “:”, such as:
CREATE PROCEDURE SPxx() Language OBJECTSCRIPT {
 :#Include %occConstant
     SET x=##Lit($$$NULLOREF)
} 
When including files at the beginning of a class, the directive does not include the pound sign. Hence, for a single file, it is:
Include MyMacros
For multiple files, it is:
Include (MyMacros, YourMacros)
The content of OS.inc might include one of the following lines:
 #Define Windows
 #Define UNIX
#NoShow
The #NoShow preprocessor directive ends a comment section that is part of an include file. It has the form:
#NoShow
where #NoShow follows a #Show directive. It is strongly recommended that every #Show have a corresponding #NoShow, even when the comment section continues to the end of the file. For an example, see the entry for #Show.
#Show
The #Show preprocessor directive begins a comment section that is part of an include file. By default, comments in an include file do not appear within the calling code. Hence, include file comments outside the #Show#NoShow bracket do not appear in the referencing code.
The directive has the form:
#Show
It is strongly recommended that every #Show have a corresponding #NoShow, even when the comment section continues to the end of the file.
In the following example, the file OS.inc (from the #Include example) includes the following comments:
#Show
  // If compilation fails, check the file 
  // OS-errors.log for the statement "No valid OS."
#NoShow
  // Valid values for the operating system are 
  // Windows or UNIX (and are case-sensitive).
where the first two lines of comments (starting with “If compilation fails...”) appear in the code that includes the include file and the second two lines of comments (starting with “Valid values...”) appear only in the include file itself.
#SQLCompile Audit
The #SQLCompile Audit preprocessor directive is a boolean that specifies whether any subsequent Embedded SQL statements should be audited. It has the form:
#SQLCompile Audit=value
where value is either ON or OFF.
For this macro preprocessor directive to have any effect, the %System/%SQL/EmbeddedStatement system audit event must be enabled. By default, this system audit event is not enabled.
#SQLCompile Mode
The #SQLCompile Mode preprocessor directive specifies the compilation mode for any subsequent Embedded SQL statements. It has the form:
#SQLCompile Mode=value
where value is one of the following:
Deferred mode and Embedded mode statements are otherwise identical. Both Deferred mode statements and Embedded mode statements are static. That is, they cannot be dynamically assembled at runtime and submitted for processing. If dynamic code is required, use Dynamic SQL, as described in Using Caché SQL.
Deferred mode can be used for INSERT, UPDATE, and DELETE operations, and SELECT statements that return a single row of data. Deferred SQL cannot be used for multi-row SELECT statements that declare a cursor and fetch rows of data; attempting to do so generates a #5663 compilation error. Like Embedded SQL, Deferred SQL does not check privileges.
In Deferred mode, SQL can refer to tables, user-defined functions, and other entities that do not yet exist at compile time.
If an embedded SQL statement contains an invalid SQL statement (for example, an SQL syntax error), the Macro Preprocessor generates the code "** SQL Statement Failed to Compile **" and continues to compile ObjectScript code. Thus when compiling a class with a method that contains invalid embedded SQL, the SQL error is reported, but the method is generated. The invalid SQL causes an error when this method is run.
Embedded SQL provides optimal SQL performance, and should be used whenever possible. Deferred SQL is generally more efficient than Dynamic SQL. Deferred SQL may be needed when converting Transact-SQL or Informix SPL stored procedures to Caché. The Caché conversion tools for stored procedures support this feature.
For further details, refer to the Embedded SQL chapter of Using Caché SQL.
#SQLCompile Path
The #SQLCompile Path preprocessor directive specifies the schema search path for any subsequent Embedded SQL query statements. It has the form:
#SQLCompile Path=schema1[,schema2[,...]]
where schema is a schema name used to look up an unqualified SQL table name or CALL procedure name in the current namespace. You can specify one schema name or a comma-separated list of schema names. Schemas are searched in the order specified. Because schemas are searched in the specified order, there is no detection of ambiguous table names. The #Import preprocessor directive also supplies a schema name to an unqualified SQL table name from a list of schema names; #Import does detect ambiguous table names.
#SQLCompile Path can be used to resolve unqualified table names for SQL queries, and for INSERT, UPDATE, and DELETE operations. #SQLCompile Path can also be used to resolve unqualified procedure names in SQL CALL statements. It cannot be used for unqualified table names in DDL statements, such as CREATE TABLE and the other CREATE, ALTER, and DROP statements.
The following example resolves the unqualified table name Person to the Sample.Person table. It first searches the Cinema schema (which does not contain a table named Person), then searches the Sample schema:
#SQLCompile Path=Cinema,Sample
  &sql(SELECT Name,Age
       INTO :a,:b
       FROM Person)
  WRITE "Name is: ",a,!
  WRITE "Age is: ",b
 
In addition to specifying schema names as search path items, you can specify the following keywords:
If you specify a schema search path, the SQL query processor uses the schema search path first when attempting to resolve an unqualified name. If it does not find the specified table or procedure, it then looks in the default schema(s) that are provided via #Import, or the configured system-wide Default SQL Schema Name. If it does not find the specified table in any of these places, it generates an SQLCODE -30 error.
#SQLCompile Path can be used with #SQLCompile Mode values Embedded or Deferred. For further details, refer to the Embedded SQL chapter of Using Caché SQL.
The scope of the schema search path is the routine or method it is defined in. If a schema path is specified in a class method, it only applies to that class method, and not to other methods in the class. If it is specified in a .MAC routine, it applies from that point forward in the routine until another #SQLCompile Path directive is found, or the end of the routine is reached.
Schemas are defined for the current namespace.
Compare with the #Import preprocessor directive.
#SQLCompile Select
The #SQLCompile Select preprocessor directive specifies the data format mode for any subsequent Embedded SQL statements. It has the form:
#SQLCompile Select=value
where value is one of the following:
The value of this macro determines the Embedded SQL output data format for SELECT output host variables, and the required input data format for Embedded SQL INSERT, UPDATE, and SELECT input host variables. For details, refer to The Macro Preprocessor in the “Using Embedded SQL” chapter of Using Caché SQL.
The following Embedded SQL examples use the different compile modes to return three fields from the Sample.Person table, which are Name (a string field), DOB (a date field), and Home (a list field):
#SQLCOMPILE SELECT=Logical
  &sql(SELECT Name,DOB,Home
       INTO :n,:d,:h
       FROM Sample.Person)
  WRITE "name is: ",n,!
  WRITE "birthdate is: ",d,!
  WRITE "home is: ",h
 
#SQLCOMPILE SELECT=Display
  &sql(SELECT Name,DOB,Home
       INTO :n,:d,:h
       FROM Sample.Person)
  WRITE "name is: ",n,!
  WRITE "birthdate is: ",d,!
  WRITE "home is: ",h
 
#SQLCOMPILE SELECT=ODBC
  &sql(SELECT Name,DOB,Home
       INTO :n,:d,:h
       FROM Sample.Person)
  WRITE "name is: ",n,!
  WRITE "birthdate is: ",d,!
  WRITE "home is: ",h
 
#SQLCOMPILE SELECT=Runtime
  &sql(SELECT Name,DOB,Home
       INTO :n,:d,:h
       FROM Sample.Person)
  WRITE "name is: ",n,!
  WRITE "birthdate is: ",d,!
  WRITE "home is: ",h
 
#UnDef
The #UnDef preprocessor directive removes the definition for an already-defined macro. It has the form:
#UnDef macro-name
where macro-name is a macro that has already been defined.
#UnDef follows an invocation of #Define or #Def1Arg. It works in conjunction with #IfDef and its associated preprocessor directives (#Else, #EndIf, and #IfNDef).
The following example demonstrates code that is conditional on a macro being defined and then undefined.
#Define TheSpecialPart
  
#IfDef TheSpecialPart
  WRITE "We're in the special part of the program.",!
#EndIf

 //
 // code here...
 //

#UnDef TheSpecialPart
 
#IfDef TheSpecialPart
  WRITE "We're in the special part of the program.",!
#Else
  WRITE "We're no longer in the special part of the program.",!
#EndIf
 
#IfNDef TheSpecialPart
  WRITE "We're still outside the special part of the program.",!
#Else
  WRITE "We're back inside the special part of the program.",!
#EndIf
where the .int code for this is:
  WRITE "We're in the special part of the program.",!
  //
  // code here...
  //
  WRITE "We're no longer in the special part of the program.",!
  WRITE "We're still outside the special part of the program.",!
##;
The ##; preprocessor directive creates a single line comment that does not appear in .int code. The comment appears only in either .mac code or in an include file. The ##; may be used anywhere in an ObjectScript code line or an SQL code line. The comment continues for the remainder of the current line. It has the form:
   WRITE "Invoking Embedded SQL",!  ##; Comment One
   &sql(SELECT Name INTO :a ##; Comment Two
    FROM Sample.Person)
##; Comment Three
   WRITE "The SQL error code is ",SQLCODE,!
   WRITE "The name is ",a
 
where the comment follows the “##;”.
##; is evaluated before evaluation of Embedded HTML or Embedded JavaScript.
When ##; appears in the first column of the line, it is functionally identical to the #; preprocessor directive.
A ##; comment indicator can appear on the same line as a #Define, #Def1Arg, or ##Continue preprocessor directive.
##Continue
The ##Continue preprocessor directive continues a macro definition on the next line, to support multiline macro definitions. It appears at the end of a line of a macro definition to signal the continuation, in the form:
#Define <beginning of macro definition> ##Continue
     <continuation of macro definition>
A macro definition can use multiple ##Continue directives.
For example,
#Define Multiline(%a,%b,%c) ##Continue
    SET line1="%a" ##Continue
    SET line2="%b" ##Continue
    SET line3="%c"
    
 $$$Multiline(Scarecrow,Tin Woodman,Lion)
 WRITE "Here is line 1: ",line1,!
 WRITE "Here is line 2: ",line2,!
 WRITE "Here is line 3: ",line3,!      
 
where the macro being defined accepts three arguments. The code of the macro then sets each of three local variables equal to the values passed in as arguments to the macro. The WRITE commands display these values.
A ##Continue line can include a ##; comment.
##Expression
The ##Expression preprocessor directive evaluates an ObjectScript expression at compile time. It has the form:
##Expression(content)
where content is valid ObjectScript code that does not include any quoted strings or any preprocessor directives (with the exception of a nested ##Expression, as described below).
The preprocessor evaluates the value of the directive’s argument at compile time and replaces ##Expression(content) with the evaluation in the ObjectScript .int code. Variables must appear in quotation marks within ##Expression; otherwise, they are evaluated at compile time. ##Expression is evaluated before evaluation of Embedded HTML or Embedded JavaScript.
The following example shows some simple expressions:
#Define NumFunc ##Expression(1+2*3)
#Define StringFunc ##Expression("""This is"_" a concatenated string""")
  WRITE $$$NumFunc,!
  WRITE $$$StringFunc,!
 
The following example defines an expression containing the compile timestamp of the current routine:
#Define CompTS ##Expression("""Compiled: " _ $ZDATETIME($HOROLOG) _ """,!")
  WRITE $$$CompTS
 
where the argument of ##Expression is parsed in three parts, which are concatenated using the “_” operator:
The routine’s intermediate (.int) code would then include a line such as:
  WRITE "Compiled: 05/19/2014 07:49:30",! 
##Expression and Literal Strings
Parsing with ##Expression does not recognize literal strings; bracketing characters inside of quotes are not treated specially. For example, in the directive
#Define MyMacro ##Expression(^abc(")",1))
the quoted right parenthesis is treated as if it is a closing parenthesis for specifying the argument.
##Expression Nesting
Caché supports nested ##Expressions. You can define an ##Expression that contains macros that expand to other ##Expressions, as long as the expansion can be evaluated at the ObjectScript level (that is, it contains no preprocessor directives) and stored in an ObjectScript variable. With nested ##Expressions, the macros with the ##Expression expression are expanded first, then the nested ##Expression is expanded. You cannot nest other ## preprocessor functions within an ##Expression.
##Expression, Subclasses, and ##SafeExpression
When a method contains an ##Expression this is detected when the class is compiled. Because the compiler does not parse the content of the ##Expression, this ##Expression could generate different code in a subclass. To avoid this, Caché causes the compiler to regenerate the method code for each subclass. For example, ##Expression(%classname) inserts the current classname; when you compile a subclass, the code expects it will insert the subclass classname. Caché forces this method to be regenerated in the subclass to ensure that this occurs.
If you know that the code will never be different in a subclass, you can avoid regenerating the method for each subclass. To do this, substitute the ##SafeExpression preprocessor directive for ##Expression. These two preprocessor directives are otherwise identical.
How ##Expression Works
The argument to ##Expression is set into a value via the ObjectScript XECUTE command:
 SET value="Set value="_expression XECUTE value
where expression is an ObjectScript expression that determines the value of value and may not contain macros or a ##Expression directive.
However, the results of the XECUTE value may contain macros, another ##Expression, or both. The ObjectScript preprocessor further expands any of these, as in this example.
Suppose the content of routine A.mac includes:
#Define BB ##Expression(10_"_"_$$aa^B())
 SET CC = $$$BB
 QUIT
and routine B.mac includes:
aa()
 QUIT "##Expression(10+10+10)"
A.int then includes the following:
 SET CC = 10_30
 QUIT 
##Function
The ##Function preprocessor directive evaluates an ObjectScript function at compile time. It has the form
##Function(content)
where content is an ObjectScript function and can be user-defined. ##Function replaces ##Function(content) with the returned value from the function.
For example, suppose there is a function in the GetCurrentTime.mac file:
Tag1()
 KILL ^x
 SET ^x = """" _ $Horolog _ """"
 QUIT ^x
It is then possible to invoke this code in a separate routine, called ShowTimeStamps.mac, as follows:
Tag2
#Define CompiletimeTimeStamp ##function($$Tag1^GetCurrentTime())
#Define RuntimeTimeStamp $$Tag1^GetCurrentTime()
 SET x=$$$CompiletimeTimeStamp
 WRITE x,!
 SET y=$$$RuntimeTimeStamp
 WRITE y,! 
The output of this at the Terminal is something like:
USER>d ^ShowTimeStamps
60569,43570
"60569,53807"

USER>
where the first line of output is the value of $Horolog at compile time and the second line is the value of $Horolog at runtime. (The first line of output is not quoted and the second line is quoted because x substitutes a quoted string for its value, so there are no quotes displayed in the Terminal, while y prints the quoted string directly to the Terminal.)
Note:
It is the responsibility of the application programmer to make sure that the return value of the ##Function call makes both semantic and syntactic sense, given the context of the call.
##Lit
The ##Lit preprocessor directive preserves the content of its argument in literal form:
##Lit(content)
where content is a string that is valid ObjectScript expression. The ##Lit preprocessor directive ensures that the string it receives is not evaluated, but that it is treated as literal text.
For example, the following code:
 #Define Macro1 "Row 1 Value"
 #Define Macro2 "Row 2 Value"
   ##Lit(;;) Column 1 Header ##Lit(;) Column 2 Header
   ##Lit(;;) Row 1 Column 1  ##Lit(;) $$$Macro1
   ##Lit(;;) Row 2 Column 1  ##Lit(;) $$$Macro2 
creates a set of lines that form a table in .int code:
 ;; Column 1 Header ; Column 2 Header
 ;; Row 1 Column 1  ; "Row 1 Value"
 ;; Row 2 Column 1  ; "Row 2 Value"  
By using the ##Lit directive, macros are evaluated and are delimited by the semicolons in the .int code
##Quote
The ##Quote preprocessor directive takes a single argument and returns that argument quoted. If the argument already contains quote characters it escapes these quote characters by doubling them. It has the form:
##Quote(value)
where value is a literal that is converted to a quoted string. In value a parenthesis character or a quote character must be paired. For example, the following is a valid value:
#Define qtest ##Quote(He said "Yes" after much debate)
   ZZWRITE $$$qtest
 
it returns "He said ""Yes"" after much debate". ##Quote(This (") is a quote character) is not a valid value.
Parentheses within a value string must be paired. The following is a valid value:
#Define qtest2 ##Quote(After (a lot of) debate)
   ZZWRITE $$$qtest2
 
The following example shows the use of ##Quote:
#Define AssertEquals(%e1,%e2) DO AssertEquals(%e1,%e2,##Quote(%e1)_" == "_##Quote(%e2))
Main ;
  SET a="abstract"
  WRITE "Test 1:",!
  $$$AssertEquals(a,"abstract")
  WRITE "Test 2:",!
  $$$AssertEquals(a_"","abstract")
  WRITE "Test 3:",!
  $$$AssertEquals("abstract","abstract")
  WRITE "All done"
  QUIT
AssertEquals(e1,e2,desc) ;
  WRITE desc_" is "_$SELECT(e1=e2:"true",1:"false"),!
  QUIT
 
##SQL
The ##SQL preprocessor directive invokes a specified SQL statement at compile time. It has the form:
##SQL(SQL-statement)
where SQL-statement is a valid SQL statement. The ##SQL preprocessor directive is analogous to the &SQL directive — ##SQL() invokes the statement at compile time, which &SQL() does so at runtime.
If a ##SQL directive contains invalid SQL (such as a syntax error) or refers to a nonexistent table or column, then the macro preprocessor generates a compilation error.
For example, the following code runs a query at first at compile time and then again at runtime:
 ##sql(SELECT COUNT(*) INTO :count1 FROM Sample.Person)
 
 &sql(SELECT COUNT(*) INTO :count2 FROM Sample.Person)
 
 WRITE "Number of instances of Sample.Person at compile time: ",count1,!
 WRITE "Number of instances of Sample.Person at runtime:      ",count2,!
 
##Unique
The ##Unique preprocessor directive creates a new, unique local variable within a macro definition for use at compile time or runtime. This directive is available for use only as part of #Define or #Def1Arg call. It has the form:
 ##Unique(new)
 ##Unique(old)
where new specifies the creation of a new, unique variable and old specifies a reference to that same variable.
The variable created by SET ##Unique(new) is a local variable with the name %mmmu1, subsequent SET ##Unique(new) operations create local variables with the names %mmmu2, %mmmu3, and so forth. These local variables are subject to the same scoping rules as all % local variables; % variables are always public variables. Like all local variables, they can be displayed using ZWRITE and can be killed using an argumentless KILL.
User code can refer to the ##Unique(old) variable just as it can refer to any other ObjectScript variable. The ##Unique(old) syntax can be used an indefinite number of times to refer to the created variable.
Subsequent calls to ##Unique(new) create a new variable; after calling ##Unique(new) again, subsequent calls to ##Unique(old) refer to the subsequently created variable.
For example, the following code uses ##Unique(new) and ##Unique(old) to swap values between two variables:
 #Define Switch(%a,%b) SET ##Unique(new)=%a, %a=%b, %b=##Unique(old)
 READ "First variable value? ",first,!
 READ "Second variable value? ",second,!
 $$$Switch(first,second)
 WRITE "The first value is now ",first," and the second is now ",second,!
To maintain uniqueness of these variables:
Using System-supplied Macros
This section describes topics related to some of the predefined macros available with Caché. Its topics are:
Making System-supplied Macros Accessible
These macros are available to all subclasses of %RegisteredObject. To make these available within a routine or a class that does not extend %RegisteredObject, include the appropriate file:
See each macro below for which include file it requires.
The syntax for such statements is:
#Include %occStatus
The names of these include files are case-sensitive. For more details on using externally defined macros, see the section Referring to External Macros (Include Files).”
System-supplied Macro Reference
Macro names are case-sensitive. Among the macros supplied with Caché are:
ADDSC(sc1, sc2)
The ADDSC macro appends a %Status code (sc2) to an existing %Status code (sc1). This macro requires %occStatus.inc.
EMBEDSC(sc1, sc2)
The EMBEDSC macro embeds a %Status code (sc2) within an existing %Status code (sc1). This macro requires %occStatus.inc.
ERROR(errorcode, arg1, arg2, ...)
The ERROR macro creates a %Status object using an object error code (errorcode) the associated text of which may accept some number of arguments of the form %1, %2, and so on. ERROR then replaces these arguments with the macro arguments that follow errorcode (arg1, arg2, and so on) based on the order of these additional arguments. This macro requires %occStatus.inc.
For a list of system-defined error codes, see General Error Messages in the Caché Error Reference.
FormatMessage(language,domain,id,default,arg1,arg2,...)
The FormatMessage macro enables you to retrieve text from the Message Dictionary, and substitute text for message arguments, all in the same macro call. It returns a %String.
Argument Description
language An RFC1766 language code. In a CSP context, you can specify %response.Language to use the default locale.
domain The message domain. In a CSP context, you may specify %response.Domain
id The message ID.
default The string to use if the message identified by language, domain, and id is not found.
arg1, arg2, and so on Substitution text for the message arguments. All of these are optional, so you can use $$$FormatMessage even if the message has no arguments.
For information on the Message Dictionary, see Localizing Text in a CSP Application in Using Caché Server Pages (CSP).
This macro requires %occMessages.inc.
Also see the FormatMessage() instance method of %Library.MessageDictionary.
FormatText(text, arg1, arg2, ...)
The FormatText macro accepts an input text message (text) which may contain arguments of the form %1, %2, etc. FormatText then replaces these arguments with the macro arguments that follow the text argument (arg1, arg2, and so on) based on the order of these additional arguments. It then returns the resulting string. This macro requires %occMessages.inc.
FormatTextHTML(text, arg1, arg2, ...)
The FormatTextHTML macro accepts an input text message (text) which may contain arguments of the form %1, %2, etc. FormatTextHTML then replaces these arguments with the macro arguments that follow the text argument (arg1, arg2, and so on) based on the order of these additional arguments; the macro then applies HTML escaping. It then returns the resulting string. This macro requires %occMessages.inc.
FormatTextJS(text, arg1, arg2, ...)
The FormatTextJS macro accepts an input text message (text) which may contain arguments of the form %1, %2, etc. FormatTextJS then replaces these arguments with the macro arguments that follow the text argument (arg1, arg2, and so on) based on the order of these additional arguments; the macro then applies JavaScript escaping. It then returns the resulting string. This macro requires %occMessages.inc.
GETERRORCODE(sc)
The GETERRORCODE macro returns the error code value from the supplied %Status code (sc). This macro requires %occStatus.inc.
ISERR(sc)
The ISERR macro returns True if the supplied %Status code (sc) is an error code. Otherwise, it returns False. This macro requires %occStatus.inc.
ISOK(sc)
The ISOK macro returns True if the supplied %Status code (sc) is successful completion. Otherwise, it returns False. This macro requires %occStatus.inc.
OK
The OK macro creates a %Status code for successful completion. This macro requires %occStatus.inc.
Text(text, domain, language)
The Text macro is used for localization. It generates a new message at compile time and generates code to retrieve the message at runtime. This macro requires %occMessages.inc.
For more information on this macro, see the section $$$Text Macros at Compile Time and Runtime in the chapter “Localizing Text in a CSP Application” of Using Caché Server Pages (CSP).
TextHTML(text, domain, language)
The TextHTML macro is used for localization. It performs the same processing as the Text macro; it then additionally applies HTML escaping. It then returns the resulting string. This macro requires %occMessages.inc.
For more information on this macro, see the section $$$Text Macros at Compile Time and Runtime in the chapter “Localizing Text in a CSP Application” of Using Caché Server Pages (CSP).
TextJS(text, domain, language)
The TextJS macro is used for localization. It performs the same processing as the Text macro; it then additionally applies JavaScript escaping. It then returns the resulting string. This macro requires %occMessages.inc.
For more information on this macro, see the section $$$Text Macros at Compile Time and Runtime in the chapter “Localizing Text in a CSP Application” of Using Caché Server Pages (CSP).
ThrowOnError(sc)
The ThrowOnError macro evaluates the specified %Status code (sc). If sc represents an error status, ThrowOnError performs a THROW operation to throw an exception of type %Exception.StatusException to an exception handler. This macro requires %occStatus.inc. For further details, refer to The TRY-CATCH Mechanism in the “Error Processing” chapter of this guide.
THROWONERROR(sc, expr)
The THROWONERROR macro evaluates an expression (expr), where the expression’s value is assumed to be a %Status code; the macro stores the %Status code in the variable passed as sc. If the %Status code is an error, THROWONERROR performs a THROW operation to throw an exception of type %Exception.StatusException to an exception handler. This macro requires %occStatus.inc.
ThrowStatus(sc)
The ThrowStatus macro uses the specified %Status code (sc) to perform a THROW operation to throw an exception of type %Exception.StatusException to an exception handler. This macro requires %occStatus.inc. For further details, refer to The TRY-CATCH Mechanism in the “Error Processing” chapter of this guide.