Skip to content
← Back

src/syntax/parsers/definitions/global_list.ghul

1
namespace Syntax.Parsers.Definitions is
2
use System.Exception
3
use IO.Std
4
use Ghul.Disposable
5
6
use Source
7
8
class GLOBAL_LIST(
9
pragma_parser: Parser[Trees.Pragmas.PRAGMA],
10
definition_parser: Parser[Trees.Definitions.Definition],
11
statement_parser: Parser[Trees.Statements.Statement],
12
precedence_map: Collections.MutableMap[string,Syntax.Parsers.Expressions.PRECEDENCE],
13
precedence_helper: PRAGMA_PRECEDENCE
14
): Base[Trees.Definitions.LIST] is
15
description: string => "definition list"
16
17
super()
18
19
_resync: DEFINITION_RESYNC
20
21
init(..) is
22
_resync = DEFINITION_RESYNC()
23
si
24
25
parse(context: CONTEXT) -> Trees.Definitions.LIST is
26
let start = context.location
27
let end mut = context.location
28
let definitions = Collections.LIST[Trees.Definitions.Definition]()
29
let statements = Collections.LIST[Trees.Statements.Statement]()
30
let file_pragmas = Collections.LIST[Trees.Pragmas.PRAGMA]()
31
32
// @@ pragmas are only read here, before the main loop: a
33
// whole-file pragma must precede everything else in the file,
34
// so one seen once anything has been collected is reported
35
// and skipped in the loop below. GLOBAL_LIST is reused as the
36
// namespace body parser (see CONTEXT.namespace_depth), so this
37
// is gated to depth 0 - a namespace body is never the file
38
// root, and `@@` inside one falls through to the misplaced-
39
// pragma report in the main loop instead.
40
while
41
context.namespace_depth == 0 /\
42
context.current.token == Lexical.TOKEN.AT_AT /\
43
!context.is_end_of_file
44
do
45
let pragma = pragma_parser.parse(context)
46
47
if !pragma? then
48
break
49
fi
50
51
file_pragmas.add(pragma)
52
53
// A file-level `precedence` applies to the rest of the
54
// file's parse and is never restored.
55
if pragma.is_name_equal_to("precedence") then
56
let operator_name: string mut = ""
57
let previous: Syntax.Parsers.Expressions.PRECEDENCE mut = _
58
let had_previous mut = false
59
60
precedence_helper.apply(context, pragma, precedence_map, operator_name ref, previous ref, had_previous ref)
61
fi
62
od
63
64
// `si` closes a namespace body (depth > 0); at the file root
65
// (depth 0) it is stray input the definition parser reports, so it
66
// must not end the loop there.
67
while
68
!context.is_end_of_file /\
69
(context.namespace_depth == 0 \/ context.current.token != Lexical.TOKEN.SI)
70
do
71
// A definition or statement attempt that fails without
72
// throwing (its parser logs an error and returns null,
73
// rather than raising) can leave the tokenizer position
74
// exactly where it started - the failed attempt's own
75
// speculation is backed out. Without a forced advance
76
// here, the loop would re-attempt the identical parse at
77
// the identical position forever.
78
let attempt_start = context.location
79
let progress_start = context.location.start
80
let errors_before = context.logger.error_count
81
82
// A closing keyword left over by a construct already
83
// reported as unclosed is skipped rather than described
84
// again as a definition that is not one. At the file root
85
// `si` is such a keyword too; inside a namespace the loop
86
// above has already stopped on it.
87
if context.at_stray_construct_closer(context.namespace_depth == 0) then
88
context.next_token()
89
90
continue
91
fi
92
93
try
94
if context.current.token == Lexical.TOKEN.AT_AT then
95
_reject_misplaced_file_pragma(context)
96
// Script mode: at the file root (no enclosing namespace) a
97
// leading token that isn't a definition keyword may begin a
98
// top-level statement. Inside any namespace this is skipped
99
// and parsing is unchanged.
100
elif context.namespace_depth == 0 /\ !_is_definition_head(context.current.token) then
101
if _is_ambiguous_head(context.current.token) then
102
// IDENTIFIER / OPERATOR / [ can each begin a global
103
// member or a statement; a shallow peek routes the
104
// clear definitions. The rest are parsed as a
105
// statement, but speculatively: an incomplete
106
// definition that the peek couldn't recognise (e.g.
107
// `foo(` with no close) fails to parse as a statement
108
// and falls back to the definition parser's recovery.
109
if _starts_definition(context) \/ !_try_collect_statement(context, statements) then
110
_parse_definition(context, definitions)
111
end = _end_of(definitions, end)
112
else
113
end = _end_of_statement(statements, end)
114
fi
115
elif _is_statement_head(context.current.token) then
116
_parse_statement(context, statements)
117
end = _end_of_statement(statements, end)
118
else
119
// Not a definition keyword and not a statement head:
120
// stray input, reported by the definition parser as
121
// before.
122
_parse_definition(context, definitions)
123
end = _end_of(definitions, end)
124
fi
125
else
126
_parse_definition(context, definitions)
127
end = _end_of(definitions, end)
128
fi
129
catch ue: UNWIND_TO_GLOBAL_EXCEPTION
130
// carry on from here
131
catch e: Exception
132
IoC.CONTAINER.instance.logger.exception(context.current.location, e, "parse exception: {e.message}")
133
134
while
135
!context.is_end_of_file /\
136
context.current.token != Lexical.TOKEN.SI /\
137
context.current.token != Lexical.TOKEN.SEMICOLON
138
do
139
context.next_token()
140
od
141
142
if context.is_end_of_file then
143
break
144
fi
145
yrt
146
147
_resync.recover(context, attempt_start, errors_before)
148
149
if context.location.start == progress_start /\ !context.is_end_of_file then
150
context.next_token()
151
fi
152
od
153
154
let result = Trees.Definitions.LIST(start::end, definitions)
155
156
if file_pragmas.count > 0 then
157
result.file_pragmas = file_pragmas
158
fi
159
160
// Hand any bare top-level statements to synthesise-top-level-entry,
161
// which wraps them into a global entry point. Only reachable at the
162
// file root (namespace depth 0).
163
//
164
// Spanned by the statements themselves rather than by the whole
165
// file: the span becomes the synthesised entry's body location, and
166
// an incremental edit treats everything inside a re-walked body as
167
// re-recorded by that re-walk. A whole-file span therefore discards
168
// what the re-walk does not produce - the imports at the head of the
169
// file among them - and nothing puts those back.
170
if statements.count > 0 then
171
result.top_level_statements =
172
Trees.Statements.LIST(
173
statements[0].location :: statements[statements.count - 1].location,
174
statements
175
)
176
fi
177
178
return result
179
si
180
181
// A @@ seen once the file has anything in it. Reports where it must
182
// have been instead, then hands off to the ordinary pragma parser
183
// to consume its tokens (name, arguments, however they're shaped)
184
// so the loop can carry on. The parsed pragma itself is discarded:
185
// a misplaced one is never applied.
186
_reject_misplaced_file_pragma(context: CONTEXT) is
187
context.error(context.current.location, "a @@ pragma must precede everything else in the file")
188
189
pragma_parser.parse(context)
190
191
_skip_semicolon(context)
192
si
193
194
_parse_definition(context: CONTEXT, definitions: Collections.MutableList[Trees.Definitions.Definition]) is
195
let definition = definition_parser.parse(context)
196
197
if definition? then
198
definitions.add(definition)
199
fi
200
si
201
202
_parse_statement(context: CONTEXT, statements: Collections.MutableList[Trees.Statements.Statement]) is
203
let statement: Trees.Statements.Statement? mut = _
204
205
context.in_top_level_statements = true
206
207
try
208
statement = statement_parser.parse(context)
209
finally
210
context.in_top_level_statements = false
211
yrt
212
213
if statement? then
214
statements.add(statement)
215
_skip_semicolon(context)
216
elif !context.is_end_of_file then
217
context.next_token()
218
fi
219
si
220
221
// Speculatively parse a statement at an ambiguous head, collecting it
222
// and returning true only if it parses cleanly to a statement boundary.
223
// Otherwise the tokens are rewound — tokenizer (the exempt bounded-probe
224
// kind, so it does not feed the loop detector) and diagnostics — so the
225
// caller can hand them to the definition parser instead. A well-formed
226
// call commits with no rewind; only an incomplete definition misrouted
227
// here takes the fallback.
228
_try_collect_statement(context: CONTEXT, statements: Collections.MutableList[Trees.Statements.Statement]) -> bool is
229
let use diagnostics_snapshot = context.diagnostics_speculate_then_backtrack()
230
let use snapshot = context.tokenizer_speculate_then_backtrack_bounded()
231
232
let errors_before = context.logger.error_count
233
let away_before = context.errors_away_from_end_of_input
234
let head_line = context.location.start_line
235
236
let statement: Trees.Statements.Statement? mut = _
237
238
context.in_top_level_statements = true
239
240
try
241
statement = statement_parser.parse(context)
242
finally
243
context.in_top_level_statements = false
244
yrt
245
246
if
247
statement? /\
248
!statement.is_poisoned /\
249
context.logger.error_count == errors_before /\
250
(
251
context.current.token == Lexical.TOKEN.SEMICOLON \/
252
context.current.token == Lexical.TOKEN.SI \/
253
context.is_end_of_file \/
254
context.at_inferred_terminator
255
)
256
then
257
snapshot.commit()
258
diagnostics_snapshot.commit()
259
260
statements.add(statement)
261
_skip_semicolon(context)
262
263
return true
264
fi
265
266
// A statement that runs out of input, with nothing wrong with it
267
// but where it stopped, is a statement not finished yet - a call
268
// whose arguments are still being typed. Read as a definition it
269
// would fail somewhere inside it instead, describing a mistake
270
// that is not there.
271
if
272
statement? /\
273
context.is_end_of_file /\
274
context.logger.error_count > errors_before /\
275
context.errors_away_from_end_of_input == away_before
276
then
277
snapshot.commit()
278
diagnostics_snapshot.commit()
279
280
statements.add(statement)
281
282
return true
283
fi
284
285
// Neither reading parses. Where the statement's error is on the
286
// line it began, it describes what was written and the peek has
287
// already ruled the definition out; an error further down is a
288
// statement that swallowed the lines after an unclosed argument
289
// list, which is a definition being typed.
290
if context.last_error_line == head_line then
291
snapshot.commit()
292
diagnostics_snapshot.commit()
293
294
if statement? then
295
statements.add(statement)
296
_skip_semicolon(context)
297
elif !context.is_end_of_file then
298
context.next_token()
299
fi
300
301
return true
302
fi
303
304
return false
305
si
306
307
_end_of(definitions: Collections.List[Trees.Definitions.Definition], previous: Source.LOCATION) -> Source.LOCATION =>
308
if definitions.count > 0 then definitions[definitions.count - 1].location else previous fi
309
310
_end_of_statement(statements: Collections.List[Trees.Statements.Statement], previous: Source.LOCATION) -> Source.LOCATION =>
311
if statements.count > 0 then statements[statements.count - 1].location else previous fi
312
313
// Definition-introducing keywords: everything the definition parser
314
// dispatches on that a statement can never begin with. The three
315
// tokens a statement *can* also begin with (IDENTIFIER, OPERATOR,
316
// SQUARE_OPEN) are deliberately excluded and disambiguated instead.
317
_is_definition_head(token: Lexical.TOKEN) -> bool =>
318
token == Lexical.TOKEN.NAMESPACE \/
319
token == Lexical.TOKEN.USE \/
320
token == Lexical.TOKEN.CLASS \/
321
token == Lexical.TOKEN.TRAIT \/
322
token == Lexical.TOKEN.STRUCT \/
323
token == Lexical.TOKEN.PARTIAL \/
324
token == Lexical.TOKEN.IMPL \/
325
token == Lexical.TOKEN.UNION \/
326
token == Lexical.TOKEN.ENUM \/
327
token == Lexical.TOKEN.AT
328
329
// The tokens shared between a global-member definition and a statement.
330
_is_ambiguous_head(token: Lexical.TOKEN) -> bool =>
331
token == Lexical.TOKEN.IDENTIFIER \/
332
token == Lexical.TOKEN.OPERATOR \/
333
token == Lexical.TOKEN.SQUARE_OPEN
334
335
// Tokens that unambiguously begin a statement (the statement parser's
336
// dispatch set, minus the ambiguous heads handled above). A token that
337
// is neither a definition head, an ambiguous head, nor one of these is
338
// stray input handled by the definition parser.
339
_is_statement_head(token: Lexical.TOKEN) -> bool =>
340
token == Lexical.TOKEN.PAREN_OPEN \/
341
token == Lexical.TOKEN.NEW \/
342
token == Lexical.TOKEN.CAST \/
343
token == Lexical.TOKEN.ISA \/
344
token == Lexical.TOKEN.TYPEOF \/
345
token == Lexical.TOKEN.INT_LITERAL \/
346
token == Lexical.TOKEN.FLOAT_LITERAL \/
347
token == Lexical.TOKEN.STRING_LITERAL \/
348
token == Lexical.TOKEN.ENTER_STRING \/
349
token == Lexical.TOKEN.CHAR_LITERAL \/
350
token == Lexical.TOKEN.TRUE \/
351
token == Lexical.TOKEN.FALSE \/
352
token == Lexical.TOKEN.NULL \/
353
token == Lexical.TOKEN.SELF \/
354
token == Lexical.TOKEN.SUPER \/
355
token == Lexical.TOKEN.REC \/
356
token == Lexical.TOKEN.VAL \/
357
token == Lexical.TOKEN.LET \/
358
token == Lexical.TOKEN.IF \/
359
token == Lexical.TOKEN.CASE \/
360
token == Lexical.TOKEN.AWAIT \/
361
token == Lexical.TOKEN.ASSERT \/
362
token == Lexical.TOKEN.RETURN \/
363
token == Lexical.TOKEN.YIELD \/
364
token == Lexical.TOKEN.THROW \/
365
token == Lexical.TOKEN.WHILE \/
366
token == Lexical.TOKEN.FOR \/
367
token == Lexical.TOKEN.DO \/
368
token == Lexical.TOKEN.TRY \/
369
token == Lexical.TOKEN.BREAK \/
370
token == Lexical.TOKEN.CONTINUE
371
372
// Decide whether an ambiguous head begins a global definition, by a
373
// shallow bounded token peek — never a full parse, so it does not feed
374
// the speculation-loop detector (its rewind is the exempt bounded-probe
375
// kind). Because parameters are always typed, a definition is
376
// recognisable from the first couple of tokens: a `: type` annotation,
377
// a `(name: ...` / `[name: ...` typed parameter list, or an empty `()`
378
// followed by a signature tail. The peek stops at a call argument's
379
// opening token, so a formatted string argument is never read into and
380
// never mis-lexed.
381
_starts_definition(context: CONTEXT) -> bool is
382
let use snapshot = context.tokenizer_speculate_then_backtrack_bounded()
383
384
let head = context.current.token
385
386
context.next_token()
387
388
if head == Lexical.TOKEN.SQUARE_OPEN then
389
// [i: int] -> T (indexer) vs [a, b]... (array literal).
390
return _is_typed_parameter_head(context)
391
fi
392
393
// IDENTIFIER or OPERATOR.
394
let after = context.current.token
395
396
// name: type — global variable or typed member. A loop keyword
397
// after the colon makes the name a loop label instead, which
398
// begins a statement.
399
if after == Lexical.TOKEN.COLON then
400
context.next_token()
401
402
let labelled = context.current.token
403
404
return
405
labelled != Lexical.TOKEN.FOR /\
406
labelled != Lexical.TOKEN.WHILE /\
407
labelled != Lexical.TOKEN.DO
408
fi
409
410
// name[T](...) — generic function definition vs name[i]... (index).
411
if after == Lexical.TOKEN.SQUARE_OPEN then
412
_skip_balanced(context, Lexical.TOKEN.SQUARE_OPEN, Lexical.TOKEN.SQUARE_CLOSE)
413
414
return _parameter_list_starts_definition(context)
415
fi
416
417
// name(...) — function definition vs call statement.
418
if after == Lexical.TOKEN.PAREN_OPEN then
419
return _parameter_list_starts_definition(context)
420
fi
421
422
return false
423
si
424
425
// The current token is expected to be `(`. Distinguishes a parameter
426
// list (definition) from a call argument list (statement). A parameter
427
// is always `name: type`, so a definition is `()` followed by a
428
// definition tail, or `(name: …`; every other shape after `(` — a
429
// literal or compound-expression argument, or `(name` continued by
430
// anything but `:` (`,`, `)`, `.`, `(`, `[`, an operator, …) — is a
431
// call argument list, i.e. a statement.
432
_parameter_list_starts_definition(context: CONTEXT) -> bool is
433
if context.current.token != Lexical.TOKEN.PAREN_OPEN then
434
return false
435
fi
436
437
// A parameter whose type is missing leaves the list looking
438
// like a call's arguments, so what settles it is what follows
439
// the list: a body or a return type is a definition written
440
// wrongly, and a definition is what it is reported as.
441
if _definition_tail_follows(context) then
442
return true
443
fi
444
445
context.next_token()
446
447
if context.current.token == Lexical.TOKEN.PAREN_CLOSE then
448
context.next_token()
449
450
return _begins_definition_tail(context.current.token)
451
fi
452
453
if context.current.token != Lexical.TOKEN.IDENTIFIER then
454
return false
455
fi
456
457
context.next_token()
458
459
return context.current.token == Lexical.TOKEN.COLON
460
si
461
462
// Whether a definition tail - a body, a return type, a modifier -
463
// follows the parenthesised list. A list whose parameters have no
464
// types reads as a call passing bare names, and the tail is what
465
// says otherwise.
466
//
467
// An interpolated string ends the peek: its interior lexes
468
// according to where the parser is, so running the tokenizer
469
// through one from here leaves the real parse reading it
470
// differently. A parameter list never contains one, so reaching
471
// one means this is a call.
472
_definition_tail_follows(context: CONTEXT) -> bool is
473
let use snapshot = context.tokenizer_speculate_then_backtrack_bounded()
474
475
let depth mut = 0
476
477
while !context.is_end_of_file /\ context.current.token != Lexical.TOKEN.SI do
478
let token = context.current.token
479
480
if token == Lexical.TOKEN.ENTER_STRING then
481
return false
482
fi
483
484
context.next_token()
485
486
if token == Lexical.TOKEN.PAREN_OPEN \/ token == Lexical.TOKEN.SQUARE_OPEN then
487
depth = depth + 1
488
elif token == Lexical.TOKEN.PAREN_CLOSE \/ token == Lexical.TOKEN.SQUARE_CLOSE then
489
depth = depth - 1
490
491
if depth <= 0 then
492
return _begins_definition_tail(context.current.token)
493
fi
494
fi
495
od
496
497
return false
498
si
499
500
// The current token should be the first parameter name, followed by `:`.
501
_is_typed_parameter_head(context: CONTEXT) -> bool is
502
if context.current.token != Lexical.TOKEN.IDENTIFIER then
503
return false
504
fi
505
506
context.next_token()
507
508
return context.current.token == Lexical.TOKEN.COLON
509
si
510
511
// What can follow the `()` of an empty parameter list in a function
512
// definition: the body (`is` / `=>`), a return type (`->`), or a
513
// trailing modifier (`static`, `public`, …). A call statement's `()`
514
// is followed by a statement continuation (`.`, `;`, an operator, …)
515
// instead.
516
_begins_definition_tail(token: Lexical.TOKEN) -> bool =>
517
token == Lexical.TOKEN.IS \/
518
token == Lexical.TOKEN.ARROW_FAT \/
519
token == Lexical.TOKEN.ARROW_THIN \/
520
token == Lexical.TOKEN.STATIC \/
521
token == Lexical.TOKEN.PUBLIC \/
522
token == Lexical.TOKEN.PRIVATE \/
523
token == Lexical.TOKEN.PROTECTED \/
524
token == Lexical.TOKEN.ABSTRACT \/
525
token == Lexical.TOKEN.FIELD
526
527
// Consume a balanced bracket group. On entry the current token is the
528
// opening bracket; on return it is the token following the matching
529
// close (or end-of-input / SI if unterminated).
530
_skip_balanced(context: CONTEXT, open: Lexical.TOKEN, close: Lexical.TOKEN) is
531
let depth mut = 0
532
533
while !context.is_end_of_file /\ context.current.token != Lexical.TOKEN.SI do
534
let token = context.current.token
535
536
context.next_token()
537
538
if token == open then
539
depth = depth + 1
540
elif token == close then
541
depth = depth - 1
542
543
if depth <= 0 then
544
return
545
fi
546
fi
547
od
548
si
549
550
_skip_semicolon(context: CONTEXT) is
551
if context.current.token == Lexical.TOKEN.SEMICOLON then
552
let semicolon = context.current
553
context.next_token()
554
context.note_written_terminator(semicolon)
555
else
556
// A top-level statement's boundary never reaches
557
// accept_terminator - the terminator is consumed here - so
558
// the inlay is recorded here too, or a file of top-level
559
// statements shows none.
560
context.note_inferred_terminator()
561
fi
562
si
563
si
564
si