Most built-in operators follow C/C++ syntax, though sometimes alternatives are available. Many operators can be overloaded. That is, structs and classes can often freely define what happens when you use a particular operator on them.
Currently, operator precedence, from highest to lowest, are:
Precedence | Associativity | Operator | Overloadable | Description |
---|---|---|---|---|
Special | None | | ...| |
Yes | Absolute value (NIY) |
|| ...|| |
Yes | Length (NIY) | ||
1 | Left | . |
See note 1 | Member reference |
?. |
See note 1 | Null-safe member reference | ||
??. |
See note 1 | Auto-allocating member reference | ||
( ...) |
Yes (NIY) | Call | ||
[ ...] |
See note 2 | Index | ||
2 | Right | @ |
No | Metaexpression |
* |
No | Expansion (NIY) | ||
3 | Left | < ...> |
No | Generic type argument |
4 | Right | (: ...) |
No | Static typecast |
ref |
No | Reference of | ||
sizeof |
No | (Static) Size of | ||
typeid |
Yes | (Dynamic) Type ID of | ||
5 | Left | as |
No | Dynamic typecast |
6 | Left | ! |
Yes | Factorial (NIY) |
~ |
Yes | Inverse (NIY) | ||
++ |
Yes | Post-increment | ||
-- |
Yes | Post-decrement | ||
7 | Right | ^ |
Yes | Power (NIY) |
8 | Right | + |
Yes | Copy |
- |
Yes | Negation | ||
~ |
Yes | Bitwise NOT | ||
++ |
Yes | Pre-increment | ||
-- |
Yes | Pre-decrement | ||
9 | Left | * |
Yes | Multiplication (inner product, matrix product) |
** |
Yes | Multiplication (cross product) (NIY) | ||
>< |
Yes | Multiplication (outer product) (NIY) | ||
/ |
Yes | Right division | ||
\ |
Yes | Left division (NIY) | ||
% |
Yes | Remainder | ||
*. |
Yes | Elementwise multiplication (NIY) | ||
/. |
Yes | Elementwise right division (NIY) | ||
\. |
Yes | Elementwise left division (NIY) | ||
div |
Yes | Euclidean division (NIY) | ||
mod |
Yes | Modulo (NIY) | ||
10 | Left | + |
Yes | Addition |
- |
Yes | Subtraction | ||
11 | Left | << |
Yes | Bit shift left |
>> |
Yes | Bit shift right | ||
12 | Left | & |
Yes | Bitwise AND |
13 | Left | <> |
Yes | Bitwise XOR |
14 | Left | | |
Yes | Bitwise OR |
15 | Left | ?? |
No | Null coalescing |
16 | None | :: |
No | Slice |
.. |
No | Range | ||
17 | None | == |
Yes | Equal to |
!= |
Yes | Not equal to | ||
=== |
No | Alias of | ||
!== |
No | Not alias of | ||
< |
Yes | Less than | ||
> |
Yes | Greater than | ||
<= |
Yes | Less than or equal to | ||
>= |
Yes | Greater than or equal to | ||
<=> |
Yes | Combined comparison | ||
~= |
Yes | Pattern match (NIY) | ||
in |
Yes | Contained in | ||
not in |
Yes | Not contained in | ||
is |
See note 3 | (Dynamic) Derived type of | ||
is not |
See note 3 | (Dynamic) Not derived type of | ||
18 | Right | ! , not |
Yes | Logical NOT |
19 | Left | && , and |
Yes | Logical AND |
20 | Left | || , or |
Yes | Logical OR |
21 | Right | ? : |
No | Conditional |
22 | Right | -> |
No | Anonymous function (NIY) |
23 | Right | := |
See note 4 | Inline assignment |
<<< |
Yes | Receive (NIY) | ||
>>> |
Yes | Send (NIY) | ||
24 | None | throw |
No | Throw exception |
Notes:
1. The member reference operator itself cannot be overloaded, but references to certain member names can be converted to method calls by defining the members as properties. It's under consideration whether to allow redirecting references to unknown member names to some fallback method (where compilation error would otherwise result).
2. References to indexes can be converted to method calls by defining indexers. Indexers can be overloaded.
3. While the is
operator can be overloaded, is null
and is not null
is exempt from any overloading, and can thus be used as a reliable way of checking if a reference is null.
4. Overloads of ordinary assignments also apply to inline assignments.
The period, .
, is used to refer to named members of objects, classes, namespaces, etc. For example, if universe
is a reference to an object with an instance variable called answer
, then you can say
universe.answer = 42
Following a reference in order to access the actual object is called dereferencing. Referencing and dereferencing is automatic, but depends on the types used (thus, typecasts affect the way this is done).
However, if an object reference is null (doesn't refer to anything), and the member is not static, then doing the above unconditionally will probably cause problems (most likely crash). If you're dealing with nullable references, then you'll usually have to explicitly check for null, but some operators are available to make the job easier.
is null
or === null
, since these constructs cannot be overloaded.??
(as seen in C#) allows you to substitute another object when the one you want is null. In the expression a ?? b
, a is used if it's non-null, otherwise b is used.?.
(as seen in Groovy) only follows the reference if it is not null. If it is null, then the expression (data access or method call) evaluates to the default value for the inferred expression type, usually null or zero. In the expression car?.drive()
, the drive
method is only called if car
isn't a null reference.car?.new()
, a new car object is allocated and assigned to car
if it was null before this.??.
will, if the reference is null, allocate a new object of the required type and assign it to the reference. Then it follows the reference as normal. In the expression car??.drive()
, a new car object is allocated (using the default allocator) and assigned to car
if it was null, and then the drive
method is called, regardless of whether car
was previously null or not.To call a function, method, or delegate, enclose its arguments in parentheses after its name (or other reference to it). Even if there are no arguments, the parentheses are necessary. For example,
exit()
calls the exit
function with no arguments.
f = fac(2)
calls the fac
function with one integer argument, and assigns its return value to the variable called f
.
Many objects that can contain other objects, such as arrays, support indexed references as a means of referring to a particular contained object. In this case, enclose the index in square brackets after the container reference. For example, if prime_list
were an array of prime numbers, then
prime = prime_list[3]
would give you the 3rd prime in this list.
Metaexpressions are evaluated at compile time, and can refer to information available to the compiler that wouldn't normally be available at runtime. A simple example is the source code filename (similar to the __FILE__
macro in C/C++). Metaexpressions are useful when used in metamethods, or in implicit parameters to ordinary methods.
The form @expression
results in a regular parser token for the compiler. If the expression creates a string, then it is converted to an identifier (a reference to a named object).
The form @#expression
results in a string. If the expression refers to an object, then the name of that object is converted to a string.
To manipulate an object's name as a string inside the metaexpression itself, refer to the object's name
property, e.g., owner.name
. This is useful for constructing new identifiers, e.g., @(owner.name + "Child")
.
Special information that can be used in metaexpressions include:
Name | Type | Description |
---|---|---|
function | Function object | Enclosing function or method |
owner | Class object | Enclosing class or struct |
super | Class object | Base class or struct |
module | String | Module name |
file | String | Source file name |
line | Integer | Source line number |