The client's expression mode supports a large number of operators. This document will explain all of them and the most important concepts.
All expressions are broken into tokens. A token is either an operand, or an operator. An operand is a “thing” that is operated upon, and an operator is the action that you apply to operands. In the expression 3 + 4, 3 and 4 are the operands, and + is the operator.
There are three types of operands: rvalues, lvalues, and numbers. An rvalue is an operand that provides a value that you can use. An lvalue is an operand that can be used as the target of an assignment operator. A number is, well, a number.
As an example, an lvalue is a variable name. In the expression foo = 5, foo is the lvalue. An rvalue is an operand that is used for its value, rather than to assign to it. In the expression var1 = var2, var2 is the rvalue. Rvalues also include string literals.
Numbers are different, because numbers look like lvalues, but behave like rvalues. Some of the operators behave differently with numbers than with rvalues.
PRECEDENCE | OPERATOR | USAGE | REDUCES TO | ASSOCIATIVITY |
---|---|---|---|---|
1 | Sub-expression | ( op ) | op | L→R |
2 | Logical NOT | ! bool | bool | R→L |
2 | Bitwise NOT | ~ int | int | R→L |
2 | Prefix Decrement | -- lval | int | R→L |
2 | Prefix Increment | ++ lval | int | R→L |
2 | Suffix Decrement | lval -- | int | R→L |
2 | Suffix Increment | lval ++ | int | R→L |
2 | Unary Plus | + float | float | R→L |
2 | Unary Minus | - float | float | R→L |
2 | String length | @ [op] | int | R→L |
2 | Word Count | # [op] | int | R→L |
2 | Variable Dereference | * [rval] | lval | R→L |
2 | Variable Dereference | * [number] | rval | R→L |
2 | Double Expansion |
The {pre,post}fix {in,de}crement operators are big timesavers that C and C++ users everywhere swear by. They have also been known to swear at them, for reasons you will soon see. Assume $foo is 5, each column shows 3 ways of doing the same thing, from least efficient to most efficient:
@ foo = foo + 1 @ foo = foo - 1 @ foo += 1 @ foo -= 1 @ foo++ @ foo--
However, these operators have pitfalls, which are mainly discovered by those who do not understand how they work. Both may either prefix or postfix a variable; prefix causes it to evaluate before the operation, postfix causes it to evaluate aster. For the examples shown above, it makes no difference. However, it does make a difference in this example:
while ( foo++ < 10 ) { ... }
The expression is evaluated for whether $foo is less than 10, and then $foo is incremented. If the autoincrement operator was instead used in prefix form, $foo would be incremented before the expression was evaluated, which would cause the loop to have one less iteration.
Another pitfall of the autoincrement and decrement operators is the ambiguity introduced by insufficient whitespace when used in conjunction with addition and subtraction operators. Consider the following:
@ foo = 4 @ bar = 8 @ foobar = foo+++bar
How should one interpret the last assignment? Should it really look like ${foo++ + bar} or ${foo + ++bar}? It's hard to tell. The best solution is to not write code that looks so silly and unreadable. Add a couple spaces, and there is no ambiguity. (The answer is, the first one.)
Another popular operator familiar to most C/C++ programmers is the tertiary operator (sometimes referred to as the alternation operator). It performs a function similar to IF, except is much more compact and efficient. We'll let $foo be 5 again:
@ bar = foo > 3 ? 1 : 0 /* sets $bar to 1 */ @ bar = foo > 8 ? 1 : 0 /* sets $bar to 0 */
Functions (built-in and scripted) can also be used within expressions. The function will be evaluated, and its return value is used in the expression:
@ foo = pattern(b* foo bar blah) /* sets $foo to "bar blah" */
All functions implicitly use a special operator, (). That is, the pair of parentheses themselves compose an operator, though of course it is somewhat different in nature from more traditional operators like '+' or '<' or '&'. Functions (aliases with return values) require the () to function properly.
A similar operator is [], which is used for alias and variable structures. We've already seen that it can be used to explicitly switch the evaluation context to text. This can be extended to structure elements, such that they can be expanded on the fly:
@ foo.1.1 = [foo] @ foo.1.2 = [bar] alias blah echo $foo[1][$0] /blah 2 /* expands to $foo.1.2 -> "bar" */
The same can be applied to aliases and functions as well. Because of the nature of the [] operator, anything may be expanded inside it, variables and functions alike.