Expressions
Expressions are made up of operators and operands. Most Object Pascal operators are
binary; they take two operands. The rest are unary and take only one operand. Binary operators use the usual algebraic form (for example, A
- B). A unary operator always precedes its operand (for example, -B).
In more complex expressions, rules of precedence clarify the order in which operations are performed.
Table 5-1 Precedence of operators
Operators |
Precedence |
Categories |
---|---|---|
@, not |
first (high) |
unary operators |
*, /, div, mod, and, shl, shr, as |
second |
multiplying operators |
+,-, or, xor |
third |
adding operators |
=, <>, <, >, <=, >=, in, is |
fourth (low) |
relational operators |
There are three basic rules of precedence:
-
An operand between two operators of different precedence is bound to
the operator with higher precedence.
-
An operand between two equal operators is bound to the one on its
left.
-
Expressions within parentheses are evaluated prior to being treated
as a single operand
Operations with equal precedence are normally performed from left to right, although the compiler may rearrange the operands to generate optimum code.
Expression syntax
The precedence rules follow from the syntax of expressions, which are built from factors, terms, and simple expressions.
A factor’s syntax follows:
factor
A function call activates a function and denotes the value returned by the function. See “Function calls” on page 50.
A set constructor denotes a value of a set type. See “Set constructors” on page 50. A value typecast changes the type of a value. See “Value typecasts” on page 51.
An address factor computes the address of a variable, procedure, function, or method. See “The @ operator” on page 49.
An unsigned constant has the following syntax:
unsigned constant
These are some examples of factors:
X { Variable reference }
@X { Pointer to a variable }
15 { Unsigned constant }
(X + Y + Z) { Subexpression }
Sin(X / 2) { Function call } exit['0'..'9', 'A'..'Z'] { Set constructor }
not Done { Negation of a Boolean }
Char(Digit + 48) { Value typecast }
Terms apply the multiplying operators to factors:
term
Here are some examples of terms:
X * Y
Z / (1 - Z)
Y shl 2
(X <= Y) and (Y < Z)
Simple expressions apply adding operators and signs to terms:
simple expression
Here are some examples of simple expressions:
X + Y
-X
Hue1 + Hue2 I * J + 1
An expression applies the relational operators to simple expressions:
expression
Here are some examples of expressions:
X = 1.5
Done <> Error
(I < J) = (J < K)
C in Hue1
Operators
Operators are classified as arithmetic operators, logical operators, string operators, character-pointer operators, set operators, relational operators, and the @ operator.
Arithmetic operators
The following tables show the types of operands and results for binary and unary arithmetic operations.
Table 5-2 Binary arithmetic operations
Operator |
Operation |
Operand types |
Result type |
---|---|---|---|
+ |
addition |
integer type |
integer type |
real type |
real type |
||
- |
subtraction |
integer type |
integer type |
real type |
real type |
||
* |
multiplication |
integer type |
integer type |
real type |
real type |
||
/ |
division |
integer type |
real type |
real type |
real type |
||
div |
integer division |
integer type |
integer type |
mod |
remainder |
integer type |
integer type |
The + operator is also used as a string or set operator, and the +, -, and * operators are also used as set operators.
Table 5-3 Unary arithmetic operations
Operator |
Operation |
Operand types |
Result type |
---|---|---|---|
+ - |
sign identity sign negation |
integer type real type integer type real type |
integer type real type integer type real type |
Any operand whose type is a subrange of an ordinal type is treated as if it were of the ordinal type.
If both operands of a +, -,*, div, or mod operator are of an integer type, the result type is of the common type of the two operands. For a definition of common types, see page 12.
If one or both operands of a +, -, or * operator are of a real type, the type of the result is Real in the {$N-} state or Extended in the {$N+} state.
If the operand of the sign identity or sign negation operator is of an integer type, the result is of the same integer type. If the operator is of a real type, the type of the result is Real or Extended.
The value of X / Y is always of type Real or Extended regardless of the operand types. A run-time error occurs if Y is zero.
The value of I div J is the mathematical quotient of I / J, rounded in the direction of zero to an integer-type value. A run-time error occurs if J is zero.
The mod operator returns the remainder obtained by dividing its two operands; that is,
I mod J = I - (I div J) * J
The sign of the result of mod is the same as the sign of I. A run-time error occurs if J
is zero.
Logical operators
The types of operands and results for logical operations are shown in the following table.
Table 5-4 Logical operations
Operator |
Operation |
Operand types |
Result type |
---|---|---|---|
not |
bitwise negation |
integer type |
Boolean |
and |
bitwise and |
integer type |
Boolean |
or |
bitwise or |
integer type |
Boolean |
xor |
bitwise xor |
integer type |
Boolean |
shl |
Operation |
integer type |
Boolean |
shr |
Operation |
integer type |
Boolean |
If the operand of the not operator is of an integer type, the result is of the same integer type. The not operator is a unary operator.
If both operands of an and, or, or xor operator are of an integer type, the result type is the common type of the two operands.
The operations I shl J and Ishr J shift the value of I to the left right by J bits. The result type is the same as the type of I.
Boolean operators
The types of operands and results for Boolean operations are shown in the following table.
Table 5-5 Boolean operations
Operator |
Operation |
Operand types |
Result type |
---|---|---|---|
not |
negation |
Boolean type |
Boolean |
and |
logical and |
Boolean type |
Boolean |
or |
logical or |
Boolean type |
Boolean |
xor |
logical xor |
Boolean type |
Boolean |
Normal Boolean logic governs the results of these operations. For instance, A and B
is True only if both A and B are True.
Object Pascal supports two different models of code generation for the and and or
operators: complete evaluation and short-circuit (partial) evaluation.
Complete evaluation means that every operand of a Boolean expression built from the and and or operators is guaranteed to be evaluated, even when the result of the entire expression is already known. This model is convenient when one or more operands of an expression are functions with side effects that alter the meaning of the program.
Short-circuit evaluation guarantees strict left-to-right evaluation and that evaluation stops as soon as the result of the entire expression becomes evident. This model is convenient in most cases because it guarantees minimum execution time, and usually minimum code size. Short-circuit evaluation also makes possible the evaluation of constructs that would not otherwise be legal. For example,
while (I <= Length(S)) and (S[I] <> ' ') do
Inc(I);
while (P <> nil) and (P^.Value <> 5) do
P := P^.Next;
In both cases, the second test isn’t evaluated if the first test is False.
The evaluation model is controlled through the $B compiler directive. The default state is {$B-}, and in this state, the compiler generates short-circuit evaluation code. In the {$B+} state, the compiler generates complete evaluation.
Because Standard Pascal doesn’t specify which model should be used for Boolean expression evaluation, programs dependent on either model aren’t truly portable. You may decide, however, that sacrificing portability is worth the gain in execution speed and simplicity provided by the short-circuit model.
String operator
The types of operands and results for string operation are shown in the following table.
Table 5-6 String operation
+ concatenation string type, Char type, or packed string type
string type
Object Pascal allows the + operator to be used to concatenate two string operands. The result of the operation S + T**,** where S and T are of a string type, a *Char* type, or a packed string type, is the concatenation of *S* and *T*. The result is compatible with any string type (but not with *Char* types and packed string types). If the resulting string is longer than 255 characters, it’s truncated after character 255.
Character-pointer operators
The extended syntax (enabled using a {$X+} compiler directive) supports a number of character-pointer operations. The plus (+) and minus (-) operators can be used to increment and decrement the offset part of a pointer value, and the minus operator
can be used to calculate the distance (difference) between the offset parts of two character pointers. Assuming that P and Q are values of type PChar and I is a value of type Word, these constructs are allowed:
Table 5-7 Permitted PChar constructs
P + I Add I to the offset part of P
I + P Add I to the offset part of P
P - I Subtract I from the offset part of P
P - Q Subtract offset part of Q from offset part of P
The operations P + I and I + P adds I to the address given by P, producing a pointer that points I characters after P. The operation P - I subtracts I from the address given by P, producing a pointer that points I characters before P.
The operation P**-** *Q* computes the distance between *Q (the lower address) and P* (the higher address), resulting in a value of type *Word* that gives the number of characters between *Q* and *P*. This operation assumes that *P* and *Q* point within the same character array. If the two character pointers point into different character arrays, the result is undefined.
Set operators
The types of operands for set operations are shown in the following table.
Table 5-8 Set operations
Operator |
Operation |
Operand types |
---|---|---|
+ |
union |
compatible set types |
- |
difference |
compatible set types |
* |
intersection |
compatible set types |
The results of set operations conform to the rules of set logic:
-
An ordinal value C is in A + B only if C is in A or
B.
-
An ordinal value C is in A - B only if C is in A and
not in B
-
An ordinal value C is in A * B only if C is in both A
and B.
If the smallest ordinal value that is a member of the result of a set operation is A and the largest is B, then the type of the result is set of A..B.
Relational operators
The types of operands and results for relational operations are shown in the following table.
Table 5-9 Relational operations
= equal compatible simple, class, class reference, pointer, set, string, or packed string
Boolean
types
<> not equal compatible simple, class, class reference, pointer, set, string, or packed string types
< less than compatible simple, string, packed string types, or PChar
> greater than compatible simple, string, packed string types, or PChar
<= less than or equal to compatible simple, string, packed string types, or PChar
Boolean
Boolean Boolean Boolean
>= greater than or
equal to
compatible simple, string, or packed string types, or PChar
Boolean
<= subset of compatible set types Boolean
>= superset of compatible set types Boolean
in member of left operand, any ordinal type T; right operand, set whose base is compatible with T
Boolean
Comparing simple types
When the operands =, <>, <, >, >=, or <= are of simple types, they must be compatible types; however, if one operand is of a real type, the other can be of an integer type.
Comparing strings
The relational operators =, <>, <, >>, >=, and <= compare strings according to the ordering of the extended ASCII character set. Any two string values can be compared because all string values are compatible.
A character-type value is compatible with a string-type value. When the two are compared, the character-type value is treated as a string-type value with length 1. When a packed string-type value with N components is compared with a string- type value, it’s treated as a string-type value with length N.
Comparing packed strings
The relational operators =, <>, <, >, >=, and <= can also be used to compare two packed string-type values if both have the same number of components. If the number of components is N, then the operation corresponds to comparing two strings, each of length N.
Comparing pointers and references
The operators = and <> can be used on compatible pointer-type, class-type, class- reference-type operands. Two pointers are equal only if they point to the same object.
Comparing character pointers
The extended syntax (enabled using a {$X+} compiler directive) allows the >, <, >=, and <= operators to be applied to PChar values. Note, however, that these relational tests assume that the two pointers being compared point within the same character array, and for that reason, the operators only compare the offset parts of the two
pointer values. If the two character pointers point into different character arrays, the result is undefined.
Comparing sets
If A and B are set operands, their comparisons produce these results**:**
- A = B is True only if A and B contain exactly the same
members; otherwise, A <>
B.
-
A <= B is True only if every member of A is also a member of B.
-
A >= B is True only if every member of B is also a member of A.
Testing set membership
The in operator returns True when the value of the ordinal-type operand is a member of the set-type operand; otherwise, it returns False.
Class operators
Object Pascal defines two operators, is and as, that operate on class and object references. See Chapter 9, “Class types.”
The @ operator
The @ operator is used in an address factor to compute the address of a variable, procedure, function, or method.
address factor
The @ operator returns the address of its operand, that is, it constructs a pointer value that points to the operand.
@ with a variable
When applied to a variable reference, @ returns a pointer to the variable. The type of the resulting pointer value is controlled through the $T compiler directive: In the
{$T-} state (the default), the result type is Pointer. In other words, the result is an untyped pointer, which is compatible with all other pointer types. In the {$T+} state, the type of the result is ^T, where T is the type of the variable reference. In other words, the result is of a type that is compatible only with other pointers to the type of the variable.
Special rules apply to use of the @ operator with a procedural variable. For more details, see “Procedural types in expressions” on page 52.
@ with a procedure, function, or method
You can apply @ to a procedure, function, or method to produce a pointer to the routine’s entry point. The type of the resulting pointer is always Pointer, regardless of the state of the $T compiler directive. In other words, the result is always an untyped pointer, which is compatible with all other pointer types.
When @ is applied to a method, the method must be specified through a qualified- method identifier (a class-type identifier, followed by a period, followed by a method identifier).
Function calls
A function call activates a function specified by a function identifier, a method designator, a qualified-method designator, or a procedural-type variable reference. The function call must have a list of actual parameters if the corresponding function declaration contains a list of formal parameters. Each parameter takes the place of the corresponding formal parameter according to parameter rules explained in Chapter 8, “Procedures and functions,” on page 75.
function call
actual parameter list
actual parameter
These are some examples of function calls:
Sum(A, 63)
Maximum(147, J) Sin(X + Y)
Eof(F)
Volume(Radius, Height)
In the extended syntax {$X+} mode, function calls can be used as statements; that is, the result of a function call can be discarded. See “Method activations” on page 93, and “Procedural types” on page 22.
Set constructors
A set constructor denotes a set-type value, and is formed by writing expressions within brackets ([]). Each expression denotes a value of the set.
set constructor
member group
The notation [ ] denotes the empty set, which is assignment-compatible with every set type. Any member group X..Y denotes as set members all values in the range X..Y. If X is greater than Y, then X..Y doesn’t denote any members and [X..Y] denotes the empty set.
All expression values in member groups in a particular set constructor must be of the same ordinal type.
These are some examples of set constructors:
[red, C, green]
[1, 5, 10..K mod 12, 23]
['A'..'Z', 'a'..'z', Chr(Digit + 48)]
Value typecasts
The type of an expression can be changed to another type through a value typecast.
value typecast
The expression type and the specified type must both be either ordinal types or pointer types. For ordinal types, the resulting value is obtained by converting the expression. The conversion may involve truncation or extension of the original value if the size of the specified type is different from that of the expression. In cases where the value is extended, the sign of the value is always preserved; that is, the value is sign-extended.
The syntax of a value typecast is almost identical to that of a variable typecast. Value typecasts operate on values, however, not on variables, and therefore they can’t participate in variable references; that is, a value typecast can’t be followed by qualifiers. In particular, value typecasts can’t appear on the left side of an assignment statement. See “Variable typecasts” on page 34.
These are some examples of value typecasts:
Integer('A') Char(48) Boolean(0) Color(2) Longint(@Buffer)
BytePtr(Ptr($40, $49))
Procedural types in expressions
Usually, using a procedural variable in a statement or an expression calls the procedure or function stored in the variable. There is one exception: When the compiler sees a procedural variable on the left side of an assignment statement, it knows that the right side has to represent a procedural value. For example, consider the following program:
type
IntFunc = function: Integer;
var
F: IntFunc;
N: Integer;
function ReadInt: Integer; far; var
I: Integer;
begin
Read(I);
ReadInt := I;
end;
begin
F := ReadInt; { Assign procedural value } N := ReadInt; { Assign function result }
end.
The first statement in the main program assigns the procedural value (address of) ReadInt to the procedural variable F, where the second statement calls ReadInt and assigns the returned value to N. The distinction between getting the procedural value or calling the function is made by the type of the variable being assigned (F or N).
Unfortunately, there are situations where the compiler can’t determine the desired action from the context. For example, in the following statement there is no obvious way the compiler can know if it should compare the procedural value in F to the procedural value of ReadInt to determine if F currently points to ReadInt, or if it should call F and ReadInt and then compare the returned values.
if F = ReadInt then
Edit1.Text := 'Equal';
Object Pascal syntax, however, specifies that the occurrence of a function identifier in an expression denotes a call to that function, so the effect of the preceding statement is to call F and ReadInt, and then compare the returned values. To compare the procedural value in F to the procedural value of ReadInt, the following construct must be used:
if @F = @ReadInt then
Edit1.Text := 'Equal';
When applied to a procedural variable or a procedure or function identifier, the address (@) operator prevents the compiler from calling the procedure, and at the
same time converts the argument into a pointer. @F converts F into an untyped pointer variable that contains an address, and @ReadInt returns the address of ReadInt; the two pointer values can then be compared to determine if F currently refers to ReadInt.
The @ operator is often used when assigning an untyped pointer value to a procedural variable. For example, the GetProcAddress function defined by Windows (in the WinProcs unit) returns the address of an exported function in a DLL as an untyped pointer value. Using the @ operator, the result of a call to GetProcAddress can be assigned to a procedural variable:
type
TStrComp = function(Str1, Str2: Pchar): Integer;
var
StrComp: TStrComp; ƒ
begin
ƒ
@StrComp := GetProcAddress(KernelHandle, 'lstrcmpi'); ƒ
end.
Note To get the memory address of a procedural variable rather than the address stored in it, use a double address (@@) operator. For example, where @P means convert P into an untyped pointer variable, @@P means return the physical address of the variable P.
C h a p t e r