Skip to main content
Previous section   Next section

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. InterSystems IRIS® data platform itself also includes various predefined macros, which are described in the relevant contexts within the 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. 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!"
Copy code to clipboard

(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
Copy code to clipboard

which, in this case, displays the string “Hello, World!” Here is the entire sample:

#Define StringMacro "Hello, World!"
   WRITE $$$StringMacro
Copy code to clipboard

Supported functionality includes:

  • String substitutions, as demonstrated above.

  • Numeric substitutions:

    #Define NumberMacro 22
    Copy code to clipboard
    #Define 25M ##Expression(25*1000*1000)
    Copy code to clipboard

    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
    Copy code to clipboard

    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,!
    Copy code to clipboard

    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))
    Copy code to clipboard

    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
    Copy code to clipboard

    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 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
Copy code to clipboard

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
Copy code to clipboard
#Define myclass "Sample.Person"
  SET x=##class($$$myclass).%New()
Copy code to clipboard

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
Copy code to clipboard

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
Copy code to clipboard

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
Copy code to clipboard

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
Copy code to clipboard

To include multiple include files at the beginning of a class definition, the syntax is of the form:

include (MyMacros, YourMacros) 
Copy code to clipboard

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 ObjectScript Reference.

See also the reference section on #Include.

Preprocessor Directives Reference

InterSystems IRIS 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...
Copy code to clipboard

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
Copy code to clipboard

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)
Copy code to clipboard

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]
Copy code to clipboard

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,!
Copy code to clipboard
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 =
Copy code to clipboard

where the macro might be invoked with code such as:

 $$$Macro7 22
Copy code to clipboard

which the preprocessor would expand to

 SET x = 22
Copy code to clipboard

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
Copy code to clipboard

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
Copy code to clipboard

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()
    Copy code to clipboard

    This is equivalent to:

      SET (a,b,c) = ##class(Sample.Person).%New()
    Copy code to clipboard

    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()
    Copy code to clipboard

    This is equivalent to:

      SET (d,e,f) = ##class(Sample.Person).%New()
    Copy code to clipboard

    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()
    Copy code to clipboard

    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"]
    Copy code to clipboard

    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
Copy code to clipboard

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
Copy code to clipboard

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
Copy code to clipboard

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>
Copy code to clipboard

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,".",!
Copy code to clipboard

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
Copy code to clipboard

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
Copy code to clipboard

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
Copy code to clipboard

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
Copy code to clipboard

#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
Copy code to clipboard

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.",!
Copy code to clipboard

#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
Copy code to clipboard

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.

InterSystems IRIS ignores non-existent schema names in #Import directives. InterSystems IRIS 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
Copy code to clipboard

In this case, InterSystems IRIS 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, InterSystems IRIS 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>
Copy code to clipboard

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)
Copy code to clipboard

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

  ZWRITE ^rINC("%occStatus",0)
Copy code to clipboard

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")
Copy code to clipboard
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)
} 
Copy code to clipboard

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
Copy code to clipboard

For multiple files, it is:

Include (MyMacros, YourMacros)
Copy code to clipboard

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

 #Define Windows
 #Define UNIX
Copy code to clipboard
#Include OS
 
#IfDef Windows
  WRITE "The operating system is not case-sensitive.",!
#Else
  WRITE "The operating system is case-sensitive.",!
#EndIf
Copy code to clipboard

#NoShow

The #NoShow preprocessor directive ends a comment section that is part of an include file. It has the form:

#NoShow
Copy code to clipboard

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
Copy code to clipboard

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).
Copy code to clipboard

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
Copy code to clipboard

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 is deprecated. At IRIS 2020.1, Embedded SQL code for most operations (including SELECT, INSERT, UPDATE, and DELETE) is compiled when the SQL code is executed (runtime), not when the routine containing this SQL code is compiled, regardless of the setting of this preprocessor directive.

In earlier releases, #SQLCompile Mode=value specified the compilation mode for Embedded SQL in code subsequent to this preprocessor directive. It specified the compilation mode for certain Embedded SQL DML commands as either Embedded (process Embedded SQL at compile time) or Deferred (defer processing of Embedded SQL until runtime).

At IRIS 2020.1, all Embedded SQL DML commands are deferred until runtime, at which time they are processed as cached queries. Therefore, Embedded SQL can always refer to tables, user-defined functions, and other SQL entities that do not yet exist at compile time.

An Embedded SQL statement is parsed at compile time. If it contains invalid SQL (for example, an SQL syntax error), the compiler 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.

For further details, refer to the Embedded SQL chapter of Using InterSystems SQL.

Note:

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

#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[,...]]
Copy code to clipboard

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.

InterSystems IRIS ignores non-existent schema names in #SQLCompile Path directives. InterSystems IRIS 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, InterSystems IRIS 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.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
Copy code to clipboard

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
    Copy code to clipboard
  • 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, InterSystems IRIS 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.

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
Copy code to clipboard

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 GetSelectMode method of the %SYSTEM.SQL class. You can set the execution-time select mode value using the SetSelectMode method of the %SYSTEM.SQL 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 InterSystems 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
Copy code to clipboard
#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
Copy code to clipboard
#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
Copy code to clipboard
#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
Copy code to clipboard

#UnDef

The #UnDef preprocessor directive removes the definition for an already-defined macro. It has the form:

#UnDef macro-name
Copy code to clipboard

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
Copy code to clipboard

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.",!
Copy code to clipboard

##;

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"
Copy code to clipboard

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>
Copy code to clipboard

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,!      
Copy code to clipboard

##Continue must appear at the end of a macro definition line. Therefore, ##Continue cannot be followed by a ##; comment or a /* comment text */ comment. The #; full-line comment also cannot be used within a ##Continue multiline directive. You can comment a ##Continue line as follows:

#Define Multiline(%a,%b,%c) ##Continue
    SET v=" of Oz" /* set a variable to a string */ ##Continue
    SET line1="%a"_v ##Continue
    SET line2="%b"_v ##Continue
    SET line3="%c"_v
Copy code to clipboard

##Expression

The ##Expression preprocessor function evaluates an ObjectScript expression at compile time. It has the form:

##Expression(content)
Copy code to clipboard

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

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,!
Copy code to clipboard

The following example defines an expression containing the compile timestamp of the current routine:

#Define CompTS ##Expression("""Compiled: " _ $ZDATETIME($HOROLOG) _ """,!")
  WRITE $$$CompTS
Copy code to clipboard

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/29/2018 07:49:30",! 
Copy code to clipboard

##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))
Copy code to clipboard

the quoted right parenthesis is treated as if it is a closing parenthesis for specifying the argument.

##Expression Nesting

InterSystems IRIS 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.

##Expression can also nest the following macro functions: ##BeginLit...##EndLit, ##Function, ##Lit##Quote, ##SafeExpression, ##StripQ##Unique.

##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, InterSystems IRIS 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. InterSystems IRIS 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 function for ##Expression. These two preprocessor functions 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
Copy code to clipboard

where expression is an ObjectScript expression that determines the value of value and may not contain macros or a ##Expression preprocessor function.

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
Copy code to clipboard

and routine B.mac includes:

aa()
 QUIT "##Expression(10+10+10)"
Copy code to clipboard

A.int then includes the following:

 SET CC = 10_30
 QUIT 
Copy code to clipboard

##Function

The ##Function preprocessor function evaluates an ObjectScript function at compile time. It has the form

##Function(content)
Copy code to clipboard

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
Copy code to clipboard

In the following example, suppose there is a user-defined function in the GetCurrentTime.mac file:

Tag1()
 KILL ^x
 SET ^x = """" _ $Horolog _ """"
 QUIT ^x
Copy code to clipboard

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,! 
Copy code to clipboard

The output of this at the Terminal is something like:

USER>d ^ShowTimeStamps
64797,43570
"64797,53807"

USER>
Copy code to clipboard

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.

##Function Nesting

InterSystems IRIS supports nested within a ##Function. ##Function can nest the following macro functions: ##BeginLit...##EndLit, ##Function, ##Lit##Quote, ##Expression, ##SafeExpression, ##StripQ, and ##Unique.

##Lit

The ##Lit preprocessor function preserves the content of its argument in literal form:

##Lit(content)
Copy code to clipboard

where content is a string that is valid ObjectScript expression. The ##Lit preprocessor function 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 
Copy code to clipboard

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"  
Copy code to clipboard

By using the ##Lit preprocessor function, 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)
Copy code to clipboard

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
Copy code to clipboard

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
Copy code to clipboard

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
Copy code to clipboard

##Quote Nesting

InterSystems IRIS supports nested within a ##Quote function. ##Quote can nest the following macro functions: ##BeginLit...##EndLit, ##Function, ##Lit##Quote, ##Expression, ##SafeExpression, ##StripQ, and ##Unique.

##QuoteExp

The ##QuoteExp preprocessor function takes as an argument an expression that gets evaluated during compilation. This expression can contain nested/recursive MPP functions. It then returns the complied result as a quoted string. If the argument already contains quote characters it escapes these quote characters by doubling them. It has the form:

##QuoteExp(expression)
Copy code to clipboard

where expression may contain any of the following nested/recursive MPP functions: ##BeginLit...##EndLit, ##Expression, ##Function, ##Lit##Quote, ##QuoteExp, ##SafeExpression, ##StripQ##Unique, and ##This.

By using ##QuoteExp you can create a general-purpose complex global macro that accepts a variable number of subscripts and returns that reference as a quoted string, whether the subscript values are passed as numeric or string. You define a macro as a complex global reference using #Def1Arg directive. To return this complex global reference as a quoted string, regardless of the subscripts provided to the macro, wrapper this macro in ##QuoteExp. The macro evaluates the expression argument passed to ##QuoteExp and returns this value as a quoted string.

For example:

#Def1Arg complexGlobal(%subs) ^GLO("dd"##expression($s(%literalargs'=$lb(""):","_$LTS(%literalargs,","),1:"")))
#Def1Arg complexGlobalQE(%subs) ##QuoteExp($$$complexGlobal(%subs))
Copy code to clipboard

##SQL

The ##SQL preprocessor directive invokes a specified Embedded SQL statement at runtime. It has the form:

##SQL(SQL-statement)
Copy code to clipboard

where SQL-statement is a valid Embedded SQL statement. The ##SQL preprocessor directive is exactly equivalent to the &SQL Embedded SQL marker. In both cases, SQL code enclosed in the parentheses is compiled at runtime (first execution), not when the enclosing routine is compiled. Refer to Compiling Embedded SQL for further details.

##StripQ

The ##StripQ preprocessor function takes a single argument and returns that argument with quotes removed. It is the inverse of the ##Quote macro function.

##StripQ(value)
Copy code to clipboard

where value is a literal or variable from which enclosing quotes, if present, are stripped.

##Unique

The ##Unique preprocessor function creates a new, unique local variable within a macro definition for use at compile time or runtime. This preprocessor function is available for use only as part of #Define or #Def1Arg call. It has the form:

 ##Unique(new)
 ##Unique(old)
Copy code to clipboard

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,!
Copy code to clipboard

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. InterSystems IRIS 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 InterSystems IRIS. 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:

  • 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
Copy code to clipboard

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 InterSystems IRIS 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 InterSystems IRIS 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. Within a web application, you can specify %response.Language to use the default locale.
domain The message domain. Within a web application, 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 “Performing Localization” in Implementing InterSystems Business Intelligence.

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.

GETERRORMESSAGE(sc,num)

The GETERRORMESSAGE macro returns the portion of the error message value from the supplied %Status code (sc) as specified by num. For example, num=1 returns SQLCODE error number, num=2 returns the error message text. 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.

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.

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.

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.

ThrowSQLCODE(sqlcode,message)

The ThrowSQLCODE macro uses the specified SQLCODE and Message to perform a THROW operation to throw an exception of type %Exception.SQL 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.

ThrowSQLIfError(sqlcode,message)

The ThrowSQLIfError macro uses the specified SQLCODE and Message to perform a THROW operation to throw an exception of type %Exception.SQL to an exception handler. It throws this exception if SQLCODE < 0 (a negative number, indicating an error). This macro requires %occStatus.inc. For further details, refer to The TRY-CATCH Mechanism in the “Error Processing” chapter of this guide.

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.