1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-05-28 13:41:31 +00:00
millfork/docs/lang/types.md
2019-07-15 13:52:05 +02:00

5.7 KiB
Raw Blame History

< back to index

Types

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

Numeric types

  • byte 1-byte value of undefined signedness, defaulting to unsigned

  • word 2-byte value of undefined signedness, defaulting to unsigned (alias: int16)

  • int24 3-byte value of undefined signedness, defaulting to unsigned (alias: farword; this alias is deprecated and will be removed in the future)

  • long 4-byte value of undefined signedness, defaulting to unsigned (alias: int32)

  • int40, int48,... int128 even larger types

  • sbyte signed 1-byte value

  • ubyte unsigned 1-byte value

  • pointer raw pointers; the same as word, but variables of this type default to be zero-page-allocated and you can index pointer-typed expressions. You can create pointer values by suffixing .addr to the name of a variable, function or array.

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

  • for 2-byte-sized variables: .lo for the least significant byte and .hi for the most significant byte

  • for larger variables: .b0 for the least significant byte and then .b1, .b2 and so on

You can also access words that are parts of variables:

  • for 3-byte-sized variables: .loword is the word formed from .b1 and .b0 and .hiword is the word formed from .b2 and .b1

  • for 4-byte-sized variables: .loword is the word formed from .b1 and .b0 and .hiword is the word formed from .b3 and .b2

Numeric types can be converted automatically:

  • from a smaller type to a bigger type (byteword)

  • from a type of undefined signedness to a type of defined signedness (bytesbyte)

  • from a type of defined signedness to a type of undefined signedness (sbytebyte)

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

  • void a unit type containing no information, can be only used as a return type for a function.

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.