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:

Expressions - 图1factor

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:

Expressions - 图2unsigned 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:

Expressions - 图3term

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:

Expressions - 图4simple 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

Expressions - 图5

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.

Expressions - 图6address 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

Expressions - 图7actual 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.

Expressions - 图8set constructor

Expressions - 图9member 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