PostExpression ::= CallExpression
| CastExpression
| PostApplyExpression
| DotExpression
| IndexExpression
| PostDecrementExpression
| PostIncrementExpression
| SliceExpression
| PrimaryExpression
;
CallExpression ::= PostExpression "(" Argument? ( "," Argument)* ")"
Argument ::= Expression
The first expression must resolve to a callable symbol.
It can be either a FunctionDeclaration, a function pointer or an OverloadDeclaration
The expression takes the type of its return.
Calls yield rvalues unless
var
(see TypeReturn)@constructor
function used to create a value-typeHowever if the return type is a class
, a struct
, a union
, a static array or a dynamic array then value returned is copied to a temporary, allowing to use the call as a lvalue.
function get(): s32[4];
assert(get().ptr);
// works because rewritten
var auto __tmp1 = get();
assert(__tmp1.ptr);
The expression list between parens represents the call arguments.
If a parameter is var
, its argument must match exactly to the Type used in the declaration and must be a lvalue, otherwise the argument just have to be implictly convertible.
If an argument does not match and if its type is TypeTuple then the argument is expanded.
If the runtime check “thiscalls” is enable and if the callee is a member function then the this
argument is verified before the call and the program crashes if this
is null
.
class A
{
function test(s32 v){}
}
function main(): s32
{
var A* a;
a.test(0); // runtime error, member function called with null `this`
return 0;
}
CastExpression ::= PostExpression ":" Type
The cast expression handles several types of conversion.
Expressions that have an integral or a floating point type can be cast to values that have for an integral or floating point type of smaller size.
Expressions that have an integral or a floating point type can be cast to wider integral or floating point values. This cast is usually only implicit.
Expressions that have a floating point types can be converted to integral values, i.e the integer part is taken. The opposite is also possible but just like for numeric extension, it is done implictly.
A cast on an IdentExpression that resolves to an OverloadDeclaration has for effect to extract the function that has the matching type.
@foreign function puts(char* str): s32;
static function one(s32 p1):s32 { return p1; }
static function two(s32 p1; s32 p2):s32 { return p1 + p2; }
overload O
{
one,
two
}
function main(): s32
{
var auto a = O:(static function(s32 p1): s32))(1); // extract "O.one" and call it
assert(a == 1);
return 0;
}
If the extraction fails then the cast returns a NullExpression.
@overload(o) function f(){}
function test()
{
alias T = function(s32 _);
assert(!echo(isFunction, o:T));
}
Bit casts, because of their memory corruption proneness, are only allowed using pointers and dereferences
struct S1 {}
struct S2 {}
var S1 s1;
var S2 s2;
s1 = s2:S1; // error, illegal cast
s1 = *(&s2:S1*);// ok, if you insist
If the expression type and the target type are both a pointer to a class then in a first time, the inheritance list of the first class type is inspected. If it contains the target class type then the cast is a simple reinterpret cast.
Otherwise then a dynamic cast is performed. A dynamic cast consists of a comparison of the address of the virtual table of the class instance given by the expression to the address of the virtual table of the class given by the target type.
Dynamic casts are only allowed if the target type contains virtual fonctions, i.e if it has a non null virtual table.
Dynamic casts are not allowed on stack allocated classes.
If an expression is cast to TypeVoid then its side-effect is ignored
DotExpression ::= PostExpression "?"? "." Identifier
The expression allows the access to a member of the aggregate given by the LHS. The expression takes the type of the RHS, i.e the member type.
Special cases are allowed where the RHS does not really give a member. The following sections describes how resolving works.
First the left had side is resolved to either a declaration or a type. This gives a scope that is used to solve the RHS.
Before handling the RHS, builtin properties are tried. Note that this means that builtin properties take the precedence over members that would have the same name.
.decref
§
This void property has for effect to decrement the reference count of the TypeRcArray given by the dot LHS.
It is designed to manage aggregate members and globals, not local rc arrays.
.incref
§
This void property has for effect to increment the reference count of the TypeRcArray given by the dot LHS.
It is designed to manage aggregate members and globals, not local rc arrays.
.refcount
§
The property yields a usize
that gives the reference count of the TypeRcArray given by the dot LHS.
.sizeof
§
The property yields a s32
that gives the byte size of the dot LHS symbol.
.stringof
§
The property yields a char*
null terminated string that represents the source code, normalized, of the dot LHS.
.length
§
The property yields a usize
value that is the length of the array represented by the LHS.
When this property is used as the LHS of an AssignExpression then the property is transformed into an internal expression that has for effect to set the array length. This internal expression is comparable to a call to a function taking a var
array and a usize
length as parameters.
is conceptually like
function setLength(var s32[+] a; const usize len);
var s32[+] array;
setLength(array, array.length + 1);
.ptr
§
The property yields a rvalue pointer to the first element of the array represented by the LHS.
as the property yields a pointer, bounds check are bypassed
var u8[+] array;
array.length += 1;
array.ptr[7] = 8; // may crash or not, depending on the memory allocator implementation
array[7] = 8; // guaranteed to crash if bounds checks are enabled
.min
and .max
§
If the LHS type is integral then the property yields an IntegerExpression that has for value either the min or max value that a variable of this type can represent.
.reverse
§
This property is only valid when used on a the expression that gives a ForeachStatement iterable.
.expand
§
If the LHS type is a TypeTuple then the tuple is expanded.
The property is supported where a comma-separated list of expressions is possible, this includes
the content of a TupleExpression
the arguments of a CallExpression
and the items of an ArrayExpression
.tupleof
§
If the LHS is a struct
then returns a TupleExpression that consists of all the non-static members that are VariableDeclarations.
struct S
{
var u64 a = 1;
var u64 b = 2;
}
var S s;
var (u64,u64) ab = s.tupleof;
assert(ab == (1,2));
If the LHS is a ParenExpression then returns a TupleExpression made of the nested expression.
Then just before taking the most straight path to resolve the RHS, the LHS is inspected. If its type is a pointer to an aggregate then the scope is set to its target.
Access to an aggregate member always requires the aggregate to be a Lvalue (that is fundamentally a pointer anyway).
Then the regular path is borrowed from the scope given by the LHS, e.g to solve enum members, struct members, etc.
In the context of a CallExpression and if the straight path does not allow to resolve to a valid member then the scope given by the LHS is ignored and solving is tried again. In case of success the LHS becomes the first parameter of the call.
struct S { var s64 member; }
function test1(S s) {}
function test2(S* s) {}
function test()
{
var S s;
s.test1(); // `S` does not contain a `test1` member
// `test1` can be resolved from the current scope
// to a function that resides in the parent scope
// `test1(s)` will be tried instead
var S* ps = &s;
ps.test2(); // auto dereference works as well
}
If the runtime check “dotvars” is enabled and if the LHS is an aggregate instance then the LHS is verified before reading the RHS and the program crashes if the LHS is null
.
Given how frequently the check is generated, emitting the matching code is costly. For that reason the check is not generated if the compiler can determine that the LHS will not be null:
this
cannot be reassigned and is already checked.Additionally it’s also possible to bypass manually the check
var a1 = new A;
a1.member1 = value1; // a1 is checked
a1.member2 = value2; // a1 is checked
var a2 = new A;
if a2 do
{
a2.member1 = value1; // a2 is not checked
a2.member2 = value2; // a2 is not checked
}
If the LHS is followed by a question mark then the full evaluation of the chain can be skipped.
The LHS type must be a evaluable as a condition.
If the LHS evaluates to false
then the DotExpression yields a default-initialized value with the same type and value category as the RHS.
Otherwise, the DotExpression yields the RHS.
Optional access is restricted to fields access and function calls.
struct Second { var char* str; }
struct First { var Second* second; }
var First* first;
var char* str = first?.second?.str;
assert(str == null);
struct Value { var s32 m = 1; }
var Value value1;
assert((&value1)?.m == 1); // 1: static init defined in `Value`
var Value* value2;
assert(value2?.m == 0); // 0: `s32` default value
As the callee is given by a DotExpression this is only possible on member function and free functions called with the UFC syntax.
struct S
{
function memberFunc(): u8* { return null; }
}
var S* s;
assert(!s?.memberFunc());
function pseudoMemberFunc(u8* p): u8* { return null; }
assert(!(null:u8*)?.pseudoMemberFunc());
Optional calls can yield TypeVoid
Extra parameters are only evaluated if the optional expression yields true
struct S
{
function test(u8* p): u8* {return null;}
}
function getXtraParam(): u8* {assert(0);}
var S* s;
s?.test(getXtraParam()); // getXtraParam not called, no runtime crash
Using an @operator(true)
function, value types become suitable for optional accesses.
struct S
{
var u8 accept;
var u64 a = 123;
@operator(true) function toCondtion(): bool
{
return (accept++ & 1) == 0;
}
}
function test()
{
var S s;
var auto a = s?.a; // a = s.toCondition() ? s.a else 0:
assert(a == 123);
var auto b = s?.a;
assert(b == 0);
}
nullthis and dotvars checks are not generated for DotExpressions where the LHS is an OptAccessExpression.
IndexExpression ::= PostExpression "[" IndexExpressionIndexes "]"
IndexExpressionIndexes ::= Expression ("," Expression)*
Multiple indexes are only supported by operator overloads, in which case the following rules dont apply.
A single index is allowed.
The type of the index must be integral.
The index is evaluated first.
The unary operand must resolve to a symbol that supports indexing, which includes
When bounds checks are active and if the unary operand gives an array then the index is verified before reading the element.
Bounds are statically checked if the index resolve to an IntegerExpression and that
Otherwise the check is performed at run-time.
If the unary operand resolves to an enum set then it is only accepted either if
If the unary operand resolves to a TypeTuple then
If the unary operand gives a Type and that this type does not implements an @operator(a[b])
function then the expression is lowered to a TypeExpression wrapping a TypeStaticArray.
PostDecrementExpression ::= PostExpression "--"
the result of the post decrement is the value of the unary operand and as a side effect, the stored value of the unary operand is decremented. The result is evaluated before the side effect.
The unary operand must be a lvalue of type integral, floating point or pointer.
The expression takes the type of the unary operand.
PostIncrementExpression ::= PostExpression "++"
the result of the post increment is the value of the unary operand and as a side effect, the stored value of the unary operand is incremented. The result is evaluated before the side effect.
The unary operand must be a lvalue of type integral, floating point or pointer.
The expression takes the type of its unary operand.
SliceExpression ::= PostExpression "[" (RangeExpression | "+")? "]"
The first expression must resolve to a symbol that supports indexing, which includes
The optional range expression gives the lower and upper (exclusive) indexes of the slice. When not specified the lower index is set to 0
and the upper index to the first expression .length
property.
function tabCount(char[] str): auto
{
var usize result;
while str.length do
{
result += str[0] == 9;
str = str[1 .. str.length];
}
return result;
}
When bounds checks are active, and if it does not resolve to a pointer, the expressions that give the slice bounds are checked.
Slices neither allocate nor own memory, they only gives a view. Slices have their own specific type, see TypeSlice.
If the range is not specified and if the unary operand gives a Type and that this type does not implement an @operator(a[])
function then the expression is lowered to a TypeExpression wrapping a TypeSlice.
Similarily, if the +
is present then the expression is lowered to a [RcArrayType].