aboutsummaryrefslogtreecommitdiff
path: root/language/type-definition.rst
blob: 9940e3b3ca78623ee8847c010e36645939ff2684 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
Defining Types
--------------

.. crowbar:element:: TypeDefinition <- StructDefinition / EnumDefinition / UnionDefinition

    Crowbar has three different kinds of user-defined types.

    Compile-time Behavior:

    When a type is defined, the compiler must then allow that type to be used.

    Runtime Behavior:

    The definition of a type has no runtime behavior.

.. crowbar:element:: StructDefinition <- NormalStructDefinition / OpaqueStructDefinition
                     NormalStructDefinition <- 'struct' identifier '{' VariableDeclaration+ '}'

    A ``struct`` defines a composite type with several members.
    Its members are stored in the order in which they are defined, and they each take up the space they normally would.

    .. todo::

        figure out alignment & padding

.. crowbar:element:: OpaqueStructDefinition <- 'opaque' 'struct' identifier ';'

    An *opaque struct* is a struct whose name is part of an API boundary but whose contents are not.
    Its size is left unspecified, and it can only be used as the target of a pointer.

.. crowbar:element:: EnumDefinition <- 'enum' identifier '{' EnumMember (',' EnumMember)* ','? '}'
                     EnumMember <- identifier ('=' Expression)?

    An ``enum`` defines a type which can take one of several specified values.

    .. todo::

        define enum value assignment, type-related behavior

.. crowbar:element:: UnionDefinition <- RobustUnionDefinition / FragileUnionDefinition

    Unions as implemented in C are not robust by default, and care must be taken to ensure that they are only used robustly.
    However, for the purpose of interoperability with C, Crowbar unions may be defined as robust or as fragile.

.. crowbar:element:: RobustUnionDefinition <- 'union' identifier '{' VariableDeclaration UnionBody '}'
                     UnionBody <- 'switch' '(' identifier ')' '{' UnionBodySet+ '}'
                     UnionBodySet <- CaseSpecifier+ (VariableDeclaration / ';')

    A robust union, or simply union, in Crowbar is what is known more broadly as a tagged union.
    It's a way to package some data alongside an ``enum`` but have the type of data depend on the value of the ``enum``.
    Since the enum value indicates which data is present, the enum value is also known as a *tag*.
    The top-level variable declaration creates the tag.
    The tag must have a type which is some ``enum``.
    The ``switch`` parameter must be the name of the tag, and the cases will declare the data associated with a given value of the tag.
    This allows for storing extra data alongside enum values while using minimal additional space in memory.
    (All the fields under the ``switch`` overlap as stored in memory, so it's important to use the tag to specify which field is available.)

    For example::

        enum TokenType {
            Identifier,
            Constant,
            Operator,
            Whitespace,
        }

        union Token {
            enum TokenType type;

            switch (type) {
                case Identifier: (const byte) * name;
                case Constant: intmax value;
                case Operator: (const byte) * op;
                case Whitespace: ;
            }
        }

    defines a ``union Token`` type, where the ``type`` field controls which of the other fields in the union is valid.

    .. todo::

        go into more depth about how tagged unions work

.. crowbar:element:: FragileUnionDefinition <- 'fragile' 'union' identifier '{' VariableDeclaration+ '}'

    A fragile union also allows for storing one of several different types of data.
    However, there is no internal indication of which type of data is actually being stored in the union.
    As such, in non-trivial cases no compiler can predict which field is or is not valid, and any statement which reads a field of a fragile union must itself be a :crowbar:ref:`FragileStatement`.

    The size of a fragile union is the largest size of any of its members.
    The address of each member is the address of the union object itself.
    The member which was most recently set will retain its value.
    Reading another member with size no larger than the most recently set member will interpret the first bytes of the most recently set member as a value of the type of the member being read.

    For example, the functions ``test1`` and ``test2`` are equivalent::

        fragile union Example {
            float32 float_data;
            uint32 uint_data;
        }

        uint32 test1(float32 arg) {
            union Example temp;
            temp.float_data = arg;
            fragile return temp.uint_data;
        }

        uint32 test2(float32 arg) {
            float32* temp = &arg;
            fragile uint32* temp_casted = (uint32*)temp;
            return *temp_casted;
        }