Type ::= TypeAuto
| TypeVoid
| TypeSpecified
;
TypeAuto ::= "auto"
A wildcard to denote a type that will be replaced by another one during the semantic pass.
TypeVoid ::= "()"
The type for function that return nothing. It has no correpsonding value and cannot be used to declare variables.
TypeSpecified ::= TypeUnambiguous
| TypeTuple
| TypeModified
| TypeRaw
;
All the types, excluding TypeAuto
TypeUnambiguous ::= "(" TypeSpecified ")"
A type can be enclosed between parens. If in some case this gives a better readability this also allows to express certain function types.
// no way to express that the type modifiers are not for the return type
var function (): s32*[2] a;
// no way to express that the static storage class is for the function type
var static function()* b;
With the help of parentheses this becomes possible
// The type identifier is a function that returns a s32
// The modified type is a static array of pointer to functions that return s32
var (function(): s32)*[2] a;
// static is for b type, not b itself.
var (static function()*) b;
TypeTuple ::= "(" Type ("," Type")* ")"
Define a type made of one or more types.
Expressions of this type are evaluable as a condition, that is “if more than one type”.
TypeRaw ::= BasicType
| TypeFunction
| TypeIdentifier
| TypeFrom
| TypeEcho
;
BasicType ::= "bool"
| "u8" | "u16" | "u32" | "u64" | "usize"
| "s8" | "s16" | "s32" | "s64" | "ssize"
| "f32" | "f64" | "char"
;
TypeFunction ::= FunctionDeclaration
TypeIdentifier ::= (TypeAppliedGeneric | Identifier) ( "." TypeIdentifier )?
TypeFrom ::= FromExpression "." TypeIdentifier
TypeEcho ::= EchoExpression
A type is always made of a raw type that can be a keyword (for the builtin types), a function type or finally a chain of identifiers.
bool
§
Unsigned 1 bit integer used to represent true
and false
in logical and relational expressions.
Expressions of this type are evaluable as a condition, that is “if not zero”.
Expressions of this type that are used in arithmetic [BinaryExpression] are exceptionally promoted to the smaller unsigned integral type, u8
.
u8
u16
u32
u64
usize
§
Unsigned integer types, with respectively a size of 8, 16, 32 or 64 bits. usize
has the size of a pointer on the targeted architecture.
Symbols and expressions that have these types are evaluable as a condition, that is “if not zero”.
s8
s16
s32
s64
ssize
§
Signed integer types, with respectively a size of 8, 16, 32 or 64 bits. ssize
has the size of a pointer on the targeted architecture.
Symbols and expressions that have these types are evaluable as a condition, that is “if not zero”.
char
§
Similar to u8
but used type of the StringExpression, so that string manipulation is distinguishable.
A function type can be identified using the same syntax as a FunctionDeclaration. The body must not be specified and the name is optional
Symbols and expressions of these types are not evaluable as a condition.
Function types are usually used with a pointer as modifier.
Type identifiers form a chain. Parts are resolved during semantic to either classes, structures, aliases, overloads, enums or units.
Even if units are not types, the chain can start with unit
identifiers
as only the last chain element has to give a type.
If the last part identifier is .expand
and that the previous part gave a TypeTuple then the resulting type is the parent tuple with the tuple expanded, non-transitively.
alias T0 = (s16, s32);
alias T1 = (s8, T0, s32); // (s8, (s16, s32), s64)
alias T1 = (s8, T0.expand, s32); // (s8, s16, s32, s64)
The expanssion is not available on TypeTuple literals, only on alias to them.
Resolve to the TypeIdentifier in the unit soecified by the FromExpression.
This type wraps an EchoExpression. If the expression yields a TypeExpression then it is replaced with the expression type.
Valid compiler echoes are func_t
, return
, getPointedType
, and type
.
Any other echo command is rejected.
TypeModified ::= TypePointer
| TypeStaticArray
| TypeElements
| TypeRcArray
| TypeSlice
| TypeEnumSet
;
Types derived from TypeModified support the IndexExpression.
TypePointer ::= TypeSpecified "*"
Gives a type that is a pointer to another type.
Symbols and expressions that have these types are evaluable as a condition, that is “if not null”.
TypeStaticArray ::= TypeSpecified "[" Expression "]"
Define the type for a fixed length array.
If the modified type is a TypeTuple and if the expression is a RangeExpression then the type is tried as a TypeElements.
The expression must be simplifiable to an IntegerExpression at compile-time.
If this condition is not verified then the type is tried as a TypeEnumSet.
Similarly to TypeSlice, static arrays support the .length
and .ptr
properties but being value types they are syntactic shortcuts that yield expressions that, contrary to TypeSlice, involves no indirections.
TypeElements ::= TypeSpecified "[" Expression ("," Expression)* "]"
Define a TypeTuple by selecting elements in another one.
The first type must give a TypeTuple.
After evaluation, each expression must either give an IntegerExpression or a RangeExpression.
If an expression gives an IntegerExpression then the type located at the position given by its value is appended to the target TypeTuple.
If an expression gives a RangeExpression then the two sides of the range must give an IntegerExpression. The types located between the position given by the left value to the right value minus one are appended to the target TypeTuple.
alias T0 = (s8, s16, s32, s64);
alias T1 = T0[3,2,1,0]; // s64 s32 s16 s8
alias T2 = T0[1 .. $]; // s16 s32 s64
It is an error to select types using out-of-bounds indexes.
TypeRcArray ::= TypeSpecified "[" "+" "]"
Define the type for a variable length array with automatic reference counting at run-time.
Reference counted arrays are non dereferencable pointer to heap-allocated chunks. They are made of a usize
reference count, a usize
length, and the variable length content. The fields are respecectively accessible using the .refcount
, .length
and the .ptr
DotExpression properties. The Additional DotExpression properties .decref
, .incref
give a low-level control of the counting.
Expressions that have this type are evaluable as a condition, that is “if length is not zero”.
The count is automatically incremented
It is automatically decremented
If the count is 0, then the array has no reference, only an owner.
If the count reaches -1, then array is automatically released to the allocator.
When the length of a refcounted array is modified or when elements are appened the array is firstly copied.
The reference that requested the modification is reassigned to the mutated copy.
var s64[+] a = [1,2]; // a refcount == 0
var s64 b; // b refcount == 0
var s64[+] b = a; // a & b refcount == 1 and point to the same array
b.length += 2; // a & b refcount == 0, each point to a different array
Operations on mutable rvalue parameters are done on a distinct copy that’s created on function entry.
function f(u64[+] p)
{
// hidden `p = p.dup;`
p.length += 1;
}
var u64[+] v = [0];
f(p);
assert(v.length == 1 && v[0] == 0);
Operations on lvalue parameters transparently affect the reference
Operations on const
rvalue parameters are limited to the array elements and transparently affect the elements of the reference that was passed. It is however not possible to corrupt the .ptr
of the argument.
function f(const u64[+] p)
{
p[0] = 1; // OK, does not currupt the original argument .ptr
p.length += 1; // NG, would corrupt `v.ptr`
}
var u64[+] v = [0];
f(p);
It is illegal to escape the .ptr
of a refcounted array or to assign it to a variable with a longer lifetime.
In the context of the a ReturnStatement this would break a protection that’s setup to prevent local cleanup.
function ensureNullTerminated(char[] str): char*
{
var char[+] result = str;
if const usize len = str.length do
{
if str[len - 1] != 0 do
result ~= 0;
}
// the following ReturnStatement is actually decomposed in 3 steps
//
// 1. evaluate the expression : `var auto r = result.ptr;`
// 2. stack cleanup : `result.decref;`
// 3. real return : return r;
return result.ptr;
}
In this situation manual increment of the reference count will preserve the memory but will also create a leak.
Only local refcounted arrays are automatically managed.
Static and members must be freed using destructors.
A refcounted array can be copied using the dup
DotExpression property. The initial count of a dup is -1, so a dup should always be assigned to a lvalue.
A refcounted array can be constructed using a [CallExppression] on a NewExpression. The call must have exactly one argument, which sets the length of the leftmost dim.
Cycles between refcounted arrays are undefined behavior
var auto a = (new u64[+])(1);
a = a; // cycle
assert(a.length == 1); // fails, a.length is undefined
TypeSlice ::= TypeSpecified "[" "]"
The type of a bounds checked view on a a pointer, a static array, or a rc array, and with a stack allocated payload.
function test(char* ptr; usize length)
{
var char[] slice = ptr[0 .. length];
while slice.length != 0 do
slice = slice[1 .. slice.length];
}
Slices are represented using a payload made of a usize
length and a TypePointer ptr. They are respecectively accessible using the .length
and the .ptr
DotExpression properties.
Expressions of this type are evaluable as a condition that is “if length is not zero”.
TypeSlice does not allocate, does not require managment but has limited capabilities. A variable of TypeSlice
While it is not disallowed to declare member of type TypeSlice, as valid use cases may be possible, it should be noted that this can easily cause lifetime issues.
In the following example member
may have a longer life time than the source p
memory, causing invalid reads, either because of reallocated or free’d, out-of-scope stack memory.
It is preferable to use TypeRcArray members so that memory remains alive at least a long as the aggregate instance.
struct S
{
var char[+] member; // keep alive for the instance
// better if called with an arg that's rc array
function f1(char[+] p)
{
member = p;
}
// alternatively
function f2(char[] p)
{
member = p;
}
@destructor function destroy()
{
member.decref;
}
}
TypeEnumSet ::= TypeSpecified "[" Expression "]"
If a TypeStaticArray is for a bool
and that the expression that gives the vector length resolves to an EnumDeclaration then the type becomes TypeEnumSet.
This type defines a fixed-length bit vector. The vector size is defined by the EnumDeclaration type. Its members must be ordered, and the last member value must no exceed the vector bit size.
The expressions allowed on TypeEnumSet are limited to
set[member] = true
, set += member
, set + member
, set += set
, set + set
set[member] = false
or set -= member
, set - member
, set -= set
, set - set
(set[member])
or member in set
or (set[set])
or set in set
.min
and .min
DotExpression propertiesenum Direction
{
N, E, S, W
}
alias Directions = bool[Direction];
@unittest function t1()
{
Directions dir;
dir += Direction.N;
dir += Direction.W;
assert(Direction.N in dir);
assert(dir[Direction.W]);
assert(Directions.max == [N,E,S,W]); // 1 + 2 + 4 + 8
}
Note that the members value is never used, instead their rank is used to left shift 1.
The membership of the value used in TypeEnumSet operations is checked during compilation, so that only literals are accepted when using an extended enum.
enum Base {b1}
enum Extended : Base {b2}
function test()
{
var bool[Base] bb;
bb[Extended.b1] = true; // OK
var Extended e;
bb[e] = true; // NG, membership is not verified
}
The initial type of the NullExpression.
It is generally cast to to another type so it is not required to name it.
However TypeNull can be specified by two ways
A context specific type allowing functions to optionally return lvalues, see TypeReturn.
The declaration that a TypeIdentifier resolve to is always associated to an internal type : ClassDeclaration is linked to a TypeClass, EnumDeclaration to a TypeEnum, and so on.