The operators defined by OADL are as follows (in precedence order):
Operator | Description |
---|---|
. [ ] #[ ] ( ) #( ) | Public ref, array indexing, flattened array indexing, function call, array call |
~ ! - @ @@ ?? | Bitwise NOT, logical NOT, negate, copy, deep copy, typeof |
=> | Type conversion |
** | Exponentiation |
* / % | Multiplication, division, modulus |
+ - | Addition, subtraction |
<< >> | Left shift, right shift |
< > <= >= | Less, Greater, Less or Equal, Greater or Equal |
== != #= ?= ~= | Equal, Not-equal, array component-wise equality,
is_a , pattern match |
& | Bitwise AND |
^ | Bitwise XOR |
| | Bitwise OR |
&& | Logical AND (pseudo-op) |
|| | Logical OR (pseudo-op) |
? : | Conditional expression |
## | Concatenation of two values |
See the chapter on Arrays, Lists, and Strings and the chapter on Dictionaries for discussion of the array indexing operators. See the chapter on Objects for discussion of the public reference operator. See the chapters on Arrays, Lists, and Strings and Classes for a more detailed discussion of how those object types work with arithmetic, bitwise, and comparison operators.
The following operators are considered arithmetic operators:
** |
* |
/ |
% |
+ |
- |
These operators automatically do arithmetic type promotion on their operands. This is done according to the following arithmetic type promotion table:
Char | Byte | Ubyte | Short | Ushort | Half | WideChar | Int | Uint | Float | Long | Ulong | Double | Enclosure | Array | Object | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Char | I | I | I | I | I | F | I | I | ui | F | L | U | D | E | A | O |
Byte | I | I | I | I | I | F | I | I | ui | F | L | U | D | E | A | O |
Ubyte | I | I | I | I | I | F | I | I | ui | F | L | U | D | E | A | O |
Short | I | I | I | I | I | F | I | I | ui | F | L | U | D | E | A | O |
Ushort | I | I | I | I | I | F | I | I | ui | F | L | U | D | E | A | O |
Half | F | F | F | F | F | F | F | F | F | F | D | D | D | E | A | O |
WideChar | I | I | I | I | I | F | I | I | ui | F | L | U | D | E | A | O |
Int | I | I | I | I | I | F | I | I | ui | F | L | U | D | E | A | O |
Uint | ui | ui | ui | ui | ui | F | ui | ui | ui | F | L | U | D | E | A | O |
Float | F | F | F | F | F | F | F | F | F | F | D | D | D | E | A | O |
Long | L | L | L | L | L | D | L | L | L | D | L | U | D | E | A | O |
Ulong | U | U | U | U | U | D | U | U | U | D | U | U | D | E | A | O |
Double | D | D | D | D | D | D | D | D | D | D | D | D | D | E | A | O |
Enclosure | E | E | E | E | E | E | E | E | E | E | E | E | E | E | A | O |
Array | A | A | A | A | A | A | A | A | A | A | A | A | A | A | A | O |
Object | O | O | O | O | O | O | O | O | O | O | O | O | O | O | O | O |
The entries in the table may be decoded thus:
Table Entry | Meaning |
---|---|
I | Int |
F | Float |
ui | Uint |
L | Long |
U | Ulong |
D | Double |
E | Enclosure |
A | Array |
O | Object |
The following operators are considered bitwise operators:
& |
| |
^ |
~ |
<< |
>> |
They operate on integral or boolean types:
Char |
Byte |
Ubyte |
Short |
Ushort |
WideChar |
Int |
Uint |
Long |
Ulong |
Bool * |
Bool
values are not allowed
with the shift operators <<
and
>>
They operate bit-by-bit on their operands. Integral operands of the bitwise operators are promoted according to the promotion table above.
The shift operators <<
and
>>
treat their operands as unsigned values.
This means that the sign bit does NOT propagate through as a result
of a right shift. Additionally, the shift count is clamped to the
range [0..64] This means that a shift by a negative number is the
same as no shift at all, and a shift of greater than 64 bits in the
left hand operand will result in all the bits being shifted off
(giving a result of zero).
The following operators are considered comparison operators:
== |
!= |
#= |
< |
> |
<= |
>= |
~= |
The comparison operators ==
or
!=
always return a scalar
Bool
result (unless operator overloading is
involved). If both arguments are arrays, the result is a lexographic
comparison of the contents of the two arrays, after type promotion:
"Hello" == L"Hello" true [1,2,3] == [1.,2.,3.] true
Otherwise, standard arithmetic promotion is performed on scalar
operands. Non-arithmetic values may be compared with the
==
and !=
operators:
public foo public::foo == 3 false
If the operands of comparison operators <
,
<=
, >
, or
>=
are both String
or
WideString
, a lexographic comparison is also
performed:
"123" < "124" true "1234" > L"123" true
Otherwise, standard arithmetic promotion and array conformance checks are performed on the operands:
"123" > '2' false false true [1,2,3] > 2.5 false false true
If a lexographic equality comparison is not wanted, use the
#=
component-wise equality operator:
[1,2,3] == [1,2,4] false [1,2,3] #= [1,2,4] true true false
The ~=
operator is the OADL string pattern
matching operator. The left-hand side is a string-valued expression.
The right hand side is a Perl-compatible
regular expression. The operator returns true
if the string matches the regular expression, and
false
otherwise. See also the
match
statement in the Procedures chapter.
Since elements of constant arrays and strings may not be
reassigned, and since it is cumbersome to type
"str.copy()" to enable modification, the
@
operator is implemented. It is shorthand syntax
for the copy()
intrinsic method. For example:
a = "hello" a[0] = 'H' Access failure a = @ a a[0] = 'H' // This will now succeed a Hello
The @@
operator is similar to the
@
operator, but it represents the
deepcopy
intrinsic method. For example:
a = {"hello","world"} a[0][0] = 'H' Access failure a = @@ a a[0][0] = 'H' a Hello world
The @
and @@
operators are
not allowed in constant initializers
The =>
operator is the OADL type conversion
operator. It can be overloaded - which is a convenient way for
classes to implement a class-to-string method. The left-hand side of
the =>
operator is the value to be converted;
the right-hand side of the =>
operator is the
type (or class!) to be converted to. See the following example:
a = 1.4; a => Int 1 a => complex; // Assuming class complex properly overloads => (1.4,0) a => String 1.4
Type conversion may also be done via the call syntax; for example:
Int(1.4) 1 3 + Float(".1415927") 3.14159
The ?=
operator is the same as the
is_a()
intrinsic method. It is used to evaluate
the class hierarchy transitively. It returns true
if the right hand side is one of the parent classes of the left hand
side, and false
otherwise.
The ??
operator is shorthand for the
typeof
intrinsic.
The test ?
expr1
:
expr2 conditional operator evaluates the
leftmost argument. If the test expression is logically TRUE then the
result of the expression is the middle argument. Otherwise, the
result of the expression is the right argument. For example, the
following implements the signum function:
proc sgn(val) { return (val < 0) ? -1 : ((val > 0) ? 1 : 0); }
If the leftmost argument is logically TRUE, then the rightmost argument is not evaluated. If the leftmost argument is logically FALSE, then the middle argument is not evaluated. This is useful to prevent exceptions from being thrown; for example, this procedure will not index the array outside its bounds:
proc safeIdx(a, i) { return (i < 0) ? nil : (i >= a.length()) ? nil : a[i]; }
The complete list of logically FALSE values is:
false |
nil |
'\x0' |
'\x0'L |
0B |
0UB |
0S |
0US |
0 |
0U |
0L |
0UL |
0.H |
-0.H |
0. |
-0. |
0.D |
-0.D |
Any other value is considered logically TRUE.
Logical AND and Logical OR are pseudo-ops; that is, they are not overloadable and both implement "short-circuit" semantics. Specifically:
( false &&
anything
) == false
( true &&
anything
) ==
anything( false ||
anything )
==
anything( true ||
anything ) ==
true
This can be useful if an expression to be evaluated or a function to be called has an error condition that would be avoided by placing it in a short-circuit evaluator. The following code will not dereference the array a outside its bounds:
if ((i < 0) || (i >= length(a)) || (a[i] == 10)) { "a[i] == 10 (or i is out of range)\n"; }
Finally, the logical operators use the same list of logically
FALSE values that the ? :
operator uses. This can
lead to different arithmetic results than bitwise AND and OR; for
example,
2 && 3 3 2 & 3 2
A logical AND expression of op1
&&
op2 is equivalent to the
conditional expression op1 ?
op2
:
op1, with the exception that each
operand is evaluated only once. A logical OR expression of op1
||
op2 is equivalent to the conditional
expression op1 ?
op1
:
op2 with the same exception.
The !
operator performs the logical NOT
operation according to the logically FALSE table above.
The following describes the syntax of expressions. The precedence of operators is encoded by the ordering of the productions.
expr : cond_expr | expr '##' cond_expr ; exprs : /* NOTHING */ | exprlist ; exprlist : expr | exprlist ',' expr ; cond_expr : logor_expr | logor_expr '?' expr ':' cond_expr ; logor_expr : logand_expr | logor_expr '||' logand_expr ; logand_expr : or_expr | logand_expr '&&' or_expr ; or_expr : excl_or_expr | or_expr '|' excl_or_expr ; excl_or_expr : and_expr | excl_or_expr '^' and_expr ; and_expr : equal_expr | and_expr '&' equal_expr ; equal_expr : rel_expr | equal_expr '==' rel_expr | equal_expr '!=' rel_expr | equal_expr '?=' rel_expr | equal_expr '~=' rel_expr | equal_expr '#=' rel_expr ; rel_expr : shift_expr | rel_expr '<' shift_expr | rel_expr '>' shift_expr | rel_expr '<=' shift_expr | rel_expr '>=' shift_expr ; shift_expr : add_expr | shift_expr '<<' add_expr | shift_expr '>>' add_expr ; add_expr : mult_expr | add_expr '+' mult_expr | add_expr '-' mult_expr ; mult_expr : pow_expr | mult_expr '*' pow_expr | mult_expr '/' pow_expr | mult_expr '%' pow_expr ; // Note that '**' is right-associative pow_expr : cvt_expr | cvt_expr '**' pow_expr ; cvt_expr : unary_expr | cvt_expr '=>' unary_expr ; unary_expr : '~' unary_expr | '!' unary_expr | '-' unary_expr | '@' unary_expr | '@@' unary_expr | '??' unary_expr | '&' qual_name // Only for internal OADL use | term ; indices : index | indices ',' index ; index : expr | opt_expr ':' opt_expr ; qual_name : IDENTIFIER | IDENTIFIER '::' IDENTIFIER | '::' IDENTIFIER ; term : qual_name | 'public' '::' IDENTIFIER | '(' expr ')' | '(' 'proc' ')' // ID of current proc | '{' exprs '}' // Inline List decl | '<<<' exprs '>>>' // Inline Dict decl | '[' exprs ']' // Inline packed array decl | '[' iterator ']' | STRING | LSTRING | INTCON | FLOATCON | CHARCON | LCHARCON | MATCH_ARG | MATCH_COUNT | LOOPBASE | 'new' qual_name // The create args are parsed... | 'new' '(' expr ')' // ... by the call syntax below | 'proc' no_name_proc | 'proc' STRING no_name_proc | 'operator' operator | '`' operator | '`' IDENTIFIER // Same as public::IDENTIFIER | term '(' exprs ')' // The call syntax | term '[' '*' ']' // For "Array[*]" | term '#( exprs ')' | term '.' public | term '[' indices ']' | term '#[' expr ']' | term '{' obj_props '}' // For new and static objects | 'foreach' '(' expr ')' '{' expr '}' ; iterator : opt_expr ':' opt_expr | opt_expr ':' opt_expr ':' opt_expr ; opt_expr : /* NOTHING */ | expr ; public : IDENTIFIER | '(' expr ')' | 'operator' operator | '`' operator | '`' IDENTIFIER ;
The no_name_proc and operator productions may be found in the OADL Program Syntax chapter.
Since procedures, classes, and objects are first-class types in OADL, they may also exist in expressions. For example, a program can put an unnamed procedure into an array:
a[3] = proc () {say("a[3]!\n");}
There is a subtle but significant difference between the static_obj and new_obj expression types. A static_obj creates a single, static object whose value persists over the entire duration of program execution. A new_obj creates a new dynamic object which will be deleted once all references to it are removed.
For more discussions of classes and objects, please see the Classes chapter.