The TADS 3 language's expression syntax and operators are essentially the same as they were in TADS 2. Some changes are:
Here is a summary of the TADS 3 operators, shown in order of precedence (each operator has higher precedence than the operators that follow it in the table).
Operator
|
Operands |
Associativity |
Description |
! ~ & + - ++ (pre) -- (pre) |
1 |
Not applicable |
Evaluates operand, then yields logical negation/bit-wise negation/address/arithmetic identity/arithmetic negative, or increments/decrements the lvalue's contents, then yields the incremented/decremented value. |
() |
2 |
Not applicable |
Evaluates the argument list within the parentheses, starting with the rightmost argument, then evaluates the left operand, then calls the method or function and yields the return value, if any |
[] . |
2 |
Not applicable |
Evaluates the left operand, then evaluates the index value/right operand, then yields the indexed value/property or method value |
++ (post) -- (post) |
1 |
Not applicable |
Increments/decrements the lvalue's contents, then yields the original value prior to the increment/decrement |
* / % |
2 |
Left-to-right |
Evaluates left operand then right operand, and yields the product/quotient/modulo |
+ - |
2 |
Left-to-right |
Evaluates left operand then right operand, and yields the sum/difference |
<< >> |
2 |
Left-to-right |
Evaluates left operand then right operand, and yields left side shifted left/right by the number of bits specified by the right side |
> < >= <= |
2 |
Left-to-right |
Evaluates left operand then right operand, and yields true if the comparison holds, nil if not |
is in not in == != |
2 |
Left-to-right |
Evaluates left operand then right operand, and yields true if the results are equal/unequal, nil otherwise (see the additional details on "is in" and "not in" below) |
& |
2 |
Left-to-right |
Evaluates left operand then right operand, and yields bit-wise AND of the values |
^ |
2 |
Left-to-right |
Evaluates left operand then right operand, and yields the bit-wise XOR of the values |
| |
2 |
Left-to-right |
Evaluates left operand then right operand, and yields the bit-wise OR of the values |
&& |
2 |
Left-to-right |
Evaluates left operand; if zero or nil, yields nil, otherwise evaluates right operand, and yields nil if zero or nil, true otherwise |
|| |
2 |
Left-to-right |
Evaluates left operand; if true, the result is true; otherwise evaluates right operand, and yields nil if zero or nil, true otherwise |
? : |
3 |
Right-to-left |
Evaluates first operand; if true, evaluates second operand, otherwise third |
, |
2 |
Left-to-right |
Evaluates left side, then right side |
= += -= *= /= %= &= |= ^= >>= <<= |
2 |
Right-to-left |
Except for =, evaluates the left operand first; then evaluates right operand, and assigns its value to the left operand (=) or combines its value with the left operand's value and assigns the result to the left operand, which must be an "lvalue" of some kind (a local variable, an indexed list, or an object property) |
It's frequently necessary to test a value for equality against several possible alternatives. The traditional way to write this kind of test is with several "==" expressions joined by "||" operators:
if (cmd == 'q' || cmd == 'quit' || cmd == 'exit')
// etc
This kind of expression is especially tedious when the common expression you're testing is more than a simple variable. For example, if you wanted to make the above comparison insensitive to case, you'd have to do something like this:
if (cmd.toLower() == 'q' || cmd.toLower() = 'quit'
|| cmd.toLower() == 'exit')
// etc
This is especially inefficient because the compiler would have evaluate the call to toLower() for each test, in case the method call had any side effects. You could always evaluate the toLower() call once and store the result in another local variable, but this is even more verbose.
The "is in" operator makes it easier and more efficient to write this kind of comparison. This operator takes a list of expressions, enclosed in parentheses and separated by commas, to compare to a given value. We could rewrite the test above using "is in" like this:
if (cmd.toLower() is in ('q', 'quit', 'exit'))
// etc
This is much less work to type in, is easier to read, and also has the benefit that the expression on the left of the "is in" operator is evaluated only once.
The result of the "is in" operator is true if the value on the left is found in the list, nil if not.
The entries in the list don't have to be constants, so you could write something like this:
if (cmd.toLower() is in (global.quitCommand, global.exitCommand))
// etc
The "is in" operator has "short-circuit" behavior, just like the || and && operators. This means that the "is in" operator only evaluates as many entries in the comparison list as are necessary to determine if the list contains a match. The operator first evaluates the left operand, then evaluates the items in the list, one at a time, in left-to-right order. If the first element matches the left operand, the operator stops and yields true as the result. If the first element doesn't match, the operator evaluates the second list element; if this element matches the left operand, the operator stops and yields true. Thus, the operator evaluates list elements only until it finds one that matches.
This short-circuit behavior is important when the expressions in the list have side effects. Consider this example:
f1(x)
{
"this is f1: x = <<x>>\n";
return x;
}
// elsewhere...
if (3 is in (f1(1), f2(2), f1(3), f1(4), f1(5))) // ...
The "if" statement will result in the following display:
this is f1: x = 1
this is f1: x = 2
this is f1: x = 3
The "is in" operator will stop there – it won't call f1(4) or f1(5), because it finds the value it's looking for after it calls f1(3).
Another operator, "not in" lets you perform the opposite test: this operator yields true if a value is not found in a list of values:
if (x not in (a, b, c)) // ...
The "not in" operator has the same short-circuit behavior as the "is in" operator: the operator only evaluates as many of the list elements as necessary to determine whether or not the value is in the list. So, the operator stops as soon as it finds the left operand value in the list.
The "is in" and "not in" operators have the same precedence and associativity as the "==" and "!=" operators (these operators all associate left-to-right).