summaryrefslogtreecommitdiff
path: root/docs/markdown/Syntax.md
blob: 2e85ca854d8c3e216550311024b87357aee5d963 (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
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
---
short-description: Syntax and structure of Meson files
...

# Syntax

The syntax of Meson's specification language has been kept as simple
as possible. It is *strongly typed* so no object is ever converted to
another under the covers. Variables have no visible type which makes
Meson *dynamically typed* (also known as *duck typed*).

The main building blocks of the language are *variables*, *numbers*,
*booleans*, *strings*, *arrays*, *function calls*, *method calls*, *if
statements* and *includes*.

Usually one Meson statement takes just one line. There is no way to
have multiple statements on one line as in e.g. *C*. Function and
method calls' argument lists can be split over multiple lines. Meson
will autodetect this case and do the right thing.

In other cases, *(added 0.50)* you can get multi-line statements by
ending the line with a `\`. Apart from line ending whitespace has no
syntactic meaning.

## Variables

Variables in Meson work just like in other high level programming
languages. A variable can contain a value of any type, such as an
integer or a string. Variables don't need to be predeclared, you can
just assign to them and they appear. Here's how you would assign
values to two different variables.

```meson
var1 = 'hello'
var2 = 102
```

One important difference in how variables work in Meson is that all
objects are immutable. When you see an operation which appears like
a mutation, actually a new object is created and assigned to the
name. This is different from, for example, how Python works for
objects, but similar to e.g. Python strings.

```meson
var1 = [1, 2, 3]
var2 = var1
var2 += [4]
# var2 is now [1, 2, 3, 4]
# var1 is still [1, 2, 3]
```

## Numbers

Meson supports only integer numbers. They are declared simply by
writing them out. Basic arithmetic operations are supported.

```meson
x = 1 + 2
y = 3 * 4
d = 5 % 3 # Yields 2.
```

Hexadecimal literals are supported since version 0.45.0:

```meson
int_255 = 0xFF
```

Octal and binary literals are supported since version 0.47.0:

```meson
int_493 = 0o755
int_1365 = 0b10101010101
```

Strings can be converted to a number like this:

```meson
string_var = '42'
num = string_var.to_int()
```

Numbers can be converted to a string:

```meson
int_var = 42
string_var = int_var.to_string()
```

## Booleans

A boolean is either `true` or `false`.

```meson
truth = true
```

Booleans can be converted to a string or to a number:

```meson
bool_var = true
string_var = bool_var.to_string()
int_var = bool_var.to_int()
```

## Strings

Strings in Meson are declared with single quotes. To enter a literal
single quote do it like this:

```meson
single quote = 'contains a \' character'
```

The full list of escape sequences is:

* `\\` Backslash
* `\'` Single quote
* `\a` Bell
* `\b` Backspace
* `\f` Formfeed
* `\n` Newline
* `\r` Carriage Return
* `\t` Horizontal Tab
* `\v` Vertical Tab
* `\ooo` Character with octal value ooo
* `\xhh` Character with hex value hh
* `\uxxxx` Character with 16-bit hex value xxxx
* `\Uxxxxxxxx` Character with 32-bit hex value xxxxxxxx
* `\N{name}` Character named name in Unicode database

As in python and C, up to three octal digits are accepted in `\ooo`.

Unrecognized escape sequences are left in the string unchanged, i.e., the
backslash is left in the string.

### String concatenation

Strings can be concatenated to form a new string using the `+` symbol.

```meson
str1 = 'abc'
str2 = 'xyz'
combined = str1 + '_' + str2 # combined is now abc_xyz
```

### String path building

*(Added 0.49)*

You can concatenate any two strings using `/` as an operator to build paths.
This will always use `/` as the path separator on all platforms.

```meson
joined = '/usr/share' / 'projectname'    # => /usr/share/projectname
joined = '/usr/local' / '/etc/name'      # => /etc/name

joined = 'C:\\foo\\bar' / 'builddir'     # => C:/foo/bar/builddir
joined = 'C:\\foo\\bar' / 'D:\\builddir' # => D:/builddir
```

Note that this is equivalent to using [[join_paths]],
which was obsoleted by this operator.

### Strings running over multiple lines

Strings running over multiple lines can be declared with three single
quotes, like this:

```meson
multiline_string = '''#include <foo.h>
int main (int argc, char ** argv) {
  return FOO_SUCCESS;
}'''
```

These are raw strings that do not support the escape sequences listed
above.  These strings can also be combined with the string formatting
functionality via `.format()` described below.

Note that multiline f-string support was added in version 0.63.

### String index

Strings support the indexing (`[<num>]`) operator. This operator allows (read
only) accessing a specific character. The returned value is guaranteed to be
a string of length 1.

```meson
foo = 'abcd'
message(foo[1])  # Will print 'b'
foo[2] = 'C'     # ERROR: Meson objects are immutable!
```

### String formatting

#### .format()

Strings can be built using the string formatting functionality.

```meson
template = 'string: @0@, number: @1@, bool: @2@'
res = template.format('text', 1, true)
# res now has value 'string: text, number: 1, bool: true'
```

As can be seen, the formatting works by replacing placeholders of type
`@number@` with the corresponding argument.

#### Format strings
*(Added 0.58)*

Format strings can be used as a non-positional alternative to the
string formatting functionality described above. Note that multiline f-string
support was added in version 0.63.

```meson
n = 10
m = 'hi'

s = f'int: @n@, string: @m@'
# s now has the value 'int: 10, string: hi'
```

Currently only identity-expressions are supported inside of format
strings, meaning you cannot use arbitrary Meson expressions inside of them.

```meson
n = 10
m = 5

# The following is not a valid format string
s = f'result: @n + m@'
```

### String methods

Strings also support a number of other methods that return transformed
copies.

#### .replace()

Since 0.58.0, you can replace a substring from a string.

```meson
# Replaces all instances of one substring with another
s = 'semicolons;as;separators'
s = s.replace('as', 'are')
# 's' now has the value of 'semicolons;are;separators'
```

#### .strip()

```meson
# Similar to the Python str.strip(). Removes leading/ending spaces and newlines
define = ' -Dsomedefine '
stripped_define = define.strip()
# 'stripped_define' now has the value '-Dsomedefine'

# You may also pass a string to strip, which specifies the set of characters to
# be removed.
string = 'xyxHelloxyx'.strip('xy')
# 'string' now has the value 'Hello'
```

Since 0.43.0, you can specify one positional string argument,
and all characters in that string will be stripped.

#### .to_upper(), .to_lower()

```meson
target = 'x86_FreeBSD'
upper = target.to_upper() # t now has the value 'X86_FREEBSD'
lower = target.to_lower() # t now has the value 'x86_freebsd'
```

#### .to_int()

```meson
version = '1'
# Converts the string to an int and throws an error if it can't be
ver_int = version.to_int()
```

#### .contains(), .startswith(), .endswith()

```meson
target = 'x86_FreeBSD'
is_fbsd = target.to_lower().contains('freebsd')
# is_fbsd now has the boolean value 'true'
is_x86 = target.startswith('x86') # boolean value 'true'
is_bsd = target.to_lower().endswith('bsd') # boolean value 'true'
```

#### .substring()

Since 0.56.0, you can extract a substring from a string.

```meson
# Similar to the Python str[start:end] syntax
target = 'x86_FreeBSD'
platform = target.substring(0, 3) # prefix string value 'x86'
system = target.substring(4) # suffix string value 'FreeBSD'
```

The method accepts negative values where negative `start` is relative to the end of
string `len(string) - start` as well as negative `end`.

```meson
string = 'foobar'
string.substring(-5, -3) # => 'oo'
string.substring(1, -1) # => 'ooba'
```

#### .split(), .join()

```meson
# Similar to the Python str.split()
components = 'a b   c d '.split()
# components now has the value ['a', 'b', 'c', 'd']
components = 'a b   c d '.split(' ')
# components now has the value ['a', 'b', '', '', 'c', 'd', '']

# Similar to the Python str.join()
output = ' '.join(['foo', 'bar'])
# Output value is 'foo bar'
pathsep = ':'
path = pathsep.join(['/usr/bin', '/bin', '/usr/local/bin'])
# path now has the value '/usr/bin:/bin:/usr/local/bin'

# For joining path elements, you should use path1 / path2
# This has the advantage of being cross-platform
path = '/usr' / 'local' / 'bin'
# path now has the value '/usr/local/bin'

# For sources files, use files():
my_sources = files('foo.c')
...
my_sources += files('bar.c')
# This has the advantage of always calculating the correct relative path, even
# if you add files in another directory or use them in a different directory
# than they're defined in

# Example to set an API version for use in library(), install_header(), etc
project('project', 'c', version: '0.2.3')
version_array = meson.project_version().split('.')
# version_array now has the value ['0', '2', '3']
api_version = '.'.join([version_array[0], version_array[1]])
# api_version now has the value '0.2'

# We can do the same with .format() too:
api_version = '@0@.@1@'.format(version_array[0], version_array[1])
# api_version now (again) has the value '0.2'
```

#### .underscorify()

```meson
name = 'Meson Docs.txt#Reference-manual'
# Replaces all characters other than `a-zA-Z0-9` with `_` (underscore)
# Useful for substituting into #defines, filenames, etc.
underscored = name.underscorify()
# underscored now has the value 'Meson_Docs_txt_Reference_manual'
```

#### .version_compare()

```meson
version = '1.2.3'
# Compare version numbers semantically
is_new = version.version_compare('>=2.0')
# is_new now has the boolean value false
# Supports the following operators: '>', '<', '>=', '<=', '!=', '==', '='
```

Meson version comparison conventions include:

```meson
'3.6'.version_compare('>=3.6.0') == false
```

It is best to be unambiguous and specify the full revision level to compare.

## Arrays

Arrays are delimited by brackets. An array can contain an arbitrary number of objects of any type.

```meson
my_array = [1, 2, 'string', some_obj]
```

Accessing elements of an array can be done via array indexing:

```meson
my_array = [1, 2, 'string', some_obj]
second_element = my_array[1]
last_element = my_array[-1]
```

You can add more items to an array like this:

```meson
my_array += ['foo', 3, 4, another_obj]
```

When adding a single item, you do not need to enclose it in an array:

```meson
my_array += ['something']
# This also works
my_array += 'else'
```

Note appending to an array will always create a new array object and
assign it to `my_array` instead of modifying the original since all
objects in Meson are immutable.

Since 0.49.0, you can check if an array contains an element like this:

```meson
my_array = [1, 2]
if 1 in my_array
# This condition is true
endif
if 1 not in my_array
# This condition is false
endif
```

### Array methods

The following methods are defined for all arrays:

 - `length`, the size of the array
 - `contains`, returns `true` if the array contains the object given as argument, `false` otherwise
 - `get`, returns the object at the given index, negative indices count from the back of the array, indexing out of bounds is a fatal error. Provided for backwards-compatibility, it is identical to array indexing.

## Dictionaries

Dictionaries are delimited by curly braces. A dictionary can contain
an arbitrary number of key: value pairs. Keys are required to be
strings, but values can be objects of any type. Prior to *0.53.0* keys
were required to be literal strings, i.e., you could not use a
variable containing a string value as a key.

```meson
my_dict = {'foo': 42, 'bar': 'baz'}
```

Keys must be unique:

```meson
# This will fail
my_dict = {'foo': 42, 'foo': 43}
```

Dictionaries are immutable and do not have a guaranteed order.

Dictionaries are available since 0.47.0.

Visit the [[@dict]] objects page in the Reference Manual to read
about the methods exposed by dictionaries.

Since 0.49.0, you can check if a dictionary contains a key like this:

```meson
my_dict = {'foo': 42, 'bar': 43}
if 'foo' in my_dict
# This condition is true
endif
if 42 in my_dict
# This condition is false
endif
if 'foo' not in my_dict
# This condition is false
endif
```

*Since 0.53.0* Keys can be any expression evaluating to a string
value, not limited to string literals any more.

```meson
d = {'a' + 'b' : 42}
k = 'cd'
d += {k : 43}
```

## Function calls

Meson provides a set of usable functions. The most common use case is
creating build objects.

```meson
executable('progname', 'prog.c')
```

Most functions take only few positional arguments but several keyword
arguments, which are specified like this:

```meson
executable('progname',
  sources: 'prog.c',
  c_args: '-DFOO=1')
```

Starting with version 0.49.0 keyword arguments can be specified
dynamically. This is done by passing dictionary representing the
keywords to set in the `kwargs` keyword. The previous example would be
specified like this:

```meson
d = {'sources': 'prog.c',
  'c_args': '-DFOO=1'}

executable('progname',
  kwargs: d)
```

A single function can take keyword arguments both directly in the
function call and indirectly via the `kwargs` keyword argument. The
only limitation is that it is a hard error to pass any particular key
both as a direct and indirect argument.

```meson
d = {'c_args': '-DFOO'}
executable('progname', 'prog.c',
  c_args: '-DBAZ=1',
  kwargs: d) # This is an error!
```

Attempting to do this causes Meson to immediately exit with an error.

### Argument flattening

Argument flattening is a Meson feature that aims to simplify using
methods and functions. For functions where this feature is active,
Meson takes the list of arguments and flattens all nested lists into
one big list.

For instance the following function calls to [[executable]] are
identical in Meson:

```meson
# A normal example:
executable('exe1', ['foo.c', 'bar.c', 'foobar.c'])

# A more contrived example that also works but certainly
# isn't good Meson code:
l1 = ['bar.c']
executable('exe1', [[['foo.c', l1]], ['foobar.c']])

# How meson will treat all the previous calls internally:
executable('exe1', 'foo.c', 'bar.c', 'foobar.c')
```

Because of an internal implementation detail, the following syntax
is currently also supported, even though the first argument of
[[executable]] is a single [[@str]] and not a [[@list]]:

```meson
# WARNING: This example is only valid because of an internal
#          implementation detail and not because it is intended
#
#          PLEASE DO NOT DO SOMETHING LIKE THIS!
#
executable(['exe1', 'foo.c'], 'bar.c', 'foobar.c')
```

This code is currently accepted because argument flattening *currently*
happens before the parameters are evaluated. "Support" for
such constructs will likely be removed in future Meson releases!

Argument flattening is supported by *most* but not *all* Meson
functions and methods. As a general rule, it can be assumed that a
function or method supports argument flattening if the exact list
structure is irrelevant to a function.

Whether a function supports argument flattening is documented in the
[Reference Manual](Reference-manual.md).

## Method calls

Objects can have methods, which are called with the dot operator. The
exact methods it provides depends on the object.

```meson
myobj = some_function()
myobj.do_something('now')
```

## If statements

If statements work just like in other languages.

```meson
var1 = 1
var2 = 2
if var1 == var2 # Evaluates to false
  something_broke()
elif var3 == var2
  something_else_broke()
else
  everything_ok()
endif

opt = get_option('someoption')
if opt != 'foo'
  do_something()
endif
```

## Logical operations

Meson has the standard range of logical operations which can be used in
`if` statements.

```meson
if a and b
  # do something
endif
if c or d
  # do something
endif
if not e
  # do something
endif
if not (f or g)
  # do something
endif
```

Logical operations work only on boolean values.

## Foreach statements

To do an operation on all elements of an iterable, use the `foreach`
command.

> Note that Meson variables are immutable. Trying to assign a new value
> to the iterated object inside a foreach loop will not affect foreach's
> control flow.

### Foreach with an array

Here's an example of how you could define two executables
with corresponding tests using arrays and foreach.

```meson
progs = [['prog1', ['prog1.c', 'foo.c']],
         ['prog2', ['prog2.c', 'bar.c']]]

foreach p : progs
  exe = executable(p[0], p[1])
  test(p[0], exe)
endforeach
```

### Foreach with a dictionary

Here's an example of you could iterate a set of components that
should be compiled in according to some configuration. This uses
a [dictionary][dictionaries], which is available since 0.47.0.

```meson
components = {
  'foo': ['foo.c'],
  'bar': ['bar.c'],
  'baz': ['baz.c'],
}

# compute a configuration based on system dependencies, custom logic
conf = configuration_data()
conf.set('USE_FOO', 1)

# Determine the sources to compile
sources_to_compile = []
foreach name, sources : components
  if conf.get('USE_@0@'.format(name.to_upper()), 0) == 1
    sources_to_compile += sources
  endif
endforeach
```

### Foreach `break` and `continue`

Since 0.49.0 `break` and `continue` keywords can be used inside foreach loops.

```meson
items = ['a', 'continue', 'b', 'break', 'c']
result = []
foreach i : items
  if i == 'continue'
    continue
  elif i == 'break'
    break
  endif
  result += i
endforeach
# result is ['a', 'b']
```

## Comments

A comment starts with the `#` character and extends until the end of the line.

```meson
some_function() # This is a comment
some_other_function()
```

## Ternary operator

The ternary operator works just like in other languages.

```meson
x = condition ? true_value : false_value
```

The only exception is that nested ternary operators are forbidden to
improve legibility. If your branching needs are more complex than this
you need to write an `if/else` construct.

## Includes

Most source trees have multiple subdirectories to process. These can
be handled by Meson's `subdir` command. It changes to the given
subdirectory and executes the contents of `meson.build` in that
subdirectory. All state (variables etc) are passed to and from the
subdirectory. The effect is roughly the same as if the contents of the
subdirectory's Meson file would have been written where the include
command is.

```meson
test_data_dir = 'data'
subdir('tests')
```

## User-defined functions and methods

Meson does not currently support user-defined functions or methods.
The addition of user-defined functions would make Meson
Turing-complete which would make it harder to reason about and more
difficult to integrate with tools like IDEs. More details about this
are [in the
FAQ](FAQ.md#why-is-meson-not-just-a-python-module-so-i-could-code-my-build-setup-in-python).
If because of this limitation you find yourself copying and pasting
code a lot you may be able to use a [`foreach` loop
instead](#foreach-statements).

## Stability Promises

Meson is very actively developed and continuously improved. There is a
possibility that future enhancements to the Meson build system will
require changes to the syntax. Such changes might be the addition of
new reserved keywords, changing the meaning of existing keywords or
additions around the basic building blocks like statements and
fundamental types. It is planned to stabilize the syntax with the 1.0
release.

## Grammar

This is the full Meson grammar, as it is used to parse Meson build definition files:

```
additive_expression: multiplicative_expression | (additive_expression additive_operator multiplicative_expression)
additive_operator: "+" | "-"
argument_list: positional_arguments ["," keyword_arguments] | keyword_arguments
array_literal: "[" [expression_list] "]"
assignment_statement: expression asssignment_operator expression
assignment_operator: "=" | "*=" | "/=" | "%=" | "+=" | "-="
binary_literal: "0b" BINARY_NUMBER
BINARY_NUMBER: /[01]+/
boolean_literal: "true" | "false"
build_definition: (NEWLINE | statement)*
condition: expression
conditional_expression: logical_or_expression | (logical_or_expression "?" expression ":" assignment_expression
decimal_literal: DECIMAL_NUMBER
DECIMAL_NUMBER: /[1-9][0-9]*/
dictionary_literal: "{" [key_value_list] "}"
equality_expression: relational_expression | (equality_expression equality_operator relational_expression)
equality_operator: "==" | "!="
expression: conditional_expression | logical_or_expression
expression_list: expression ("," expression)*
expression_statement: expression
function_expression: id_expression "(" [argument_list] ")"
hex_literal: "0x" HEX_NUMBER
HEX_NUMBER: /[a-fA-F0-9]+/
id_expression: IDENTIFIER
IDENTIFIER: /[a-zA-Z_][a-zA-Z_0-9]*/
identifier_list: id_expression ("," id_expression)*
integer_literal: decimal_literal | octal_literal | hex_literal
iteration_statement: "foreach" identifier_list ":" id_expression NEWLINE (statement | jump_statement)* "endforeach"
jump_statement: ("break" | "continue") NEWLINE
key_value_item: expression ":" expression
key_value_list: key_value_item ("," key_value_item)*
keyword_item: id_expression ":" expression
keyword_arguments: keyword_item ("," keyword_item)*
literal: integer_literal | string_literal | boolean_literal | array_literal | dictionary_literal
logical_and_expression: equality_expression | (logical_and_expression "and" equality_expression)
logical_or_expression: logical_and_expression | (logical_or_expression "or" logical_and_expression)
method_expression: postfix_expression "." function_expression
multiplicative_expression: unary_expression | (multiplicative_expression multiplicative_operator unary_expression)
multiplicative_operator: "*" | "/" | "%"
octal_literal: "0o" OCTAL_NUMBER
OCTAL_NUMBER: /[0-7]+/
positional_arguments: expression ("," expression)*
postfix_expression: primary_expression | subscript_expression | function_expression | method_expression
primary_expression: literal | ("(" expression ")") | id_expression
relational_expression: additive_expression | (relational_expression relational_operator additive_expression)
relational_operator: ">" | "<" | ">=" | "<=" | "in" | ("not" "in")
selection_statement: "if" condition NEWLINE (statement)* ("elif" condition NEWLINE (statement)*)* ["else" (statement)*] "endif"
statement: (expression_statement | selection_statement | iteration_statement | assignment_statement) NEWLINE
string_literal: ("'" STRING_SIMPLE_VALUE "'") | ("'''" STRING_MULTILINE_VALUE "'''")
STRING_MULTILINE_VALUE: \.*?(''')\
STRING_SIMPLE_VALUE: \.*?(?<!\\)(\\\\)*?'\
subscript_expression: postfix_expression "[" expression "]"
unary_expression: postfix_expression | (unary_operator unary_expression)
unary_operator: "not" | "-"
```