about summary refs log tree commit diff
path: root/docs/grammar.md
blob: eee54e2a1e2fa79a0f126e6a6323410486e0b24b (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
# Catskill Language Grammar

Catskill is a low-level systems programming language inspired by recent modern
developments in language design.
This document aims to describe the grammar as currently implemented by the
initial bootstrapping compiler's (named "catboot") parsing pass.
It represents the current reality of the language, as it is now.
This does mean that the grammar will change in the future!

Rule names match the kinds in `boot/tree.c` so this doc can double as a
reference when reading the parser.
The production naming as defined in this grammar is mirrored as closely
as possible by the actual parse tree the compiler produces, and can be
used as a reference into the tree and the parser.

Also, please note that here we do not encode every single grammar production
into the exact mathematical language grammar, some things are enforced by the
parser and the passes further on on the fly, like associativity or precedence,
or even some construct which are not allowed to occur in some specific places.
This saves us some code and also allows for slightly easier diagnostical messages
for some cases. (This document isn't really an exercise in scientific rigor,
it's supposed to be a reference for the actual practical implementation!)

## Notation

The grammar uses a relaxed EBNF flavour, because following the exact syntax is annoying: :)

- `production = rule` defines single a production (possibly over multiple lines).
- `a | b` is alternation; `a b` is concatenation.
- `[ a ]` makes `a` optional.
- `{ a }` is zero or more repetitions of `a`.
- `( a )` groups alternatives.
- `"text"` is a literal token.
- UPPERCASE names are terminal tokens produced by lexing.
- `#` starts a line-comment.

## Lexical grammar

```ebnf
source = { token } EOF
token = WHITESPACE
	| COMMENT
	| NEWLINE
	| symbol
	| word
	| literal
	| NAME

# whitespace and comments are completely discarded.
WHITESPACE = " " | "\t"
COMMENT = "#" { not_newline }
# newlines can terminate statements.
NEWLINE = "\n"

# words and names share a single lexical shape.
# we match any lexeme against the keywords first, anything else becomes a name.
word_or_name = ident_char { ident_tail_char }
ident_char = "A".."Z" | "a".."z" | "_"
ident_tail_char = ident_char | "0".."9"

word = "fun" | "if" | "else" | "for" | "loop"
	| "break" | "continue" | "defer" | "switch" | "return"
	| "var" | "let"
	| "type" | "variant" | "class"
	| "true" | "false"

# literals
literal = INT_LITERAL | FLOAT_LITERAL | STRING_LITERAL
INT_LITERAL = digit { digit }
FLOAT_LITERAL = digit { digit } "." digit { digit }
STRING_LITERAL = '"' { not_quote } '"'
digit = "0".."9"

# multi-character symbols are matched eagerly, e.g. "<<=" before "<<".
symbol = "(" | ")" | "{" | "}" | "[" | "]" | ","
	| "." | ".." | "..."
	| "!" | "?" | "~" | "|" | "^"
	| "+" | "-" | "*" | "/" | "%" | "&" | "="
	| "++" | "--" | "**"
	| "&&" | "||" | "==" | "!=" | "<" | "<=" | ">" | ">="
	| "<<" | ">>"
	| "+=" | "-=" | "*=" | "/=" | "%="
	| "&&=" | "||="
	| "&=" | "|=" | "^="
	| "<<=" | ">>="
```

## Tree grammar

```ebnf
# a translation unit is a stream of statements at the top level.
unit = { statement statement_end }
statement_end = NEWLINE | EOF

statement = statement_declaration
	| statement_conditional
	| statement_loop
	| statement_block
	| statement_return
	| statement_break
	| statement_continue
	| statement_defer
	| statement_pragma
	| statement_expression

# declarations

statement_declaration = ( "var" | "let" ) bare_declaration
bare_declaration = NAME { "," NAME } type "=" expression

# statements

statement_conditional = "if" expression block
	{ "else" "if" expression block }
	[ "else" block ]

statement_loop = statement_loop_for_each
	| statement_loop_c
	| statement_loop_while
	| statement_loop_endless
statement_loop_for_each = "for" bare_declaration block
statement_loop_c = "for" bare_declaration "," expression "," expression block
statement_loop_while = "loop" expression block
statement_loop_endless = "loop" block

statement_block = block
statement_return = "return" [ expression ]
statement_break = "break"
statement_continue = "continue"
statement_defer = "defer" ( block | expression )
statement_expression = expression

# pragmas
# one pragma statement may carry multiple.
statement_pragma = "|" pragma { "," pragma }
pragma = NAME { pragma_argument }
pragma_argument = INT_LITERAL | FLOAT_LITERAL | STRING_LITERAL | NAME

# blocks
# the final statement may omit terminator.
block = "{" { statement statement_end } [ statement ] "}"

# expressions

expression = expression_binary_operation
expression_binary_operation = expression_unary_operation
	[ binary_op expression_binary_operation ]

# unary
expression_unary_operation = unary_op expression_unary_operation
	| ( "++" | "--" ) expression_unary_operation
	| expression_postfix

unary_op = "-" | "!" | "~" | "&" | "*"

# postfix chain
expression_postfix = expression_primary { postfix_tail }
postfix_tail = expression_call
	| expression_construct
	| expression_subscript
	| expression_member
	| expression_try
	| expression_must
	| expression_increment_decrement

expression_call = "(" argument_group ")"
expression_construct = "{" argument_group "}" # forbidden in statement-clause contexts!
expression_subscript = "[" expression "]"
expression_member = "." NAME
expression_try = "?"
expression_must = "!"
expression_increment_decrement = "++" | "--" # postfix form

argument_group = argument { "," argument }
argument = [ NAME "=" ] expression

# primary expressions
expression_primary = expression_name
	| expression_integer_literal
	| expression_float_literal
	| expression_string_literal
	| expression_boolean_literal
	| expression_group
	| expression_function
	| expression_type

expression_name = NAME
expression_integer_literal = INT_LITERAL
expression_float_literal = FLOAT_LITERAL
expression_string_literal = STRING_LITERAL
expression_boolean_literal = "true" | "false"
expression_group = "(" expression ")"
expression_function = "fun" function_header block

expression_type = "type" type
	| "variant" "{" variant_cases "}"
	| "class" "{" class_methods "}"

# binary operators, weakest to strongest
# precedence and associativity are resolved as an additional step,
# the grammar does not carry this information:
# * `..` is non-associative
# * right-associative: `=` (& compounds) and **`
# * left-associative: everything else
binary_op = "=" | "+=" | "-=" | "*=" | "/=" | "%=" # 1 (precedence)
	| "&&=" | "||="
	| "&=" | "|=" | "^=" | "<<=" | ">>="
	| ".." # 2
	| "||" # 3
	| "&&" # 4
	| "|" # 5
	| "^" # 6
	| "&" # 7
	| "==" | "!=" # 8
	| "<" | "<=" | ">" | ">=" # 9
	| "<<" | ">>" # 10
	| "+" | "-" # 11
	| "*" | "/" | "%" # 12
	| "**" # 13

# types

type = type_inner [ "?" ] # maybe `?`
type_inner = type_name
	| type_variant
	| type_class
	| type_function
	| type_structure
	| type_tuple
	| type_array
	| type_map
	| type_reference

type_name = NAME
type_variant = "variant" "{" variant_cases "}"
type_class = "class" "{" class_methods "}"
type_function = "fun" function_header
type_structure = "{" struct_fields "}"
type_tuple = "(" [ type { "," type } ] ")"
type_array = "[" type "]"
type_map = "[" type "=" type "]"
type_reference = "&" type

# common shapes

function_header = "(" [ parameter { "," parameter } ] ")" [ type ]
parameter = [ "..." ] NAME [ type ]

struct_fields = { NAME type ( "," | NEWLINE ) }
variant_cases = { variant_case ( "," | NEWLINE ) }
variant_case = NAME [ type ]
class_methods = { NAME function_header ( "," | NEWLINE ) }
```