< back to index

Types

Millfork puts extra limitations on which types can be used in which contexts.

Numeric types

You can access single bytes of variables by using the following notations:

You can also access words that are parts of variables:

Numeric types can be converted automatically:

Typed pointers

For every type T, there is a pointer type defined called pointer.T.

Unlike raw pointers, they are not subject to arithmetic.

If the type T is of size 1, you can index the pointer like a raw pointer.

If the type T is of size 2, you can index the pointer only with the constant 0.

Examples:

pointer.t p
p.raw       // expression of type pointer, pointing to the same location in memory as 'p'
p.lo        // equivalent to 'p.raw.lo'
p.hi        // equivalent to 'p.raw.lo'
p[0]        // valid only if the type 't' is of size 1 or 2, accesses the pointed element
p[i]        // valid only if the type 't' is of size 1, equivalent to 't(p.raw[i])'
p->x        // valid only if the type 't' has a field called 'x', accesses the field 'x' of the pointed element
p->x.y[0]->z[0][6]   // you can stack it

nullptr

There is a 2-byte constant nullptr that can be assigned to any 2-byte pointer type. Its actual value is defined using the feature NULLPTR, by default it's 0.

nullptr isn't directly assignable to non-pointer types.

Boolean types

TODO

Special types

Enumerations

Enumeration is a 1-byte type that represents a set of values:

enum <name> { <variants, separated by commas or newlines> }

The first variant has value 0. Every next variant has a value increased by 1 compared to a previous one.

Alternatively, a variant can be given a custom constant value, which will change the sequence.

If there is at least one variant and no variant is given a custom constant value, then the enumeration is considered plain. Plain enumeration types can be used as array keys. For plain enumerations, a constant <name>.count is defined, equal to the number of variants in the enumeration.

Assigment between numeric types and enumerations is not possible without an explicit type cast:

enum E { EA, EB }
byte b
E e
e = EA      // ok
e = b       // won't compile
b = e       // won't compile
b = byte(e) // ok
e = E(b)    // ok

array a[E]  // E is plain, array has size 2
a[0]        // won't compile
a[EB]       // ok

Plain enumerations have their variants equal to byte(0) to byte(<name>.count - 1).

Tip: You can use an enumeration with no variants as a strongly checked alternative byte type, as there are no checks on values when converting bytes to enumeration values and vice versa.

Structs

Struct is a compound type containing multiple fields of various types:

struct <name> { <field definitions (type and name), separated by commas or newlines>}

A struct is represented in memory as a contiguous area of variables laid out one after another.

Struct can have a maximum size of 255 bytes. Larger structs are not supported.

You can access a field of a struct with the dot:

struct point { word x, word y }

point p
p.x = 3
p.y.lo = 4

Offsets are available as structname.fieldname.offset:

pointer ptr
ptr = p.addr
ptr += point.y.offset
// ptr points now at p.y

// alternatively:
ptr = p.y.addr

You can create constant expressions of struct types using so-called struct constructors, e.g.:

point(5,6)

All arguments to the constructor must be constant.

Unions

union <name> { <field definitions (type and name), separated by commas or newlines>}

Unions are pretty similar to structs, with the difference that all fields of the union start at the same point in memory and therefore overlap each other.

struct point { byte x, byte y }
union point_or_word { point p, word w }

point_or_word u
u.p.x = 0
u.p.y = 0
if u.w == 0 { ok() }

Offset constants are also available, but they're obviously all zero.

Unions currently do not have an equivalent of struct constructors. This may be improved on in the future.