Type

Grammar§

Type ::= TypeAuto | TypeSpecified ;

TypeAuto§

Grammar§

TypeAuto ::= "auto"

Semantics§

A wildcard to denote a type that will be replaced by another one during the semantic pass.

TypeSpecified§

Grammar§

TypeSpecified ::= TypeUnambiguous | TypeModified | TypeRaw ;

Semantics§

All the types, excluding TypeAuto

TypeUnambiguous§

Grammar§

TypeUnambiguous ::= "(" TypeSpecified ")"

Semantics§

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 parens this is 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;

TypeRaw§

Grammar§

TypeRaw ::= BasicType | TypeFunction | TypeIdentifier ; BasicType ::= "bool" | "u8" | "u16" | "u32" | "u64" | "usize" | "s8" | "s16" | "s32" | "s64" | "ssize" | "f32" | "f64" ; TypeFunction ::= FunctionDeclaration TypeIdentifier ::= Identifier ( "." TypeIdentifier )?

A type is always made of a raw type that can be a keyword (for the builtin types), auto, 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.

Symbols and expressions that have these types are evaluable as a condition, that is “if not zero”.

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”.

TypeFunction§

A function type can be identified using the same syntax as a FunctionDeclaration. The body must not be specified and the identifier is optional

alias F = function();

Function types are usually used with a pointer as modifier.

static function f(){}
var (static function())* fPtr = &f;

Symbols and expressions of these types are not evaluable as a condition.

TypeIdentifier§

Type identifiers form a chain. Parts are resolved during semantic to either classes, structures, aliases, overloads, enums or units.

struct F { static struct B {} }
var F.B* fbPtr; // pointer to a struct F.B

Even if units are not types, the chain can start with unit identifiers

import a.b;
var a.b.F f; // f resolves to a type declared in a.b

as only the last chain element has to give a type.

TypeModified§

Grammar§

TypeModified ::= TypePointer | TypeStaticArray | TypeRcArray | TypeSlice | TypeEnumSet ;

Semantics§

Types derived from TypeModified support the IndexExpression.

TypePointer§

Grammar§

TypePointer ::= TypeSpecified "*"

Semantics§

Gives a type that is a pointer to another type.

struct S {}
var S* s;  // s is a pointer to a S instance

Symbols and expressions that have these types are evaluable as a condition, that is “if not null”.

TypeStaticArray§

Grammar§

TypeStaticArray ::= TypeSpecified "[" Expression "]"

Semantics§

Define the type for a fixed length array.

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.

struct S {}
var S*[2] s;  // s is a fixed length vector of two pointers to S instances

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.

TypeRcArray§

Grammar§

TypeRcArray ::= TypeSpecified "[" "+" "]"

Semantics§

Define the type for a variable length array with automatic reference counting.

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 rc array is modified or when elements are appened the array is copied then modified. The reference that requested the modification is assigned 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 that was passed

function f(var u64[+] p)
{
    p.length += 1;
}
var u64[+] v = [0];
f(p);
assert(v.length == 2);

It is illegal to escape the .ptr of an rc array because that prevents the protection against being freed during the locals cleanup.

function ensureNullTerminated(s8[] str): s8*
{
    var s8[+] 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 frame-local rc arrays are automatically managed. Static and members must be freed using destructors.

struct S
{
    var u64[+] member;
    @destructor function destroy()
    {
        member.decref;
    }
}

A TypeRcArray arrays 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.

TypeSlice§

Grammar§

TypeSlice ::= TypeSpecified "[" "]"

Semantics§

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(s8* ptr; usize length)
{
    var s8[] 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

TypeEnumSet§

Grammar§

TypeEnumSet ::= TypeSpecified "[" Expression "]"

Semantics§

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

enum 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]);
}

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
}

Other internal types§

TypeNull§

The initial type of the NullExpression. NullExpressions however are always cast to another type.

// null alone is of TypeNull but of type s8* after implicit conversion
var s8* v = null;

TypeReturn§

A context specific type allowing functions to optionally return lvalues, see TypeReturn.

TypeVoid§

The return type of function declared without explicit TypeReturn.

TypeDeclared§

The declarations that a TypeIdentifier can resolve to are all associated to an internal type : ClassDeclaration are linked to a TypeClass, EnumDeclaration to a TypeEnum, and so on.