aboutsummaryrefslogtreecommitdiff
path: root/docs/language_manual/toplevels.md
blob: fc6e9ffd4afee2d5f0aabb52c307370fe56e75b8 (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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252

Top-levels
==========
In an LRL file there are three kinds of basic **top-level declarations**:

  - data
  - functions
  - types

Of these three, data and type declarations may also appear inside functions.
There are also some special top-levels:

  - namespace
  - interop
  - uses
  
All declarations declare a name in the current namespace. The name may
optionally use nested namespaces in the current namespace, or may be a
"here" identifier. See identifiers.md.

Any declaration may optionally have a linkname before it. See the section on
"name mangling" for more information.

In addition, there are **declaration flags**, which modify the behavior in some
way. Not all kinds of declarations can have all of these flags.

  - alias
  - deprecated
  - incomplete

In addition, a data or function declaration may optionally have one of these
linkage flags:

  - declonly
  - export
  - import
  - local

Data declarations
-----------------
A data declaration has the following syntax:

    flags type name = value;

The flags and the value is optional. For example:

    int x;
    local int y;
    alias int z = 2;

The value can be any kind of expression, with one exception. When a data
declaration is used as a top-level (and not as a statement in a function),
the value must compile-time constant.

If a data declaration has the **alias** flag, then it is simply replaced with
it's value everywhere where it's used. Otherwise it's allocated in the data
section.

If the value is omitted, the initial value will be undefined.
It is an error to attempt to read from an undefined value, and what will
happen is undefined and depends on the backend.

Function declarations
---------------------
A function declaration has the following syntax:

    flags return-type name(parameter-declarations) {
        function-body
    }

The flags and the function body are optional parts. For example:

    local () do_work();
    
    alias int add_numbers(int x, int y) {
        return x + y;
    }
    
    (int, bool) return_a_struct() {
        return (3, :true);
    }
    
    noreturn fail() {
        print_error();
        exit_the_program();
    }

If a function does not return a value, use the empty struct () as the return
type. If a function _never_ returns, use the **noreturn** keyword in place
of the return type. This should be used on functions that always exit the
program when called.

If a function has the **alias** flag, then it will be inlined in each place
where it is used. Otherwise it is allocated in the code section.

Type declarations
-----------------
A type declarations has the following syntax:

    typedef flags name = type;

The flags are optional. For example:

    typedef checksum = uint32;
    typedef alias cheksum = checksum; // e.g. mispelled in old version
    
    typedef Point = (int x, int y);
    typedef incomplete Screen = (
        int width,
        int height,
        bool is_external,
        private  // the struct may have more members here, but only the
                 // the part(s) of the program that have complete
                 // definition of the struct can access them
    );
    typedef Secret = private;

The **alias** flag makes types compatible with each other. By default, types
with different names are not compatible.

The **incomplete** flag indicates that the full type is not known. Incomplete
types can be used to prevent access information outside of a particular part
of a program or library. That part can contain the full definition.

It is also possible to create a fully hidden type, by defining it to be the
**private** type. In this case, the incomplete keyword should not be used.


Namespace
---------
A namespace declaration has the following syntax:

    namespace name {
        contents
    }

For example:

    namespace calc {
        int add(int x, int y);
        int sub(int x, int y);
    }

Note that each file also has an automatically created namespace based on
the filename and directories it's contained in. So usually it's not necessary
to create namespaces with the **namespace** keyword.

The same namespace may be declared in multiple places. In that case the
namespaces are simply merged together.

Also, a namespace may have the same name as data declaration, function
declaration or type declaration. For example:

    typedef Num = int;
    namespace Num {
        Num add(Num x, Num y);
    }

Uses
----
**TODO**

Interop
-------
**TODO**

Linkage flags
-------------
Function declarations and data declarations may have linkage flags.

**TODO**

Name mangling
-------------
The output files compiled by LRL can typically NOT contain namespaces.
Therefore, the namespaces are glued into a string. This process is called
"name mangling". LRL uses the "_" to glue namespaces together, which makes
LRL code easy to call from C code, and vice versa. However, C++ compilers use
different name mangling schemes, which are not compatible with LRL.

For example, names are be "name mangled" as follows. It works the same way for
both functions and data:

    int a;       //  a
    int a:b;     //  a_b
    namespace x {
        int c;   //  x_c
        () f();  //  x_f
    }

In addition the filename is also added, if you compile from a file. So
if the file above would be called "file.lc" in a directory called "dir",
then we would get the following result:

    int a;       //  dir_file_a
    int a:b;     //  dir_file_a_b
    namespace x {
        int c;   //  dir_file_x_c
        () f();  //  dir_file_x_f
    }

To override the default name mangling used by LRL, one can use the
**linkname** keyword. It may be placed in front of declarations like this:

    linkname "aa" int a;       //  aa
    linkname "bb" int a:b;     //  bb
    linkname "example" namespace x {
        int c;                 //  example_c
        linkname "g" () f();   //  g
    }

The linkname keyword may also be written on a single line, in which case
it changes the linkname of the namespace it's placed in.

    // change linkname for the file.
    // this line must appear first in the file or namespace.
    linkname "changed";
    
    int a;      //  changed_a;
    int a:b     //  changed_a_b
    
    namespace x {
        // make linkname empty in this namespace.
        // again, it must appear first.
        linkname "";
        
        int c;   //  c
        () f();  //  f
    }

**TODO:** Add a check in the parser that the linkname statement is actually
first.

It is not allowed to have different identifiers with the same mangled name.
It is also not allowed to have different linknames for the same namespace.
For example:

    linkname "";
    
    linkname "b" int a;
    int b; // problem!
    
    linkname "one" namespace to_be_merged {
        int x;
    }
    
    // problem! we can't have a different linkname here.
    linkname "another" namespace to_be_merged {
        int y;
    }

Linknames do not affect the "uses" or "interop" declarations.