Skip to main content

ObjectScript Macros and the Macro Preprocessor

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.

Preprocessor directives are included in the .MAC version of code. When you compile .MAC code, the ObjectScript compiler performs preprocessing and generates .INT (intermediate, readable ObjectScript) code and .OBJ (executable object) code.

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.

The following globals return MAC code information. Use ZWRITE to display these globals and their subscripts:

  • ^rINDEX(routinename,"MAC") contains the timestamp when the MAC code was last saved after being modified, and the character count for this MAC code file. The character count including comments and blank lines. The timestamp when the MAC code was last saved, when it was compiled, and information about #include files used are recorded in the ^ROUTINE global for the .INT file. For further details about .INT code, refer to the ZLOAD command.

  • ^rMAC(routinename) contains a subscript node for each line of code in the MAC routine, as well as ^rMAC(routinename,0,0) containing the line count, ^rMAC(routinename,0) containing the timestamp when it was last saved, and ^rMAC(routinename,0,"SIZE") containing the character count.

  • ^rMACSAVE(routinename) contains the history of the MAC routine. It contains the same information as ^rMAC(routinename) for the past five saved versions of the MAC routine. It does not contain information about the current MAC version.

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!"

(You can use ##continue to continue a #define directive to the next line.)

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:

  • String substitutions, as demonstrated above.

  • Numeric substitutions:

    #define NumberMacro 22
    #define 25M ##expression(25*1000*1000)

    As is typical in ObjectScript, the definition of the numeric macro does not require quoting the number, while the string must be quoted in the string macro’s definition.

  • Variable substitutions:

    #define VariableMacro Variable

    Here, the macro name substitutes for the name of a variable that is already defined. If the variable is not defined, there is an <UNDEFINED> error.

  • Command and argument invocations:

    #define CommandArgumentMacro(%Arg) WRITE %Arg,!

    Macro argument names must start with the “%” character, such as the %Arg argument above. Here, the macro invokes the WRITE command, which uses the %Arg argument.

  • Use of functions, expressions, and operators:

    #define FunctionExpressionOperatorMacro ($ZDate(+$Horolog))

    Here, the macro as a whole is an expression whose value is the return value of the $ZDate function. $ZDate operates on the expression that results from the operation of the “+” operator on the system time, which the system variable $Horolog holds. As shown above, it is a good idea to enclose expressions in parentheses so that they minimize their interactions with the statements in which they are used.

  • References to other macros:

    #define ReferenceOtherMacroMacro WRITE $$$ReferencedMacro

    Here, the macro uses the expression value of another macro as an argument to the WRITE command.

    Note:

    If one macro refers to another, the referenced macro must appear on a line of code that is compiled before the referencing macro.

Macro Naming Conventions

  • The first character must be an alphanumeric character or the percent character (%).

  • The second and subsequent characters must be alphanumeric characters. A macro name may not include spaces, underscores, hyphens, or other symbol characters.

  • Macro names are case-sensitive.

  • Macro names can be up to 500 characters in length.

  • Macro names can contain Japanese ZENKAKU characters and Japanese HANKAKU Kana characters. For further details, refer to the “Pattern Codes” table in the Pattern Matching section of the “Operators and Expressions” chapter of this book.

  • Macro names should not begin with ISC, because ISCname.inc files are reserved for system use.

Macro Whitespace Conventions

  • By convention, a macro directive is not indented and appears in column 1. However, a macro directive may be indented.

  • One or more spaces may follow a macro directive. Within a macro, any number of spaces may appear between macro directive, macro name, and macro value.

  • A macro directive is a single-line statement. The directive, macro name, and macro value must all appear on the same line. You can use ##continue to continue a macro directive to the next line.

  • #if and #elseIf directives take a test expression. This test expression may not contain any spaces.

  • An #if expression, an #elseIf expression, the #else directive, and the #endif directive all appear on their own line. Anything following one of these directives on the same line is considered a comment and is not parsed.

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.

You can invoke a macro to substitute a value in contexts where you cannot supply a variable value. For example:

#define StringMacro "Hello",!,"World!"
   WRITE $$$StringMacro
#define myclass "Sample.Person"
  SET x=##class($$$myclass).%New()

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, which is not case-sensitive.

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. For example, #include is treated the same as #INCLUDE (and other case variations).

#;

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 “#;”.

#; makes an entire line a comment. Compare with the ##; preprocessor directive, which makes the rest of the current line a comment.

#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

  • Macro is the name of the macro being defined, which accepts only one argument. A valid macro name is an alphanumeric string.

  • %Arg is the name of the argument for the macro. The name of the variable specifying the macro’s argument must begin with a percent sign.

  • Value is the macro’s value, which includes the use of value of %Arg specified at runtime

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

  • Macro is the name of the macro being defined. A valid macro name is an alphanumeric string.

  • Args (optional) are the one or more arguments that it accepts. These are of the form (arg1, arg2, ...). The name of each variable specifying a macro argument must begin with a percent sign. Argument values cannot include commas.

  • Value (optional) is the value being assigned to the macro, where the value can be any valid ObjectScript code. This can be something as simple as a literal or as complex as an expression.

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.

You can use ##continue to continue a #define directive to the next line. You can use ##; to append a comment to a #define line. But you cannot use ##continue and ##; on the same line.

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:

  • A string

  • A numeric value

  • A class property

  • The invoking of a method, function, or other code

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 data type of a local variable and can optionally specify its initial value. #dim is provided as an optional convenience for documenting code. ObjectScript is a “typeless” language; it does not declare a data type for a variable, nor does it enforce the data typing specified in #dim. It has the form:

#dim VariableName As DataTypeName
#dim VariableName As DataType = InitialValue
#dim VariableName As List Of DataType
#dim VariableName As Array Of DataType

where:

  • VariableName is the variable being defined, or a comma-separated list of variables.

  • DataType is the type of VariableName. Specifying a data type is optional, you can omit As DataType and just specify =InitialValue.

  • InitialValue is a value optionally specified for VariableName. This is equivalent to SET VariableName=InitialValue. (This syntax is not available for lists or arrays.)

DataType is principally for use with Studio Assist. For example, you can use Studio Assist drop-down menus to select the package and class of a user-defined data type. For further details, see Variable Declaration in the “Variables” chapter of this manual.

InitialValue

  • If VariableName specifies a comma-separated list of data variables and DataType is omitted, all the variables are assigned the same initial value. For example:

    #dim a,b,c = ##class(Sample.Person).%New()

    This is equivalent to:

      SET (a,b,c) = ##class(Sample.Person).%New()

    which assigns the same OREF to each variable.

  • If VariableName specifies a comma-separated list of data variables, and DataType is a standard %Library data type, all the variables are assigned the same data type and initial value. For example:

    #dim d,e,f As %String = ##class(Sample.Person).%New()

    This is equivalent to:

      SET (d,e,f) = ##class(Sample.Person).%New()

    which assigns the same OREF to each variable.

  • If VariableName specifies a comma-separated list of object variables and DataType not a %Library data type, each variable is assigned a separate OREF. For example:

    #dim j,k,l As Sample.Person = ##class(Sample.Person).%New()

    assign separate OREFs to each variable.

  • If VariableName specifies a comma-separated list of variables and DataType is a Dynamic data type, each variable is assigned a separate OREF. For example:

    #dim m,n,o As %DynamicObject = {"name":"Fred"}
    #dim p,q,r As %DynamicArray = ["element1","element2"]

    assign separate OREFs to each variable.

#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:

  • A variable used at compile time may be out of scope at runtime.

  • With multiple routines or methods, the variable may not be available when referenced. This issue may be exacerbated by the fact that the application programmer does not control compilation order.

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 the schema search path for any subsequent Embedded SQL DML statements.

#import specifies one or more schema names to search to supply the schema name for an unqualified table, view, or stored procedure name. You can specify a single schema name, or a comma-separated list of schema names. Schemas are searched in the current namespace. This is shown in the following example, which locates the Employees.Person table:

#import Customers,Employees,Sales
  &sql(SELECT Name,DOB INTO :n,:date FROM Person)
  WRITE "name: ",n," birthdate: ",date,!
  WRITE "SQLCODE=",SQLCODE

All of the schemas specified in the #import directive are searched. The Person table must be found in exactly one of the schemas listed in #import. Because #import requires a match within the schema search path, the system-wide default schema is not used.

Dynamic SQL uses the %SchemaPath property to supply a schema search path to resolve unqualified 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 detects ambiguous table names. #import searches all specified schemas, detecting all matches. #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 Customers,Employees,Sales must find exactly one occurrence of Person in the Customers, Employees, and Sales schemas; if it finds more than one occurrence of this table name an SQLCODE -43 error occurs: “Table 'PERSON' is ambiguous within schemas”.

  • #import cannot take the system-wide default. If #import cannot find the Person table in any of its listed schemas, an SQLCODE -30 error occurs. If #sqlcompile path cannot find the Person table in any of its listed schemas, it checks the system-wide default schema.

  • #import directives are additive. If there are multiple #import directives, the schemas in all of the directives must resolve to exactly one match. 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; #sqlcompile path does not overwrite schema names specified in prior #import directives.

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.

  • #import is applied to SQL DML statements. It can be used to resolve unqualified table names and view names for SQL SELECT queries, and for INSERT, UPDATE, and DELETE operations. #import can also be used to resolve unqualified procedure names in SQL CALL statements.

  • #import is not applied to SQL DDL statements. It cannot be used to resolve unqualified table, view, and procedure names in data definition statements such as CREATE TABLE and the other CREATE, ALTER, and DROP statements. If you specify an unqualified name for a table, view, or stored procedure when creating, modifying, or deleting the definition of this item, Caché will ignore #import values and use the system-wide default schema.

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.

To list all of the system-supplied #include filenames, issue the following command:

  ZWRITE ^rINC("%occInclude",0)

To list the contents of one of these #include files, specify the desired include file. For example:

  ZWRITE ^rINC("%occStatus",0)

To list the #include files pre-processed when generating an INT routine, use the ^ROUTINE global. Note that these #include directives do not have to be referenced in the ObjectScript code:

  ZWRITE ^ROUTINE("myroutine",0,"INC")
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)

For example, suppose there is an OS.inc header file that contains macros:

 #define Windows
 #define UNIX
#include OS
 
#ifDef Windows
  WRITE "The operating system is not case-sensitive.",!
#else
  WRITE "The operating system is case-sensitive.",!
#endif

#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:

  • Embedded — Compiles ObjectScript code and embedded SQL code prior to runtime. This is the default.

  • Deferred — Compiles ObjectScript code, but defers compiling embedded SQL code until runtime. This enables you to compile a routine containing SQL that references a table that does not yet exist at compile time.

    Note:

    #sqlcompile mode=Deferred should not be confused with the similarly-name %SYSTEM.SQL.SetCompileModeDeferred() method, which is used for a completely different purpose.

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 DML statements. It has the form:

#sqlcompile path=schema1[,schema2[,...]]

where schema is a schema name used to look up an unqualified SQL table name, view name, or 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. Searching ends and the DML operation is performed when the first match occurs. If none of the schemas contain a match, the system-wide default schema is searched.

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, view, or procedure name from a list of schema names; #import does detect ambiguous names.

Caché ignores non-existent schema names in #sqlcompile path directives. Caché ignores duplicate schema names in #sqlcompile path directives.

  • #sqlcompile path is applied to SQL DML statements. It can be used to resolve unqualified table names and view names for SQL SELECT queries, and for INSERT, UPDATE, and DELETE operations. #sqlcompile path can also be used to resolve unqualified procedure names in SQL CALL statements.

  • #sqlcompile path is not applied to SQL DDL statements. It cannot be used to resolve unqualified table, view, and procedure names in data definition statements such as CREATE TABLE and the other CREATE, ALTER, and DROP statements. If you specify an unqualified name for a table, view, or stored procedure when creating, modifying, or deleting the definition of this item, Caché will ignore #sqlcompile path values and use the system-wide default schema.

Dynamic SQL uses the %SchemaPath property to supply a schema search path to resolve unqualified names.

The following example resolves the unqualified table name Person to the Sample.PersonOpens in a new tab 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:

  • CURRENT_PATH: specifies the current schema search path, as defined in a prior #sqlcompile path preprocessor directive. This is commonly used to add schemas to the beginning or end of an existing schema search path, as shown in the following example:

    #sqlcompile path=schema_A,schema_B,schema_C
    #sqlcompile path=CURRENT_PATH,schema_D
  • CURRENT_SCHEMA: specifies the current schema container class name. If #sqlcompile path is defined in a class method, the CURRENT_SCHEMA is the schema mapped to the current class package. If #sqlcompile path is defined in a .MAC routine, the CURRENT_SCHEMA is the configuration default schema.

    For example, if you define a class method in the class User.MyClass that specifies #sqlcompile path=CURRENT_SCHEMA, the CURRENT_SCHEMA will (by default) resolve to SQLUser, since SQLUser is the default schema name for the User package. This is useful when you have a superclass and subclass in different packages, and you define a method in the superclass that has an SQL query with an unqualified table name. Using CURRENT_SCHEMA, you can have the table name resolve to the superclass schema in the superclass and to the subclass schema in the subclass. Without the CURRENT_SCHEMA search path setting, the table name would resolve to the superclass schema in both classes.

    If #sqlcompile path=CURRENT_SCHEMA is used in a trigger, the schema container class name is used. For example, if class pkg1.myclass has a trigger than specifies #sqlcompile path=CURRENT_SCHEMA, and class pkg2.myclass extends pkg1.myclass, Caché resolves the non-qualified table names in the SQL statements in the trigger to the schema for package pkg2 when the pkg2.myclass class is compiled.

  • DEFAULT_SCHEMA specifies the system-wide default schema. This keyword enables you to search the system-wide default schema as a item within the schema search path, before searching other listed schemas. The system-wide default schema is always searched after searching the schema search path if all the schemas specified in the path have been searched without a match.

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 schema(s) that are provided via #import (if specified), or the configured system-wide default schema. 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:

  • Display — Formats data for screen and print.

  • Logical — Leaves data in its in-memory format.

  • ODBC — Formats data for presentation via ODBC or JDBC.

  • Runtime — Supports automatic conversion of input data values from a display format (DISPLAY or ODBC) to logical storage format based on the execution-time select mode value. The output values are converted to the current mode.

    You can get the execution-time select mode value using the GetSelectModeOpens in a new tab method of the %SYSTEM.SQLOpens in a new tab class. You can set the execution-time select mode value using the SetSelectModeOpens in a new tab method of the %SYSTEM.SQLOpens in a new tab class.

  • Text — Synonym for Display.

  • FDBMS — Allows Embedded SQL to format data the same as FDBMS.

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.PersonOpens in a new tab 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 makes the remaining part of the current line a comment that does not appear in .int code. The comment appears only in either .mac code or in an include file. The ##; comment indicator should always be used for comments in a preprocessor directive:

#define alphalen ##function($LENGTH("abcdefghijklmnopqrstuvwxyz")) ##; + 100
   WRITE $$$alphalen," is the length of the alphabet"

A ##; comment indicator can appear in a #define, #def1arg, or #dim preprocessor directive. It cannot be used following a ##continue preprocessor directive. Use of // or ; remainder-of-the-line comments should be avoided in preprocessor directives.

##; may also be used anywhere in an ObjectScript code line or an Embedded SQL code line to specify a comment that does not appear in .int code. The comment continues for the remainder of the current line.

##; is evaluated before evaluation of Embedded HTML or Embedded JavaScript.

Compare with #;, which appears in column 1 and makes an entire line a comment. ##; makes the rest of the current line a comment. When ##; appears in the first column of the line, it is functionally identical to the #; 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 v=" of Oz" ##continue
    SET line1="%a"_v ##continue
    SET line2="%b"_v ##continue
    SET line3="%c"_v
    
 $$$Multiline(Scarecrow,Tin Woodman,Lion)
 WRITE "Here is line 1: ",line1,!
 WRITE "Here is line 2: ",line2,!
 WRITE "Here is line 3: ",line3,!      

##continue must appear at the end of every macro definition line, except the last line. This includes comment lines. Therefore, ##continue must end each line of a ##; or #; single-line comment or a multi-line /* comment text */, as follows:

#define <beginning of macro definition> ##continue
#; single line comment ##continue
    /* Multi-line long ##continue
       wordy comment */ ##continue
     <continuation of macro definition>

##expression

The ##expression preprocessor function 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 initial string, """Compiled: ". This is delimited by double-quotes. Within that, the pair of double-quotes specifies a double-quote to appear after evaluation.

  • The value, $ZDATETIME($HOROLOG). The value of the $HOROLOG special variable at compile-time, as converted and formatted by the $ZDATETIME function.

  • The final string, """,!". This is also delimited by double-quotes. Within that, there are a pair of double-quotes (which results in a single double-quote after evaluation). Since the value being defined is being passed to the WRITE command, the final string includes ,!, so that the WRITE command includes a carriage return.

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 function 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.

The following example returns the value from an ObjectScript function:

#define alphalen ##function($LENGTH("abcdefghijklmnopqrstuvwxyz"))
   WRITE $$$alphalen

In the following example, suppose there is a user-defined 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 function 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 function 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 Embedded SQL statement. It has the form:

##sql(SQL-statement)

where SQL-statement is a valid Embedded SQL statement. The ##sql preprocessor directive is exactly equivalent to the &SQL Embedded SQL marker. Refer to Compiling Embedded SQL and the Macro Preprocessor for further details.

##unique

The ##unique preprocessor function 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:

  • Do not attempt to set ##unique(new) outside of a #define or #def1arg preprocessor directive.

  • Do not set ##unique(new) in a preprocessor directive within a method or procedure. These will generate a variable name that is unique to the method (%mmmu1); however, because this is a % variable, it is globally scoped. Invoking another method that sets ##unique(new) also creates %mmmu1, overwriting the variable created by the first method.

  • Never set a %mmmu1 variable directly. Caché reserves all % variables (except %z and %Z variables) for system use; they should never be set by user code.

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 %RegisteredObjectOpens in a new tab. To make these available within a routine or a class that does not extend %RegisteredObjectOpens in a new tab, include the appropriate file:

  • For status-related macros, include %occStatus.inc.

  • For message-related macros, include %occMessages.inc

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 %StatusOpens in a new tab code (sc2) to an existing %StatusOpens in a new tab code (sc1). This macro requires %occStatus.inc.

EMBEDSC(sc1, sc2)

The EMBEDSC macro embeds a %StatusOpens in a new tab code (sc2) within an existing %StatusOpens in a new tab code (sc1). This macro requires %occStatus.inc.

ERROR(errorcode, arg1, arg2, ...)

The ERROR macro creates a %StatusOpens in a new tab 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 in this namespace, and substitute text for message arguments, all in the same macro call. It returns a %StringOpens in a new tab.

Argument Description
language An RFC1766Opens in a new tab natural 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 message dictionaries, see String Localization and Message Dictionaries.

This macro requires %occMessages.inc.

Also see the FormatMessage() instance method of %Library.MessageDictionaryOpens in a new tab.

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 %StatusOpens in a new tab code (sc). This macro requires %occStatus.inc.

ISERR(sc)

The ISERR macro returns True if the supplied %StatusOpens in a new tab 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 %StatusOpens in a new tab code (sc) is successful completion. Otherwise, it returns False. This macro requires %occStatus.inc.

OK

The OK macro creates a %StatusOpens in a new tab 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 %StatusOpens in a new tab code (sc). If sc represents an error status, ThrowOnError performs a THROW operation to throw an exception of type %Exception.StatusExceptionOpens in a new tab 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 %StatusOpens in a new tab code; the macro stores the %StatusOpens in a new tab code in the variable passed as sc. If the %StatusOpens in a new tab code is an error, THROWONERROR performs a THROW operation to throw an exception of type %Exception.StatusExceptionOpens in a new tab to an exception handler. This macro requires %occStatus.inc.

ThrowStatus(sc)

The ThrowStatus macro uses the specified %StatusOpens in a new tab code (sc) to perform a THROW operation to throw an exception of type %Exception.StatusExceptionOpens in a new tab 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.

FeedbackOpens in a new tab