aboutsummaryrefslogtreecommitdiff
path: root/docs/language_manual/statements.md
blob: 66ba74f59575eff66bdbb120e7e3ef39bbf6b611 (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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471

Statements
==========
A function consists of a "list of statements", which is also called a
**compound statement**. A compound statement looks like this:

    {
        statement1
        statement2
        statement3
        ...
    }

Statements are typically terminated by a semicolon, but no semicolon should
be placed at the end of a compound statement.

Compound statements may also be nested, and may appear anywhere where a
regular statement is allowed.

In a compund statement, each sub-statement makes up a nested namespace which
lasts until the end of the statement. So declaring a variable is statement2
in the example above, would make it visible in both statement2 and statement3,
but not in statement1.

Declarations inside compound statements are also hidden to outside statements,
which can be used to e.g. declare variables with the same name:

    {
        int x;
        {
            int y;
            // "x" and "y" can be accessed
        }
        // "y" is no longer visible
        {
            int y; // this is a new variable, also called "y"
        }
        // new "y" is not longer visible.
    }

**TODO** where break/continue/return is allowed, goto/skipto/repeatfrom is also allowed.

Expression statements
---------------------
Syntax:

    expr;

Examples:

    n = 1 + n*2;
    arr#[0] = 123;
    func();

See the file **expressions.md** for descriptions of the available expressions.

Data declaration statements
---------------------------
Syntax:

    type name = expr;

The initial expression is optional. Examples:

    int a;
    int b = 123;
    (struct,

If the expression 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.

Type declaration statements
---------------------------
Syntax:

    typedef flags name = type;

The flags are optional. Examples:

    typedef color = enum (red, yellow, green, blue);
    typedef alias length = count;

The typedef statement works exactly like at the top level (see
**toplevels.md**), but declares the identifier in the current and following
statements.

assert
------
Syntax:

    assert expr;

Produces an error if expr is false. Since the behavior of errors is undefined,
the compiler is free to choose if it should e.g. print an error and exit,
or to assume that the expr is never false and use that information to optimize
the program.

**NOTE** should "assert :false;" be allowed or not? perhaps it's better to use "undefined;" for cases where you want to indicate that a part of code is unreachable. or perhaps a new keyword?

break
-----
Syntax:

    break;

Jumps out of the containing loop (for, while or do/while). If there are
several nested loops, it breaks out of the innermost one. Example:

    for int i in [1,1,2,3,5] {
        for int j in [2,3,5,7,11] {
            if i == j break;
        }
        // the break jumps here
    }

**TODO:** Fix the compiler so it can detect the type of the arrays.

continue
--------
Syntax:

    continue;

Skips the rest of the current step in the containing loop (for, while or
do/while). If there are several nested loops, it does this in the innermost
one. Example:

    for int i in [1,1,2,3,5] {
        for int j in [2,3,5,7,11] {
            if i == j continue;
            
            // this line is not reached when i==j
            // (but the loop will continue at j+1)
        }
    }

**TODO:** Fix the compiler so it can detect the type of the arrays.

do
--
Syntax:

    do {
        statements
    } while expression;

Executes the statements until the expression is false, but at least once
(the check is done after each loop iteration).

**TODO** "end" and "empty" statements

The expression must be of **bool** type.

for
---
Syntax:

    for type name in expression loopstatement
    end endstatement
    empty emptystatement

**FIXME** make the example more readable

Iterates over the expression and executes the loop statement for each value
from the iteration. What "iteration" means depends on the type of the
expression.

For expressions for which there's an identifier called **next\_element** in
the inside the scope of the type of the expression, that function will be
called for each iteration to obtain the next value and determine when to stop
iterating. In this case the value of the expression is called an _iterator_.

**TODO:** will the compiler handle this for non-struct types?

**TODO:** shorten these names to next\_elem and get\_iter

**TODO** example

For expressions for which there's an identifier called **get\_iterator** in
the inside the scope of the type of the expression, that function will be
called first to obtain an _iterator_. Then **next\_element** will be called
on the iterator like above.

**TODO** example

For array expressions, iteration means to go over all elements in the array.
For example:

    int#[3] arr = [11,22,33];
    for int i in arr {
        // do someting
    }

The "end" and "empty" statements are executed depending on what the loop
executed. If no "break" statement was executed in the loop (so the loop
actually reached is end), then the **end** statement will be executed.
If the loop statement was not executed at all (due to an empty array or
iterator), then the **empty** expression will be executed.

Just like in **if** statements, the loop statement must be either a compound
statement, or a break, continue or return statement. Usually it's a compound
statement.

goto
----
Syntax (for goto, skipto and repeatfrom):

    goto labelname;
    skipto labelname;
    repeatfrom labelname;

These statements all jump to the given label. There's no difference between
them, except that the keyword describes how they are used. A **skipto** jumps
forward in the order of statements, a **repeatform** jumps backwards
and a goto is used to jump when there's no forward/backwards relation between
the jump and it's label, for instance between if/else statements. For example:

    () test() {
        skipto one;
        x();
      label one:
        if 1 == 2 {
            a();
          label inside:
            c();
        } else {
            b();
            if 2 == 2 goto inside;
            y();
        }
        repeatfrom one;
    }

It's only possible to jump within a function, not between functions.

**TODO:** actually check that they are used correctly.

**TODO:** change label syntax to not use : and use ; instead?

if
--
Syntax:

    if expression statement1
    else statement2

If expression is true, then statement1 is executed. Otherwise statement2 is
executed. The else part is optional. The else statement may be another
if statement, so "else if"s can be written like this:

    if i == 1 return "one";
    else if i == 2 return "two";
    else return "other";

Each of the statements must be either a compound statement, or break, continue
or return statement. In other words, you'll need to write the statement inside
{ } brackets, unless the statemet is a break, continue or return. Example:

    if i == 1 {
        // {} are required becuase this is an expression statement
        // and not a break, continue or return.
        do_something();
    } else if i == 2 {
        // here we have a break, but when there are multiple statements
        // we must also wrap them in {}
        do_something_else();
        break;
    } else break; // no {} needed for break, continue or return

The expression after the **if** keyword must be of **bool** type.

label
-----
Syntax:

    label name:

Defines a label for goto/skipto/repeatfrom.

Note that it ends with a colon. Usually it is de-indented two spaces compared
to the surrounding lines. Example:

    () test() {
        int x = 5;
        
        if x == 5 skipto dont_work;
        work();
        
      label dont_work:
        fun();
    }

**TODO:** change label syntax to not use : and use ; instead?

repeatfrom
--
See **goto**

return
------
Syntax:

    return expression;

Evalutes the expression and returns from the current function with the value
of the expression. If no expression is given it returns an empty struct.

The type of the expression must be compatible (assignable to) the return type
of the function.

skipto
------
See **goto**

switch
------
Syntax:
    
    switch expression {
        case value1 casestatement1
        case value21 with withstatement21
        case value22 with withstatement22
        ...
        case value23 with withstatement23 casestatement2
        case value3 casestatement3
        default defaultstatement
    }

Evaluates the expression once and then compares it with the case values.
The expression can have any type, including array or struct type, but the case
values must be compile-time constants. When a matching case value is found,
the following statement is executed. If no matching case value is found then
the default statement is executed.

When the end of a case statement is reached, then the switch statement is
exited. Unlike some other languages there is no need to "break" out of the
switch statement, and the "break" keyword does not apply to the switch
statement.

A single case statement may have multiple case values before it.
Here's an example:

    switch i {
        case 1
        case 2
        case 3 {
            // value is 1, 2 or 3
        }
        case 9 return; // value is 9
        default {
            // if neither 1,2,3 or 9
        }
    }

When you have multiple case expressions, it can be useful to know which one
was matched. This can be done with the "with" syntax:

    var int type;
    switch compute_value() {
        case 111 with type = 10
        case 222 with type = 20 {
            // type is set to either 10 or 20.
        }
        case 333 with type = 30
        case 444 with type = 40 {
            // type is set to either 30 or 40.
        }
    }

Just like with the **if** statement, the case statements must be either
compound statements, or break, continue or return statements.

typeassert
----------
Syntax:

    typeassert name is type in range do statement else statement

Checks if the identifier (which must be a constant data declaration) called
name is within the given range and may be converted to the given type.

**FIXME** doesn't just check the type, it also casts it. Strictly speaking it create a new variable with the same name, but the new type, that shadows the old variable.

**TODO:** decide how the range part should work and implement it.

**TODO:** mutable data is currently allowed. it should probably be forbidden.

The "is" and "in" parts are optional, but one of them must be specified.
If one of them is omitted, then the other one is determined automatically.

The "do" and "else" parts are both optional. If the do part is specified it
will be executed if the type check succeeds, otherwise the following statements
are executed. If there an "else" part it is executed if the type check fails.
If there's no "do" part and no "else" part, a type failure will be considered
an error. If there's no "do" part but there's an "else" part, then only
reaching the end of the "else" statement will be considered an error.

What happens on an error is undefined and depends on the backend.

Here's an example:

    () test() {
        int i = 10;
        typeassert i is byte; // type check is OK, execution continues
        
        typeassert i is byte do { // type check is OK
            // this will execute
        } else {
            // this will NOT execute
        }
        
        // Now test with an value that's out of range
        int j = 1000;
        typeassert j is byte do {
            // will NOT execute
        } else {
            // will execute
        }
        
        typeassert j is byte else {
            // will execute
            skipto dont_crash;
            // if we reach the end, then that's an error!
        }
      label dont_crash:
        
        typeassert j is byte;
        // the statement above causes an error.
        // depending on the backend, anything can happen now.
    }

**TODO:** change label syntax to not use : and use ; instead?

Just like with the **if** statement, the case statements must be either
compound statements, or break, continue or return statements.

unreachable
-----------
Syntax:

    unreachable;

Tells the compiler that the statement will never be executed, which can prevent
compiler warnings or allow compiler optimizations. If the unreachable
statement is actually executed, then that's an error and has undefined
behavior.

while
-----
Syntax:

    while expression {
        statements
    }

Executes the statements until the expression is false. The check is done
before each loop iteration, so the statements inside the loop might not
be executed at all.

**TODO** "end" and "empty" statements

The expression must be of **bool** type.

Special rules
-------------
**FIXME** none of the above statements require a compound statement
Where the syntaxes above sometimes require a compound statement, it is also
_allowed_ but _not recommended_ to use a break, continue, or return statement.
The compiler is allowed to generate a warning in that case (but the current
version of **lrlc** does not show any warnings at all).