Back Contents Next

12. Extras

12.1 Blocks

One special form of object is the block. A block is an anonymous object of an anonymous type. It is introduced with a left curly brace '{' and terminated by a right curly brace '}'. No colon is necessary. Normally such an object is an object literal expression. This form is a statement. No semicolon is required at the end of this statement. A block is no different from any other object of its type except that it stands alone as a statement. When the statement is executed the block is created. As an example:

{
    //The contents of the block.
}

This example block is equivalent to (not legal code):

:{
    //The contents of the block.
};

This is the form that almost all blocks have. However, some blocks may have yield a value (see 12.3.8 Yield).

12.2 Operators

Operators are familiar to programmers from other languages and mathematics. Most operators are really just a special syntax for method invocations. The exceptions to this are the fundamental operators (see 6.1 Expressions).

12.2.1 Operator Precedence

Unary, assignment, exponent and comma operators are right associative. All other operators are left associative. In the chart below operators in the same box have the same precedence, operators in a higher box have higher precedence. Both fundamental and none fundamental operators are listed to show their relative precedence.

Operator Summary
member selection
accept
subscripting
function call
magnitude
object . member
object .. object
object [ expr ]
object ( expr-list )
| expr |
exponentiation expr ^ expr
increment
decrement
not
unary minus
unary plus
complement
create
++ lvalue
-- lvalue
! expr
- expr
+ expr
complement expr
create type ( expr-list )
multiply
divide
modulo (remainder)
expr * expr
expr / expr
expr % expr
add (plus)
subtract (minus)
expr + expr
expr - expr
shift left
shift right
expr shift_left expr
expr shift_right expr
less than
less than or equal
greater than
greater than or equal
equal
not equal
expr < expr
expr <= expr
expr > expr
expr >= expr
expr == expr
expr != expr
bitwise AND
bitwise XOR
bitwise OR
expr bit_and expr
expr bit_xor expr
expr bit_or expr
logical AND expr and expr
logical XOR expr xor expr
logical OR expr or expr
output
input
expr << expr
expr >> expr
assignment
multiply and assign
divide and assign
modulo and assign
add and assign
subtract and assign
shift left and assign
shift right and assign
AND and assign
OR and assign
XOR and assign
value assign
lvalue = expr
lvalue *= expr
lvalue /= expr
lvalue %= expr
lvalue += expr
lvalue -= expr
lvalue shift_left= expr
lvalue shift_right= expr
lvalue bit_and= expr
lvalue bit_or= expr
lvalue bit_xor= expr
expr <- expr
comma (sequencing) expr , expr

12.2.2 Operator Overloading

Operators which are simply methods may be written for any object. This is done by giving the method the special name 'operator op' where op is the operator one is defining. Only non-fundamental operators may be overloaded and their are restrictions on the form of the operator method based on its use. These are detailed with each operator. Other than special naming and usage operators are essentially just normal methods.

12.2.3 This Parameter

Because operators are invoked differently it is not always obvious which object the operator is being invoked on. A this parameter allows an operator to be invoked on the second operand in some cases. Imagine that one is trying to define the plus operator to work on an int and a user defined type. Furthermore consider the case where the int is placed before the user defined type in the addition expression. Normally it would call the addition operator on the int passing the user defined type. But the int has no method for for that type. In this case you can declare an addition operator in your class whose first parameter is an int and whose second parameter is simply 'this'. That syntax tells the compiler that in this case it should call the user defined object's addition operator passing it the first argument. In operations that are not commutative it is important to remember that 'this' is now the second operand. In general this may be done for any binary operator (there are a few exceptions). When the compiler attempts to resolve operator calls it will first attempt to find an appropriate method on the first object. If it finds one it will use that. If no appropriate method exists it will try to find such a specially declared method on the second object. This syntax is really just a special naming system to distinguish the two forms.

12.2.4 Accept ( '..' ) Operator

The accept operator is not defined for the primitive types except bool. It should be overloaded to form the accept method of the Visitor Design Pattern. The accept operator may be used by:

object..visitor-object( params );

This shows visitors to be vary similar in use to method calls. When the accept operator is overloaded the first parameter is the visitor object followed by the parameters that should appear in parenthesis. A 'this' parameter cannot be used with the accept operator. To reinforce the symmetry with normal method calls, an object with an accepted visitor that isn't called can be used as a function. As an example:

function : () -> void;
function = object.method;
function = object..visitorObject;

This effect is achieved by generating a function object at the assignment which holds both the object and visitor object and makes the call when invoked.

12.2.5 Arithmetic Operators

Arithmetic operators are defined on all numerical primitive types. In addition some are overloaded for string operations. The most basic operators are the prefix unary '+' and '-'. The unary minus operator can be used to invert the sign of a number. The unary plus operator is included for symmetry and has no effect. Either operator may be overloaded. The operator method should be a constant method and must have no parameters (a warning is generated if the method isn't constant).

Two more basic operators are prefix unary increment and decrement ('++' and '--' respectively). Note that their are no postfix versions of this operator. These two operators proceed their argument and increment it then return the result. These operators may be overloaded by providing no argument methods. If the class is constant then a new incremented value may be returned and it will automatically be assigned to the property. If the class is not constant it should be incremented and then return this. It is recommended that these operators be overloaded on constant to provide both these behaviors for none constant classes.

The magnitude operator is equivalent to the absolute value for all standard numerical types. It is overloaded to provide array lengths. Overloading to mean the length of a vector is also reasonable. It may be overloaded by a const method taking no arguments (warning is generated if method not const).

Finally the arithmetic operators are completed by the standard binary operators '+', '-', '*', '/', '%' and '^'. The modulo operator is actually a remainder (this distinction is important for negative numbers). These operators may be overloaded. The method and all its parameters should be constant (warning is generated otherwise). A this parameter may be used when overloading these operators.

12.2.6 Assignment Operators

The assignment operators may not be overloaded. If one of the operators which assignment combines with is overloaded then the compiler will allow that to be combined with assignment. If one desires a value changing instead of reference changing assignment that is what '<-' is for and they should overload it to mean that.

All the combined assignment operators of the form 'lvalue x= expr' are equivalent to 'lvalue = lvalue x expr' and are define if x is defined. Assignment assigns to the reference and doesn't change the object originally referred to by the lvalue.

12.2.7 Bitwise Operators

The bitwise operators 'complement', 'bit_and', 'bit_xor' and 'bit_or' the named bitwise operators. They are defined on all integer primitives. They always evaluate both of their arguments. They are not defined on boolean types. They may be overloaded just as the binary arithmetic operators may. The complement oparator returns the bitwise complement of its argument, it may be overloaded with a const method (warning if it is not const).

A second group of bitwise operators are 'shift_left' and 'shift_right'. They are left and right bit shift and are defined only on the integer primitives. They may be overloaded like the binary arithmetic operators. The right shift operator performs sign extension if its operand is signed otherwise it fills in with zeros. When overloading the operator should be overloaded on the sign of its argument to provide this behavior.

12.2.8 Comparison Operators

The comparison operators '<', '<=', '>', '>=', '==' and '!=' are less than, less than or equal to, greater than, greater than or equal to, equal and not equal respectively. All comparison operators are defined on integer, real, and character primitives (mix by conversion rules) and return a boolean value. Equal and not equal are also defined on booleans. The equals and not equals operators are defined for all objects where they test if two references refer to the same instance. The comparison operators may be overloaded as the arithmetic operators except they should return booleans. It is recommend that equals and not equal be overloaded only for constant classes where and objects value determines its identity. When it is necessary to determine if two objects are value equivalent the equivalent( ) method should be used.

12.2.9 IO Operators

The output and input operators are not defined for any of the primitive types. They are used in the Opal streams io module to represent input and output (similar to C++ streams). These operators may be overloaded as the arithmetic operators except that they are usually side effecting, and hence not constant, to one of their arguments (the destination to which the arrow points).

12.2.10 Logical Operators

The '!', 'and', 'xor', and 'or' operators provide not, logical and, logical xor and logical or respectively. They are defined only on the boolean type. The 'and' and 'or' operators are what is known as short circuiting which means that they always evaluate their first argument followed by their second only if it is necessary to determine the logical value of the expression. They may be overloaded but because of short circuiting this works in an unusual way. Short circuiting is achieved by first calling a unary 'and' or 'or' on the first argument which returns its logical value. If the second argument must be evaluated then it is and a binary 'and' or 'or' is invoked. These two steps occur separately and the two methods could be called on different object (when a this parameter is involved). The binary operations are overloaded exactly as the binary arithmetic operators. The unary 'and' and 'or' operators should be constant methods and must take no parameters and return a boolean. The not operator may be overloaded by a no parameter method which should be constant.

12.2.11 Member Selection ( '.' ) Operator

The member selection operator is not a method call and may not be overloaded. It always selects an accessible member from an object based on legal members for that type.

12.2.12 Sequencing and Grouping Operators

The sequencing and grouping operators ',' and '()' respectively have many uses and may not be overloaded. When used alone the comma executes (from right to left) a sequence of statements which it separates and returns the result of the last if it is not a list or tuple. Otherwise the comma operator acts as the cons operator and builds a list by consing on earlier statements to the list. The parentheses are commonly used to group and order operations in expressions. When these are combined they may be used to describe lists and tuples. A list or tuple is formed by a comma separated sequence enclosed in parenthesis (see list and tuples). For this reason empty parentheses evaluate to the empty list. Note that a single item in parentheses is a tuple, however tuples are overloaded in such a way that they evaluate to their value when used in standard arithmetic, logical and comparison expressions. Finally a function call may be formed by the juxtaposition of a pattern with a tuple of parameters.

12.2.13 Subscripting ( '[]' ) Operator

The brackets may be used as a subscript operator taking one argument that is passed inside the brackets. This operator is define on arrays by default and is overloaded for strings. The operator may be overloaded. The 'this' keyword may not be used as a parameter. Two overloaded forms are possible. The first must be constant and take only an index (which need not be an integer). This method is used as an r-value. The second method form must be none constant and take both an index and a value to assign to that index.

12.3 Flow Control

Flow control provides an efficient implementation of the two fundamental operations of conditional execution and tail-recursion. Both can be achieved in Opal through purely object-oriented means. These flow control statements are provided simply as nice syntax for certain of those operations.

All flow control statements are based on C++. A name declared inside a block of a flow control statement may hide a name outside the block and is valid only inside the block. The major difference is that all flow control statements allow the declaration of entities in their initial line (like for) which are then in scope inside the statement. This is done by placing any number of entity declarations inside the parentheses followed by the normal expression for that flow control statement.

12.3.1 If-Else

The most basic form of the conditional control flow is the if else statement. Its syntax is:

if(decl-list boolean-expression)
    then-statement
else
    else-statement

Where the else clause is optional. The declaration list is optional, if it is included the declared variables are in scope both in the then and else statements. The boolean expression is evaluated and when it is true the then-statement is executed, when it is false the else-statement is executed if it is present. Using blocks prevents all ambiguity, however if blocks are not used an else statement is matched to the most recent if statement without an else.

12.3.2 Switch

The switch statement allows a multi-way branch on any primitive type. Each case must be declared by a constant expression providing the value on which to execute that case. The syntax is:

switch(decl-list expression)
{
    case(constant-expr1)
        statements1
        break;
    case(constant-expr2)
        statements2
    case(constant-expr3)
        statements3
        break;
    default
        default-statement
}

Any number of cases may be included so long as each case's constant expression evaluates to a different value of the correct type. The default case is executed if the variable doesn't match any of the other cases. Default is optional and doesn't have to be last. Break statements are used to exit the switch statement (see break). If no break statement is present execution may fall through to another case as is show with case 2 falling through to case 3 in the example. Variables in the declaration list are available to all cases.

12.3.3 While and Do-While

The syntax for while is:

while(decl-list boolean-expression)
    statement

The boolean expression is evaluated and when it is true the statement is executed repeatedly until the statement becomes false. Variables in the declaration list are in scope during all the executions of the statement.

Standard while will execute zero or more times. Sometimes it is useful to have a statement execute one or more times, this can be done with a do-while:

do(decl-list)
    statement
while(boolean-expression);

The statement is executed once then repeatedly executed as long as the boolean expression is true. The statement is almost always a block. If the declaration list is empty then it is an error to use the parentheses after the do.

12.3.4 For

The for statement is used to loop over a range of values from beginning to end, it looks like:

for(decl-list init-expression; boolean-expression; increment-expression)
    statement

A for statement almost always includes at least one variable declaration (the int-expression) which is then advanced by the increment expression. Any of the expressions could be left out in which case execution of it is skipped. If the boolean expression is left out it is assumed true. When the init-expression is omitted so is the semicolon following it. Thus an idiomatic way to write forever is:

for(;)
    statement

12.3.5 Break

A break statement is a means to immediately break out of any block, though it is most often used to break out of loops. A break statement 'break;' terminates the innermost switch, for, while, or do. More complex control is not possible with break and should be achieved by a combination of return and the evaluate to ':=' operator.

12.3.6 Continue

A continue statement skips to the end of a loop and evaluates the boolean expression that controls the loop. A continue has meaning only inside loops (while, do and for).

12.3.7 Return

A return statement terminates execution and returns to a methods caller. If the method returns void then a simple return statement works:

return;

If the method has a return type then following the return must be an expression that evaluates to an object of that type.

return expression;

Return statements are also used to terminate create methods and destruction blocks

12.3.8 Yield

A yield statment may be used inside any loop, conditional statement, try/catch or block to make it an expression which evaluates to the expression given by yield. This is very much like a mini return statement. In the case of a conditonal, if only one branch of the conditional contains a yeild statment the rest of the statement containing the if will not be evauated if the yeild is not hit. Since the result of an if can be yielded this can form a conditional break out several levels. For example:

value :int = 5 + for(;)
   yield if (done)
   {
      foo++;
      yield 5;
   }

This will yield 5 from the for loop and assign it into value as soon as done becomes true. The type of the statement is determined by the type of the argument given to yield. If it is possible for values of two different types to be yielded the compiler will try and find a type they are both assignment compatable to and make this the yielded type. If that fails an error will be reported.



Back Contents Next

jwalker@cs.oberlin.edu