Welly supports all the operators supported by C. Over the years, these have become quite standard: they are shared by C++, Java, Javascript, Python, and so on.
When a user defines a type, Welly allows them to define its methods and the actions of the operators on it.
Binary operators take two operands and combine them to make the result. Most binary operators are infix operators, meaning that they are written in between the two operand expressions. For example the +
in 2 + 3
is an infix operator that adds 2 and 3 to make 5. The complete list of infix operators is as follows:
Symbol | Meaning |
---|---|
+ |
Add or concatenate. |
- |
Subtract. |
* |
Multiply. |
/ |
Divide. [1] |
% |
Remainder. |
** |
Raise left operand to the power of right operand. |
<< |
Shift bits left, filling the vacated bits with zeros. |
>> |
Shift bits right, preserving the sign. |
>>> |
Shift bits right, filling the vacated bits with zeros. |
& |
Bitwise AND. |
| |
Bitwise OR. |
^ |
Bitwise XOR. |
&& |
AND, but right operand is only evaluated if necessary. |
|| |
OR, but right operand is only evaluated if necessary. |
< |
Test if less than. |
> |
Test if greater than. |
<= |
Test if less than or equal. |
>= |
Test if greater than or equal. |
== |
Test if equal. |
!= |
Test if unequal. |
<> |
Test if less than or greater than. [2] |
in |
Test if contains. |
: |
Cast to type. |
[1] For integers, division rounds the result towards minus infinity.
[2] For integers,<>
does the same thing as !=
.
Unary operators take one operand and compute the result from it. Most unary operators are prefix operators, meaning that they are written before the operand expression. For example, the -
in -5
is a prefix operator that negates 5 to make -5. The complete list of prefix operators is as follows:
Symbol | Meaning |
---|---|
+ | Copy. |
- | Negate. |
~ | Bitwise NOT. |
! | Convert to boolean, then NOT. |
* | Dereference. [1] |
& | Address of. [1] |
new | Address of a "var" copy. |
typeof | Type of. [2] |
array const | Immutable array of. [3] |
array var | Mutable array of. [3] |
array | Any array of. [3] |
ref const | Immutable reference to. [3] |
ref var | Mutable reference to. [3] |
ref | Any reference to. [3] |
defer | Immutable uninitialised instance of. [3][4] |
[1] See [the section on references].
[2] Types in Welly are themselves values of type type
.
[3] These operators can only be applied to types.
[4] See [the section on cyclic values].
The ternary operator is written x ? y : z
. It evaluates x
first, and converts is to a boolean
. If the result is true
, it computes the result by evaluating y
, otherwise by evaluating z
. The other operand z
or y
is not evaluated.
>>>> 4<7 ? "Less" : "Greater"
"Less": str
The "tuple" and "call" operators are variadic, meaning that the number of operands they take can vary.
The tuple operator takes zero or more operands and groups them into a tuple. For example (x, y)
constructs the pair whose first element is x
and whose second element is y
. The notation is as follows:
() | Constructs the empty tuple. |
(x,) | Constructs a 1-tuple containing x . |
(x, y) or (x, y,) | Constructs the pair containing x and y . |
(x, y, z) or (x, y, z,) | Constructs a triplet. |
Etc. |
Note that the syntax "(x)"
does not construct a 1-tuple, but instead means the same as "x"
. Round brackets can therefore be used to group expressions, for example if the default precedence and associativity rules don't do what you want (see the section on precedence and associativity).
The function call operator takes a function operand and zero or more argument operands. It calls the function, passing the operands. The result is the value returned by the function. The notation is as follows:
f() | Calls "f" passing no arguments. |
f(x) or f(x,) | Calls "f" passing "x". |
f(x, y) or f(x, y,) | Calls "f" passing "x" and "y". |
f(x, y, z) or f(x, y, z,) | Calls "f" passing "x", "y" and "z". |
Etc. |
Expressions that use more than one operator can sometimes be ambiguous. The ambiguity is resolved in Welly using precedence and associativity rules, exactly as in other languages. The complete list of precedence rules is as follows:
-x.y |
means | -(x.y) |
not | (-x).y |
-x[y] |
means | -(x[y]) |
not | (-x)[y] |
-x(y) |
means | -(x(y)) |
not | (-x)(y) |
-x ** y |
means | (-x) ** y |
not | -(x ** y) |
x * y ** z |
means | x * (y ** z) |
not | (x * y) ** z |
x + y * z |
means | x + (y * z) |
not | (x + y) * z |
x << y + z |
means | x << (y + z) |
not | (x << y) + z |
x < y << z |
means | x < (y << z) |
not | (x < y) << z |
x == y < z |
means | x == (y < z) |
not | (x == y) < z |
x & y == z |
means | x & (y == z) |
not | (x & y) == z |
x ^ y & z |
means | x ^ (y & z) |
not | (x ^ y) & z |
x | y ^ z |
means | x | (y ^ z) |
not | (x | y) ^ z |
x && y | z |
means | x && (y | z) |
not | (x && y) | z |
x || y && z |
means | x || (y && z) |
not | (x || y) && z |
x : y || z |
means | x : (y || z) |
not | (x : y) || z |
w ? x : y || z |
means | w ? x : (y || z) |
not | (w ? x : y) || z |
All postfix operators have the same precedence, which is the same as subscript and call, and higher than prefix operators. All prefix operators have the same precedence, which is higher than all remaining operators. "/"
and "%"
have the same precedence as "*"
. Binary "-"
has the same precedence as binary "+"
. The three shift operators have the same precedence. The five comparison operators have the same precedence. "=="
and "!="
have the same precedence.
x ** y ** z |
means | x ** (y ** z) |
not | (x ** y) ** z |
x * y / z |
means | (x * y) / z |
not | x * (y / z) |
x + y - z |
means | (x + y) - z |
not | x + (y - z) |
x << y >> z |
means | x << (y >> z) |
not | (x << y) >> z |
x < y > z |
means | (x < y) > z |
not | x < (y > z) |
x == y != z |
means | (x == y) != z |
not | x == (y != z) |
x & y & z |
means | (x & y) & z |
not | x & (y & z) |
x ^ y ^ z |
means | (x ^ y) ^ z |
not | x ^ (y ^ z) |
x | y | z |
means | (x | y) | z |
not | x | (y | z) |
x && y && z |
means | (x && y) && z |
not | x && (y && z) |
x || y || z |
means | (x || y) || z |
not | x || (y || z) |
x : y : z |
is forbidden. |
w ? x : y : z |
is forbidden. |
w : x ? y : z |
is forbidden. |
v ? w : x ? y : z |
is forbidden. |
v ? w ? x : y : z |
is forbidden. |
If these rules do not do what you want, or if you want to make your code clearer, you can enclose expressions in round brackets to make the meaning explicit.