Operators & Operations
To keep this concise, only exceptions from other C-like languages are listed. Operator precedence is different from C in some places, but will not be included here at the moment.
Note that many of these operators also support the <op>=
form, such as ~=
for appending.
Note that in all cases, each operand is only evaluated once, regardless of how many times it appears within “equivalent to”.
// ternary shorthands
a ?: b // boolify(a) ? a : b
a ?? b // a !is null ? a! : b
// null handling
a! // `if(a is null) error()`; type is `remove_nullable(typeof(a))`
a?.b // `a !is null ? a.b : null`; type is `typeof(a.b)?`
// arithmetic
a ** b // a to the power of b (TBD: semantics when both are *signed* integers?)
a %% b // feature TBD; modulo operation (`a % b` is remainder), which is to say: `a%b`, but with the sign of `b`. Example use case: `arr[(a - 1) %% length]`
a .* b // feature TBD; a different type of product (e.g. dot product for vectors?)
// shifts (most languages with separate shifts do this)
a >> b // arithemetic shift (when `a` is signed)
a >>> b // logical shift
// bit-rotations were considered, but dropped
// miscellaneous
a is b // identity test
a !is b
a in b // membership test
a !in b
a ~ b // concatenation
// comparisons (work mathematically, to allow "chaining")
min <= x < max // does the mathematically-expected: `(min <= x) && (x < max)`
a == b != c // also works, equivalent to `(a == b) && (b != c)`
a == b <= c != d > e // a more complex example that mixes both of above
// the "spaceship operator" is supported
a <=> b // returns `a<b ? -something : a>b ? +something : 0`; *may* be forced to be -1/+1/0 by compiler (to allow for things like `arr[(a<=>b)+1]`), this is TBD
a <=> b <=> c // feature TBD, as it looks confusing. Equivalent to `a<=>b ?: b<=>c`, except `b` is evaluated once. See also: `?:` operator above.
// feature TBD (Is there a good way to check this automatically, but without huge performance drops? Even if we end up only allowing this on explicitly `discardable` items [which might not be a bad idea anyways])
discard x; // Invokes opDiscard operator overload (if present), which may abort it; calls destructor if not aborted. Object is then considered dead. Doubles as a hint to the garbage collector that it can free some related memory (if applicable). GC may ignore the hint, depending on implementation.
Array Operators
There is also a number of array operators available. Out of bounds indexing, including negative indices or out-of-bounds slices are an error. Slices use half-open ranges, meaning the index end of the slice is not included in the input. Perhaps the best way to represent this mentally is to view the slice indices are numbers that exist between array members (starting with 0 at the very start of array).
Note that array slices do not copy the data: They are merely views. (this is TBD, but I’d really like to preserve it)
int[] arr;
int[][] arrJagged;
int[,] arr2D;
// indexing
arr[3]
arrJagged[3][4]
arr2D[3,4]
// in simple arrays, both `length` and `$` are a shorthands for `currentArray.length`; the following are equivalent:
arr[arr.length-1]
arr[length-1]
arr[$-1]
// in multidimensional arrays, `length` is an array (per dimension); `$` contains the length of the *current dimension*; `$n` contains the length of `n-th dimension`; the following are equivalent:
arr2D[0..length[1],0..length[1]]
arr2D[0..$1,0..$1]
arr2D[0..$1,0..$]
// slicing (uses half-open ranges)
arr[3..5] // a view of [arr[3], arr[4]]
arr[5..$] // arr[5] to the end
arrJagged[3][4..6] // TBD: arrJagged[3..5][4..6] may or may not be supported
arr2D[3..5,4..6]
// blank slices represent the full slice; the following two lines are equivalent:
arr[0..$]
arr[]
// empty slices
arr[0..0] // always works, always empty
arr[5..5] // may be an error if `arr.length < 5`
// errors
arr[-1..0] // ERROR: start index out of bounds
arr[0..$+1] // ERROR: end index out of bounds
arr[5..3] // ERROR: end index must be smaller or equal to start (TBD: Should we allow this? If we do, should this be an empty slice, or should it be a reversed result?)
arr[0..-1] // ERROR: (either end index out of bounds, or end must be <= start; TBD, and it doesn't really matter)
Array Casts
Feature TBD: It does encode a requirement for a specific memory arrangement and it disables a bunch of aliasing optimizations, but it is a powerful feature. Plus there’s the general language complexity concerns.
Because arrays are views, multidimensional arrays can be cast to single-dimensional ones, and vice-versa.
int[,] arr2D;
// view the entire array; this is just like `int arr[2][3]; int* a = (int[])arr;` in C, but without the undefined behavior
var a = bitcast(int[])arr2D;
assert(a.length == arr2D.length[0] * arr2D.length[1]);
if(a.length > 0)
{
assert(a[0] == arr2D[0,0]);
assert(a[1] == arr2D[0,1]); // TBD: this depends on storage order of arr2D, but I think this order makes sense (as it matches C int[N][M])
}
Bitcasts
Note the use of bitcast
above. This is a special operator that allows casting some POD types to others. It is a safe equivalent of C++'s reinterpret_cast
. Note that syntax is not yet 100% determined (will either be this or generics-based).
// scalars
float f = 3.2f;
int a = bitcast(int)f; // okay (but value of `a` is implementation-defined)
long a = bitcast(long)f; // error: types are of different size
// arrays
int[] arr = [1, 2, 3];
byte[] barr = bitcast(byte[])arr; // okay (but endianness is implementation-defined)
assert(barr.length == arr.length * 4);
assert(barr[0] == arr[0] || barr[3] == arr[0]); // which one is true depends on endianness
SomeClass x = new SomeClass;
var xID = bitcast(uintword)x; // TBD. Either an error ("unsafe bitcast") *or* okay, but only in this direction, and typically yielding a pointer (in which case, it's equivalent to Python's `id(x)`)