Contents | Prev | Next The T Language Specification, Version 2
Spring 2006

CHAPTER 8

Expressions


Much of the work in a program is done by evaluating expressions, either for their side effects, such as assignments to variables, or for their values, which can be used as arguments or operands in larger expressions, or to affect the execution sequence in statements, or both.

This chapter specifies the meanings of expressions and the rules for their evaluation.

8.1 Evaluation, Denotation, and Result

When an expression in a program is evaluated (executed), the result denotes one of two things:

Evaluation of an expression can also produce side effects, because expressions may contain embedded assignments and method invocations.

Each expression occurs either in the main block (§7.1) or in the declaration of some class type that is being declared. In a class declaration the expression might occur in a constructor declaration, in a destructor declaration, or in the code for a method.

8.2 Variables as Values

If an expression denotes a variable, and a value is required for use in further evaluation, then the value of that variable is used. In this context, if the expression denotes a variable or a value, we may speak simply of the value of the expression.

8.3 Type of an Expression

If an expression denotes a variable or a value, then the expression has a type known at compile time. The rules for determining the type of an expression are explained separately below for each kind of expression.

The value of an expression is always assignment compatible (§3.2) with the type of the expression, just as the value stored in a variable is always compatible with the type of the variable. In other words, the value of an expression whose type is T is always suitable for assignment to a variable of type T.

8.4 Expressions and Run-Time Checks

If the type of an expression is an integer, then the value of the expression is an integer. But if the type of an expression is a reference type, then the class of the referenced object, or even whether the value is a reference to an object rather than null, is not necessarily known at compile time. There are a few places in the T programming language where the actual class of a referenced object affects program execution in a manner that cannot be deduced from the type of the expression. They are as follows:

The first of the cases just listed ought never to result in detecting a type error, as it is compile-time constrained to be valid. Thus, a run-time type error can occur only when the actual class of the object referenced by the value to be assigned (either implicitly or explicitly) is not compatible with the actual run-time reference variable or component type of the array. In these cases, the program terminates with a Run-Time error (§8.17).

8.5 Evaluation Order

The T programming language guarantees that the operands of operators appear to be evaluated in a specific evaluation order, namely, from left to right.

It is recommended that code not rely crucially on this specification. Code is usually clearer when each expression contains at most one side effect, as its outermost operation.

8.5.1 Evaluate Left-Hand Operand First

The left-hand operand of a binary operator appears to be fully evaluated before any part of the right-hand operand is evaluated. For example, if the left-hand operand contains an assignment to a variable and the right-hand operand contains a reference to that same variable, then the value produced by the reference will reflect the fact that the assignment occurred first.

8.5.2 Evaluate Operands before Operation

The T programming language also guarantees that every operand of an operator appears to be fully evaluated before any part of the operation itself is performed.

8.5.3 Evaluation Respects Parentheses and Precedence

T programming language implementations must respect the order of evaluation as indicated explicitly by parentheses and implicitly by operator precedence. An implementation may not take advantage of algebraic identities such as the associative law to rewrite expressions into a more convenient computational order unless it can be proven that the replacement expression is equivalent in value and in its observable side effects for all possible computational values that might be involved.

Note that integer addition and multiplication are provably associative in the T programming language.

For example a+b+c, where a, b, and c are main block variables will always produce the same answer whether evaluated as (a+b)+c or a+(b+c); if the expression b+c occurs nearby in the code, a smart compiler may be able to use this common subexpression.

8.5.4 Argument Lists are Evaluated Left-to-Right

In a method or constructor invocation or class instance creation expression, argument expressions may appear within the parentheses, separated by commas. Each argument expression appears to be fully evaluated before any part of any argument expression to its right.

8.6 Primary Expressions

Primary expressions include most of the simplest kinds of expressions, from which all others are constructed: literals, field accesses, method invocations, array accesses and names. A parenthesized expression is also treated syntactically as a primary expression.




Primary:

    ArrayCreationExpression

    Identifier

    PrimaryNoNewArray



PrimaryNoNewArray:

    ParenExpression

    THIS

    FieldAccess

    MethodInvocation

    ArrayAccess

    ClassInstanceCrreationExpression

    Literal



8.6.1 Lexical Literals

A literal denotes a fixed, unchanging value.

The following production from (§1.7) is repeated here for convenience:




Literal: 

	INTEGER_LITERAL

	NULL_LITERAL



The type of a literal is determined as follows:

8.6.2 this

The keyword this may be used only in the body of an method, constructor, or destructor.

When used as a primary expression, the keyword this denotes a value, that is a reference to the object for which the method was invoked, or to the object being constructed or destroyed. The type of this is the class C within which the keyword this occurs. At run time, the class of the actual object referred to may be the class C or any subclass of C.

8.6.3 Parenthesized Expressions

A parenthesized expression is a primary expression whose type is the type of the contained expression and whose value at run time is the value of the contained expression. If the contained expression denotes a variable then the parenthesized expression also denotes that variable.

8.6.4 Expression Names

The rules for evaluating expression names are given in §4.5.3.

8.7 Class Instance Creation Expressions

A class instance creation expression is used to create new objects that are instances of classes.




ClassInstanceCreationExpression:

    new ClassType Arguments 



Arguments:

    ( ArgumentList )

    ( )



ArgumentList

    ArgumentList , Expression

    Expression



We say that a class is instantiated when an instance of the class is created by a class instance creation expression. Class instantiation involves determining what class is to be instantiated, what constructor should be invoked to create the new instance and what arguments should be passed to that constructor.

8.7.1 Determining the Class being Instantiated

The class being instantiated is the class denoted by ClassType.

The type of the class instance creation expression is the class type being instantiated.

8.7.2 Choosing the Constructor and its Arguments

Let C be the class type being instantiated. To create an instance of C, i, a constructor of C is chosen at compile-time by the following rules:

8.7.3 Run-time Evaluation of Class Instance Creation Expressions

At run time, a class instance creation expression requires memory space to be allocated for the new class instance. If there is insufficient space to allocate the object, the program terminates with a run-time error.

The new object contains new instances of all the fields declared in the specified class type and all its superclasses. As each new field instance is created, it is initialized to its default value.

Next, the actual arguments to the constructor are evaluated, left-to-right.

Next, the selected constructor of the specified class type is invoked. This results in invoking at least one constructor for each superclass of the class type.

The value of a class instance creation expression is a reference to the newly created object of the specified class. Every time the expression is evaluated, a fresh object is created.

8.8 Array Creation Expressions

An array instance creation expression is used to create new arrays (§6).




ArrayCreationExpression:

    new ClassType DimensionExpressions Dimensions

    new ClassType DimensionExpressions

    new PrimitiveType DimensionExpressions Dimensions

    new PrimitiveType DimensionExpressions

	

DimensionExpressions:

    DimensionExpressions DimensionExpression

    DimensionExpression 



DimensionExpression:

    [ Expression ]



Dimensions:

    Dimensions Dimension

    Dimension



Dimension:

    [ ]



An array creation expression creates an object that is a new array whose elements are of the type specified by the PrimitiveType or ClassType.

The type of the array creation expression is an array type that can be denoted by a copy of the creation expression from which the new keyword and every DimensionExpression expression have been deleted.

For example, the type of the creation expression:




new int[3][3][]



is:



int[][][]



The type of each dimension expression within a DimensionExpression must be an integer type, or a compile-time error occurs.

8.8.1 Run-time Evaluation of Array Creation Expressions

An array creation expression specifies the element type, the number of levels of nested arrays, and the length of the array for at least one of the levels of nesting. The array's length is available as an instance variable length.

Each array dimension expression denotes the length of the corresponding array. The array dimensions are evaluated left-to-right.

Next, the values of the dimension expressions are checked. If the value of any DimensionExpression expression is less than zero, then the program terminates with a run-time error.

Next, space is allocated for the new array. If there is insufficient space to allocate the array, then the program terminates with a run-time error.

Then, if a single DimensionExpression appears, a single-dimensional array is created of the specified length, and each component of the array is initialized to its default value.

If an array creation expression contains N DimensionExpression expressions, then it effectively executes a set of nested loops of depth N - 1 to create the implied arrays of arrays.

A multidimensional array need not have arrays of the same length at each level.

8.9 Field Access Expressions

A field access expression may access a field of an object or array, a reference to which is the value of either an expression or the special keyword super.




FieldAccess: 

    Primary . Identifier

    super . Identifier



8.9.1 Field Access Using a Primary

The type of the Primary must be a reference type T, or a compile-time error occurs. The meaning of the field access expression is determined as follows:

Note, specifically, that only the type of the Primary expression, not the class of the actual object referred to at run time, is used in determining which field to use.

8.9.2 Accessing Superclass Members using super

The special forms using the keyword super are valid only in an instance method, constructor, or destructor of a class; these are exactly the same situations in which the keyword this may be used.

Suppose that a field access expression super.name appears within class C, and the immediate superclass of C is class S. Then super.name refers to the field named name of the current object, but with the current object viewed as an instance of the superclass. Thus it can access the field named name that is visible in class S, even if that field is hidden by a declaration of a field named name in class C.

8.10 Method Invocation Expressions

A method invocation expression is used to invoke a class or instance method.




MethodInvocation:

    Identifier ( ArgumentListopt )

    Primary . Identifier ( ArgumentListopt )

    super . Identifier ( ArgumentListopt )



8.10.1 Compile-Time Step 1: Determine Class to Search

The first step in processing a method invocation at compile time is to figure out the name of the method to be invoked and which class to check for definitions of methods of that name. There are several cases to consider, depending on the form that precedes the left parenthesis, as follows:

8.10.2 Compile-Time Step 2: Determine Method Signature

The second step searches the class determined in the previous step for method declarations. This step uses the name of the method and the types of the argument expressions to locate method declarations that are applicable, that is, declarations that can be correctly invoked on the given arguments. There may be more than one such method declaration, in which case the most specific one is chosen. The descriptor (signature plus return type) of the most specific method declaration is one used at run time to do the method dispatch.

8.10.2.1 Find Methods that are Applicable

A method declaration is applicable to a method invocation if and only if both of the following are true:

The class determined by the process described in §8.10.1 is searched for all method declarations applicable to this method invocation; method definitions inherited from superclasses are included in this search.

If the class has no method declaration that is applicable, then a compile-time error occurs.

8.10.2.2 Choose the Most Specific Method

If more than one method declaration is applicable to a method invocation, it is necessary to choose one to provide the descriptor for the run-time method dispatch. In this case the most specific method is chosen.

The informal intuition is that one method declaration is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time type error.

The precise definition is as follows:

A method is said to be maximally specific for a method invocation if it is applicable and there is no other applicable method that is more specific.

If there is exactly one maximally specific method, then it is in fact the most specific method; it is necessarily more specific than any other method that is applicable.

It is possible that no method is the most specific, because there are two or more maximally specific methods. In this case a compile-time error occurs.

The type of the method invocation expression is the result type specified in the compile-time declaration of the most specific method.

8.10.3 Runtime Evaluation of Method Invocation

At run time, method invocation requires four steps. First, a target reference may be computed. Second, the argument expressions are evaluated. Third, the actual code for the method to be executed is located. Fourth, a new activation frame is created and control is transferred to the method code.

8.10.3.1 Compute Target Reference (If Necessary)

There are several cases to consider, depending on which of the three productions for MethodInvocation (§8.10) is involved:

8.10.3.2 Evaluate Arguments

The argument expressions are evaluated in order, from left to right.

8.10.3.3 Locate Method to Invoke

If the target reference is null, a run-time error occurs and the program terminates. Otherwise, the target reference is said to refer to a target object and will be used as the value of the keyword this in the invoked method.

A dynamic method lookup is used. The dynamic lookup process starts from a class S, determined as follows:

The dynamic method lookup uses the following procedure to search class S, and then the superclasses of class S, as necessary, for method m.

We note that the dynamic lookup process, while described here explicitly, will often be implemented implicitly, for example as a side-effect of the construction and use of per-class method dispatch tables, or the construction of other per-class structures used for efficient dispatch.

8.10.3.4 Create Frame

A method m in some class S has been identified as the one to be invoked.

Now a new activation frame is created, containing the target reference and the argument values (if any), as well as enough space for the stack for the method to be invoked and any other bookkeeping information that may be required by the implementation (stack pointer, program counter, reference to previous activation frame, and the like). If there is not sufficient memory available to create such an activation frame, a run-time error occurs and the program terminates.

The newly created activation frame becomes the current activation frame. The effect of this is to assign the argument values to corresponding freshly created parameter variables of the method, and to make the target reference available as this. Before each argument value is assigned to its corresponding parameter variable, it is subjected to method invocation conversion (§3.3).

8.11 Array Access Expressions

An array access expression refers to a variable that is a component of an array.




ArrayAccess:

    ExpressionName [ Expression ]

    PrimaryNoNewArray [ Expression ]



An array access expression contains two subexpressions, the array reference expression (before the left bracket) and the index expression (within the brackets). Note that the array reference expression may be a name or any primary expression that is not an array creation expression.

The type of the array reference expression must be an array type (call it T[], an array whose components are of type T) or a compile-time error results. Then the type of the array access expression is T.

The index expression must be of type int.

The result of an array reference is a variable of type T, namely the variable within the array selected by the value of the index expression.

8.11.1 Runtime Evaluation of Array Access

An array access expression is evaluated using the following procedure:

Note: in an array access, the expression to the left of the brackets appears to be fully evaluated before any part of the expression within the brackets is evaluated.

8.12 Unary Operators

The unary operators include -, ! and cast operators. Expressions with unary operators group right-to-left, so that -!x means the same as -(!x).




UnaryExpression:

    - UnaryExpression

    ! UnaryExpression

    CastExpression

CastExpression:

    ParenExpression CastExpression

    ( ArrayType ) CastExpression

    Primary


8.12.1 Unary Minus Operator -

The type of the operand of the unary minus operator must be integer type, or a compile-time error occurs. The type of the unary minus expression is int.

At run time, the value of the unary minus expression is the arithmetic negation of the value of the operand.

For integer values, negation is the same as subtraction from zero. The T programming language uses two's-complement representation for integers, and the range of two's-complement values is not symmetric, so negation of the maximum negative int results in that same maximum negative number. Overflow occurs in this case.

8.12.2 Logical Complement Operator !

The type of the operand of the unary logical complement operator must be integer type, or a compile-time error occurs. The type of the unary logical complement expression is int.

At run time, the value of the unary logical complement expression is 1 if the operand value is 0 and 0 if the operand value is not 0.

8.12.3 Cast Operator

Conceptually, the grammar for cast expressions is:




CastExpression:

    ( ReferenceType ) CastExpression

    Primary

However, for technical reasons (to make the grammar LALR(1)), the grammar was rewritten to parse a simple class name as an Expression. This eliminates an ambiguity that exists with one-token lookahead, where a parenthesized name cannot be distinguished from a cast. (See Section 19.1.5 of the first edition of the Java Language Specification for a discussion of this same problem in Java.)

The type of a cast expression is the type whose name appears within the parentheses. (The parentheses and the type they contain are sometimes called the cast operator.) The result of a cast expression is not a variable, but a value, even if the result of the operand expression is a variable.

At compile time, the type of the operand expression must be convertible by casting conversion (§3.4) to the type of the cast operator.

A run-time error (§8.17) occurs if the type of the cast operator is an array or reference type and the run-time type of the cast operand is not assignable to that type. That is, for reference types, the run-time type of the right-hand operand must be the same type as the left-hand type, or it must be a subclass of that type. For array types,

Note that any array type can be cast to a type of Object.

8.13 Arithmetic Operators

8.13.1 Multiplicative Operators

The operators * and / are called the multiplicative operators. They have the same precedence and are syntactically left-associative (they group left-to-right).




MultiplicativeExpression:

    UnaryExpression

    MultiplicativeExpression * UnaryExpression

    MultiplicativeExpression / UnaryExpression



The type of each of the operands of a multiplicative operator must be integer type, or a compile-time error occurs. The type of the multiplicative expression is int.

8.13.1.1 Multiplication Operator *

The binary * operator performs multiplication, producing the product of its operands. Multiplication is a commutative operation if the operand expressions have no side effects. Integer multiplication is associative.

If an integer multiplication overflows, then the result is the low-order bits of the mathematical product as represented in some sufficiently large two's-complement format. As a result, if overflow occurs, then the sign of the result may not be the same as the sign of the mathematical product of the two operand values.

Despite the fact that overflow, underflow, or loss of information may occur, evaluation of a multiplication operator * never causes a run-time error.

8.13.1.2 Division Operator /

The binary / operator performs division, producing the quotient of its operands. The left-hand operand is the dividend and the right-hand operand is the divisor.

Integer division rounds toward 0. That is, the quotient produced for operands n and d that are integers is an integer value q whose magnitude is as large as possible while satisfying |dq| ≤ |n|; moreover, q is positive when |n| ≥ |d| and n and d have the same sign, but q is negative when |n| ≥ |d| and n and d have opposite signs. There is one special case that does not satisfy this rule: if the dividend is the negative integer of largest possible magnitude for its type, and the divisor is -1, then integer overflow occurs and the result is equal to the dividend. Despite the overflow, no run-time error coccurs in this case. On the other hand, if the value of the divisor in an integer division is 0, then a run-time error occurs and the program terminates.

8.13.2 Additive Operators

The operators + and - are called the additive operators. They have the same precedence and are syntactically left-associative (they group left-to-right).




AdditiveExpression:

    MultiplicativeExpression

    AdditiveExpression + MultiplicativeExpression

    AdditiveExpression - MultiplicativeExpression



The type of each of the operands of the additive operators must be integer type, or a compile-time error occurs. The type of the additive expression is int.

The binary + operator performs addition, producing the sum of the operands. The binary - operator performs subtraction, producing the difference of the operands.

Addition is a commutative operation if the operand expressions have no side effects. Integer addition is associative.

If an integer addition overflows, then the result is the low-order bits of the mathematical sum as represented in some sufficiently large two's-complement format. If overflow occurs, then the sign of the result is not the same as the sign of the mathematical sum of the two operand values.

The binary - operator performs subtraction of its two operands, producing the difference of its operands; the left-hand operand is the minuend and the right-hand operand is the subtrahend. It is always the case that a-b produces the same result as a+(-b).

Note that subtraction from zero is the same as negation.

Despite the fact that overflow, underflow, or loss of information may occur, evaluation of a additive operator never causes a run-time error.

8.14 Relational Operators

The relational operators are syntactically left-associative (they group left-to-right).




RelationalExpression:

    AdditiveExpression

    RelationalExpression < AdditiveExpression

    RelationalExpression > AdditiveExpression



8.14.1 Integer Comparison Operators < and >

The type of each of the operands of a integer comparison operator must be integer type, or a compile-time error occurs. Signed integer comparison is performed.

The following rules then hold for integer operands:

8.15 Equality Operator

The equality operator is syntactically left-associative (it groups left-to-right).




EqualityExpression:

    RelationalExpression

    EqualityExpression == RelationalExpression



The == (equal to) operator is analogous to the relational operators except for its lower precedence. Thus, a < b==c > d is 1 whenever a < b and c < d have the same truth value.

The equality operator may be used to compare two operands of integer type or two operands that are each of either reference type or the null type. All other cases result in a compile-time error. The type of an equality expression is always an integer.

8.15.1 Numerical Equality Operator ==

If the operands are integers, then an integer equality test is performed.

The following rules then hold for integer operands:

8.15.2 Reference Equality Operator ==

If the operands of the equality operator are both of either reference type or the null type, then the operation is object equality.

It is a compile-time error if it is impossible to convert the type of either operand to the type of the other by a casting conversion (§3.4). The run-time values of the two operands would necessarily be unequal.

At run time, the result of == is 1 if the operand values are both null or both refer to the same object or array; otherwise, the result is 0.

8.16 Assignment Operator

The assignment operator is syntactically right-associative (groups right-to-left). Thus, a=b=c means a=(b=c), which assigns the value of c to b and then assigns the value of b to a.




Assignment:

    LeftHandSide = AssignmentExpression



LeftHandSide:

    Identifier

    FieldAccess

    ArrayAccess



AssignmentExpression:

    EqualityExpression

    Assignment



The result of the first operand of an assignment operator must be a variable, or a compile-time error occurs. This operand may be a named variable, such as a field of the current object or class, or it may be a computed variable, as can result from a field access or an array access. The type of the assignment expression is the type of the variable.

At run time, the result of the assignment expression is the value of the variable after the assignment has occurred. The result of an assignment expression is not itself a variable.

A compile-time error occurs if the type of the right-hand operand cannot be converted to the type of the variable by assignment conversion.

At run time, the expression is evaluated in one of two ways. If the left-hand operand expression is not an array access expression, then three steps are required:

If the left-hand operand expression is an array access expression, then many steps are required:

8.17 Run-Time Errors

There is no exception "handling" in the T programming language. Run-Time errors will cause the abrupt termination of the program with an associated error message printed to stdout.

Run-Time Error conditions and messages are as follows:


ERROR: Out of memory (line n).
A class instance creation expression (§8.7) or array creation expression (§8.8) fails due to insufficient memory available.


ERROR: Negative array size (line n).
An array creation expression (§8.8) has a value of any dimension expression evaluate to less than zero.


ERROR: Null reference (line n).
A field access (§8.9) is attempted when the value of the object reference expression is null.

A method invocation expression (§8.10) that invokes an instance method is attempted when the target reference is null.

An array access (§8.11) is attempted when the value of the array reference expression is null.

A delete statement (§7.9) is attempted on a null reference.


ERROR: Index out of bounds (line n).
An array access (§8.11) is attempted where the value of the array index expression is negative or greater than or equal to the length of the array.


ERROR: Divide by zero (line n).
An integer division (§8.13.1.2) operator is attempted where the value of the right-hand operand expression is zero.


ERROR: Invalid cast (line n).
A cast (§8.12.2) to a reference type is attempted where the actual type of the operand expression is incompatible with (not a subclass of) the reference type to which it is being cast.


ERROR: Invalid array assignment (line n).
An assignment to an array component of reference type (§8.11) is attempted where the value to be assigned is not compatible with the component type of the array.


Contents | Prev | Next The T Language Specification, Version 2
Spring 2006

Author(s): Pete Mitchell (§8.1-8.5, §8.17), Jakub Mokny (§8.6-8.8), Christopher Sayles (§8.9, §8.11, §8.16), Dennis Tolstenko (§8.10), Sam Winter (§8.12-8.15) and Spring 2006 CS712/CS812 class (edits, §8.12.3) and Phil Hatcher (edits, §8.15.2).