### Expressions and Operators

The TADS 3 language's expression syntax and operators are essentially the same as they were in TADS 2. Some changes are:

- The new operators "is in" and "not in" have been added to the language.
- List constants now require commas separating the elements. In TADS 2, commas were optional. Omitting commas frequently led to ambiguity in the intuitive meaning of a list constant expression, so the commas are now required to make the meaning clear. So, it is now illegal to write a list value like [1 2 3]; instead, write [1, 2, 3].
- Java/C-style assignment operators are now the default: use `=` for assignment and `==` for equality comparisons.
- The `new` operator allows constructor arguments. After the name of the object, you can specify in parentheses a list of arguments; these arguments are passed to the new instance's `construct` method.
- The `delete` operator has been deleted (or, more to the point, it has been collected because it is no longer referenced). Explicit object deletion is no longer required or allowed, thanks to the automatic garbage collection mechanism.
- The new `delegated keyword` allows a method call to be delegated to an unrelated object.
- The `inherited` and `delegated` expressions do not require a property to be specified explicitly. When an expression of one of these types does not specify a property to inherit or delegate to, the expression implicitly inherits or delegates to the same property that was evaluated to invoke the current method.
- The new `targetprop pseudo-variable` provides run-time access to the current target property, which is the property that was invoked to reach the current method.
- The new `targetobj pseudo-variable` provides run-time access to the current target object, which is the object whose method was originally invoked to reach the current method.
- The new `definingobj pseudo-variable` provides run-time access to the current defining object, which is the object that defines the method currently being executed.
- You can control the treatment of newlines in strings using #pragma newline_spacing().

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

`[]` `.` `` | 2 | Not applicable | Evaluates the left operand, then evaluates the index value/right operand, then yields the indexed value/property or method value
`&` | 1 | Not applicable | Valid with a property name operand only; yields a property "pointer" to the named property, which can be stored in a variable and used to invoke a property/method via the "." operator
`!` `~` `+` `-` `++``(pre)``` `--``(pre)``` | 1 | Not applicable | Evaluates operand, then yields logical negation/bit-wise negation/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
`++``(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)

### The "is in" and "not in" Operators

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

In addition to being tedious, this is inefficient: the compiler has to evaluate the call to toLower() again 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 that requires extra typing for the variable declaration (although the expression would become simpler to type, which sometimes makes up the difference).

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, and it's easier to read. It 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 parenthesized list of values, 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).

### The `delegated` keyword

It is sometimes desirable to be able to circumvent the normal inheritance relationships between objects, and call a method in an unrelated object as though it were inherited from a base class of the current object. For example, you might want to create an object that sometimes acts as though it were derived from one base class, and sometimes acts as though it were derived from another class, based on some dynamic state in the object. Or, you might wish to create a specialized set of inheritance relationships that don't fit into the usual class tree model.

The `delegated` keyword can be useful for these situations. This keyword is similar to the `inherited` keyword, in that it allows you to invoke a method in another object while retaining the same "self" object as the caller. `delegated` differs from `inherited`, though, in that you can delegate a call to any object, whether or not the object is related to "self." In addition, you can use an object expression with `delegated`, whereas `inherited` requires a compile-time constant object.

The syntax of `delegated` is similar to that of `inherited`:

      return_value = delegated object_expression.property optional_argument_list

For example:

`book: Item`

`handler = Readable`

`doTake(actor) { return delegated handler.doTake(actor); }`

`;`

In this example, the doTake method delegates its processing to the doTake method of the object given by the "handler" property of the "self" object, which in this case is the Readable object. When Readable.doTake executes, its "self" object will be the same as it was in book.doTake, because `delegated` preserves the "self" object in the delegatee.

In the delegatee, the `targetobj` pseudo-variable contains the object that was the target of the `delegated` expression.

### Implicit `inherited` and `delegated` properties

It is legal to omit the property name or expression in an `inherited` or `delegated` expression. When the property name or expression is omitted, the property inherited or delegated to is implicitly the same as the current target property. For example, consider this code:

    myObj: myClass

      myMethod(a, b)

      {

        inherited(a*2, b*2);

      }

    ;

This invokes the inherited myMethod(), as though we had instead written `inherited.myMethod(a*2, b*2)`. Because the current method is myMethod when the `inherited` expression is evaluated, myMethod is the implied property of the `inherited` expression.

### The `targetprop` pseudo-variable

The new pseudo-variable `targetprop` provides access at run-time to the current target property, which is the property that was invoked to reach the current method. This complements `self`, which gives the object whose property was invoked.

The `targetprop` pseudo-variable can be used in expressions as though it were a normal local variable containing a property pointer value, except that `targetprop` cannot be assigned a new value explicitly. You can use `targetprop` only in contexts where `self` is valid.

### The `targetobj` pseudo-variable

The new pseudo-variable `targetobj` provides access at run-time to the original target object of the current method. This is the object that was specified in the method call that reached the current method. Note that the target object remains unchanged when you use `inherited` to inherit a superclass method, because the method is still executing in the context of the original call to the inheriting method.

The `targetobj` value is the same as `self` in normal method calls, but not in calls initiated with the `delegated` keyword. When `delegated` is used, the value of `self` stays the same as it was in the delegating method, and `targetobj` gives the target of the `delegated`.

You can use this variable only in contexts where `self` is valid.

### The `definingobj` pseudo-variable

This new pseudo-variable provides access at run-time to the current method definer. This is the object that actually defines the method currently executing; in most cases, this is the object that defined the current method code in the source code of the program.

You can use this variable only in contexts where `self` is valid.

### Controlling string newline spacing: #pragma newline_spacing()

Unlike C and Java, a string in TADS 3 doesn't have to fit all on one line. You can instead write a string that spans several lines:

      "This is a string

      that spans several

      lines of source code. ";

The compiler essentially ignores the line breaks; it converts each line break into a single space character. If a line break is immediately followed by one or more whitespace characters (spaces, tabs, and so on), the compiler removes all of those spaces; so each line break turns into a single space, no matter how many leading spaces are on each line. This is important because most people like to use indentation to make their source code easier to read; ignoring the leading whitespace on each extra line within a string means that you don't have to worry about indentation affecting the appearance of a string.

In some languages, it's undesirable to have any extra whitespace in a string. For example, Chinese is usually written without using any spaces between words. To accommodate languages where extra spacing is unwanted, the compiler has a directive, #pragma newline_spacing(), that lets you control how the compiler treats newlines in strings. By default, newline spacing is on:

    #pragma newline_spacing(on)

This tells the compiler to use the behavior described above: each line break within a string is converted to a single space character, and all whitespace characters immediately following a line break are removed. This default is suitable for most Western languages, where spaces are used to separate adjacent words.

For languages where extra whitespace is unwanted, you can turn off newline spacing:

    #pragma newline_spacing(off)

This tells the compiler to simply remove each line break entirely, including any whitespace immediately following a line break. In this mode, the compiler doesn't convert a line break to a space, but simply removes the line break entirely.

This pragma only affects strings following the pragma in the source file, so you can change the mode at any time within the same source file. Whenever you change the mode, the new mode is in effect until the next mode change, or until the end of the current source file. Note that if you change the mode within a header file, the mode will revert to its previous setting at the end of the header file – this means that you can #include a file without worrying about any mode changes, because even if the included file changes the mode, the mode will revert back once the compiler finishes with the included file.