# Operators and Expressions

Caché supports many different operators, which perform various actions, including mathematical actions, logical comparisons, and so on. Operators act on expressions, which are variables or other entities that ultimately evaluated to a value. This chapter describes expressions and the various ObjectScript operators. It contains the following topics:

## Introduction to Operators and Expressions

Operators are symbolic characters that specify the action to be performed on their associated operands. Each operand consists of one or more expressions or expression atoms. When used together, an operator and its associated operands have the following form:

[operand] operator operand

Some operators take only one operand and are known as unary operators; others take two operands and are known as binary operators.

An operator and any of its operands taken together constitute an expression. Such expressions produce a result that is the effect of the operator on the operand(s). They are classified based on the types of operators they contain.

An arithmetic expression contains arithmetic operators, gives a numeric interpretation to the operands, and produces a numeric result.

A string expression contains string operators, gives a string interpretation to the operands, and produces a string result.

A logical expression contains relational and logical operators, gives a logical interpretation to the operands, and produces a boolean result: TRUE (1) or FALSE (0).

### Table of Operator Symbols

ObjectScript includes the following operators:

Operator | Operation Performed |
---|---|

. | Object property or method access. |

() | Array index or function call arguments. |

+ | Addition (Binary), Positive (Unary) |

– | Subtraction (Binary), Negative (Unary) |

* | Multiplication |

/ | Division |

\ | Integer division |

** | Exponentiation |

# | Modulus (remainder) |

_ | Concatenation |

' | Logical complement (NOT) |

= | Test for equality, Assignment |

'= | Test for non-equality |

> | Greater than |

'>
<= |
Not greater than (less than or equal to) |

< | Less than |

'<
>= |
Not less than (greater than or equal to) |

[ | Contains |

] | Follows |

]] | Sorts After |

&
&& |
Logical AND (&& is “short-circuit” AND) |

!
|| |
Logical OR (|| is “short-circuit” OR) |

@ | Indirection |

? | Pattern Match |

These are described in more detail in the following sections.

### Operator Precedence

Operator precedence in ObjectScript is strictly left-to-right; within an expression operations are performed in the order in which they appear. This is different from other languages in which certain operators have higher precedence than others. You can use explicit parentheses within an expression to force certain operations to be carried ahead of others.

WRITE "1 + 2 * 3 = ", 1 + 2 * 3,! // returns 9 WRITE "2 * 3 + 1 = ", 2 * 3 + 1,! // returns 7 WRITE "1 + (2 * 3) = ", 1 + (2 * 3),! // returns 7 WRITE "2 * (3 + 1) = ", 2 * (3 + 1),! // returns 8

#### Unary Negative Operators

ObjectScript gives the unary negative operator precedence over the binary arithmetic operators. ObjectScript first scans a numeric expression and performs any unary negative operations. Then, ObjectScript evaluates the expression and produces a result.

WRITE -123 - 3,! // returns -126 WRITE -123 + -3,! // returns -126 WRITE -(123 - 3),! // returns -120

#### Parentheses and Precedence

You can change the order of evaluation by nesting expressions within each other with matching parentheses. The parentheses group the enclosed expressions (both arithmetic and relational) and control the order in which ObjectScript performs operations on the expressions. Consider the following expression:

SET TorF = ((4 + 7) > (6 + 6)) // False (0) WRITE TorF

Here, because of the parentheses, four and seven are added, as are six and six; this results in the logical expression 11 > 12, which is false. Compare this to:

SET Value = (4 + 7 > 6 + 6) // 7 WRITE Value

In this case, precedence proceeds from left to right, so four and seven are added. Their sum, eleven, is compared to six; since eleven is greater than six, the result of this logical operation is one (TRUE). One is then added to six, and the result is seven.

Note that the precedence even determines the result type, since the first expression’s final operation results in a boolean and the second expression’s final operation results in a numeric.

The following example shows multiple levels of nesting:

WRITE 1+2*3-4*5,! // returns 25 WRITE 1+(2*3)-4*5,! // returns 15 WRITE 1+(2*(3-4))*5,! // returns -5 WRITE 1+(((2*3)-4)*5),! // returns 11

Precedence from the innermost nested expression and proceeds out level by level, evaluating left to right at each level.

For all but the simplest ObjectScript expressions, it is good practice to fully parenthesize expressions. This is to eliminate any ambiguity about the order of evaluation and to also eliminate any future questions about the original intention of the code.

For example, because the “&&” operator, like all operators, is subject to left-to-right precedence, the final statement in the following code fragment evaluates to 0:

SET x = 3 SET y = 2 IF x && y = 2 { WRITE "True",! } ELSE { WRITE "False",! }

This is because the evaluation occurs as follows:

The first action is to check if x is defined and has a non-zero value. Since x equals 3, evaluation continues.

Next, there is a check if y is defined and has a non-zero value. Since y equals 2, evaluation continues.

Next, the value of 3 && 2 is evaluated. Since neither 3 nor 2 equal 0, this expression is true and evaluates to 1.

The next action is to compare the returned value to 2. Since 1 does not equal 2, this evaluation returns 0.

For those accustomed to many programming languages, this is an unexpected result. If the intent is to return True if x is defined with a non-zero value and if y equals 2, then parentheses are required:

SET x = 3 SET y = 2 IF x && (y = 2) { WRITE "True",! } ELSE { WRITE "False",! }

#### Functions and Precedence

Some types of expressions, such as functions, can have side effects. Suppose you have the following logical expression:

IF var1 = ($$ONE + (var2 * 5)) { DO ^Test }

ObjectScript first evaluates var1, then the function $$ONE, then var2. It then multiplies var2 by 5. Finally, ObjectScript tests to see if the result of the addition is equal to the value in var1. If it is, it executes the DO command to call the Test routine.

As another example, consider the following logical expression:

SET var8=25,var7=23 IF var8 = 25 * (var7 < 24) { WRITE !,"True" } ELSE { WRITE !,"False" }

Caché evaluates expressions strictly left-to-right. The programmer must use parentheses to establish any precedence. In this case, Caché first evaluates var8=25, resulting in 1. It then multiplies this 1 by the results of the expression in parentheses. Because var7 is less than 24, the expression in parentheses evaluates to 1. Therefore, Caché multiplies 1 * 1, resulting in 1 (true).

### Expressions

An ObjectScript expression is one or more “tokens” that can be evaluated to yield a value. The simplest expression is simply a literal or variable:

SET expr = 22 SET expr = "hello" SET expr = x

You can create more complex expressions using arrays, operators, or one of the many ObjectScript functions:

SET expr = +x SET expr = x + 22 SET expr = array(1) SET expr = ^data("x",1) SET expr = $Length(x)

An expression may consist of, or include, an object property, instance method call, or class method call:

SET expr = person.Name SET expr = obj.Add(1,2) SET expr = ##class(MyApp.MyClass).Method()

You can directly invoke an ObjectScript routine call within an expression by placing $$ in front of the routine call:

SET expr = $$MyFunc^MyRoutine(1)

Expressions can be classified according to what kind of value they return:

An arithmetic expression contains arithmetic operators, gives a numeric interpretation to the operands, and produces a numeric result:

SET expr = 1 + 2 SET expr = +x SET expr = a + b

Copy code to clipboardNote that a string used within an arithmetic expression is evaluated as a numeric value (or 0 if it is not a valid numeric value). Also note that using the unary addition operator (+) will implicitly convert a string value to a numeric value.

A string expression contains string operators, gives a string interpretation to the operands, and produces a string result.

SET expr = "hello" SET expr = "hello" _ x

Copy code to clipboardA logical expression contains relational and logical operators, gives a logical interpretation to the operands, and produces a boolean result: TRUE (1) or FALSE (0):

SET expr = 1 && 0 SET expr = a && b SET expr = a > b

Copy code to clipboardAn object expression produces an object reference as a result:

SET expr = object SET expr = employee.Company SET expr = ##class(Person).%New()

Copy code to clipboard

#### Logical Expressions

Logical expressions use logical operators, numeric relational operators, and string relational operators. They evaluate expressions and result in a Boolean value: 1 (TRUE) or 0 (FALSE). Logical expressions are most commonly used with:

In a Boolean test, any expression that evaluates to a non-zero numeric value returns a Boolean 1 (TRUE) value. Any expression that evaluates to a zero numeric value returns a Boolean 0 (FALSE) value. Caché evaluates a non-numeric string as having a zero numeric value. For further details, refer to String-to-Number Conversion.

You can combine multiple Boolean logical expressions by using logical operators. Like all Caché expressions, they are evaluated in strict left-to-right order. There are two types of logical operators: regular logical operators (& and !) and short-circuit logical operators (&& and ||).

When regular logical operators are used to combine logical expressions, Caché evaluates all of the specified expressions, even when the Boolean result is known before all of the expressions have been evaluated. This assures that all expressions are valid.

When short-circuit logical operators are used to combine logical expressions, Caché evaluates only as many expressions as are needed to determine the Boolean result. For example, if there are multiple AND tests, the first expression that returns 0 determines the overall Boolean result. Any logical expressions to the right of this expression are not evaluated. This allows you to avoid unnecessary time-consuming expression evaluations.

Some commands allow you to specify a comma-separated list as an argument value. In this case, Caché handles each listed argument like an independent command statement. Therefore, IF x=7,y=4,z=2 is parsed as IF x=7 THEN IF y=4 THEN IF z=2, which is functionally identical to the short-circuit logical operators statement IF (x=7)&&(y=4)&&(z=2).

In the following example, the IF test uses a regular logical operator (&). Therefore, all functions are executed even though the first function returns 0 (FALSE) which automatically makes the result of the entire expression FALSE:

LogExp IF $$One() & $$Two() { WRITE !,"Expression is TRUE." } ELSE { WRITE !,"Expression is FALSE." } One() WRITE !,"one" QUIT 0 Two() WRITE !,"two" QUIT 1

In the following example, the IF test uses a short-circuit logical operator (&&). Therefore, the first function is executed and returns 0 (FALSE) which automatically makes the result of the entire expression FALSE. The second function is not executed:

LogExp IF $$One() && $$Two() { WRITE !,"Expression is TRUE." } ELSE { WRITE !,"Expression is FALSE." } One() WRITE !,"one" QUIT 0 Two() WRITE !,"two" QUIT 1

In the following example, the IF test specifies comma-separated arguments. The comma is not a logical operator, but has the same effect as specifying the short-circuit && logical operator. The first function is executed and returns 0 (FALSE) which automatically makes the result of the entire expression FALSE. The second function is not executed:

LogExp IF $$One(),$$Two() { WRITE !,"Expression is TRUE." } ELSE { WRITE !,"Expression is FALSE." } One() WRITE !,"one" QUIT 0 Two() WRITE !,"two" QUIT 1

### Assignment

Within ObjectScript the SET command is used along with the assignment operator ( = ) to assign a value to a variable. The right-hand side of an assignment command is an expression:

SET value = 0 SET value = a + b

Within ObjectScript it is also possible to use certain functions on the left-hand side of an assignment command:

SET pies = "apple,banana,cherry" WRITE "Before: ",pies,! // set the 3rd comma-delimited piece of pies to coconut SET $Piece(pies,",",3) = "coconut" WRITE "After: ",pies

## String-to-Number Conversion

A string can be numeric, partially numeric, or non-numeric.

A numeric string consists entirely of numeric characters. For example, "123", "+123", ".123", "++0007", "-0".

A partially numeric string is a string that begins with numeric symbols, followed by non-numeric characters. For example, "3 blind mice", "-12 degrees".

A non-numeric string begins with a non-numeric character. For example, " 123", "the 3 blind mice", "three blind mice".

### Numeric Strings

When a numeric string or partially numeric string is used in an arithmetic expression, it is interpreted as a number. This numeric value is obtained by scanning the string from left to right to find the longest sequence of leading characters that can be interpreted as a numeric literal. The following characters are permitted:

The digits 0 through 9.

The PlusSign and MinusSign property values. By default these are the “+” and “-” characters, but are locale-dependent. Use the %SYS.NLS.Format.GetFormatItem() method to return the current settings.

The DecimalSeparator property value. By default this is the “.” character, but is locale-dependent. Use the %SYS.NLS.Format.GetFormatItem() method to return the current setting.

The letters “e”, and “E” may be included as part of a numeric string when in a sequence representing scientific notation, such as 4E3.

Note that the NumericGroupSeparator property value (the “,” character, by default) is not considered a numeric character. Therefore, the string "123,456" is a partially numeric string that resolves to the number "123".

Numeric strings and partial numeric strings are converted to canonical form prior to arithmetic operations (such as addition and subtraction) and greater than/less than comparison operations (<, >, <=, >=). Numeric strings are not converted to canonical form prior to equality comparisons (=, '=), because these operators are also used for string comparisons.

The following example shows arithmetic comparisons of numeric strings:

WRITE "3" + 4,! // returns 7 WRITE "003.0" + 4,! // returns 7 WRITE "++--3" + 4,! // returns 7 WRITE "3 blind mice" + 4,! // returns 7

The following example shows less than (<) comparisons of numeric strings:

WRITE "3" < 4,! // returns 1 WRITE "003.0" < 4,! // returns 1 WRITE "++--3" < 4,! // returns 1 WRITE "3 blind mice" < 4,! // returns 1

The following example shows <= comparisons of numeric strings:

WRITE "4" <= 4,! // returns 1 WRITE "004.0" <= 4,! // returns 1 WRITE "++--4" <= 4,! // returns 1 WRITE "4 horsemen" <= 4,! // returns 1

The following example shows equality comparisons of numeric strings. Non-canonical numeric strings are compared as character strings, not as numbers. Note that –0 is a non-canonical numeric string, and is therefore compared as a string, not a number:

WRITE "4" = 4.00,! // returns 1 WRITE "004.0" = 4,! // returns 0 WRITE "++--4" = 4,! // returns 0 WRITE "4 horsemen" = 4,! // returns 0 WRITE "-4" = -4,! // returns 1 WRITE "0" = 0,! // returns 1 WRITE "-0" = 0,! // returns 0 WRITE "-0" = -0,! // returns 0

### Non-numeric Strings

If the leading characters of the string are not numeric characters, the string’s numeric value is 0 for all arithmetic operations. For <, >, '>, <=, '<, and >= comparisons a non-numeric string is also treated as the number 0. Because the equal sign is used for both the numeric equality operator and the string comparison operator, string comparison takes precedence for = and '= operations. You can append the PlusSign property value (+ by default) to force numeric evaluation of a string. This results in the following logical values, when x and y are different non-numeric strings (for example x=”Fred”, y=”Wilma”).

x=y is FALSE | x=x is TRUE | +x=y is FALSE | +x=+y is TRUE | +x=+x is TRUE |

x'=y is TRUE | x'=x is FALSE | +x'=y is TRUE | +x'=+y is FALSE | +x'=+x is FALSE |

x<y is FALSE | x<x is FALSE | +x<y is FALSE | +x<+y is FALSE | +x<+x is FALSE |

x<=y is TRUE | x<=x is TRUE | +x<=y is TRUE | +x<=+y is TRUE | +x<=+x is TRUE |

## Arithmetic Operators

The arithmetic operators interpret their operands as numeric values and produce numeric results. When operating on a string, an arithmetic operators treats the string as its numeric value, according to the rules described in the section “String-to-Number Conversion.”

### Decimal and $DOUBLE Floating-Point Numbers

Caché supports two representations of decimal-point numbers: ObjectScript decimal floating-point and IEEE double-precision binary floating-point.

Caché represents a numeric constant as an ObjectScript decimal floating-point value by default. This is referred to as a $DECIMAL number. A $DECIMAL number can exactly represent a fractional value, such as 2.2. You use the $DECIMAL() function to explicitly convert an IEEE double-precision binary floating-point number to the corresponding ObjectScript decimal floating-point number.

Caché also supports IEEE double-precision binary floating-point values. You use the $DOUBLE() function to explicitly convert a numeric constant to the corresponding IEEE double-precision binary floating-point value (referred to as a $DOUBLE number). A $DOUBLE number can only approximately represent a fractional value, such as 2.2. $DOUBLE representation is usually preferred when doing high-speed scientific calculations because most computers include high-speed hardware for binary floating-point arithmetic.

ObjectScript automatically converts a numeric to the corresponding $DOUBLE value in the following situations:

If an arithmetic operation involves a $DOUBLE value, ObjectScript converts all numbers in the operation to $DOUBLE. For example, 2.2 + $DOUBLE(.1) is the same as $DOUBLE(2.2) + $DOUBLE(.1).

If an operation results in a number that is too large to be represented in ObjectScript decimal floating-point (larger than 9.223372036854775807E145), ObjectScript automatically converts this number to $DOUBLE, rather than issuing a <MAXNUMBER> error.

### Unary Positive Operator (+)

The unary positive operator (+) gives its single operand a numeric interpretation. If its operand has a string value, it converts it to a numeric value. It does this by sequentially parsing the characters of the string as a number, until it encounters an invalid character. It then returns whatever leading portion of the string was a well-formed numeric. For example:

WRITE + "32 dollars and 64 cents" // 32

If the string has no leading numeric characters, the unary positive operator gives the operand a value of zero. For example:

WRITE + "Thirty-two dollars and 64 cents" // 0

The unary positive operator has no effect on numeric values. It does not alter the sign of either positive or negative numbers. For example:

SET x = -23 WRITE " x: ", x,! // -23 WRITE "+x: ",+x,! // -23

### Unary Negative Operator (-)

The unary negative operator (-) reverses the sign of a numerically interpreted operand. For example:

SET x = -60 WRITE " x: ", x,! // -60 WRITE "-x: ",-x,! // 60

If its operand has a string value, the unary negative operator interprets it as a numeric value before reversing its sign. This numeric interpretation is exactly the same as that performed by the unary positive operator, described above. For example:

SET x = -23 WRITE -"32 dollars and 64 cents" // -32

ObjectScript gives the unary negative operator precedence over the binary arithmetic operators. ObjectScript first scans a numeric expression and performs any unary negative operations. Then, ObjectScript evaluates the expression and produces a result.

In the following example, ObjectScript scans the string and encounters the numeric value of 2 and stops there. It then applies the unary negative operator to the value and uses the Concatenate operator (_) to concatenate the value “Rats” from the second string to the numeric value.

WRITE -"2Cats"_"Rats" // -2Rats

To return the absolute value of a numeric expression, use the $ZABS function.

### Addition Operator (+)

The addition operator produces the sum of two numerically interpreted operands. It uses any leading valid numeric characters as the numeric values of the operands and produces a value that is the sum of the numeric value of the operands.

The following example performs addition on two numeric literals:

WRITE 2936.22 + 301.45 // 3237.67

The following example performs addition on two locally defined variables:

SET x = 4 SET y = 5 WRITE "x + y = ",x + y // 9

The following example performs string arithmetic on two operands that have leading digits, adding the resulting numerics:

WRITE "4 Motorcycles" + "5 bicycles" // 9

The following example illustrates that leading zeros on a numerically evaluated operand do not affect the results the operator produces:

WRITE "007" + 10 // 17

### Subtraction Operator (-)

The subtraction operator produces the difference between two numerically interpreted operands. It interprets any leading, valid numeric characters as the numeric values of the operand and produces a value that is the remainder after subtraction.

The following example performs subtraction on two numeric literals:

WRITE 2936.22 - 301.45 // 2634.77

The following example performs subtraction on two locally defined variables:

SET x = 4 SET y = 5 WRITE "x - y = ",x - y // -1

The following example performs string arithmetic on two operands that have leading digits, subtracting the resulting numerics:

WRITE "8 apples" - "4 oranges" // 4

If the operand has no leading numeric characters, ObjectScript assumes its value to be zero. For example:

WRITE "8 apples" - "four oranges" // 8

### Multiplication Operator (*)

Binary Multiply produces the product of two numerically interpreted operands. It uses any leading numeric characters as the numeric value of the operands and produces a result that is the product.

The following example performs multiplication on two numeric literals:

WRITE 9 * 5.5 // 49.5

The following example performs multiplication on two locally defined variables:

SET x = 4 SET y = 5 WRITE x * y // 20

The following example performs string arithmetic on two operands that have leading digits, multiplying the resulting numerics:

WRITE "8 apples" * "4 oranges" // 32

If an operand has no leading numeric characters, Binary Multiply assigns it a value of zero.

WRITE "8 apples"*"four oranges" // 0

### Division Operator (/)

Binary Divide produces the result of dividing two numerically interpreted operands. It uses any leading numeric characters as the numeric value of the operands and products a result that is the quotient.

The following example performs division on two numeric literals:

WRITE 9 / 5.5 // 1.636363636363636364

The following example performs division on two locally defined variables:

SET x = 4 SET y = 5 WRITE x / y // .8

The following example performs string arithmetic on two operands that have leading digits, dividing the resulting numerics:

WRITE "8 apples" / "4 oranges" // 2

If the operand has no leading numeric characters, Binary Divide assumes its value to be zero. For example:

WRITE "eight apples" / "4 oranges" // 0 // "8 apples"/"four oranges" generates a <DIVIDE> error

Note that the second of these operations is invalid. Dividing a number by zero is not allowed. ObjectScript returns a <DIVIDE> error message.

### Exponentiation Operator (**)

The Exponentiation Operator produces the exponentiated value of the left operand raised to the power of the right operand.

0**0: Zero raised to the power of zero is 0. However, if either operand is an IEEE double-precision number, (for example, 0**$DOUBLE(0) or $DOUBLE(0)**0) zero raised to the power of zero is 1. For further details, refer to the $DOUBLE function.

0**n: 0 raised to the power of any positive number n is 0. This includes 0**$DOUBLE("INF"). Attempting to raise 0 to the power of a negative number results in an error: Caché negative numbers generate an <ILLEGAL VALUE> error; $DOUBLE negative numbers generate a <DIVIDE> error.

num**0: Any non-zero number (positive or negative) raised to the power of zero is 1. This includes $DOUBLE("INF")**0.

1**n: 1 raised to the power of any number (positive, negative, or zero) is 1.

-1**n: -1 raised to the power of zero is 1. -1 raised to the power of 1 or -1 is -1. For exponents larger than 1, see below.

num**n: A positive number (integer or fractional) raised to any power (integer or fractional, positive or negative) returns a positive number.

-num**n: A negative number (integer or fractional) raised to the power of an even integer (positive or negative) returns a positive number. A negative number (integer or fractional) raised to the power of an odd integer (positive or negative) returns a negative number.

-num**.n: Attempting to raise a negative number to the power of a fractional number results in an <ILLEGAL VALUE> error.

$DOUBLE("INF")**n: An infinite number (positive or negative) raised to the power of 0 is 1. An infinite number (positive or negative) raised to the power of any positive number (integer, fractional, or INF) is INF. An infinite number (positive or negative) raised to the power of any negative number (integer, fractional, or INF) is 0.

$DOUBLE("NAN"): NAN on either side of the exponentiation operator always returns NAN, regardless of the value of the other operand.

Very large exponents may result in overflow and underflow values:

num**nnn: A positive or negative number greater than 1 with a large positive exponent value (such as 9**153 or -9.2**152) generates a <MAXNUMBER> error.

num**-nnn: A positive or negative number greater than 1 with a large negative exponent value (such as 9**-135 or -9.2**-134) returns 0.

.num**nnn: A positive or negative number less than 1 with a large positive exponent value (such as .22**196 or -.2**184) returns 0.

.num**-nnn: A positive or negative number less than 1 with a large negative exponent value (such as .22**-196 or -.2**-184) generates a <MAXNUMBER> error.

An exponent that exceeds the maximum value supported by Caché numbers either issues a <MAXNUMBER> error or automatically converts to an IEEE double-precision floating point number. This automatic conversion is specified by using either the TruncateOverflow() method of the %SYSTEM.Process class on a per-process basis, or the TruncateOverflow property of the Config.Miscellaneous class on a system-wide basis. For further details, refer to the $DOUBLE function.

The following examples performs exponentiation on two numeric literals:

WRITE "9 ** 2 = ",9 ** 2,! // 81 WRITE "9 ** -2 = ",9 ** -2,! // .01234567901234567901 WRITE "9 ** 2.5 = ",9 ** 2.5,! // 242.9999999994422343

The following example performs exponentiation on two locally defined variables:

SET x = 4, y = 3 WRITE "x ** y = ",x ** y,! // 64

The following example performs string arithmetic. Exponentiation uses any leading numeric characters as the values of the operands and produces a result.

WRITE "4 apples" ** "3 oranges" // 64

If an operand has no leading numeric characters, Exponentiation assumes its value to be zero.

The following example demonstrates how to use exponentiation to find the square root of a number.

WRITE 256 ** .5 // 16

Exponentiation can also be performed using the $ZPOWER function.

### Integer Divide Operator ( \ )

The Integer Divide operator produces the integer result of the division of the left operand by the right operand. It does not return a remainder, and does not round up the result.

The following example performs integer division on two integer operands. ObjectScript does not return the fractional portion of the number:

WRITE "355 \ 113 = ", 355 \ 113 // 3

The following example performs string arithmetic. Integer Divide uses any leading numeric characters as the values of the operands and produces an integer result.

WRITE "8 Apples" \ "3.1 oranges" // 2

If an operand has no leading numeric characters, ObjectScript assumes its value to be zero. If you attempt integer division with a zero-valued divisor, ObjectScript returns a <DIVIDE> error.

### Modulo Operator (#)

The Modulo operator produces the value of an arithmetic modulo operation on two numerically interpreted operands. When the two operands are positive, then the modulo operation is the remainder of the left operand integer divided by the right operand.

The following examples perform modulo operations on numeric literals, returning the remainder:

WRITE "37 # 10 = ",37 # 10,! // 7 WRITE "12.5 # 3.2 = ",12.5 # 3.2,! // 2.9

The following example performs string arithmetic. When operating on strings, they are converted to numeric values (according the values described in the section Variable Typing and Conversion) before the modulo operator is applied. Hence, the following two expressions are equivalent:

WRITE "8 apples" # "3 oranges",! // 2 WRITE 8 # 3 // 2

Because Caché evaluates a string with no leading numeric characters to zero, a right operand of this kind yields a <DIVIDE> error.

## Logical Comparison Operators

The logical comparison operators compare the values of their operands and return a boolean value: TRUE (1) or FALSE (0).

### Unary Not

Unary Not inverts the truth value of the boolean operand. If the operand is TRUE (1), Unary Not gives it a value of FALSE (0). If the operand is FALSE (0), Unary Not gives it a value of TRUE (1).

For example, the following statements produce a result of FALSE (0):

SET x=0 WRITE x

While the following statements produces a result of TRUE (1).

SET x=0 WRITE 'x

Unary Not with a comparison operator inverts the sense of the operation it performs. It effectively inverts the result of the operation. For example, the following statement displays a result of FALSE (0):

WRITE 3>5

But, the following statement displays a result of TRUE (1):

WRITE 3'>5

### Precedence and Logical Operators

Because ObjectScript performs a strict left-to-right evaluation of operators, logical comparisons involving other operators must use parentheses to group operations to achieve the desired precedence. For example, you would expect the logical Binary Or (!) test in the following program to return TRUE (1):

SET x=1,y=0 IF x=1 ! y=0 {WRITE "TRUE"} ELSE {WRITE "FALSE" } // Returns 0 (FALSE), due to evaluation order

However, to properly perform this logical comparison, you must use parentheses to nest the other operations. The following example gives the expected results:

SET x=1,y=0 IF (x=1) ! (y=0) {WRITE "TRUE"} ELSE {WRITE "FALSE" } // Returns 1 (TRUE)

### Binary And

Binary And tests whether both its operands have a truth value of TRUE (1). If both operands are TRUE (that is, have nonzero values when evaluated numerically), ObjectScript produces a value of TRUE (1). Otherwise, ObjectScript produces a value of FALSE (0).

There are two forms to Binary And: & and &&.

The & operator evaluates both operands and returns a value of FALSE (0) if either operand evaluates to a value of zero. Otherwise it returns a value of TRUE (1).

The && operator evaluates the left operand and returns a value of FALSE (0) if it evaluates to a value of zero. Only if the left operand is nonzero does the && operator then evaluates the right operand. It returns a value of FALSE (0) if the right operand evaluates to a value of zero. Otherwise it returns a value of TRUE (1).

The following examples evaluate two nonzero-valued operands as TRUE and produces a value of TRUE (1).

SET A=-4,B=1 WRITE A&B // TRUE (1)

returns 1.

SET A=-4,B=1 WRITE A&&B // TRUE (1)

returns 1.

The following examples evaluate one true and one false operand and produces a value of FALSE (0).

SET A=1,B=0 WRITE "A = ",A,! WRITE "B = ",B,! WRITE "A&B = ",A&B,! // FALSE (0) SET A=1,B=0 WRITE "A&&B = ",A&&B,! // FALSE (0)

both return FALSE (0).

The following examples show the difference between the “&” operator and the “&&” operator. In these examples, the left operand evaluates to FALSE (0) and the right operand is not defined. The “&” and “&&” operators respond differently to this situation:

The “&” operator attempts to evaluate both operands, and fails with an <UNDEFINED> error.

TRY { KILL B SET A=0 WRITE "variable A defined?: ",$DATA(A),! WRITE "variable B defined?: ",$DATA(B),! WRITE A&B WRITE !,"Success" RETURN } CATCH exp { IF 1=exp.%IsA("%Exception.SystemException") { WRITE !,"System exception",! WRITE "Name: ",$ZCVT(exp.Name,"O","HTML"),! WRITE "Data: ",exp.Data,!! } ELSE { WRITE "not a system exception"} }

Copy code to clipboardThe “&&” operator evaluates only the left operand, and produces a value of FALSE (0).

TRY { KILL B SET A=0 WRITE "variable A defined?: ",$DATA(A),! WRITE "variable B defined?: ",$DATA(B),! WRITE A&&B WRITE !,"Success" RETURN } CATCH exp { IF 1=exp.%IsA("%Exception.SystemException") { WRITE !,"System exception",! WRITE "Name: ",$ZCVT(exp.Name,"O","HTML"),! WRITE "Data: ",exp.Data,!! } ELSE { WRITE "not a system exception"} }

Copy code to clipboard

#### Not And (NAND)

You can specify the Boolean Not And (NAND) operation by using the Unary Not operator with the Binary And (&) operator in either of the following equivalent formats:

operand '& operand '(operand & operand)

The Not And operation reverses the truth value of the & Binary And applied to both operands. It produces a value of TRUE (1) when either or both operands are false. It produces a value of FALSE when both operands are TRUE.

The && Binary And operator cannot be prefixed with a Unary Not operator: the format “'&&” is not supported. However, the following format is supported:

'(operand && operand)

The following example performs two equivalent Not And operations. Each evaluates one FALSE (0) and one TRUE (1) operand and produces a value of TRUE (1).

SET A=0,B=1 WRITE !,A'&B // Returns 1 WRITE !,'(A&B) // Returns 1

The following example performs a Not And operation by performing a && Binary And operation and then using a Unary Not to invert the result. The && operation tests the first operand and, because the boolean value is FALSE (0), && does not test the second operand. The Unary Not inverts the resulting boolean value, so that the expression returns TRUE (1):

SET A=0 WRITE !,'(A&&B) // Returns 1

### Binary Or

Binary Or produces a result of TRUE (1) if either operand has a value of TRUE or if both operands have a value of TRUE (1). Binary Or produces a result of FALSE (0) only if both operands are FALSE (0).

There are two forms to Binary Or: ! (exclamation point) and || (two vertical bars).

The ! operator evaluates both operands and returns a value of FALSE (0) if both operand evaluates to a value of zero. Otherwise it returns a value of TRUE (1).

The || operator evaluates the left operand. If the left operand evaluates to a nonzero value, the || operator returns a value of TRUE (1) without evaluating the right operand. Only if the left operand evaluates to zero does the || operator then evaluate the right operand. It returns a value of FALSE (0) if the right operand also evaluates to a value of zero. Otherwise it returns a value of TRUE (1).

The following examples evaluate two TRUE (nonzero) operands, apply the Binary Or to them, and produces a TRUE result:

SET A=5,B=7 WRITE "A!B = ",A!B,! SET A=5,B=7 WRITE "A||B = ",A||B,!

both return TRUE (1).

The following examples evaluate one false and one true operand, apply the Binary Or to them, and produces a TRUE result:

SET A=0,B=7 WRITE "A!B = ",A!B,! SET A=0,B=7 WRITE "A||B = ",A||B,!

both return TRUE (1).

The following examples evaluate two false operands and produces a result with a value of FALSE.

SET A=0,B=0 WRITE "A!B = ",A!B,! SET A=0,B=0 WRITE "A||B = ",A||B,!

both return FALSE (0).

#### Not Or (NOR)

You can produce a Not Or (NOR) operation by using Unary Not with the ! Binary Or in either of the following equivalent formats:

operand '! operand '(operand ! operand)

The Not Or operation produces a result of TRUE (1) if both operands have values of FALSE. The Not Or operation produces a result of FALSE (0) if either operand has a value of TRUE or if both operands are TRUE.

The || Binary Or operator cannot be prefixed with a Unary Not operator: the format “'||” is not supported. However, the following format is supported:

'(operand || operand)

The following Not Or examples evaluate two false operands and produce a TRUE result.

SET A=0,B=0 WRITE "A'!B = ",A'!B // Returns 1

SET A=0,B=0 WRITE "'(A!B) = ",'(A!B) // Returns 1

The following Not Or examples evaluate one TRUE and one false operand and produce a result of FALSE.

SET A=0,B=1 WRITE "A'!B = ",A'!B // Returns 0

SET A=0,B=1 WRITE "'(A!B) = ",'(A!B) // Returns 0

The following Not Or (||) example evaluates the left operand, and because it is TRUE (1) does not evaluate the right operand. The Unary Not inverts the resulting boolean value, so the expression returns FALSE (0).

SET A=1 WRITE "'(A||B) = ",'(A||B) // Returns 0

## String Concatenate Operator

The string Concatenate operator (_) is a binary (two-operand) operator that interprets its operands as strings and returns a string value.

You use Concatenate to combine string literals, numbers, expressions, and variables. It takes the form:

operand_operand

Concatenate produces a result that is a string composed of the right operand appended to the left operand. Concatenate gives its operands no special interpretation. It treats them as string values.

The following example concatenates two strings:

WRITE "High"_"chair"

returns “Highchair”.

When concatenating a numeric literal to another numeric literal or to a non-numeric string, Caché first converts each of the numbers to canonical form. The following example concatenates two numeric literals:

WRITE 7.00_+008

returns 78.

The following example concatenates a numeric literal and a numeric string:

WRITE ++7.00_"+007"

returns the string 7+007.

The following example concatenates two strings and the null string:

SET A="ABC"_""_"DEF" WRITE A

returns “ABCDEF”.

The null string has no effect on the length of a string. You can concatenate an infinite number of null strings to a string.

The maximum string size is configurable. If long strings are enabled, the maximum string size to 3,641,144 characters. Otherwise, the maximum string size is 32,767 characters. Long strings are enabled by default. See the “Long Strings” section in the “Data Types and Values” chapter.

Concatenation that creates an extremely long string can result in a <STORE> error, when the concatenated string is too long to be stored in a variable. In the event of a <STORE> error, the variable being enlarged by concatenation retains its value prior to the concatenation.

### Concatenate Encoded Strings

Some Caché strings contain internal encoding that can limit whether these strings can be concatenated:

A bit string cannot be concatenated, either with another bit string, with a string that is not a bit string, or with the empty string (""). Attempting to do so results in an <INVALID BIT STRING> error when accessing the resulting string.

A List structure string can be concatenated with another List structure string, or with the empty string (""). It cannot be concatenated with a non-List string. Attempting to do so results in an <LIST> error when accessing the resulting string.

A JSON string cannot be concatenated, either with another JSON string, with a string that is not a JSON string, or with the empty string (""). Attempting to do so results in an <INVALID OREF> error when accessing the resulting string.

## Numeric Relational Operators

There are two types of relational operators: string relational operators and numeric relational operators. Numeric relational operators use the numeric values of the operands to produce a Boolean result. When operating on strings, a numeric relational operator treats each of the strings as its numeric value, according to the rules described in the section “String-to-Number Conversion”.

Numeric relational operators should not be used to compare non-numeric strings.

Comparisons between IEEE double-precision fractional numbers ($DOUBLE numbers) and standard Caché floating point numbers ($DECIMAL numbers) are performed exactly, without rounding. Equality comparisons between $DOUBLE and $DECIMAL numbers often yield unexpected results, and should be avoided. For further details on arithmetic operations involving IEEE double numbers, see the appendix “Numeric Computing in InterSystems Applications” in the Caché Programming Orientation Guide.

### Binary Less Than

The Binary Less Than operator tests whether its left operand is numerically less than its right operand. ObjectScript evaluates both operands numerically and returns a Boolean result of TRUE (1) is the left operand has a lesser numeric value than its right operand. ObjectScript returns a Boolean result of FALSE (0) if the left operand has an equal or greater numeric value than the right operand. For example:

WRITE 9 < 6

returns 0.

WRITE 22 < 100

returns 1.

### Binary Greater Than

Binary Greater Than tests whether the left operand is numerically greater than the right operand. ObjectScript evaluates the two operands numerically and produces a result of TRUE (1) if the left operand is numerically larger than the right operand. It produces a result of FALSE (0) if the left operand is numerically equal to or smaller than the right operand. For example:

WRITE 15 > 15

returns 0.

WRITE 22 > 100

returns 0.

### Greater Than or Equal To

You can produce a Greater Than or Equal To operation by:

Combining the Binary Greater Than (>) and Equals (=) operators. The two operators used together give return TRUE if either one returns TRUE.

Using a Unary NOT operator (') with Binary Less Than (<). The two operators used together reverse the truth value of the Binary Less Than.

ObjectScript produces a result of TRUE (1) when the left operand is numerically greater than or equal to the right operand. It produces a result of FALSE (0) when the left operand is numerically less than the right operand.

You can express the Greater Than or Equal To operation in any of the following ways:

operand_A >= operand_B operand_A '< operand_B '(operand_A < operand_B)

### Less Than or Equal To

You can produce a Less Than or Equal To operation by:

Combining the Binary Less Than (<) and Equals (=) operators. The two operators used together give return TRUE if either one returns TRUE.

Using a Unary NOT operator (') with Binary Greater Than (>). The two operators used together reverse the truth value of the Binary Greater Than.

ObjectScript produces a result of TRUE (1) when the left operand is numerically less than or equal to the right operand. It produces a result of FALSE (0) when the left operand is numerically greater than the right operand.

You can express the Less Than or Equal To operation in any of the following ways:

operand_A <= operand_B operand_A '> operand_B '(operand_A > operand_B)

The following example tests two variables in a Less Than or Equal To operation. Because both variables have an identical numerical value, the result is TRUE.

SET A="55",B="55" WRITE A'>B

returns 1.

## String Relational Operators

There are two types of relational operators: numeric relational operators and string relational operators. String relational operators use the string interpretation of the operands to produce a Boolean result. You can precede any of the string relational operators with the NOT logical operator (') to obtain the negation of the logical result.

### Binary Equals

Binary Equals tests two operands for string equality. When you apply Binary Equals to two strings, ObjectScript returns a result of TRUE (1) if the two operands are identical strings with identical character sequences and no intervening characters, including spaces; otherwise it returns a result of FALSE (0). For example:

WRITE "SEVEN"="SEVEN"

returns TRUE (1).

Binary Equals does not imply any numeric interpretation of either operand. For example, the following statement produces a value of FALSE (0), even though the two operands are numerically identical:

WRITE "007"="7"

returns FALSE (0).

You can use Binary Equals to test for numeric equality if both operands have a numeric value. For example:

WRITE 007=7

returns TRUE (1).

You can also force a numeric conversion by using the Unary Arithmetic Positive. For example:

WRITE +"007"="7"

returns TRUE (1).

If the two operands are of different types, both operands are converted to strings and those strings are compared. Note that this may cause inaccuracy because of rounding and the number of significant digits for conversion to string. For example:

WRITE "007"=7,! // converts 7 to "7", so FALSE (0) WRITE 007="7",! // converts 007 to "7", so TRUE (1) WRITE 17.1=$DOUBLE(17.1),! // converts both numbers to "17.1", so TRUE (1) WRITE 1.2345678901234567=$DOUBLE(1.2345678901234567),! // compares "1.2345678901234567" to "1.23456789012346", so FALSE (0)

#### Not Equals

You can specify a Not Equals operation by using the Unary Not operator with Binary Equals. You can express the Not Equals operation in two ways:

operand '= operand '(operand = operand)

Not Equals reverses the truth value of the Binary Equals operator applied to both operands. If the two operands are not identical, the result is TRUE (1). If the two operands are identical, the result is FALSE (0).

### Binary Contains

Binary Contains tests whether the sequence of characters in the right operand is a substring of the left operand. If the left operand contains the character string represented by the right operand, the result is TRUE (1). If the left operand does not contain the character string represented by the right operand, the result is FALSE (0). If the right operand is the null string, the result is always TRUE.

The following example tests whether L contains S. Because L does contain S, the result is TRUE (1).

SET L="Steam Locomotive",S="Steam" WRITE L[S

returns TRUE (1).

The following example tests whether P contains S. Because the character sequence in the strings is different (a period in P and an exclamation point in S), the result is FALSE (0).

SET P="Let's play.",S="Let's play!" WRITE P[S

returns FALSE (0).

#### Does Not Contain

You can produce a Does Not Contain operation by using the Unary Not character with Binary Contains in either of the following equivalent formats:

operand A '[ operand B

'(operand A [ operand B)

The Does Not Contain operation returns TRUE if operand A does not contain the character string represented by operand B and FALSE if operand A does contain the character string represented by operand B. For example,

SET P="Beatles", S="Mick Jagger" WRITE P'[S

returns 1.

### Binary Follows

Binary Follows tests whether the characters in the left operand come after the characters in the right operand in ASCII collating sequence. Binary Follows tests both strings starting with the left most character in each. The test ends when either:

A character is found in the left operand that is different from the character at the corresponding position in the right operand.

There are no more characters left to compare in either of the operands.

ObjectScript returns a value of TRUE if the first unique character in the left operand has a higher ASCII value than the corresponding character in the right operand (that is, if the character in the left operand comes after the character in the right operand in ASCII collating sequence.) If the right operand is shorter than the left operand, but otherwise identical, ObjectScript also returns a value of TRUE.

ObjectScript returns a value of FALSE if any of the following conditions prevail:

The first unique character in the left operand has a lower ASCII value than the corresponding character in the right operand.

The left operand is identical to the right operand.

The left operand is shorter than the right operand, but otherwise identical.

The following example tests whether the string LAMPOON follows the string LAMP in ASCII collation order. The result is TRUE.

WRITE "LAMPOON"]"LAMP"

returns TRUE (1).

The following example tests whether the string in B follows the string in A. Because BO follows BL in ASCII collation sequence, the result is TRUE.

SET A="BLUE",B="BOY" WRITE B]A

returns TRUE (1).

#### Not Follows

You can produce a Not Follows operation by using the Unary Not operator with Binary Follows in either of the following equivalent formats:

operand A ']operand B '(operand A ] operand B)

If all characters in the operands are identical or if the first unique character in operand A has a lower ASCII value that the corresponding character in operand B, the Not Follows operation returns a result of TRUE. If the first unique character in operand A has a higher ASCII value that the corresponding character in operand B, the Not Follows operation returns a result of FALSE.

In the following example, because C in CDE does follow A in ABC, the result is FALSE.

WRITE "CDE"']"ABC",! WRITE '("CDE"]"ABC")

returns FALSE (0).

### Binary Sorts After

Binary Sorts after tests whether the left operand sorts after the right operand in numeric subscript collation sequence. In numeric collation sequence, the null string collates first, followed by canonical numbers in numeric order with negative numbers first, zero next, and positive numbers, followed lastly by nonnumeric values.

The Binary Sorts After operator returns a TRUE (1) if the first operand sorts after the second and a FALSE (0) if the first operand does not sort after the second. For example:

WRITE 122]]2

returns TRUE (1).

WRITE "LAMPOON"]]"LAMP"

returns TRUE (1).

#### Not Sorts After

You can produce a Not Sorts After operation by using the Unary Not operator with Binary Sorts After in either of the following equivalent formats:

operand A ']] operand B '(operand A ]] operand B)

If operand A is identical to operand B or if operand B sorts after operand A, then ObjectScript returns a result of TRUE. If operand A sorts after operand B, ObjectScript returns a result of FALSE.

## Pattern Matching

Caché supports two systems of pattern matching:

ObjectScript pattern matching, described here, a syntax that demarcates the beginning of a pattern string with a question mark (?) or its inverse ('?).

Regular Expressions, a pattern match syntax supported (with variants) by many software vendors. Regular expressions can be used with the $LOCATE and $MATCH functions, and with methods of the %Regex.Matcher class. They are described in a separate chapter of this manual.

These pattern match systems are wholly separate. Each pattern match system can only be used in its own context. It is, however, possible to combine pattern match tests from different pattern match systems using logical AND and OR syntax, as shown in the following example:

SET var = "abcDEf" IF (var ?.e2U.e) && $MATCH(var, "^.{3,7}") { WRITE "It's a match!"} ELSE { WRITE "No match"}

The ObjectScript pattern tests that the string must contain two consecutive uppercase letters. The Regular Expression pattern tests that the string must contain between 3 and 7 characters.

### ObjectScript Pattern Matching

The ObjectScript pattern match operator tests whether the characters in its left operand are correctly specified by the pattern in its right operand. It returns a boolean value. The pattern match operator produces a result of TRUE (1) when the pattern correctly specifies the pattern of characters in the left operand. It produces a result of FALSE (0) if the pattern does not correctly specify the pattern of characters in the left operand.

For example, the following tests if the string ssn contains a valid U.S. Social Security Number (3 digits, a hyphen, 2 digits, a hyphen, and 4 digits):

SET ssn="123-45-6789" SET match = ssn ?3N1"-"2N1"-"4N WRITE match

The left operand (the test value) and the right operand (the pattern) are distinguished by the leading ? of the right operand. The two operands may be separated by one or more blank spaces, or not separated by blank spaces, as shown in the following equivalent program example:

SET ssn="123-45-6789" SET match = ssn?3N1"-"2N1"-"4N WRITE match

No white space is permitted following the ? operator. White space within the pattern must be within a quoted string and is interpreted as being part of the pattern.

The general format for a pattern match operation is as follows:

operand?pattern

operand | An expression that evaluates to a string or number, the characters of which you want to test for a pattern. |

pattern | A pattern match sequence beginning with a ? character (or with ‘? for a not-match test). The pattern sequence can be one of the following: a sequence of one or more pattern-elements; an indirect reference that evaluates to a sequence of one or more pattern-elements |

A pattern-element consists of one of the following:

repeat-count pattern-codes

repeat-count literal-string

repeat-count alternation

repeat-count | A repeat count — the exact number of instances to be matched. The repeat-count can evaluate to an integer or to the period wildcard character (.). Use the period to specify any number of instances. |

pattern-codes | One or more pattern codes. If more than one code is specified, the pattern is satisfied by matching any one of the codes. |

literal-string | A literal string enclosed in double quotes. |

alternation | A set of pattern-element sequences to choose from (in order to perform pattern matching on a segment of the operand string). This provides logical OR capability in pattern specifications. |

Use a literal string enclosed in double quotes in a pattern if you want to match a specific character or characters. In other situations, use the special pattern codes provided by ObjectScript. Characters associated with a particular pattern code are (to some extent) locale-dependent. The following table shows the available pattern codes and their meanings:

Code | Meaning |
---|---|

A | Matches any uppercase or lowercase alphabetic character. The 8-bit character set for the locale defines what is an alphabetic character. For the English locale (based on the Latin-1 character set), this includes the ASCII values 65 through 90 (A through Z), 97 through 122 (a through z), 170, 181, 186, 192 through 214, 216 through 246, and 248 through 255. |

C | Matches any of the ASCII control characters (ASCII values 0 through 31 and the extended ASCII values 127 through 159). |

E | Matches any character, including non-printing characters, whitespace characters, and control characters. |

L | Matches any lowercase alphabetic character. The 8-bit character set for the locale defines what is a lowercase character. For the English locale (based on the Latin-1 character set) this includes the ASCII values 97 through 122 (a through z), 170, 181, 186, 223 through 246, and 248 through 255. |

N | Matches any of the 10 numeric characters 0 through 9 (ASCII 48 through 57). |

P | Matches any punctuation character. The character set for the locale defines what is a punctuation character for an extended (8-bit) ASCII character set. For the English locale (based on the Latin-1 character set), this includes the ASCII values 32 through 47, 58 through 64, 91 through 96, 123 through 126, 160 through 169, 171 through 177, 180, 182 through 184, 187, 191, 215, and 247. |

U | Matches any uppercase alphabetic character. The 8-bit character set for the locale defines what is an uppercase character. For the English locale (based on the Latin-1 character set), this includes the ASCII values 65 through 90 (A through Z), 192 through 214, and 216 through 222. |

R
B
M |
Matches Cyrillic 8–bit alphabetic character mappings. R matches any Cyrillic character (ASCII values 192 through 255). B matches uppercase Cyrillic characters (ASCII values 192 through 223). M matches lowercase Cyrillic characters (ASCII values 224 through 255). These pattern codes are only meaningful in the Russian 8-bit Windows locale (ruw8). In other locales they execute successfully but fail to match any character. |

ZFWCHARZ | Matches any of the characters in the Japanese ZENKAKU character set. ZFWCHARZ matches full-width characters, such as those in the Kanji range, as well as many non-Kanji characters that occupy a double cell when displayed by some terminal emulators. ZFWCHARZ also matches the 303 surrogate pair characters defined in the JIS2004 standard, treating each surrogate pair as a single character. For example, the surrogate pair character $WC(131083) matches ?1ZFWCHARZ. This pattern match code requires a Japanese locale. |

ZHWKATAZ | Matches any of the characters in the Japanese HANKAKU Kana character set. These are Unicode values 65377 (FF61) through 65439 (FF9F). This pattern match code requires a Japanese locale. |

Pattern codes are not case-sensitive; you can specify them in either uppercase or lowercase. For example, ?5N is equivalent to ?5n. You can specify multiple pattern codes to match a specific character or string. For example, ?1NU matches either a number or an uppercase letter.

As stated in the Caché Glossary of Terms, the ASCII character set refers to an extended, 8-bit character set, rather than the more limited, 7-bit character set.

Pattern matching with double-quote characters can yield inconsistent results, especially when data is supplied from Caché implementations using different NLS locales. The straight double-quote character ($CHAR(34) = ") matches as a punctuation character. Directional double-quote characters (curly quotes) do not match as punctuation characters. The 8-bit directional double-quote characters ($CHAR(147) = “ and $CHAR(148) = ”) match as control characters. The Unicode directional double-quote characters ($CHAR(8220) = “ and $CHAR(8221) = ”) do not match as either punctuation or control characters.

The Pattern Match operator differs from the Binary Contains ([) operator. The Binary Contains operator returns TRUE (1) even if only a substring of the left-hand operand matches the right-hand operand. Also, Binary Contains expressions do not provide the range of options available with the Pattern Match operator. In Binary Contains expressions, you can use only a single string as the right-hand operand, without any special codes.

For example, assume that variable var2 contains the value “abc”. Consider the following Pattern Match expression:

SET match = var2?2L

This sets match to FALSE (0) because var2 contains three lowercase characters, not just two.

Here are some examples of basic pattern matching:

PatternMatchTest SET var = "O" WRITE "Is the letter O",! WRITE "...an alphabetic character? " WRITE var?1A,! WRITE "...a numeric character? " WRITE var?1N,! WRITE "...an alphabetic or ",!," a numeric character? " WRITE var?1AN,! WRITE "...an alphabetic or ",!," a ZENKAKU Kanji character? " WRITE var?1AZFWCHARZ,! WRITE "...a numeric or ",!," a HANKAKU Kana character? " WRITE var?1ZHWKATAZN

You can extend the scope of a pattern code by specifying:

### Specifying How Many Times a Pattern Can Occur

To define a range for the number of times that pattern can occur in the target operand, use the form:

n.n

The first n defines the lower limit for the range of occurrences; the second n defines the upper limit.

For the following example, assume that the variable var3 contains multiple copies of the string “AB” (and no other characters). 1.4 indicates that from one to four occurrences of “AB” are recognized:

SET match = var3?1.4"AB"

If var3 =“ABABAB”, the expression returns a result of TRUE (1) even though var3 contains only three occurrences of “AB”.

As another example, consider the following expression:

SET match = var4?1.6A

This expression checks to see whether var4 contains from one to six alphabetic characters. A result of FALSE (0) is returned if var4 contains zero or more than six alphabetic characters, or contains a non-alphabetic character.

If you omit either n, ObjectScript supplies a default. The default for the first n is zero (0). The default for the second n is any number. Consider the following example:

SET match = var5?.E1"AB".E

This example returns a result of TRUE (1) as long as var5 contains at least one occurrence of the pattern string “AB”.

### Specifying Multiple Patterns

To define multiple patterns, you can combine n and pattern in a sequence of any length. Consider the following example:

SET match = date?2N1"/"2N1"/"2N

This expression checks for a date value in the format mm/dd/yy. The string “4/27/98” would return FALSE (0) because the month has only one digit. To detect both one and two digit months, you could modify the expression as:

SET match = date?1.2N1"/"2N1"/"2N

Now the first pattern match (1.2N) accepts either 1 or 2 digits. It uses the optional period (.) to define a range of acceptable occurrences as described in the previous section.

### Specifying a Combination Pattern

To define a combination pattern, use the form:

Pattern1Pattern2

With a combination pattern, the sequence consisting of pattern1 followed by pattern2 is checked against the target operand. For example, consider the following expression:

SET match = value?3N.4L

This expression checks for a pattern in which three numeric digits are followed by zero to four lowercase alphabetic characters. The expression returns TRUE (1) only if the target operand contains exactly one occurrence of the combined pattern. For example, the strings “345g” and “345gfij” would qualify, but “345gfijhkbc” “345gfij276hkbc” would not.

### Specifying an Indefinite Pattern

To define an indefinite pattern, use the form:

.pattern

With an indefinite pattern, the target operand is checked for an occurrence of pattern, but any number of occurrences is accepted (including zero occurrences). For example, consider the expression:

SET match = value?.N

This expression returns TRUE (1) if the target operand contains zero, one, or more than one numeric character, and contains no characters of any other type.

### Specifying an Alternating Pattern (Logical OR)

Alternation allows for testing if an operand matches one or more of a group of specified pattern sequences. It provides logical OR capability to pattern matching.

An alternation has the following syntax:

( pattern-element sequence {, pattern-element sequence }...)

Thus, the following pattern returns TRUE (1) if val contains one occurrence of the letter “A” or one occurrence of the letter “B”.

SET match = value?1(1"A",1"B")

You can have nested alternation patterns, as in the following pattern match expression:

SET match = value?.(.(1A,1N),1P)

For example, you may want to validate a U.S. telephone number. At a minimum, the phone number must be a 7-digit phone number with a hyphen (-) separating the third and fourth digits. For example:

nnn-nnnn

The phone number can also include a three-digit area code that must either have surrounding parentheses or be separated from the rest of the number by a hyphen. For example:

(nnn) nnn-nnnn nnn-nnn-nnnn

The following pattern match expressions describe three valid forms of a U.S. telephone number:

SET match = phone?3N1"-"4N SET match = phone?3N1"-"3N1"-"4N SET match = phone?1"("3N1") "3N1"-"4N

Without an alternation, the following compound Boolean expression would be required to validate any form of U.S. telephone number.

SET match = ( (phone?3N1"-"4N) || (phone?3N1"-"3N1"-"4N) || (phone?1"("3N1") "3N1"-"4N) )

With an alternation, the following single pattern can validate any form of U.S. telephone number:

SET match = phone?.1(1"("3N1") ",3N1"-")3N1"-"4N

The alternation in this example allows the area code component of the phone number to be satisfied by either 1"("3N1") " or 3N1"-". The alternation count range of 0 to 1 indicates that the operand phone can have 0 or 1 area code components.

Alternations with a repeat count greater than one (1) can produce many combinations of acceptable patterns. The following alternation matches the string shown and matches 26 other three-character strings.

SET match = "CAT"?3(1"C",1"A",1"T")

### Using Incomplete Patterns

If a pattern match successfully describes only part of a string, then the pattern match returns a result of FALSE (0). That is, there cannot be any string left over when the pattern is exhausted. The following expression evaluates to a result of FALSE (0) because the pattern does not match the final “R”:

SET match = "RAW BAR"?.U1P2U

### Multiple Pattern Interpretations

There can be more than one interpretation of a pattern as it is matched against an operand. For example, the following expression can be interpreted in two ways:

SET match = "/////A#####B$$$$$"?.E1U.E

The first “.E” matches the substring “/////”, the 1U matches the “A”, and the second “.E” matches the substring “#####B$$$$$”.

The first “.E” matches the substring “/////A#####”, the 1U matches the character “B”, and the second “.E” matches the substring “$$$$$”.

As long as at least one interpretation of the expression is TRUE (1), then the expression has a value of TRUE.

### Not Match Operator

You can produce a Not Match operation by using the Unary Not operator ( ' ) with Pattern Match:

operand'?pattern

Not Match reverses the truth value of the Pattern Match. If the characters in the operand cannot be described by the pattern, then Not Match returns a result of TRUE (1). If the pattern matches all of the characters in the operand, then Not Match returns a result of FALSE (0).

The following example uses the Not Match operator:

WRITE !,"abc" ?3L WRITE !,"abc" '?3L WRITE !,"abc" ?3N WRITE !,"abc" '?3N WRITE !,"abc" '?3E

### Pattern Complexity

A pattern match with multiple alternations and indefinite patterns, when applied to a long string, can recurse many levels into the system stack. In rare cases, this recursion can rise to several thousand levels, threatening stack overflow and a process crash. When this extreme situation occurs, Caché issues a <COMPLEX PATTERN> error rather than risking a crash of the current process.

In the unusual event that such an error occurs, it is recommended that you either simplify your pattern, or apply it to shorter subunits of the original string.

You can interrupt pattern execution by issuing a Crtl-C key command, resulting in an <INTERRUPT> error.

## Indirection

The ObjectScript indirection operator (@) allows you to assign values indirectly to variables. Indirection is a technique that provides dynamic runtime substitution of part or all of a command line, a command, or a command argument by the contents of a data field. Caché performs the substitution before execution of the associated command.

Although indirection can promote more economical and more generalized coding than would be otherwise available, it is never essential. You can always duplicate the effect of indirection by other means, such as by using the XECUTE command.

You should use indirection only in those cases where it offers a clear advantage. Indirection can have an impact on performance because Caché performs the required evaluation at runtime, rather than during the compile phase. Also, if you use complicated indirections, be sure to document your code clearly. Indirections can sometimes be difficult to decipher.

Indirection is specified by the indirection operator (@) and, except for subscript indirection, takes the form:

@variable

where variable identifies the variable from which the substitution value is to be taken. All variables referenced in the substitution value are public variables, even when used in a procedure. The variable can be an array node.

The following routine illustrates that indirection looks at the entire variable value to its right.

IndirectionExample SET x = "ProcA" SET x(3) = "ProcB" ; The next line will do ProcB, NOT ProcA(3) DO @x(3) QUIT ProcA(var) WRITE !,"At ProcA" QUIT ProcB(var) WRITE !,"At ProcB" QUIT

Caché recognizes five types of indirection:

Name indirection

Pattern indirection

Argument indirection

Subscript indirection

$TEXT argument indirection

Which type of indirection is performed depends on the context in which the @variable occurs. Each type of indirection is described separately below.

Indirection cannot be used with dot syntax. This is because dot syntax is parsed at compile time, not at runtime.

### Name Indirection

In name indirection, the indirection evaluates to a variable name, a line label, or a routine name. Caché substitutes the contents of variable for the expected name before executing the command.

Name indirection can only access public variables. For further details, refer to the User-defined Code chapter of this manual.

When you use indirection to reference a named variable, the value of the indirection must be a complete global or local variable name, including any necessary subscripts. In the following example, Caché sets the variable B to the value of 6.

SET Y = "B",@Y = 6

If a call attempts to use indirection to get or set the value of object properties, it may result in an error. Do not use calls of this kind, as they attempt to bypass property accessor methods (<PropertyName>Get and <PropertyName>Set). Instead, use the $CLASSMETHOD, $METHOD, and $PROPERTY functions, which are designed for this purpose; see the section “Dynamically Accessing Objects” in the “Using Objects with ObjectScript” chapter of Using Caché Objects for more information.

When you use indirection to reference a line label, the value of the indirection must be a syntactically valid line label. In the following example, Caché sets D to:

The value of the line label FIG if the value of N is 1.

The value of the line label GO if the value of N is 2.

The value of STOP in all other cases.

Later, Caché passes control to the label whose value was given to D.

B SET D = $SELECT(N = 1:"FIG",N = 2:"GO",1:"STOP") ; ... LV GOTO @D

When you use indirection to reference a routine name, the value of the indirection must be a syntactically valid routine name. In the following example, name indirection is used on the DO command to supply the appropriate procedure name. At execution time, the contents of variable loc are substituted for the expected name:

Start READ !,"Enter choice (1, 2, or 3): ",num SET loc = "Choice"_num DO @loc RETURN Choice1() ; ... Choice2() ; ... Choice3() ; ...

Name indirection can substitute only a name value. The second SET command in the following example returns an error message because of the context. When evaluating the expression to the right of the equal sign, Caché interprets @var1 as an indirect reference to a variable name, not a numeric value.

SET var1 = "5" SET x = @var1*6

You can recast the example to execute correctly as follows:

SET var1 = "var2",var2 = 5 SET x = @var1*6

### Pattern Indirection

Pattern indirection is a special form of indirection. The indirection operator replaces a pattern match. The value of the indirection must be a valid pattern. (Pattern matching is described under "Pattern Matching".) Pattern indirection is especially useful when you want to select several possible patterns and then use them as a single pattern.

In the following example, indirection is used with pattern matching to check for a valid U.S. Postal (ZIP) code. Such codes can take either a five-digit (nnnnn) or a nine-digit (nnnnnnnnn) form.

The first SET command sets the pattern for the five-digit form. The second SET command sets the pattern for the nine-digit form. The second SET command is executed only if the postconditional expression ($LENGTH(zip) = 10) evaluates to TRUE (nonzero), which occurs only if the user inputs the nine digit form.

GetZip() SET pat = "5N" READ !,"Enter your ZIP code (5 or 9 digits): ",zip SET:($LENGTH(zip)=10) pat = "5N1""-""4N" IF zip'?@pat { WRITE !,"Invalid ZIP code" DO GetZip() }

The use of indirection with pattern matching is a convenient way to localize the patterns used in an application. In this case, you could store the patterns in separate variables and then reference them with indirection during the actual pattern tests. (This is also an example of name indirection.) To port such an application, you would have to modify only the pattern variables themselves.

### Argument Indirection

In argument indirection, the indirection evaluates to one or more command arguments. By contrast, name indirection applies only to part of an argument.

To illustrate this difference, compare the following example with the example given under Name Indirection.

Start SET rout = "^Test1" READ !,"Enter choice (1, 2, or 3): ",num SET loc = "Choice"_num_rout DO @loc QUIT

In this case, @loc is an example of argument indirection because it supplies the complete form of the argument (that is, label^routine). In the name indirection example, @loc is an example of name indirection because it supplies only part of the argument (the label name, whose entry point is assumed to be in the current, rather than a separate, routine).

In the following example, the second SET command is an example of name indirection (only part of the argument, the name of the variable), while the third SET command is an example of argument indirection (the entire argument).

SET a = "var1",b = "var2 = 3*4" SET @a = 5*6 SET @b WRITE "a = ",a,! WRITE "b = ",b,!

### Subscript Indirection

Subscript indirection is an extended form of name indirection. In subscript indirection, the value of the indirection must be the name of a local or global array node. Subscript indirection is syntactically different than the other forms of indirection. Subscript indirection uses two indirection operators in the following format:

@array@(subscript)

Assume that you have a global array called ^client in which the first-level node contains the client’s name, the second-level node contains the client’s street address, and the third-level node contains the client’s city, state, and ZIP code. To write out the three nodes for the first record in the array, you can use the following form of the WRITE command:

WRITE !,^client(1),!,^client(1,1),!,^client(1,1,1)

When executed, this command might produce output similar to following:

John Jones 42 Arnold St. Boston, MA 02745

To write out a range of records (say, the first 10), you could modify the code so that the WRITE is executed within a FOR loop. For example:

FOR i = 1:1:10 { WRITE !,^client(i),!,^client(i,1),!,^client(i,1,1) }

As the FOR loop executes, the variable i is incremented by 1 and used to select the next record to be output.

While more generalized than the previous example, this is still very specialized code because it explicitly specifies both the array name and the number of records to output.

To transform this code into a more generalized form that would allow a user to list a range of records from any array (global or local) that stores name, street, and city information in three node levels, you could use subscript indirection as shown in the following example:

Start READ !,"Output Name, Street, and City info.",! READ !,"Name of array to access: ",name READ !,"Global or local (G or L): ",gl READ !,"Start with record number: ",start READ !,"End with record number: ",end IF (gl["L")!(gl["l") {SET array = name} ELSEIF (gl["G")!(gl["g") {SET array = "^"_name} SET x = 1,y = 1 FOR i = start:1:end {DO Output} RETURN Output() WRITE !,@array@(i) WRITE !,@array@(i,x) WRITE !,@array@(i,x,y) QUIT

The WRITE commands in the Output subroutine use subscript indirection to reference the requested array and the requested range of records.

In the evaluation of subscript indirection, if the instance of indirection refers to an unsubscripted global or local variable, the value of the indirection is the variable name and all characters to the right of the second Indirection operator, including the parentheses.

For a local variable, the maximum number of subscript levels is 255. Subscript indirection cannot reference more than 254 subscripts for a multidimensional object property. For a global variable, the maximum number of subscript levels depends on the subscript, and may be higher than 255, as described in Global Structure in Using Caché Globals. Attempting to use indirection to populate a local variable with more than 255 subscript levels results in a <SYNTAX> error.

A class parameter can be used as the base for subscript indirection in the same way that a local or global variable can be used as the base. For example, you can perform subscript indirection using a class parameter with the following syntax:

SET @..#myparam@(x,y) = "stringval"

### $TEXT Argument Indirection

As its name implies, $TEXT argument indirection is allowed only in the context of a $TEXT function argument. The value of the indirection must be a valid $TEXT argument.

You use $TEXT argument indirection primarily as a convenience to avoid multiple forms of indirection that produce the same result. For example, if the local variable LINE contains the entry reference " START^MENU", you can use name indirection to the line label and to the routine name to obtain the text for the line, as follows:

SET LINETEXT = $TEXT(@$PIECE(LINE,"^",1)^@$PIECE(LINE,"^",2))

You can use $TEXT argument indirection to produce the same result in a simpler manner, as follows:

SET LINETEXT = $TEXT(@LINE)